hcengineering/platform · warning

Cache path is outside of cache directory

Error message

Cache path is outside of cache directory

What it means

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.

Source

Thrown at pods/preview/src/cache.ts:179

  async delete (key: string): Promise<void> {
    this.cache.delete(key)
  }

  private getFilePath (key: string): string {
    if (key.length === 0) {
      throw new Error('Key cannot be empty')
    }

    if (key.includes('..') || key.includes('./') || key.includes('/.')) {
      throw new Error('Key contains invalid path sequences')
    }

    key = key.replace(/[^a-zA-Z0-9-_/]/g, '_')
    const path = join(this.cachePath, key)

    if (!this.isPathWithinCache(path)) {
      throw new Error('Cache path is outside of cache directory')
    }

    return path
  }

  private isPathWithinCache (filePath: string): boolean {
    const normalizedPath = resolve(normalize(filePath))
    const relativePath = relative(this.cachePath, normalizedPath)

    // If the relative path starts with '..', it's outside the cache directory
    return !relativePath.startsWith('..') && !isAbsolute(relativePath)
  }
}

export async function streamToBuffer (data: Buffer | Readable): Promise<Buffer> {
  if (Buffer.isBuffer(data)) {
    return data
  }

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Ensure cachePath is an absolute, normalized directory (resolve it once at construction)
  2. Always pass hashed keys (hex digests) so keys cannot influence path structure
  3. Verify no symlinks inside the cache directory point outside; resolve and compare realpath
  4. Log the offending key and path to identify the escape vector

Example fix

// before
this.cachePath = config.cachePath // relative: 'cache'
// after
this.cachePath = resolve(config.cachePath)
// plus always hash keys
const key = createHash('sha256').update(rawKey).digest('hex')
Defensive patterns

Strategy: validation

Validate before calling

const cachePath = resolve(config.cacheDir)
if (!cachePath.startsWith(resolve(os.tmpdir())) && !fs.existsSync(cachePath)) throw new Error('bad cache dir')

Try / catch

try {
  await cache.put(key, value)
} catch (e) {
  if (e.message === 'Cache path is outside of cache directory') {
    console.error('cache escape attempt', e)
    return null
  }
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/a106bab21d9fce07. Report an issue: GitHub.