* Normalize and validate a candidate auto-memory directory path. * * SECURITY: Rejects paths that would be dangerous as a read-allowlist root * or that normalize() doesn't fully resolve: * - relative (!isAbsolute): "../foo" — would be interpreted relative to CWD * - root/near-root (length < 3):
( raw: string | undefined, expandTilde: boolean, )
| 107 | * or undefined if the path is unset/empty/rejected. |
| 108 | */ |
| 109 | function validateMemoryPath( |
| 110 | raw: string | undefined, |
| 111 | expandTilde: boolean, |
| 112 | ): string | undefined { |
| 113 | if (!raw) { |
| 114 | return undefined |
| 115 | } |
| 116 | let candidate = raw |
| 117 | // Settings.json paths support ~/ expansion (user-friendly). The env var |
| 118 | // override does not (it's set programmatically by Cowork/SDK, which should |
| 119 | // always pass absolute paths). Bare "~", "~/", "~/.", "~/..", etc. are NOT |
| 120 | // expanded — they would make isAutoMemPath() match all of $HOME or its |
| 121 | // parent (same class of danger as "/" or "C:\"). |
| 122 | if ( |
| 123 | expandTilde && |
| 124 | (candidate.startsWith('~/') || candidate.startsWith('~\\')) |
| 125 | ) { |
| 126 | const rest = candidate.slice(2) |
| 127 | // Reject trivial remainders that would expand to $HOME or an ancestor. |
| 128 | // normalize('') = '.', normalize('.') = '.', normalize('foo/..') = '.', |
| 129 | // normalize('..') = '..', normalize('foo/../..') = '..' |
| 130 | const restNorm = normalize(rest || '.') |
| 131 | if (restNorm === '.' || restNorm === '..') { |
| 132 | return undefined |
| 133 | } |
| 134 | candidate = join(homedir(), rest) |
| 135 | } |
| 136 | // normalize() may preserve a trailing separator; strip before adding |
| 137 | // exactly one to match the trailing-sep contract of getAutoMemPath() |
| 138 | const normalized = normalize(candidate).replace(/[/\\]+$/, '') |
| 139 | if ( |
| 140 | !isAbsolute(normalized) || |
| 141 | normalized.length < 3 || |
| 142 | /^[A-Za-z]:$/.test(normalized) || |
| 143 | normalized.startsWith('\\\\') || |
| 144 | normalized.startsWith('//') || |
| 145 | normalized.includes('\0') |
| 146 | ) { |
| 147 | return undefined |
| 148 | } |
| 149 | return (normalized + sep).normalize('NFC') |
| 150 | } |
| 151 | |
| 152 | /** |
| 153 | * Direct override for the full auto-memory directory path via env var. |
no test coverage detected