{"record":{"id":"40cd11dea8b0b189","repo":"paperclipai/paperclip","slug":"codex-auth-cache-label-is-empty","errorCode":null,"errorMessage":"codex auth cache: ${label} is empty","messagePattern":"codex auth cache: (.+?) is empty","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapters/codex-local/src/server/codex-auth-cache.ts","lineNumber":72,"sourceCode":" * (`0`, `false`, `no`, or `off`).\n */\nexport function isCodexAuthCacheEnabled(env: NodeJS.ProcessEnv = process.env): boolean {\n  const raw = env[CODEX_AUTH_CACHE_OFF_SWITCH_ENV];\n  if (typeof raw !== \"string\") return true;\n  return !FALSY_ENV_RE.test(raw.trim());\n}\n\n/**\n * Sanitizes one raw value to a single safe path segment. Rejects an empty value,\n * a relative segment (`.` or `..`), a path separator (`/` or `\\`), and a NUL\n * byte, so the value can never become a path traversal. Returns the trimmed,\n * safe segment. The `label` names the value in the error message. (Security\n * condition 3.)\n */\nfunction toSafePathSegment(value: string, label: string): string {\n  const trimmed = typeof value === \"string\" ? value.trim() : \"\";\n  if (trimmed.length === 0) {\n    throw new Error(`codex auth cache: ${label} is empty`);\n  }\n  if (trimmed === \".\" || trimmed === \"..\") {\n    throw new Error(`codex auth cache: ${label} is a relative path segment`);\n  }\n  if (trimmed.includes(\"/\") || trimmed.includes(\"\\\\\") || trimmed.includes(\"\\0\")) {\n    throw new Error(`codex auth cache: ${label} contains a path separator`);\n  }\n  // Defense in depth: a safe segment is exactly its own basename. Anything else\n  // carries a separator or a relative segment the checks above must have caught.\n  if (path.basename(trimmed) !== trimmed) {\n    throw new Error(`codex auth cache: ${label} is not a single path segment`);\n  }\n  return trimmed;\n}\n\n/**\n * Sanitizes an `account_id` to one safe path segment. Rejects an empty value, a\n * relative segment (`.` or `..`), a path separator, and a NUL byte, so a raw","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/adapters/codex-local/src/server/codex-auth-cache.ts#L54-L90","documentation":"Thrown by toSafePathSegment in the codex auth cache when a value used as a path segment is empty after trimming. The cache stores per-identity Codex credentials under companies/<companyId>/codex-auth-cache/<accountId>/auth.json, so each segment must be a real basename; an empty value would collapse the path and is treated as a fail-loud security defect (Security condition 3).","triggerScenarios":"toCacheKey(accountId) is called with an empty/whitespace accountId (e.g. the auth.json had no account_id), or resolveCodexAuthCacheDir(env, companyId) is called with an empty companyId. The label in the message identifies which value was empty (\"account_id\" or \"companyId\").","commonSituations":"A Codex auth.json is an API-key-only file with no tokens.account_id, so the cache write path receives an empty accountId; or a code path constructs the cache dir with a missing company id during a test/migration.","solutions":["Ensure the Codex auth.json being cached has a non-empty tokens.account_id (re-run codex login so the account id is populated).","Ensure the companyId passed to resolveCodexAuthCacheDir is a real, non-empty company id.","If you intentionally have API-key-only credentials with no account_id, disable the cache via PAPERCLIP_CODEX_AUTH_CACHE=0 so the vend path is skipped."],"exampleFix":"// before\ntoCacheKey(auth.tokens?.account_id ?? \"\")\n// after — guard before caching\nconst accountId = auth.tokens?.account_id;\nif (accountId && accountId.trim()) { toCacheKey(accountId); }","handlingStrategy":"validation","validationCode":"function hasNonEmptySegment(value: unknown): value is string {\n  return typeof value === \"string\" && value.trim().length > 0;\n}\n// before caching\nif (!hasNonEmptySegment(accountId)) { /* skip cache vend, or disable cache */ }","typeGuard":"function isNonEmptySafeSegment(value: unknown): value is string {\n  if (typeof value !== \"string\") return false;\n  const t = value.trim();\n  return t.length > 0 && t !== \".\" && t !== \"..\" && !t.includes(\"/\") && !t.includes(\"\\\\\") && !t.includes(\"\\0\");\n}","tryCatchPattern":"try {\n  const key = toCacheKey(accountId);\n} catch (e) {\n  if (e instanceof Error && /codex auth cache: .* is empty/.test(e.message)) {\n    // credential has no account_id; skip the cache path, do not crash the run\n  } else throw e;\n}","preventionTips":["Validate account_id/companyId are non-empty opaque ids before they reach the cache layer.","For API-key-only auth.json (no account_id), set PAPERCLIP_CODEX_AUTH_CACHE=0.","Never feed test placeholder ids (\".\", \"\") into cache paths; assert in fixtures."],"tags":["codex","auth-cache","security","path-traversal","validation"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}