fix(security): path-traversal guards for chunkCache and fetch-chunk (CWE-22) - #2
fix(security): path-traversal guards for chunkCache and fetch-chunk (CWE-22)#2jamubc wants to merge 1 commit into
Conversation
…CWE-22)
- Add ^[a-f0-9]{8}$ format validation as the first check in getChunks()
so every caller is protected regardless of entry point.
- Add path.resolve + startsWith(CACHE_DIR + sep) containment check as
defense-in-depth.
- Remove silent fs.unlinkSync on parse errors in getChunks(); the previous
behaviour created a DELETE path-traversal primitive when cacheKey was
not validated upstream.
- Add cacheKey format validation at the entry of fetch-chunk.tool.ts.
- Add chunkCacheKey format validation in ask-qwen.tool.ts, which previously
bypassed the fetch-chunk check entirely.
There was a problem hiding this comment.
Code Review
This pull request introduces input validation for cache keys across tools and utility functions, ensuring they conform to an 8-character hexadecimal format to prevent path traversal vulnerabilities. It also adds a defense-in-depth path containment check in the chunk cache utility and removes the automatic file deletion on read errors to mitigate potential path-traversal deletion risks. The review feedback suggests a more robust and platform-independent way to perform the path containment check using path.relative instead of string concatenation.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const resolved = path.resolve(filePath); | ||
| if (!resolved.startsWith(path.resolve(CACHE_DIR) + path.sep)) { | ||
| Logger.debug(`Rejected cacheKey escaping CACHE_DIR: ${cacheKey}`); | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Using string concatenation (path.resolve(CACHE_DIR) + path.sep) for path containment checks can be fragile and may fail on certain platforms or edge cases (for example, if CACHE_DIR resolves to a root directory like / or C:\).
A more robust and standard way to verify that a resolved path remains within a target directory in Node.js is to use path.relative and check if the result starts with .. or is absolute.
| const resolved = path.resolve(filePath); | |
| if (!resolved.startsWith(path.resolve(CACHE_DIR) + path.sep)) { | |
| Logger.debug(`Rejected cacheKey escaping CACHE_DIR: ${cacheKey}`); | |
| return null; | |
| } | |
| const relative = path.relative(CACHE_DIR, filePath); | |
| if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) { | |
| Logger.debug(`Rejected cacheKey escaping CACHE_DIR: ${cacheKey}`); | |
| return null; | |
| } |
Summary
Closes the CWE-22 path-traversal vector in the chunk cache subsystem.
getChunks()— format validation (primary guard): adds a^[a-f0-9]{8}$regex check as the first statement. Any cacheKey that is not exactly 8 lowercase hex characters (i.e. a sha256 slice) is rejected immediately. This blocks../, absolute paths, and other traversal payloads at the source, protecting every caller regardless of entry point.getChunks()— path containment (defense-in-depth): addspath.resolve+startsWith(CACHE_DIR + sep)to verify the resolved file path stays insideCACHE_DIReven after format validation passes.getChunks()— remove DELETE primitive: thecatchblock previously calledfs.unlinkSync(filePath)on any parse error. With an unvalidated cacheKey this was a path-traversal-driven delete. The unlink is removed; the file is left in place on error.fetch-chunk.tool.ts— entry validation: adds the same^[a-f0-9]{8}$check at the tool boundary before callinggetChunks().ask-qwen.tool.ts— entry validation: adds format validation onchunkCacheKeyin the cache-continuation path, which previously bypassed thefetch-chunkcheck entirely.Test Plan
npm run lint(tsc --noEmit) passes clean../../etc,/etc/passwd,abcare rejected with a clear error message