diff --git a/packages/personality/src/index.ts b/packages/personality/src/index.ts index f0a1dc49dde876abf86babf5158e361a4fd1d963..b9c18d292a1367beaacc27685f54ae2b0726ae33 100644 --- a/packages/personality/src/index.ts +++ b/packages/personality/src/index.ts @@ -49,29 +49,56 @@ function listPersonalities(): string[] { const dir = getPersonalitiesDir(); if (!fs.existsSync(dir)) return []; - return fs - .readdirSync(dir) - .filter((f) => f.endsWith(".md")) - .map((f) => f.slice(0, -3)) - .sort(); + try { + return fs + .readdirSync(dir) + .filter((f) => f.endsWith(".md")) + .map((f) => f.slice(0, -3)) + .sort(); + } catch (err) { + console.error(`Failed to list personalities: ${err}`); + return []; + } } -/** Read the persisted active personality name, or null if none. */ +/** + * Read the persisted active personality name, or null if none. + * Validates the name against available personalities to prevent + * path traversal via a tampered persistence file. + */ function readActivePersonality(): string | null { const filePath = getActivePersonalityPath(); if (!fs.existsSync(filePath)) return null; - const name = fs.readFileSync(filePath, "utf-8").trim(); - return name || null; + try { + const name = fs.readFileSync(filePath, "utf-8").trim(); + if (!name) return null; + + // Reject names that could escape the personalities directory + const available = listPersonalities(); + if (!available.includes(name)) { + console.error(`Persisted personality "${name}" not in available list, ignoring`); + return null; + } + + return name; + } catch (err) { + console.error(`Failed to read active personality: ${err}`); + return null; + } } /** Persist the active personality name (or remove the file to clear). */ function writeActivePersonality(name: string | null): void { const filePath = getActivePersonalityPath(); - if (name) { - fs.writeFileSync(filePath, `${name}\n`, "utf-8"); - } else if (fs.existsSync(filePath)) { - fs.unlinkSync(filePath); + try { + if (name) { + fs.writeFileSync(filePath, `${name}\n`, "utf-8"); + } else if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + } catch (err) { + console.error(`Failed to write active personality: ${err}`); } } @@ -79,7 +106,12 @@ function writeActivePersonality(name: string | null): void { function readPersonalityContent(name: string): string | null { const filePath = path.join(getPersonalitiesDir(), `${name}.md`); if (!fs.existsSync(filePath)) return null; - return fs.readFileSync(filePath, "utf-8"); + try { + return fs.readFileSync(filePath, "utf-8"); + } catch (err) { + console.error(`Failed to read personality "${name}": ${err}`); + return null; + } } const PERSONALITY_TAG_OPEN =