{"record":{"id":"77505d62fbd62691","repo":"paperclipai/paperclip","slug":"invalid-normalized-opencode-session-id","errorCode":null,"errorMessage":"Invalid normalized OpenCode session id","messagePattern":"Invalid normalized OpenCode session id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts","lineNumber":2483,"sourceCode":"    \"HTTP_PROXY\",\n    \"HTTPS_PROXY\",\n    \"NO_PROXY\",\n    \"ALL_PROXY\",\n    \"SSL_CERT_FILE\",\n    \"SSL_CERT_DIR\",\n    \"OPENROUTER_API_KEY\",\n  ];\n}\n\nfunction sessionRoot(\n  runtimeDirectory: string,\n  normalizedSessionId: string,\n): string {\n  const safe = normalizedSessionId\n    .replace(/[^a-zA-Z0-9._-]/g, \"_\")\n    .slice(0, 120);\n  if (!safe || safe === \".\" || safe === \"..\")\n    throw new Error(\"Invalid normalized OpenCode session id\");\n  return join(resolve(runtimeDirectory), safe);\n}\n\nfunction validateWorkspace(value: string): string {\n  const cwd = resolve(value);\n  if (!value.trim() || cwd === dirname(cwd))\n    throw new Error(\"OpenCode working directory must not be a filesystem root\");\n  return cwd;\n}\n\nfunction validModel(value: string): boolean {\n  const slash = value.indexOf(\"/\");\n  return slash > 0 && slash < value.length - 1;\n}\n\nfunction compareVersion(left: string, right: string): number {\n  const a = left.split(\".\").map(Number);\n  const b = right.split(\".\").map(Number);","sourceCodeStart":2465,"sourceCodeEnd":2501,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts#L2465-L2501","documentation":"Thrown when normalizing a provider session id into a filesystem-safe path segment under the runtime directory produces an empty string, '.', or '..' — i.e. the id is unusable as a directory/file name. This guards against escaping the runtime directory (path traversal) and against empty identifiers.","triggerScenarios":"normalizedSessionId contains only characters replaced by '_' mapping to '.'/'..' after sanitization, or is empty/whitespace — typically an unset or empty provider session id passed before the server assigned a real id.","commonSituations":"Calling session-path helpers before the first server response populated #providerSessionId; an upstream API change returning a different id field so undefined/empty is coerced into the sanitizer; ids crafted to collide with '..' for traversal.","solutions":["Ensure the provider session id is assigned (from the OpenCode server create-session response) before any code resolves paths from it.","Log the raw id passed in — if it is empty/undefined, fix the code path that reads the id field (possible API contract drift).","Treat this as a traversal defense: never bypass the sanitizer; validate the id upstream instead (require /^[a-zA-Z0-9._-]+$/).","If a placeholder path is needed before the id exists, generate a local uuid rather than passing an empty id."],"exampleFix":"// before\nconst dir = sessionPath(runtimeDir, providerSessionId ?? \"\");\n\n// after\nif (!providerSessionId || !/^[a-zA-Z0-9._-]+$/.test(providerSessionId)) {\n  throw new Error(\"Provider session id not yet assigned\");\n}\nconst dir = sessionPath(runtimeDir, providerSessionId);","handlingStrategy":"validation","validationCode":"if (!providerSessionId || !/^[a-zA-Z0-9._-]+$/.test(providerSessionId)) {\n  throw new Error(\"Provider session id missing or unsafe — cannot resolve runtime path\");\n}","typeGuard":"function isSafeId(v: unknown): v is string {\n  return typeof v === \"string\" && /^[a-zA-Z0-9._-]+$/.test(v) && !(v === \".\" || v === \"..\");\n}","tryCatchPattern":"try {\n  const dir = sessionPath(runtimeDir, providerSessionId);\n} catch (e) {\n  if (e instanceof Error && e.message === \"Invalid normalized OpenCode session id\") {\n    // id empty/unset — ensure create-session response was processed first\n  }\n}","preventionTips":["Only resolve runtime paths after the server has assigned a real session id","Validate ids upstream with a strict allowlist regex instead of relying on sanitization","Use a local uuid placeholder when a path is needed before the id exists"],"tags":["path-sanitization","identifier","validation","security"],"backgroundTag":"invalid-identifier-format","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}