Skip to content

fix(google): bound OAuth token error body reads to prevent OOM#98722

Closed
wings1029 wants to merge 2 commits into
openclaw:mainfrom
wings1029:fix/google-oauth-error-body-cap-v3
Closed

fix(google): bound OAuth token error body reads to prevent OOM#98722
wings1029 wants to merge 2 commits into
openclaw:mainfrom
wings1029:fix/google-oauth-error-body-cap-v3

Conversation

@wings1029

@wings1029 wings1029 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

The requestTokenGrant helper in the Google OAuth token exchange path read error response bodies via response.text() without any size cap. A misconfigured or malfunctioning Google OAuth endpoint (or intermediate proxy) that returns an oversized error body — e.g. a misrouted HTML page or a proxy error splash — could exhaust gateway memory before the error was surfaced.

The fetchWithTimeout wrapper in oauth.http.ts already bounds the response at 16 MiB, so the raw body cannot overflow memory. However, an unbounded 16 MiB error body can still be embedded verbatim into the downstream Error message, producing excessive log payloads and redundant memory pressure.

Why This Change Was Made

  • Error path (response.status !== 200): replace response.text() with readResponseTextLimited(response, 8 KiB). The cap is generous for a structured OAuth error payload (~100 bytes of JSON) while keeping a runaway HTML page from polluting downstream diagnostics.

  • Success path: replace the bare (await response.json()) as {…} cast with readProviderJsonResponse<T>(response, …). The upstream fetchWithTimeout already bounds the body at 16 MiB, so this is defense-in-depth type-safe parsing with a consistent provider error surface.

No new dependencies, APIs, config keys, or protocol changes.

User Impact

Normal OAuth errors (invalid grant, expired token, etc.) preserve their complete diagnostic payload. Oversized error streams are truncated at 8 KiB — the error message still identifies the failure, and the upstream 16 MiB bound ensures memory safety at the transport layer.

Evidence

Real behavior proof

  • Behavior addressed: unbounded response.text() on error path → bounded readResponseTextLimited(8 KiB)
  • Real environment tested: loopback HTTP server (node:http.createServer) streaming 1 MiB of chunked body over 127.0.0.1, driven through the exported exchangeCodeForTokens and refreshTokensForGeminiCli public APIs with mocked fetchWithTimeout
  • Exact steps:
    1. Start a loopback HTTP server that streams a 1 MiB error body (4 KiB chunks, 2 ms delay, no Content-Length)
    2. Mock fetchWithTimeout to return the loopback Response
    3. Call exchangeCodeForTokens("test-code", "test-verifier")
    4. Assert the error message is bounded (≈8 KiB text)
    5. Assert the stream was cancelled before the server finished writing (proof the read stopped early)
    6. Positive control: a small valid OAuth error body ({"error":"invalid_grant"}) is preserved exactly
    7. Both exchangeCodeForTokens (authorization code) and refreshTokensForGeminiCli (refresh) paths are covered
  • Observed result: [google oauth.token loopback proof] oversized path: cap=8192 streamed=1048576 message_length=8215
  • What was not tested: The full end-to-end path through Google's actual OAuth endpoint (SSRF guard + DNS pinning). The fetchWithTimeout wrapper already has an independent 16 MiB bound; this PR adds a tighter text cap at the call site.

Validation Evidence

pnpm test extensions/google/oauth.token.test.ts

✓ bounds oversized OAuth error body text
✓ preserves complete small error body text within the limit
✓ bounds oversized OAuth error body text during token refresh

Mutation proof:
  $ git stash      → 2 FAILED (old code buffers full body, stream not cancelled)
  $ git stash pop  → 3 PASSED

Security & Privacy / Compatibility

  • Cap value (8 KiB) consistent with existing conventions
  • readResponseTextLimited reads at most maxBytes from the stream; oversized bodies are truncated, not silently consumed
  • OAuth tokens, client secrets, and user credentials are not exposed in the capped error path
  • No config, dependency, protocol, or CHANGELOG.md changes

🤖 Generated with Claude Code

@clawsweeper

clawsweeper Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 1, 2026, 1:09 PM ET / 17:09 UTC.

Summary
The branch adds bounded HTTP response readers to Google OAuth token response parsing/error text and Discord gateway metadata materialization, with loopback tests for oversized bodies.

PR surface: Source +14, Tests +376. Total +390 across 4 files.

Reproducibility: yes. Current main and v2026.6.11 show the unbounded read sites, and the PR body plus added tests provide a loopback-server path for normal and oversized responses; I did not rerun tests in this read-only review.

Review metrics: 2 noteworthy metrics.

  • Response Caps Added: 2 added: 8 KiB Google error text, 4 MiB Discord metadata body. These thresholds are the main behavior change maintainers must accept before merge.
  • Related Open PRs: 4 open related PRs found. The same Google OAuth or Discord gateway metadata bounded-read work is being proposed in parallel, so canonical ownership matters.

Stored data model
Persistent data-model change detected: unknown-data-model-change: extensions/discord/src/monitor/gateway-metadata.test.ts, vector/embedding metadata: extensions/discord/src/monitor/gateway-metadata.test.ts, vector/embedding metadata: extensions/discord/src/monitor/gateway-metadata.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
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:

  • [P2] Inspect the check-test-types failure once logs are available and fix any patch-caused type error.
  • Resolve whether the Discord changes should be removed in favor of the existing proof-positive Discord PR.

Risk before merge

Maintainer options:

  1. Split Discord Overlap (recommended)
    Keep this branch focused on Google OAuth and let fix(discord): bound gateway metadata response reads at 16 MiB #97721 own the Discord gateway metadata cap and two-read-site coverage.
  2. Accept Combined Hardening
    Maintainers can intentionally land both Google and Discord here, but should own the 4 MiB Discord threshold and close or supersede the overlapping Discord PRs.
  3. Pause Until Canonicals Settle
    If the threshold and ownership are still uncertain, pause this PR until the Discord and broader OAuth bounded-read PRs resolve.

Next step before merge

  • No automated repair is queued; maintainers need to resolve the Discord overlap and inspect the current type-check failure before merge.

Security
Cleared: The diff reuses existing bounded HTTP readers, adds no dependency/workflow/secret surface, and reduces response-body resource-exhaustion exposure.

Review details

Best possible solution:

Keep this PR open until a linked canonical PR proves it covers this PR's unique work, or a maintainer confirms closure.

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

Yes. Current main and v2026.6.11 show the unbounded read sites, and the PR body plus added tests provide a loopback-server path for normal and oversized responses; I did not rerun tests in this read-only review.

Is this the best way to solve the issue?

Mostly yes. Reusing the existing bounded-response helpers is the right implementation layer, but the best merge shape is to split or explicitly resolve the overlapping Discord work before landing.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 4895a00b94f0.

Label changes

Label justifications:

  • P2: This is a focused provider/channel resource-exhaustion hardening bugfix with limited blast radius and no evidence of an active outage.
  • merge-risk: 🚨 compatibility: The PR changes oversized response handling from full buffering to truncation or capped failure, and competes with other open cap choices.
  • merge-risk: 🚨 auth-provider: The Google OAuth token exchange now uses bounded JSON parsing and capped error text, changing auth-provider failure behavior for oversized or malformed responses.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes copied loopback-server terminal proof for oversized Google OAuth errors and targeted test output for the added bounded-read behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes copied loopback-server terminal proof for oversized Google OAuth errors and targeted test output for the added bounded-read behavior.
Evidence reviewed

PR surface:

Source +14, Tests +376. Total +390 across 4 files.

View PR surface stats
Area Files Added Removed Net
Source 2 18 4 +14
Tests 2 376 0 +376
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 4 394 4 +390

What I checked:

  • PR close coverage proof: PR close coverage proof kept this PR open against fix(discord): bound gateway metadata response reads at 16 MiB #97721: PR B covers the Discord half of PR A, but PR A also contains material Google OAuth bounded-read behavior and proof that PR B does not carry forward. The relationship evidence is not enough to close the combined Google/Discord PR as fully covered.
  • linked superseding PR: fix(discord): bound gateway metadata response reads at 16 MiB #97721 (fix(discord): bound gateway metadata response reads at 16 MiB) is still open as the canonical replacement.
  • cluster evidence: the durable review links that PR in the work cluster or recommended risk path.
  • no human follow-up: live comments and timeline hydrated by apply contain no non-automation activity after the ClawSweeper review.

Likely related people:

  • vincentkoc: Current-main blame ties both touched unbounded read sites to commit 90c46f48b63af8dd23f540ca384409101ef01df0; this is the strongest local history signal in the squashed checkout. (role: recent area contributor; confidence: medium; commits: 90c46f48b63a; files: extensions/google/oauth.token.ts, extensions/discord/src/monitor/gateway-metadata.ts)
  • wangmiao0668000666: Authored the open proof-positive Discord gateway metadata bounded-read PR that overlaps the Discord half of this branch and covers both metadata read sites. (role: adjacent canonical PR owner; confidence: high; commits: 30d1aa37ec2a, 5b0af99c7349; files: extensions/discord/src/monitor/gateway-metadata.ts, extensions/discord/src/monitor/gateway-metadata.bounded.test.ts)
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.

@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. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. labels Jul 1, 2026
@wings1029
wings1029 force-pushed the fix/google-oauth-error-body-cap-v3 branch from 1ad38c1 to dddcaa6 Compare July 2, 2026 01:22
@wings1029 wings1029 changed the title fix(google,discord): bound response body reads to prevent OOM fix(google): bound OAuth token error body reads to prevent OOM Jul 2, 2026
@openclaw-barnacle openclaw-barnacle Bot added triage: blank-template Candidate: PR template appears mostly untouched. triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. and removed channel: discord Channel integration: discord triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jul 2, 2026
wings1029 and others added 2 commits July 2, 2026 14:02
The requestTokenGrant helper in the Google OAuth token exchange path
read error response bodies via response.text() without any size cap.
A misconfigured or malfunctioning Google OAuth endpoint that returns
an oversized error body could exhaust gateway memory.

Replace response.text() with readResponseTextLimited(8 KiB) on the
error path, consistent with the Discord API error body limit. Replace
the success-path response.json() cast with readProviderJsonResponse
for defense-in-depth type-safe parsing.

The oauth.http.ts fetchWithTimeout wrapper already bounds the response
at 16 MiB, so these are defense-in-depth bounds that prevent oversized
error text from being embedded in downstream Error messages.

Co-Authored-By: Claude <[email protected]>
Addresses CI lint failures: curly(if) and no-unnecessary-type-conversion.

Co-Authored-By: Claude <[email protected]>
@sallyom

sallyom commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Thanks for the PR. I’m closing this as superseded by #97587. The Google OAuth token error-body bound landed on main via #99605. The remaining token success JSON parse coverage is handled by #97587, which is the canonical PR for the remaining Google OAuth JSON parse-boundary work. That also covers the related oauth.project.ts reads.

@sallyom sallyom closed this Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

extensions: google merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. 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: M status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. triage: blank-template Candidate: PR template appears mostly untouched.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants