{"record":{"id":"3759657a104022c9","repo":"paperclipai/paperclip","slug":"public-replay-contains-a-private-field","errorCode":null,"errorMessage":"Public replay contains a private field","messagePattern":"Public replay contains a private field","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/paperclip-runner/scripts/public-eval-viewer.mjs","lineNumber":162,"sourceCode":"        ![\"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      }\n    }\n  };\n  visit(payload);\n  for (const section of [\n    \"tools\",\n    \"authorization\",\n    \"control_plane\",\n    \"runner\",\n    \"state\",\n    \"traceability\",\n    \"parity\",\n  ]) {\n    if (\n      !Array.isArray(payload.view.evidence?.[section]) ||\n      payload.view.evidence[section].length\n    )","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/public-eval-viewer.mjs#L144-L180","documentation":"While recursing through object entries, visit() rejects any non-null child stored under a private field name matching ^(managedProfile|acpxProfile|providerTrace|mockState|stateHistory|trace|environment|env|apiKey|accessToken|password|secret)$ (case-insensitive). This is a final catch-all so private runtime state cannot be smuggled into the public replay even if the shape allowlists above were updated; violation throws \"Public replay contains a private field\".","triggerScenarios":"Calling validatePublicChatPayload with a payload where any nested object has a key like apiKey, env, trace, mockState, stateHistory, providerTrace, environment, accessToken, password, or secret (any casing) with a non-null value — e.g. a tool_activity item embedding call.environment or a run retaining trace objects.","commonSituations":"A new projected section includes debugging state (mockState/stateHistory) added for internal tooling; a turn item captures the call environment; an internal payload object was spread into the public payload carrying a trace field.","solutions":["Delete or null the offending private field in the projection step before validation.","Project each nested section explicitly (pick allowlisted keys) instead of spreading whole internal objects into the public payload.","Extend the public projector to cover the newly added section containing the private field.","Regenerate the payload and revalidate; ensure the field isn't reintroduced by a shared builder used for both internal and public payloads."],"exampleFix":"// before\nturnItems: internalItems.map((i) => ({ ...i, environment: i.env })),\n// after\nturnItems: internalItems.map(({ environment, ...rest }) => rest),","handlingStrategy":"validation","validationCode":"const PRIVATE_FIELD = /^(?:managedProfile|acpxProfile|providerTrace|mockState|stateHistory|trace|environment|env|apiKey|accessToken|password|secret)$/i;\nconst findPrivate = (v) => {\n  if (v && typeof v === \"object\") for (const [k, c] of Object.entries(v)) {\n    if (PRIVATE_FIELD.test(k) && c != null) return k;\n    const hit = findPrivate(c); if (hit) return hit;\n  }\n  return null;\n};\nif (findPrivate(payload)) console.warn(\"private field present:\", findPrivate(payload));","typeGuard":"const hasNoPrivateFields = (payload) => findPrivate(payload) === null;","tryCatchPattern":"try {\n  validatePublicChatPayload(payload);\n} catch (err) {\n  if (err.message === \"Public replay contains a private field\") {\n    throw new Error(\"A private field (trace/env/profile/credential-shaped key) is non-null in the payload; strip it in the projector\");\n  }\n  throw err;\n}","preventionTips":["Project payloads by explicitly picking allowlisted keys instead of spreading internal objects","Treat the private-field regex as private-by-default; never add such keys to public projections","Run the private-field walker over projected payloads in unit tests","Keep internal debugging state (mockState, stateHistory, trace) out of shared builders used for public output"],"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"}