{"record":{"id":"cf9f958644166f25","repo":"paperclipai/paperclip","slug":"public-replay-contains-a-private-session-identity","errorCode":null,"errorMessage":"Public replay contains a private session identity","messagePattern":"Public replay contains a private session identity","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/paperclip-runner/scripts/public-eval-viewer.mjs","lineNumber":146,"sourceCode":"    \"tools calls authorization control_plane runner state traceability parity\",\n  );\n  for (const check of payload.checks) {\n    fields(\n      check,\n      \"id kind passed detail evidenceRefs title description definition anchor\",\n    );\n    fields(check.definition, \"id kind\");\n    fields(check.anchor, \"kind id\");\n    if (check.evidenceRefs.length)\n      throw new Error(\"Public replay contains raw evidence references\");\n  }\n  const visit = (value, key = \"\") => {\n    if (typeof value === \"string\") {\n      if (\n        /(?:sessionId|providerSessionId)$/i.test(key) &&\n        ![\"public-report\", \"unknown\", \"redacted\"].includes(value)\n      )\n        throw new Error(\"Public replay contains a private session identity\");\n      for (const pattern of SECRET_TEXT) {\n        pattern.lastIndex = 0;\n        if (pattern.test(value))\n          throw new Error(\n            \"Public replay contains credential or private reference material\",\n          );\n      }\n    } else if (value && typeof value === \"object\") {\n      for (const [name, child] of Object.entries(value)) {\n        if (\n          /^(?:managedProfile|acpxProfile|providerTrace|mockState|stateHistory|trace|environment|env|apiKey|accessToken|password|secret)$/i.test(\n            name,\n          ) &&\n          child != null\n        )\n          throw new Error(\"Public replay contains a private field\");\n        visit(child, name);\n      }","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/public-eval-viewer.mjs#L128-L164","documentation":"The visit() walker in validatePublicChatPayload() recursively scans every string value in the payload. Any string stored under a key ending in sessionId or providerSessionId (case-insensitive) must be exactly \"public-report\", \"unknown\", or \"redacted\"; anything else means an internal session identifier survived projection, and \"Public replay contains a private session identity\" is thrown.","triggerScenarios":"Calling validatePublicChatPayload with a payload containing any nested {sessionId: \"<real-id>\"} or {providerSessionId: \"...\"} — e.g. turns, evidence calls, or run blocks that retained their original session IDs instead of redacting to \"public-report\"/\"unknown\"/\"redacted\".","commonSituations":"A newly added payload section (e.g. a new turn item or call field) includes raw sessionId and the projector doesn't touch it; the redaction allowlist words were changed; nested objects deep in the tree bypassed the projector's top-level redaction.","solutions":["Redact every sessionId/providerSessionId value in the projection to \"public-report\" (or \"unknown\"/\"redacted\") before validation.","Locate the offending key path and extend the projector to redact that nested section, not just the top-level view.","Confirm the projector's redaction matches the allowed values exactly (case-sensitive: \"public-report\", \"unknown\", \"redacted\").","Regenerate the payload with the fixed projector and revalidate."],"exampleFix":"// before\nitem: { id, sessionId: run.providerSessionId },\n// after\nitem: { id, sessionId: \"redacted\" },","handlingStrategy":"validation","validationCode":"const SESSION_OK = new Set([\"public-report\", \"unknown\", \"redacted\"]);\nconst walk = (v, key = \"\") => typeof v === \"string\"\n  ? (/(?:sessionId|providerSessionId)$/i.test(key) && !SESSION_OK.has(v) && console.warn(\"unredacted session id at\", key))\n  : v && typeof v === \"object\" && Object.entries(v).forEach(([k, c]) => walk(c, k));\nwalk(payload);","typeGuard":"const sessionIdsRedacted = (payload) => { let ok = true; const walk = (v, k = \"\") => { if (typeof v === \"string\") { if (/(?:sessionId|providerSessionId)$/i.test(k) && ![\"public-report\",\"unknown\",\"redacted\"].includes(v)) ok = false; } else if (v && typeof v === \"object\") Object.entries(v).forEach(([n, c]) => walk(c, n)); }; walk(payload); return ok; };","tryCatchPattern":"try {\n  validatePublicChatPayload(payload);\n} catch (err) {\n  if (err.message === \"Public replay contains a private session identity\") {\n    throw new Error(\"A nested sessionId survived projection; redact all sessionId/providerSessionId values before publishing\");\n  }\n  throw err;\n}","preventionTips":["Redact session IDs at the point of projection, not at publish time","Use a single redaction helper for every new payload section that could carry IDs","Test the walker against deeply nested fixtures containing real-looking session IDs","Keep the allowed sentinel values (public-report/unknown/redacted) in a shared constant"],"tags":["security","privacy","data-leak","validation"],"backgroundTag":"schema-validation-failed","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"}