Skip to content

feat(browser): capture ChatGPT's own conversation record as evidence - #399

Open
frontierkodiak wants to merge 4 commits into
steipete:mainfrom
frontierkodiak:provider-native-capture
Open

feat(browser): capture ChatGPT's own conversation record as evidence#399
frontierkodiak wants to merge 4 commits into
steipete:mainfrom
frontierkodiak:provider-native-capture

Conversation

@frontierkodiak

Copy link
Copy Markdown
Contributor

What

Oracle's answer capture is a rendering of what ChatGPT displayed. For most answers that is the same thing as the answer; for notation it is not.

On a live Pro run in a ChatGPT project, the captured Markdown for a math-heavy turn differed from the provider's own record of that same turn:

  • \tfrac{1}{2}\,\Gamma\tfrac{1}{2},\Gamma (escape dropped)
  • \mathcal{F}_s\mathcal{F}*s, \sum_{n=0}\sum*{n=0}
  • \(…\) → bare parens

No fallback fired — this was the preferred copy-button path — and every keyword-level check passed. The text simply was not what the model wrote. A sentinel built from "are the markers present" structurally cannot see a transformation that preserves markers, and notation is where those hide.

Approach

--browser-capture-provider-native (off by default) additionally fetches /backend-api/conversation/<id> from inside the authenticated page and saves two files beside the run's other artifacts:

  • the conversation document, verbatim — written from the same string that was hashed, with no parse-and-reserialize in between;
  • an evidence file from a second, independent fetch, normalized and hashed in the page so only digests cross the boundary. Node never sees the second body, so a mistake on this side cannot make the two agree by construction.

The run's own answer is then compared against those digests and recorded as matched / divergent / unknown. That replaces a length heuristic with the provider's bytes.

It discriminates rather than rubber-stamps: on the same conversation, a math-heavy turn reports divergent while a prose follow-up reports matched.

Deliberate limits

  • Capture never gates an answer. /backend-api/* sits behind bot mitigation that can return 403 to an in-page fetch while the user is logged in, so every failure is a typed reason and a normal result. A conversation with no id — a temporary chat, or a run whose URL never settled — is unavailable, not an error.
  • Document-level hashes are recorded, never gated. The backend document carries nested metadata that changes between fetches at identical turn content; the same conversation captured twice produced both true and false. The per-turn comparison is the load-bearing one.
  • The bearer token from /api/auth/session is used in the page and never returned or logged, per the existing note in navigation.ts.
  • Payload is drained in bounded chunks with a ceiling and a timeout, and exceptionDetails is checked, so an in-page throw is reported rather than collapsed into an empty result.
  • Capture is invoked at each terminal producer (fresh-run and deep-research, local and remote) rather than inside one closure.

Tests

The in-page normalization is checked byte-for-byte against an independent reference implementation, over a fixture whose expected digests that reference produced — covering text, code, thoughts, reasoning_recap, multimodal_text, and the unknown-content-type JSON fallback, including where Python's sort_keys/ensure_ascii dumping and its int-vs-float rendering diverge from JSON.stringify. Plus the failure paths: no conversation id, a bot-mitigation challenge, and an in-page exception.

Full suite green: 1766 passed / 43 skipped; docs check ok.

Note

Enabling this over the bridge additionally needs captureProviderNative on the accepted client-config list; that is one line, and it lands with #398 rather than here to keep the two reviewable apart.

Oracle's answer capture is a rendering of what ChatGPT displayed. For most
answers that is the same thing as the answer; for notation it is not. On a live
Pro run in a ChatGPT project, the captured Markdown for a math-heavy turn
differed from the provider's own record of that same turn: `\,` lost its
backslash and `\mathcal{F}_s` came back as `\mathcal{F}*s`. Nothing failed, no
fallback fired, and every keyword-level check passed — the text simply was not
what the model wrote.

`--browser-capture-provider-native` (off by default) additionally fetches
`/backend-api/conversation/<id>` from inside the authenticated page and saves two
files beside the run's other artifacts:

- the conversation document, verbatim — the bytes are written from the same
  string that was hashed, with no parse-and-reserialize in between;
- an evidence file from a SECOND, independent fetch, normalized and hashed in the
  page so only digests cross the boundary. Node never sees the second body, so a
  mistake on this side cannot make the two agree by construction.

The run's own answer is then compared to those digests, and the result recorded
as matched / divergent / unknown. That replaces a length heuristic with the
provider's bytes: "this transcript is the provider's text" becomes checkable
rather than assumed.

Deliberate limits:
- Capture never gates an answer. `/backend-api/*` sits behind bot mitigation that
  can return 403 to an in-page fetch while the user is logged in, so every
  failure is a typed reason and a normal result. A conversation with no id —
  temporary chats, or a run whose URL never settled — is `unavailable`, not an
  error.
- Document-level hashes of the two fetches are recorded, never gated: the backend
  document carries volatile nested metadata and can differ between fetches at
  identical turn content. The per-turn comparison is the load-bearing one.
- The bearer token from /api/auth/session is used in the page and never returned
  or logged, per the existing note in navigation.ts.
- Payload is drained in bounded chunks with a ceiling and a timeout, and
  `exceptionDetails` is checked, so an in-page throw is reported rather than
  collapsed into an empty result.

The in-page normalization is checked byte-for-byte against the reference
implementation it must agree with, over a fixture whose expected digests that
reference produced — including the JSON-fallback branches where Python's
sort_keys/ensure_ascii dumps and its int-vs-float rendering diverge from
JSON.stringify.

Claude-Session: https://claude.ai/code/session_01HsXirqcfqtr1Cae9zYCLDk
… not the second fetch

The evidence file reported the second fetch's document hash as though it
described the raw JSON sitting next to it. Verifiers use that field to confirm
the file on disk was not altered between capture and ingest, so pointing it at a
different fetch made the check fail for a reason unrelated to what it tests —
and fail intermittently, since the two fetches sometimes agree and sometimes do
not.

Both Quiet conversations were captured twice while working on this: the first
pass produced matching document hashes for one and differing hashes for the
other; the second pass produced differing hashes for both, over identical turn
content. That is the nested-metadata volatility this format already expects, and
it is exactly why document-level equality is a poor fidelity criterion.

`raw_backend_api_json` now describes the document actually on disk. The second
fetch moves to its own `independent_fetch` block, where its document hash is a
volatility record rather than a criterion, and its per-turn digests remain what
the fidelity comparison is built on.

Claude-Session: https://claude.ai/code/session_01HsXirqcfqtr1Cae9zYCLDk
The flag's plumbing landed with the capture feature but its option registration
did not, so the config field existed and nothing could set it from the command
line. Belongs squashed into the capture commit before this goes upstream.

Claude-Session: https://claude.ai/code/session_01HsXirqcfqtr1Cae9zYCLDk
@clawsweeper

clawsweeper Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@clawsweeper clawsweeper Bot added merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 18, 2026
@clawsweeper

clawsweeper Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed August 18, 2026, 3:04 PM ET / 19:04 UTC.

ClawSweeper review

What this changes

The PR adds an opt-in browser flag that fetches ChatGPT’s conversation document from the authenticated page, stores it as an artifact, and compares independently computed per-turn digests against Oracle’s captured answer.

Merge readiness

Blocked until real behavior proof is added - 12 items remain

Keep open: the feature has a direct remote data-export boundary and an incorrect evidence metadata flag that should be resolved before merge.

Priority: P1
Reviewed head: 09dc763714fe16df4ef055d0c42ea6dac5b7f789
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🧂 unranked krab (1/6) The patch has focused rationale and unit coverage, but it has two blocking defects and lacks real browser behavior proof.
Proof confidence 🧂 unranked krab (1/6) Needs real behavior proof before merge: The PR describes a live Pro result but provides no inspectable redacted runtime artifact, transcript, or recording showing after-fix capture behavior. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Patch quality 🦪 silver shellfish (2/6) Security review found an item that needs attention.

Verification

Check Result Evidence
Real behavior Needs proof Needs real behavior proof before merge: The PR describes a live Pro result but provides no inspectable redacted runtime artifact, transcript, or recording showing after-fix capture behavior. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed 8 items Provider-native capture writes complete conversation data: The new finalization path writes the raw authenticated conversation document into the session artifacts directory and returns it as a file artifact.
Remote service forwards browser configuration and registers artifacts: The service only strips cookie fields, forwards the remaining browser configuration to the browser runner, then registers every returned artifact for bridge transfer.
New setting enters remote payload shape: Remote payloads use BrowserSessionConfig, and this PR adds captureProviderNative to that shared configuration type.
Findings 2 actionable findings [P1] Keep provider-native export out of client-controlled bridge config
[P2] Mark the raw document as materialized
Security Needs attention Bridge clients can request raw conversation export: The new shared browser configuration field is accepted by the current remote configuration forwarding path, and capture returns the raw conversation as a transferable artifact.

How this fits together

Oracle browser mode drives an authenticated ChatGPT session and stores answers and session artifacts. This optional capture runs after an answer, fetches the provider’s conversation record, and emits raw/evidence artifacts plus a fidelity summary.

flowchart LR
  A[ChatGPT browser session] --> B[Captured answer]
  B --> C[Optional native capture]
  C --> D[Authenticated conversation fetch]
  D --> E[Raw conversation artifact]
  D --> F[Independent digest evidence]
  E --> G[Bridge artifact transfer]
  F --> H[Fidelity summary]
Loading

Decision needed

Question Recommendation
Should a bridge client credential be allowed to request transfer of the host account’s complete provider conversation document, or must this remain host-authorized only? Keep raw export host-authorized: Drop client-supplied native capture from remote requests unless a host-controlled policy explicitly enables it.

Why: The branch turns a browser configuration field into an export of authenticated conversation history through the bridge; this credential scope cannot be chosen safely by patch mechanics alone.

Before merge

  • Add real behavior proof - Needs real behavior proof before merge: The PR describes a live Pro result but provides no inspectable redacted runtime artifact, transcript, or recording showing after-fix capture behavior. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • Keep provider-native export out of client-controlled bridge config (P1) - BrowserSessionConfig is the remote payload type, and the server forwards non-cookie fields into browser execution before registering returned artifacts for transfer. This new field therefore lets a bridge client request the raw authenticated conversation document, including history beyond the answer it requested. Do not permit that export through ordinary client config without an explicit host authorization policy; the related open work at fix(serve)!: accept only conversation-scoped fields from remote clients #398 must not treat it as an ordinary conversation setting by default.
  • Mark the raw document as materialized (P2) - This file immediately writes capture.rawText to rawPath, then describes that same file and hash in the evidence document, but sets materialized_to_disk to false. A verifier that trusts the field will reject or misclassify a valid persisted document. Set it to true and cover the serialized evidence shape.
  • Resolve security concern: Bridge clients can request raw conversation export - The new shared browser configuration field is accepted by the current remote configuration forwarding path, and capture returns the raw conversation as a transferable artifact.
  • Resolve merge risk (P1) - A bridge bearer-token holder can enable this setting and receive a raw provider conversation artifact, expanding the token from requesting an answer to exporting conversation history.
  • Resolve merge risk (P1) - The evidence file incorrectly labels its persisted raw document as not materialized.
  • Resolve merge risk (P1) - No redacted real browser run demonstrates the enabled capture and a non-gating unavailable/challenged outcome.
  • Complete next step (P2) - Resolve the bridge credential scope before merge, then correct the evidence metadata and obtain redacted real browser proof.
  • Improve patch quality - Resolve the bridge export authorization decision and add a regression for the chosen boundary.
  • Improve patch quality - Correct and test the evidence document’s materialization metadata.
  • Improve patch quality - Post a redacted terminal transcript or artifact pair from an enabled browser run, including a normal unavailable/challenged result without affecting the answer.

Findings

  • [P1] Keep provider-native export out of client-controlled bridge config — src/sessionManager.ts:89
  • [P2] Mark the raw document as materialized — src/browser/chatgptConversation.ts:783-789
  • [high] Bridge clients can request raw conversation export — src/sessionManager.ts:89
Agent review details

Security

Needs attention: The new authenticated capture can expose full provider conversation data through the remote bridge unless its activation is host-authorized.

Review metrics

Metric Value Why it matters
Patch scope 1,143 added, 9 removed across 11 files The change introduces a substantial new authenticated capture and artifact subsystem.
Production versus test growth production +968, tests/fixtures +168, docs +7 Most added code implements the new capture boundary, while current tests do not exercise its artifact finalization or bridge exposure.

Merge-risk options

Maintainer options:

  1. Constrain remote activation (recommended)
    Make the raw conversation export host-authorized and add a regression proving a remote client cannot enable it by sending browser configuration.
  2. Accept an explicit bridge export scope
    If maintainers want bearer-token clients to receive complete conversation records, document that elevated scope and require an explicit operator opt-in.

Technical review

Best possible solution:

Keep local CLI capture opt-in, make remote export require an explicit host-controlled authorization policy, correct the materialization flag, and add a focused remote-boundary regression plus redacted browser proof.

Do we have a high-confidence way to reproduce the issue?

Yes — source establishes the path from remote browser configuration through capture to bridge-registered raw artifacts; this does not require a live credential to verify the boundary.

Is this the best way to solve the issue?

No — enabling the field through the existing bridge configuration path silently broadens credential scope; a host-controlled export permission is the safer design.

Full review comments:

  • [P1] Keep provider-native export out of client-controlled bridge config — src/sessionManager.ts:89
    BrowserSessionConfig is the remote payload type, and the server forwards non-cookie fields into browser execution before registering returned artifacts for transfer. This new field therefore lets a bridge client request the raw authenticated conversation document, including history beyond the answer it requested. Do not permit that export through ordinary client config without an explicit host authorization policy; the related open work at fix(serve)!: accept only conversation-scoped fields from remote clients #398 must not treat it as an ordinary conversation setting by default.
    Confidence: 0.98
  • [P2] Mark the raw document as materialized — src/browser/chatgptConversation.ts:783-789
    This file immediately writes capture.rawText to rawPath, then describes that same file and hash in the evidence document, but sets materialized_to_disk to false. A verifier that trusts the field will reject or misclassify a valid persisted document. Set it to true and cover the serialized evidence shape.
    Confidence: 0.99

Overall correctness: patch is incorrect
Overall confidence: 0.97

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 083bba7e61f4.

Labels

Label changes:

  • add P1: A remote client can turn a normal browser request into export of the authenticated account’s complete conversation record.
  • add merge-risk: 🚨 security-boundary: Merging adds a client-controlled path from authenticated ChatGPT data to bridge-transferable session artifacts.
  • add rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦪 silver shellfish.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR describes a live Pro result but provides no inspectable redacted runtime artifact, transcript, or recording showing after-fix capture behavior. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Label justifications:

  • P1: A remote client can turn a normal browser request into export of the authenticated account’s complete conversation record.
  • merge-risk: 🚨 security-boundary: Merging adds a client-controlled path from authenticated ChatGPT data to bridge-transferable session artifacts.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦪 silver shellfish.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR describes a live Pro result but provides no inspectable redacted runtime artifact, transcript, or recording showing after-fix capture behavior. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Evidence

Security concerns:

  • [high] Bridge clients can request raw conversation export — src/sessionManager.ts:89
    The new shared browser configuration field is accepted by the current remote configuration forwarding path, and capture returns the raw conversation as a transferable artifact.
    Confidence: 0.98

What I checked:

  • Provider-native capture writes complete conversation data: The new finalization path writes the raw authenticated conversation document into the session artifacts directory and returns it as a file artifact. (src/browser/chatgptConversation.ts:765, 09dc763714fe)
  • Remote service forwards browser configuration and registers artifacts: The service only strips cookie fields, forwards the remaining browser configuration to the browser runner, then registers every returned artifact for bridge transfer. (src/remote/server.ts:276, 09dc763714fe)
  • New setting enters remote payload shape: Remote payloads use BrowserSessionConfig, and this PR adds captureProviderNative to that shared configuration type. (src/sessionManager.ts:89, 09dc763714fe)
  • Evidence metadata contradicts persisted artifact: The evidence document says the raw backend JSON was not materialized even though the preceding code writes that exact raw document to disk. (src/browser/chatgptConversation.ts:787, 09dc763714fe)
  • Proof is mock-only: Tests cover normalizer fixtures and mocked Runtime failure outcomes, but the PR context contains no inspectable redacted browser run, artifact pair, or terminal transcript. (tests/browser/chatgptConversation.test.ts:24, 09dc763714fe)
  • Adjacent authorization work: The PR itself identifies the open allowlist work as required for bridge activation, but that work is not merged and the current service forwards non-cookie configuration. (src/remote/server.ts:302, 09dc763714fe)

Likely related people:

  • steipete: Recent current-history work covers browser configuration and related browser safety behavior. (role: recent browser configuration contributor; confidence: medium; commits: 3a185f55918a; files: src/browser/config.ts, src/browser/index.ts)
  • rtl-ai: Recent work on the remote serve lifecycle is directly adjacent to the configuration forwarding path. (role: recent remote-service contributor; confidence: medium; commits: 653c621b7ff7; files: src/remote/server.ts)
  • Trịnh Đức Hoàng: Introduced the secure bridge artifact-transfer area that now carries the new raw conversation artifact. (role: bridge artifact-transfer contributor; confidence: medium; commits: bda0326d43b0; files: src/remote/server.ts, src/remote/client.ts)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant