TencentCloud/TencentDB-Agent-Memory · error

Path traversal rejected in scoped storage key: ${key}

Error message

Path traversal rejected in scoped storage key: ${key}

What it means

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.

Source

Thrown at MemoryCore/src/core/storage/adapter.ts:32

import type { IStorageBackend, StorageObject, ListEntry, ListObjectsOptions, ListResult, PutObjectOptions } from "./types.js";

class ScopedStorageBackend implements IStorageBackend {
  readonly type: "local" | "cos";
  private readonly prefix: string;

  constructor(private readonly base: IStorageBackend, prefix: string) {
    this.type = base.type;
    const normalized = prefix.replace(/^\/+/, "").replace(/\/+/g, "/");
    this.prefix = normalized && !normalized.endsWith("/") ? `${normalized}/` : normalized;
  }

  private key(key: string): string {
    if (typeof key !== "string" || key.includes("\0") || key.startsWith("/") || key.startsWith("\\")) {
      throw new Error(`Invalid scoped storage key: ${JSON.stringify(key)}`);
    }
    const normalized = key.replace(/^\/+/, "").replace(/\\+/g, "/").replace(/\/+/g, "/");
    if (normalized.split("/").some((part) => part === "..")) {
      throw new Error(`Path traversal rejected in scoped storage key: ${key}`);
    }
    return `${this.prefix}${normalized}`;
  }

  private unkey(key: string): string {
    return key.startsWith(this.prefix) ? key.slice(this.prefix.length) : key;
  }

  async putObject(key: string, content: string | Buffer, opts?: PutObjectOptions): Promise<void> {
    return this.base.putObject(this.key(key), content, opts);
  }

  async appendObject(key: string, content: string | Buffer): Promise<void> {
    return this.base.appendObject(this.key(key), content);
  }

  async getObject(key: string): Promise<StorageObject | null> {
    const obj = await this.base.getObject(this.key(key));

View on GitHub (pinned to 3efcd317b8)

Solutions

  1. Sanitize the key before use: resolve it and reject/strip any '..' segments
  2. Encode or hash user-supplied identifiers into safe key components (e.g. encodeURIComponent or an id)
  3. Use path.posix.normalize and verify the result stays inside the intended root
  4. Return a 400-class validation error to the caller instead of attempting the storage operation

Example fix

// before
await adapter.putObject(`files/${userPath}`, buf);
// after
const safe = path.posix.normalize(userPath).replace(/^(\.\.(\/|$))+/, '');
if (safe.split('/').includes('..')) throw new ValidationError('bad path');
await adapter.putObject(`files/${safe}`, buf);
Defensive patterns

Strategy: validation

Validate before calling

function sanitizeKey(input) {
  const normalized = String(input).replace(/\\+/g, '/').replace(/\/+/g, '/');
  if (normalized.split('/').some((p) => p === '..')) throw new Error(`path traversal rejected: ${input}`);
  return normalized.replace(/^\/+/, '');
}
await adapter.putObject(sanitizeKey(userPath), buf);

Type guard

const hasNoTraversal = (k) => !String(k).split('/').includes('..');

Try / catch

try {
  await adapter.obj(key);
} catch (e) {
  if (String(e.message).startsWith('Path traversal rejected')) {
    throw new ForbiddenError('key escapes storage scope');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling any scoped storage operation (putObject, obj, exists, deleteObject, etc.) with a key whose normalized segments include '..', such as '../escape' or 'a/../../b'.

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

Related errors


AI-assisted analysis of TencentCloud/TencentDB-Agent-Memory@3efcd317b8 (2026-09-01). Data as JSON: /api/errors/3873e2d667512c8f. Report an issue: GitHub.