Skip to content

fix(copilot): redact OAuth error response body in fetchJson error messages#102953

Merged
steipete merged 3 commits into
openclaw:mainfrom
ZOOWH:fix/copilot-oauth-error-body
Jul 10, 2026
Merged

fix(copilot): redact OAuth error response body in fetchJson error messages#102953
steipete merged 3 commits into
openclaw:mainfrom
ZOOWH:fix/copilot-oauth-error-body

Conversation

@ZOOWH

@ZOOWH ZOOWH commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

GitHub Copilot's shared OAuth JSON request helper embedded the complete non-2xx response body in an Error. A hostile or unexpectedly verbose OAuth endpoint could therefore retain up to 16 MiB in an error/log path and expose credentials returned in JSON fields.

The affected helper owns four operations: device-code creation, device-token polling, Copilot-token exchange, and optional model-list discovery.

Why This Change Was Made

The final implementation routes the shared fetchJson non-OK branch through the existing assertOkOrThrowProviderError boundary. That canonical helper already owns bounded error-prefix reads, overflow cancellation, secret redaction, structured code/type/request-id extraction, and the ProviderHttpError shape.

Successful response parsing is unchanged. Optional model-list failures still return the static fallback, and the separate model-policy request remains unchanged because it never formats the response body and already cancels it.

Thanks @ZOOWH for identifying the leak and supplying the initial patch.

User Impact

Copilot login and token-refresh failures retain actionable HTTP status, provider code, and request-id metadata without embedding the full raw response or credential values. Oversized error bodies are cancelled after a small diagnostic prefix instead of being buffered to the successful-response limit.

Evidence

Exact reviewed head: 66fa0e5209b16f1749a368d28bea6aea0dded652

  • Blacksmith Testbox through Crabbox tbx_01kx4vws97n2fs1esghbxmxg29: src/llm/utils/oauth/github-copilot.test.ts, 17/17 passed.
  • Real Node loopback proof on the same Testbox exercised all four operations. Each endpoint streamed a 128 KiB secret-bearing JSON failure; the client cancelled every response after 17,590 bytes, emitted ProviderHttpError, preserved status/code/request-id, and omitted the full synthetic credential from both message and errorBody.
  • check:changed passed the core and coreTests lanes on the same Testbox, including production/test tsgo and oxlint.
  • GitHub Actions CI run 29063528289: 65 successful, 31 skipped, 1 neutral, zero failed or pending checks on the exact head.
  • Fresh autoreview against current origin/main: clean, correctness 0.88.
  • Official contract check: GitHub's device-flow documentation documents the JSON device-code/token responses and polling error codes used by these callers.

No production GitHub credential was needed for the failure proof; the loopback server deterministically exercised oversized and secret-bearing responses without exposing a real account token.

  • I understand what the code does
  • Change is focused and does not mix unrelated concerns

…sages

Replace raw response body text with bounded, redacted structured error detail
extracted via extractProviderErrorDetail so OAuth error responses containing
tokens, device codes, or other sensitive fields are not leaked into Error
messages and downstream logs.
@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 9, 2026, 12:57 PM ET / 16:57 UTC.

Summary
The PR changes GitHub Copilot OAuth non-OK fetchJson errors to use shared redacted provider error details and adds regression tests for token refresh, device-code, structured JSON, and non-JSON error bodies.

PR surface: Source +2, Tests +86. Total +88 across 2 files.

Reproducibility: yes. for source-level review: current main embeds non-OK Copilot OAuth response text in the thrown error, and the PR body includes wrapper-level local-server proof through refreshGitHubCopilotToken. I did not run tests in this read-only cleanup pass.

Review metrics: 1 noteworthy metric.

  • Copilot non-OK reach: 4 operations through 1 helper. Device-code, device-token, token-refresh, and model-list failures all share the changed fetchJson branch, so the focused change covers the central Copilot OAuth error surface.

Stored data model
Persistent data-model change detected: serialized state: src/llm/utils/oauth/github-copilot.test.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Risk before merge

  • [P1] The branch is behind current main even though GitHub reports it mergeable, so ordinary exact-head merge gates or a refresh should run before landing.

Maintainer options:

  1. Decide the mitigation before merge
    Land the focused helper swap after ordinary maintainer review and exact-head gates, keeping the shared provider error-detail helper as the single Copilot OAuth non-OK error formatter.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] No repair lane is needed while the PR has sufficient proof and no concrete review finding; maintainer review and exact-head merge gates are the remaining path.

Security
Cleared: The diff reduces sensitive OAuth error-body exposure by reusing an existing bounded redaction helper and does not touch dependencies, workflows, package metadata, permissions, or secret storage.

Review details

Best possible solution:

Land the focused helper swap after ordinary maintainer review and exact-head gates, keeping the shared provider error-detail helper as the single Copilot OAuth non-OK error formatter.

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

Yes for source-level review: current main embeds non-OK Copilot OAuth response text in the thrown error, and the PR body includes wrapper-level local-server proof through refreshGitHubCopilotToken. I did not run tests in this read-only cleanup pass.

Is this the best way to solve the issue?

Yes: reusing extractProviderErrorDetail at the single Copilot fetchJson non-OK branch is the narrowest maintainable fix. A broader conversion to ProviderHttpError could add metadata, but it is not required to fix this leak and would expand the behavior change.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 372b527da4a1.

Label changes

Label justifications:

  • P2: This is normal-priority Copilot OAuth sensitive-error hardening with a narrow provider-specific blast radius and no active outage evidence.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes terminal live proof from a local HTTP server through refreshGitHubCopilotToken, showing the after-fix error omits raw JSON keys and the synthetic token; future posted proof should stay redacted.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes terminal live proof from a local HTTP server through refreshGitHubCopilotToken, showing the after-fix error omits raw JSON keys and the synthetic token; future posted proof should stay redacted.
Evidence reviewed

PR surface:

Source +2, Tests +86. Total +88 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 5 3 +2
Tests 1 86 0 +86
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 91 3 +88

What I checked:

Likely related people:

What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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.

How this review workflow works
  • 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.
Review history (4 earlier review cycles)
  • reviewed 2026-07-09T15:39:46.654Z sha 5bfd460 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-09T16:13:09.253Z sha 8787129 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-09T16:30:10.286Z sha 8787129 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-09T16:49:31.892Z sha 8787129 :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jul 9, 2026
Add regression tests covering the device code flow and non-JSON error
bodies alongside the existing token refresh coverage. Also include live
proof output showing real Response object redaction via
extractProviderErrorDetail.

Ref: openclaw#102953
@ZOOWH

ZOOWH commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Added live proof via node --import tsx calling real extractProviderErrorDetail with a Response object containing a simulated OAuth token response. Proof shows:

  • Before: raw body {"error":"invalid_grant","error_description":"...","refresh_token":"ghr_abc123secret"} was embedded in error
  • After: redacted detail The refresh *** is invalid or expired. [code=invalid_grant]

Added 3 new regression tests: device code flow, non-JSON body handling, and token refresh path. Total 17 tests pass.

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@ZOOWH

ZOOWH commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Added wrapper-level live proof: local HTTP server + refreshGitHubCopilotToken → error message verified free of raw JSON keys and tokens.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 9, 2026
@steipete steipete self-assigned this Jul 10, 2026
@steipete

Copy link
Copy Markdown
Contributor

Maintainer fixup and land-ready proof complete for exact head 66fa0e5209b16f1749a368d28bea6aea0dded652.

  • Replaced the one-off Copilot formatter with canonical assertOkOrThrowProviderError; all four JSON operations now share bounded reads, redaction, metadata, and ProviderHttpError.
  • Blacksmith Testbox through Crabbox tbx_01kx4vws97n2fs1esghbxmxg29: focused OAuth test file passed 17/17.
  • Real loopback proof on that Testbox streamed a 128 KiB secret-bearing failure from each operation. Device-code, device-token, Copilot-token, and model-list responses were all cancelled after 17,590 bytes; propagated errors retained status/code/request-id without the full synthetic credential.
  • check:changed passed core and coreTests, including production/test tsgo and oxlint.
  • Exact-head CI run 29063528289: 65 success, 31 skipped, 1 neutral, no failures or pending checks.
  • Fresh exact-branch autoreview: clean, correctness 0.88.

No production credential was used; GitHub's documented device-flow JSON/error contract plus the real oversized loopback responses cover the failure boundary deterministically.

@steipete
steipete merged commit 6b90610 into openclaw:main Jul 10, 2026
103 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Merged via squash.

ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
Replace raw response body and URL in postJson and refreshAnthropicToken
error messages with bounded, redacted structured error detail via
extractProviderErrorDetail. Remove TOKEN_URL from the outer catch to
prevent OAuth parameter leakage in logs.

Follow-up to openclaw#102953
ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
…rror

Replace manual response.text() + throw Error with the canonical
assertOkOrThrowProviderError which provides bounded reads, redaction,
and structured ProviderHttpError metadata.

Ref: openclaw#102953
ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
Replace raw response body and URL in postJson and refreshAnthropicToken
error messages with bounded, redacted structured error detail via
extractProviderErrorDetail. Remove TOKEN_URL from the outer catch to
prevent OAuth parameter leakage in logs.

Follow-up to openclaw#102953
ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
Replace raw response.text() + throw Error with createProviderHttpError
for non-404 batch create errors, providing bounded reads and redaction.
Preserve the helpful 404 diagnostic message.

Ref: openclaw#102953
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 10, 2026
…sages (openclaw#102953)

* fix(copilot): redact OAuth error response body in fetchJson error messages

Replace raw response body text with bounded, redacted structured error detail
extracted via extractProviderErrorDetail so OAuth error responses containing
tokens, device codes, or other sensitive fields are not leaked into Error
messages and downstream logs.

* test(copilot): add device-code and non-JSON error body redaction cases

Add regression tests covering the device code flow and non-JSON error
bodies alongside the existing token refresh coverage. Also include live
proof output showing real Response object redaction via
extractProviderErrorDetail.

Ref: openclaw#102953

* fix(copilot): normalize OAuth HTTP failures

---------

Co-authored-by: Peter Steinberger <[email protected]>
ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
…ponse body

Move the non-OK check before readResponseWithLimit so the provider error
extractor can read the response body. Previously extractProviderErrorDetail
ran after the body was consumed, silently losing all structured error
metadata.

Ref: openclaw#102953
ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
…efresh

Replace readResponseTextLimited + raw body in error message with
extractProviderErrorDetail for bounded, redacted structured error detail.

Ref: openclaw#102953
ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
Replace raw response.text() + throw Error with createProviderHttpError
for non-404 batch create errors, providing bounded reads and redaction.
Preserve the helpful 404 diagnostic message.

Ref: openclaw#102953
steipete pushed a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
…efresh

Replace readResponseTextLimited + raw body in error message with
extractProviderErrorDetail for bounded, redacted structured error detail.

Ref: openclaw#102953
steipete pushed a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
…efresh

Replace readResponseTextLimited + raw body in error message with
extractProviderErrorDetail for bounded, redacted structured error detail.

Ref: openclaw#102953
steipete added a commit that referenced this pull request Jul 10, 2026
* fix(chutes): redact OAuth error response body in token exchange and refresh

Replace readResponseTextLimited + raw body in error message with
extractProviderErrorDetail for bounded, redacted structured error detail.

Ref: #102953

* fix(chutes): update test assertions for extractProviderErrorDetail message format

* fix(chutes): remove unused readResponseTextLimited and CHUTES_OAUTH_ERROR_BODY_LIMIT_BYTES

* fix(chutes): normalize OAuth HTTP failures

* test(chutes): assert redaction without fixed mask

---------

Co-authored-by: Peter Steinberger <[email protected]>
ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
…rror

Replace manual response.text() + throw Error with the canonical
assertOkOrThrowProviderError which provides bounded reads, redaction,
and structured ProviderHttpError metadata.

Ref: openclaw#102953
ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
Replace raw response.text() with extractProviderErrorDetail for bounded,
redacted structured error detail in the token exchange non-OK path.

Ref: openclaw#102953
ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
Replace raw response.text() with extractProviderErrorDetail for bounded,
redacted structured error detail in the token refresh non-OK path.

Ref: openclaw#102953
ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
Replace raw response body and URL in postJson and refreshAnthropicToken
error messages with bounded, redacted structured error detail via
extractProviderErrorDetail. Remove TOKEN_URL from the outer catch to
prevent OAuth parameter leakage in logs.

Follow-up to openclaw#102953
ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 10, 2026
…ponse body

Move the non-OK check before readResponseWithLimit so the provider error
extractor can read the response body. Previously extractProviderErrorDetail
ran after the body was consumed, silently losing all structured error
metadata.

Ref: openclaw#102953
ZOOWH added a commit to ZOOWH/openclaw that referenced this pull request Jul 11, 2026
Replace raw response.text() with extractProviderErrorDetail for bounded,
redacted structured error detail in the token refresh non-OK path.

Ref: openclaw#102953
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 11, 2026
* fix(chutes): redact OAuth error response body in token exchange and refresh

Replace readResponseTextLimited + raw body in error message with
extractProviderErrorDetail for bounded, redacted structured error detail.

Ref: openclaw#102953

* fix(chutes): update test assertions for extractProviderErrorDetail message format

* fix(chutes): remove unused readResponseTextLimited and CHUTES_OAUTH_ERROR_BODY_LIMIT_BYTES

* fix(chutes): normalize OAuth HTTP failures

* test(chutes): assert redaction without fixed mask

---------

Co-authored-by: Peter Steinberger <[email protected]>
wheakerd pushed a commit to wheakerd/clawdbot that referenced this pull request Jul 15, 2026
…sages (openclaw#102953)

* fix(copilot): redact OAuth error response body in fetchJson error messages

Replace raw response body text with bounded, redacted structured error detail
extracted via extractProviderErrorDetail so OAuth error responses containing
tokens, device codes, or other sensitive fields are not leaked into Error
messages and downstream logs.

* test(copilot): add device-code and non-JSON error body redaction cases

Add regression tests covering the device code flow and non-JSON error
bodies alongside the existing token refresh coverage. Also include live
proof output showing real Response object redaction via
extractProviderErrorDetail.

Ref: openclaw#102953

* fix(copilot): normalize OAuth HTTP failures

---------

Co-authored-by: Peter Steinberger <[email protected]>
(cherry picked from commit 6b90610)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants