(virtualPath: string)
| 130 | * model-recoverable message on invalid input. |
| 131 | */ |
| 132 | export function parseMemoryPath(virtualPath: string): ParsedMemoryPath { |
| 133 | const trimmed = virtualPath.trim(); |
| 134 | if (!trimmed.startsWith(MEMORY_VIRTUAL_ROOT)) { |
| 135 | throw new MemoryCommandError( |
| 136 | `Invalid memory path '${virtualPath}': paths must start with ${MEMORY_VIRTUAL_ROOT}/ (e.g. ${MEMORY_VIRTUAL_ROOT}/global/notes.md)` |
| 137 | ); |
| 138 | } |
| 139 | const rest = trimmed.slice(MEMORY_VIRTUAL_ROOT.length).replace(/\/+$/, ""); |
| 140 | if (rest === "") { |
| 141 | return { scope: null, relPath: "" }; |
| 142 | } |
| 143 | if (!rest.startsWith("/")) { |
| 144 | throw new MemoryCommandError( |
| 145 | `Invalid memory path '${virtualPath}': expected ${MEMORY_VIRTUAL_ROOT}/<scope>/...` |
| 146 | ); |
| 147 | } |
| 148 | const segments = rest.slice(1).split("/"); |
| 149 | const scope = segments[0] as MemoryScope; |
| 150 | if (!MEMORY_SCOPES.includes(scope)) { |
| 151 | throw new MemoryCommandError( |
| 152 | `Invalid memory scope '${segments[0]}': expected one of ${MEMORY_SCOPES.join(", ")}` |
| 153 | ); |
| 154 | } |
| 155 | const relSegments = segments.slice(1); |
| 156 | for (const segment of relSegments) { |
| 157 | if (segment === "" || segment === ".") { |
| 158 | throw new MemoryCommandError( |
| 159 | `Invalid memory path '${virtualPath}': empty or '.' path segments are not allowed` |
| 160 | ); |
| 161 | } |
| 162 | if (segment === ".." || segment.includes("..")) { |
| 163 | throw new MemoryCommandError( |
| 164 | `Invalid memory path '${virtualPath}': path traversal ('..') is not allowed` |
| 165 | ); |
| 166 | } |
| 167 | if (segment.includes("~")) { |
| 168 | throw new MemoryCommandError( |
| 169 | `Invalid memory path '${virtualPath}': '~' is not allowed in memory paths` |
| 170 | ); |
| 171 | } |
| 172 | if (segment.includes("\\")) { |
| 173 | throw new MemoryCommandError( |
| 174 | `Invalid memory path '${virtualPath}': backslashes are not allowed (use '/')` |
| 175 | ); |
| 176 | } |
| 177 | if (ENCODED_TRAVERSAL_PATTERN.test(segment)) { |
| 178 | throw new MemoryCommandError( |
| 179 | `Invalid memory path '${virtualPath}': URL-encoded traversal sequences are not allowed` |
| 180 | ); |
| 181 | } |
| 182 | if (CONTROL_CHARS_PATTERN.test(segment)) { |
| 183 | throw new MemoryCommandError( |
| 184 | `Invalid memory path '${virtualPath}': control characters are not allowed` |
| 185 | ); |
| 186 | } |
| 187 | // Paths are rendered into prompt context (the memory tool's index and |
| 188 | // the <hot_memories> block): names containing XML metacharacters could |
| 189 | // reassemble structure-breaking markup across segments (e.g. 'a<' + |
no test coverage detected