paperclipai/paperclip · error

Invalid object key.

Error message

Invalid object key.

What it means

Thrown by normalizeStorageObjectKey when, after backslash-to-slash conversion and trimming, the key is empty or begins with a forward slash. An empty key has no target object; a leading slash would produce an absolute path that escapes the company-prefixed namespace. This is the first of two normalization guards inside the function.

Source

Thrown at cli/src/commands/worktree.ts:299

function resolveWorktreeStartPoint(explicit?: string): string | undefined {
  return explicit ?? nonEmpty(process.env.PAPERCLIP_WORKTREE_START_POINT) ?? undefined;
}

type ConfiguredStorage = {
  getObject(companyId: string, objectKey: string): Promise<Buffer>;
  putObject(companyId: string, objectKey: string, body: Buffer, contentType: string): Promise<void>;
};

function assertStorageCompanyPrefix(companyId: string, objectKey: string): void {
  if (!objectKey.startsWith(`${companyId}/`) || objectKey.includes("..")) {
    throw new Error(`Invalid object key for company ${companyId}.`);
  }
}

function normalizeStorageObjectKey(objectKey: string): string {
  const normalized = objectKey.replace(/\\/g, "/").trim();
  if (!normalized || normalized.startsWith("/")) {
    throw new Error("Invalid object key.");
  }
  const parts = normalized.split("/").filter((part) => part.length > 0);
  if (parts.length === 0 || parts.some((part) => part === "." || part === "..")) {
    throw new Error("Invalid object key.");
  }
  return parts.join("/");
}

function resolveLocalStoragePath(baseDir: string, objectKey: string): string {
  const resolved = path.resolve(baseDir, normalizeStorageObjectKey(objectKey));
  const root = path.resolve(baseDir);
  if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`)) {
    throw new Error("Invalid object key path.");
  }
  return resolved;
}

async function s3BodyToBuffer(body: unknown): Promise<Buffer> {

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Pass a non-empty relative key: normalizeStorageObjectKey('attachments/file.png').
  2. Trim and strip any leading slash before calling: key.replace(/^\/+/, '').
  3. Guard upstream: if (!key || !key.trim()) throw a clearer validation error first.

Example fix

// before
const key = normalizeStorageObjectKey(`/${userPath}`);
// after
const key = normalizeStorageObjectKey(userPath.replace(/^\/+/, ''));
Defensive patterns

Strategy: validation

Validate before calling

function safeRelativeKey(raw: string): string {
  const trimmed = raw.replace(/\\/g, '/').trim().replace(/^\/+/, '');
  if (!trimmed) throw new Error('Object key must not be empty');
  return trimmed;
}
// const key = safeRelativeKey(userPath);  // guarantees non-empty, no leading slash

Type guard

function isNormalizableObjectKey(raw: string): boolean {
  const n = raw.replace(/\\/g, '/').trim();
  return n.length > 0 && !n.startsWith('/');
}

Prevention

When it happens

Trigger: Calling normalizeStorageObjectKey (directly or via resolveLocalStoragePath / storage get-put) with '', ' ', '/abs/path', or a backslash-only input that trims to empty. The leading-slash branch rejects keys like '/foo/bar'.

Common situations: Constructing a key from an unset env var or empty form field. Concatenating path segments with a leading separator by mistake (`/${file}`). Receiving a Windows absolute path ('C:\...') that normalizes to a leading slash form.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/67db43c17da96088. Report an issue: GitHub.