{"record":{"id":"7fa5da6a74e00c02","repo":"paperclipai/paperclip","slug":"invalid-object-key-for-company-companyid","errorCode":null,"errorMessage":"Invalid object key for company ${companyId}.","messagePattern":"Invalid object key for company (.+?)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/src/commands/worktree.ts","lineNumber":292,"sourceCode":"  return value.startsWith(WORKTREE_NAME_PREFIX) ? value : `${WORKTREE_NAME_PREFIX}${value}`;\n}\n\nfunction resolveWorktreeHome(explicit?: string): string {\n  return explicit ?? process.env.PAPERCLIP_WORKTREES_DIR ?? DEFAULT_WORKTREE_HOME;\n}\n\nfunction resolveWorktreeStartPoint(explicit?: string): string | undefined {\n  return explicit ?? nonEmpty(process.env.PAPERCLIP_WORKTREE_START_POINT) ?? undefined;\n}\n\ntype ConfiguredStorage = {\n  getObject(companyId: string, objectKey: string): Promise<Buffer>;\n  putObject(companyId: string, objectKey: string, body: Buffer, contentType: string): Promise<void>;\n};\n\nfunction assertStorageCompanyPrefix(companyId: string, objectKey: string): void {\n  if (!objectKey.startsWith(`${companyId}/`) || objectKey.includes(\"..\")) {\n    throw new Error(`Invalid object key for company ${companyId}.`);\n  }\n}\n\nfunction normalizeStorageObjectKey(objectKey: string): string {\n  const normalized = objectKey.replace(/\\\\/g, \"/\").trim();\n  if (!normalized || normalized.startsWith(\"/\")) {\n    throw new Error(\"Invalid object key.\");\n  }\n  const parts = normalized.split(\"/\").filter((part) => part.length > 0);\n  if (parts.length === 0 || parts.some((part) => part === \".\" || part === \"..\")) {\n    throw new Error(\"Invalid object key.\");\n  }\n  return parts.join(\"/\");\n}\n\nfunction resolveLocalStoragePath(baseDir: string, objectKey: string): string {\n  const resolved = path.resolve(baseDir, normalizeStorageObjectKey(objectKey));\n  const root = path.resolve(baseDir);","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/cli/src/commands/worktree.ts#L274-L310","documentation":"Thrown by assertStorageCompanyPrefix when a storage object key does not start with `${companyId}/` or contains a '..' substring. This is a tenant-isolation guard: object keys must be namespaced under the owning company so that one company cannot read or write another company's storage prefix. The '..' check blocks trivial path-traversal escapes.","triggerScenarios":"Calling the worktree storage get/put flow (which calls assertStorageCompanyPrefix) with an objectKey that is unprefixed, prefixed with a different companyId, absolute, or contains '..'. Reached on every getObject/putObject invocation in the ConfiguredStorage adapter used by the worktree command.","commonSituations":"Caller building an object key from user input without prepending `${companyId}/`. Cross-company data migration that forgot the prefix. Bug where companyId is undefined so the prefix check string is 'undefined/'. Attempted traversal via '../../other-company/secret'.","solutions":["Ensure every object key is constructed as `${companyId}/${relativePath}` before passing to get/put.","Strip any leading slash from the relative path so the companyId prefix stays first.","Run normalizeStorageObjectKey on the relative portion first, then prefix with `${companyId}/`."],"exampleFix":"// before\nawait storage.putObject(companyId, attachmentPath, body, ct);\n// after\nconst safeKey = `${companyId}/${normalizeStorageObjectKey(attachmentPath)}`;\nawait storage.putObject(companyId, safeKey, body, ct);","handlingStrategy":"validation","validationCode":"function safeCompanyKey(companyId: string, relativePath: string): string {\n  const clean = relativePath.replace(/\\\\/g, '/').trim().replace(/^\\/+/, '');\n  if (!clean || clean.includes('..')) throw new Error('Invalid relative path');\n  return `${companyId}/${clean}`;\n}\n// const key = safeCompanyKey(companyId, userPath);  // always prefixed, never traverses","typeGuard":"function isCompanyPrefixedKey(companyId: string, objectKey: string): boolean {\n  return objectKey.startsWith(`${companyId}/`) && !objectKey.includes('..');\n}","tryCatchPattern":null,"preventionTips":["Always construct object keys as `${companyId}/${normalizedRelativePath}` at the call site.","Never accept raw user input as an object key — sanitize and prefix first.","Treat cross-company access as a security boundary; unit-test the prefix guard."],"tags":["security","path-traversal","storage","validation"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}