{"record":{"id":"a106bab21d9fce07","repo":"hcengineering/platform","slug":"cache-path-is-outside-of-cache-directory","errorCode":null,"errorMessage":"Cache path is outside of cache directory","messagePattern":"Cache path is outside of cache directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pods/preview/src/cache.ts","lineNumber":179,"sourceCode":"\n  async delete (key: string): Promise<void> {\n    this.cache.delete(key)\n  }\n\n  private getFilePath (key: string): string {\n    if (key.length === 0) {\n      throw new Error('Key cannot be empty')\n    }\n\n    if (key.includes('..') || key.includes('./') || key.includes('/.')) {\n      throw new Error('Key contains invalid path sequences')\n    }\n\n    key = key.replace(/[^a-zA-Z0-9-_/]/g, '_')\n    const path = join(this.cachePath, key)\n\n    if (!this.isPathWithinCache(path)) {\n      throw new Error('Cache path is outside of cache directory')\n    }\n\n    return path\n  }\n\n  private isPathWithinCache (filePath: string): boolean {\n    const normalizedPath = resolve(normalize(filePath))\n    const relativePath = relative(this.cachePath, normalizedPath)\n\n    // If the relative path starts with '..', it's outside the cache directory\n    return !relativePath.startsWith('..') && !isAbsolute(relativePath)\n  }\n}\n\nexport async function streamToBuffer (data: Buffer | Readable): Promise<Buffer> {\n  if (Buffer.isBuffer(data)) {\n    return data\n  }","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/pods/preview/src/cache.ts#L161-L197","documentation":"Error thrown by getFilePath as a final containment check: after sanitizing the key and joining it to cachePath, isPathWithinCache verifies the resolved path stays inside the cache directory; if not, the operation is rejected. This is a defense-in-depth guard against symlink or join-based escapes.","triggerScenarios":"A crafted key whose sanitized form still resolves outside cachePath (e.g. via absolute-path segments or symlinks inside the cache dir pointing outward), or a misconfigured this.cachePath that makes even normal keys resolve outside.","commonSituations":"cachePath configured with a trailing mismatch or relative path such that join() produces unexpected results; symlinked subdirectories in the cache; adversarial keys supplied by an attacker with control over key input.","solutions":["Ensure cachePath is an absolute, normalized directory (resolve it once at construction)","Always pass hashed keys (hex digests) so keys cannot influence path structure","Verify no symlinks inside the cache directory point outside; resolve and compare realpath","Log the offending key and path to identify the escape vector"],"exampleFix":"// before\nthis.cachePath = config.cachePath // relative: 'cache'\n// after\nthis.cachePath = resolve(config.cachePath)\n// plus always hash keys\nconst key = createHash('sha256').update(rawKey).digest('hex')","handlingStrategy":"validation","validationCode":"const cachePath = resolve(config.cacheDir)\nif (!cachePath.startsWith(resolve(os.tmpdir())) && !fs.existsSync(cachePath)) throw new Error('bad cache dir')","typeGuard":null,"tryCatchPattern":"try {\n  await cache.put(key, value)\n} catch (e) {\n  if (e.message === 'Cache path is outside of cache directory') {\n    console.error('cache escape attempt', e)\n    return null\n  }\n  throw e\n}","preventionTips":["Configure cachePath as an absolute resolved directory","Use hashed keys only (hex digests)","Check for symlinks in the cache dir that point outside","Keep the containment check (isPathWithinCache) and never bypass it"],"tags":["security","path-traversal","cache","filesystem"],"backgroundTag":"path-traversal","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}