Skip to content

Commit 5364c62

Browse files
authored
Fix source-store citeKey path traversal
1 parent 23e0287 commit 5364c62

2 files changed

Lines changed: 8 additions & 5 deletions

File tree

src/services/source-store.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ const CITE_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
3737
function buildSourceFilePath(citeKey: string, sourcesDir: string): string {
3838
if (!CITE_KEY_PATTERN.test(citeKey)) {
3939
throw new Error(
40-
`Invalid citeKey '${citeKey}'. Only letters, numbers, underscores, and hyphens are allowed.`,
40+
`Invalid citeKey '${citeKey}'. Must start with a letter or number and then use only letters, numbers, underscores, or hyphens.`,
4141
);
4242
}
4343

4444
const baseDir = path.resolve(sourcesDir);
4545
const filePath = path.resolve(baseDir, `${citeKey}.md`);
46-
if (!filePath.startsWith(`${baseDir}${path.sep}`)) {
46+
const relative = path.relative(baseDir, filePath);
47+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
4748
throw new Error(`Resolved source path escapes sources directory for citeKey '${citeKey}'.`);
4849
}
4950

src/test/unit/source-store.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,11 @@ describe("storeSource", () => {
183183
});
184184

185185
it("rejects path traversal citeKey values", async () => {
186-
await expect(storeSource("../escape", TEST_URL, null, SOURCES_DIR)).rejects.toThrow(
187-
/Invalid citeKey/,
188-
);
186+
for (const key of ["../escape", "..\\escape", "/etc/passwd", "%2e%2e/escape", ""]) {
187+
await expect(storeSource(key, TEST_URL, null, SOURCES_DIR)).rejects.toThrow(
188+
/Invalid citeKey/,
189+
);
190+
}
189191
expect(fetchDocumentText).not.toHaveBeenCalled();
190192
expect(mockFs.writeFile).not.toHaveBeenCalled();
191193
});

0 commit comments

Comments
 (0)