{"record":{"id":"3873e2d667512c8f","repo":"TencentCloud/TencentDB-Agent-Memory","slug":"path-traversal-rejected-in-scoped-storage-key-k","errorCode":null,"errorMessage":"Path traversal rejected in scoped storage key: ${key}","messagePattern":"Path traversal rejected in scoped storage key: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"MemoryCore/src/core/storage/adapter.ts","lineNumber":32,"sourceCode":"import type { IStorageBackend, StorageObject, ListEntry, ListObjectsOptions, ListResult, PutObjectOptions } from \"./types.js\";\n\nclass ScopedStorageBackend implements IStorageBackend {\n  readonly type: \"local\" | \"cos\";\n  private readonly prefix: string;\n\n  constructor(private readonly base: IStorageBackend, prefix: string) {\n    this.type = base.type;\n    const normalized = prefix.replace(/^\\/+/, \"\").replace(/\\/+/g, \"/\");\n    this.prefix = normalized && !normalized.endsWith(\"/\") ? `${normalized}/` : normalized;\n  }\n\n  private key(key: string): string {\n    if (typeof key !== \"string\" || key.includes(\"\\0\") || key.startsWith(\"/\") || key.startsWith(\"\\\\\")) {\n      throw new Error(`Invalid scoped storage key: ${JSON.stringify(key)}`);\n    }\n    const normalized = key.replace(/^\\/+/, \"\").replace(/\\\\+/g, \"/\").replace(/\\/+/g, \"/\");\n    if (normalized.split(\"/\").some((part) => part === \"..\")) {\n      throw new Error(`Path traversal rejected in scoped storage key: ${key}`);\n    }\n    return `${this.prefix}${normalized}`;\n  }\n\n  private unkey(key: string): string {\n    return key.startsWith(this.prefix) ? key.slice(this.prefix.length) : key;\n  }\n\n  async putObject(key: string, content: string | Buffer, opts?: PutObjectOptions): Promise<void> {\n    return this.base.putObject(this.key(key), content, opts);\n  }\n\n  async appendObject(key: string, content: string | Buffer): Promise<void> {\n    return this.base.appendObject(this.key(key), content);\n  }\n\n  async getObject(key: string): Promise<StorageObject | null> {\n    const obj = await this.base.getObject(this.key(key));","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/3efcd317b84146d6a08518ac0f7ee7c8a8d200ec/MemoryCore/src/core/storage/adapter.ts#L14-L50","documentation":"ScopedStorageAdapter.key() rejects any key that, after normalizing separators and slashes, contains a '..' path segment. This prevents path traversal out of the adapter's prefix scope (e.g. reading '../../etc/passwd'), a security hardening measure.","triggerScenarios":"Calling any scoped storage operation (putObject, obj, exists, deleteObject, etc.) with a key whose normalized segments include '..', such as '../escape' or 'a/../../b'.","commonSituations":"Building keys from untrusted user input; joining relative paths computed with path.relative() which can yield '..'; archive extraction or upload handlers that don't sanitize filenames.","solutions":["Sanitize the key before use: resolve it and reject/strip any '..' segments","Encode or hash user-supplied identifiers into safe key components (e.g. encodeURIComponent or an id)","Use path.posix.normalize and verify the result stays inside the intended root","Return a 400-class validation error to the caller instead of attempting the storage operation"],"exampleFix":"// before\nawait adapter.putObject(`files/${userPath}`, buf);\n// after\nconst safe = path.posix.normalize(userPath).replace(/^(\\.\\.(\\/|$))+/, '');\nif (safe.split('/').includes('..')) throw new ValidationError('bad path');\nawait adapter.putObject(`files/${safe}`, buf);","handlingStrategy":"validation","validationCode":"function sanitizeKey(input) {\n  const normalized = String(input).replace(/\\\\+/g, '/').replace(/\\/+/g, '/');\n  if (normalized.split('/').some((p) => p === '..')) throw new Error(`path traversal rejected: ${input}`);\n  return normalized.replace(/^\\/+/, '');\n}\nawait adapter.putObject(sanitizeKey(userPath), buf);","typeGuard":"const hasNoTraversal = (k) => !String(k).split('/').includes('..');","tryCatchPattern":"try {\n  await adapter.obj(key);\n} catch (e) {\n  if (String(e.message).startsWith('Path traversal rejected')) {\n    throw new ForbiddenError('key escapes storage scope');\n  }\n  throw e;\n}","preventionTips":["Treat all user-derived key segments as untrusted; encode or hash identifiers","Reject '..' segments at the API boundary, not just at the storage layer","Prefer generated ids over filenames for user uploads","Add security tests with traversal payloads ('../..', '..\\\\', encoded variants)"],"tags":["security","path-traversal","storage","validation"],"backgroundTag":"path-traversal","analyzedSha":"3efcd317b84146d6a08518ac0f7ee7c8a8d200ec","analyzedAt":"2026-09-01T05:44:22.276Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}