Skip to content

fix(auth): bound GitHub OAuth verification phases - #2482

Merged
steipete merged 2 commits into
mainfrom
codex/oauth-github-deadline-n852
Sep 22, 2026
Merged

steipete merged 2 commits into
mainfrom
codex/oauth-github-deadline-n852

Conversation

@steipete

@steipete steipete commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Summary

GitHub OAuth code exchange and identity/email lookups still awaited response headers and JSON bodies without a deadline. Bound the exchange to one 15-second attempt, and give each existing post-exchange verification attempt one shared 15-second budget across identity, verified-email, organization, and team checks.

Move the membership deadline, canonical GitHub API headers, and transient-error type into one shared request owner. OAuth retains its retry and credential-custody rules: code exchange is never automatically retried; post-exchange verification still retries once; an already exchanged credential stays encrypted for later callback retries. Late responses cannot store credentials or finish a timed-out callback. An exchange timeout leaves GitHub's remote outcome unknown and may require starting a new login. No persisted schema, authorization policy, credential configuration, or permission is added.

Verification

The stalled-exchange regression failed against unchanged production code because the callback remained pending after its intended deadline. Added coverage includes stalled exchange headers and successful/error bodies, absence of automatic exchange retries and late credential writes, a shared post-exchange budget, and recovery using the already encrypted credential.

  • Full Worker suite: 3,405 passed, 15 skipped.
  • Formatting, lint, Cloudflare and Node typechecks: passed.
  • Cloudflare dry-run and Node builds: passed.
  • Independent Codex review of the complete introduced change through P2: no actionable findings.

Observed real-clock HTTP proof

The existing native HTTP OAuth scenario also passed with its test clock controls replaced by actual waits; production deadline logic was unchanged. Passive observation recorded one loopback POST, HTTP 200 headers with an incomplete JSON body, abort after approximately 15 real seconds, and response closure before explicit test cleanup. The passing test asserts callback status 503, one exchange request, and no credential, issued token, or active callback claim in the pending record.

The recorded production/test source hashes were verified identical to commit c49820ca80b00b14793df3ed486775817d74ae4a after committing. The trace run itself occurred before that commit. Final head 3be32181ad022f6a5d40cca70e70331031be98d4 changes only the changelog link; all recorded runtime/test hashes also match that final head. elapsedMS is real monotonic time; the two abort observations do not represent additional requests.

[
  {
    "event": "server_received_exchange",
    "elapsedMS": 134.264417,
    "method": "POST",
    "loopback": true
  },
  {
    "event": "server_sent_headers",
    "elapsedMS": 134.63175,
    "status": 200
  },
  {
    "event": "deadline_aborted_signal",
    "elapsedMS": 15132.930792,
    "aborted": true
  },
  {
    "event": "deadline_aborted_signal",
    "elapsedMS": 15132.98275,
    "aborted": true
  },
  {
    "event": "server_response_closed",
    "elapsedMS": 15134.011,
    "bodyEnded": false
  }
]

An independent read-only evidence audit confirmed the trace and assertion ordering: 1 passed, 15 skipped, exit 0. This uses native loopback HTTP, synthetic credentials, and in-memory coordinator storage—not live GitHub or a production database. No credentials, request bodies, headers, host addresses, or unrelated traffic were retained in the trace.

Share request deadlines, API headers, and transient errors across membership and OAuth. Give code exchange its own bounded attempt without automatic retry, and bound each post-exchange identity/email/membership verification attempt while preserving encrypted credential reuse and the existing retry. Prevent late responses from sealing credentials or completing timed-out callbacks.
@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. 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. labels Sep 22, 2026
@clawsweeper

clawsweeper Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed September 22, 2026, 7:08 AM ET / 11:08 UTC.

ClawSweeper review

What this changes

The PR gives GitHub OAuth exchange and verification bounded 15-second phases using a shared request helper, with regression coverage and operator documentation.

Merge readiness

Ready for maintainer review

Keep open: this fixes an OAuth timeout gap still present on main and in v0.64.0. No actionable patch defect was found, and the supplied native HTTP trace supports the changed timeout behavior.

Priority: P2
Reviewed head: 3be32181ad022f6a5d40cca70e70331031be98d4

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) A focused, well-supported reliability repair with relevant real transport evidence and no actionable findings.
Proof confidence 🐚 platinum hermit (4/6) Sufficient (logs): The captured native HTTP trace exercises the production OAuth callback and deadline owner against a stalled response body, observing abort and transport closure after about 15 seconds with HTTP 503 and no credential or session stored. Recovery and late-response fencing have supplemental regression coverage; no stored-data contract changes.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (logs): The captured native HTTP trace exercises the production OAuth callback and deadline owner against a stalled response body, observing abort and transport closure after about 15 seconds with HTTP 503 and no credential or session stored. Recovery and late-response fencing have supplemental regression coverage; no stored-data contract changes.
Evidence reviewed 10 items Introduced scope: Reviewed the complete pinned introduction delta: three production files, two test files, operations documentation, and one changelog entry.
Main still needs this fix: Main directly awaits OAuth exchange, identity, and email fetches and JSON bodies without deadlines. Its existing deadline covers membership verification only.
Latest release comparison: The v0.64.0 OAuth callback also invokes exchange and identity verification without the proposed request deadlines.
Findings None None.
Security None None.

How this fits together

Crabbox’s coordinator exchanges GitHub login codes and verifies identity, email, and membership before issuing CLI or portal sessions. These changes bound upstream waits while preserving credential custody and callback retries.

flowchart TD
  A[CLI or portal login callback] --> B[Validate callback binding]
  B --> C[Exchange code within 15 seconds]
  C --> D[Retain encrypted credential]
  D --> E[Verify identity and membership within 15 seconds]
  E --> F[Issue session]
  E --> G[Retry verification once]
  G --> E
  C --> H[Return temporary failure on timeout]
Loading

Before merge

None.

Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production and test delta Production +121/-112 (net +9); tests +228/-1 (net +227) The small net production growth shares an existing timeout implementation and adds focused failure-path coverage.

Technical review

Best possible solution:

Use one shared deadline owner for bounded OAuth phases while retaining the existing authorization checks, encrypted credentials, and retry semantics.

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

Yes, from source: a GitHub exchange or identity response that never completes leaves main awaiting an unbounded fetch or JSON body. This review did not execute a current-main reproduction.

Is this the best way to solve the issue?

Yes. Reusing the existing deadline mechanism closes the uncovered OAuth phases without introducing a competing retry policy or changing stored credentials.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against 52ae9994e14c.

Labels

Label changes:

  • add P2: This is a bounded authentication reliability fix for stalled GitHub requests, without evidence of a widespread active outage.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The captured native HTTP trace exercises the production OAuth callback and deadline owner against a stalled response body, observing abort and transport closure after about 15 seconds with HTTP 503 and no credential or session stored. Recovery and late-response fencing have supplemental regression coverage; no stored-data contract changes.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (logs): The captured native HTTP trace exercises the production OAuth callback and deadline owner against a stalled response body, observing abort and transport closure after about 15 seconds with HTTP 503 and no credential or session stored. Recovery and late-response fencing have supplemental regression coverage; no stored-data contract changes.

Label justifications:

  • P2: This is a bounded authentication reliability fix for stalled GitHub requests, without evidence of a widespread active outage.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (logs): The captured native HTTP trace exercises the production OAuth callback and deadline owner against a stalled response body, observing abort and transport closure after about 15 seconds with HTTP 503 and no credential or session stored. Recovery and late-response fencing have supplemental regression coverage; no stored-data contract changes.
  • proof: sufficient: Contributor real behavior proof is sufficient. The captured native HTTP trace exercises the production OAuth callback and deadline owner against a stalled response body, observing abort and transport closure after about 15 seconds with HTTP 503 and no credential or session stored. Recovery and late-response fencing have supplemental regression coverage; no stored-data contract changes.

Evidence

What I checked:

  • Introduced scope: Reviewed the complete pinned introduction delta: three production files, two test files, operations documentation, and one changelog entry. (3be32181ad02)
  • Main still needs this fix: Main directly awaits OAuth exchange, identity, and email fetches and JSON bodies without deadlines. Its existing deadline covers membership verification only. (worker/src/oauth.ts:717, 52ae9994e14c)
  • Latest release comparison: The v0.64.0 OAuth callback also invokes exchange and identity verification without the proposed request deadlines. (worker/src/oauth.ts:344, aed7d31615d6)
  • Related merged work does not supersede OAuth coverage: GitHub confirms fix(auth): bound GitHub membership verification #2481 merged as the pinned main commit. It bounded account and membership checks, leaving the OAuth exchange and identity/email gap addressed here. (worker/src/github-membership.ts, 52ae9994e14c)
  • Deadline and credential boundaries: Exchange completes its deadline wrapper before credential sealing; post-exchange verification completes before session issuance. Timeout rejection exits those paths, while existing claim checks, encrypted credential reuse, browser binding, and authorization predicates remain intact. No persisted format or authority grant is introduced. (worker/src/oauth.ts:344, 3be32181ad02)
  • Real transport proof: The supplied PR body, captured under sourceRevision 508762605a76a811e18df8313943426db636c00bd0ca39e9dc89ad71fcc240fe, records the production OAuth callback using native loopback HTTP: one POST, incomplete HTTP 200 JSON, abort near 15.13 real seconds, and response closure before cleanup. The corresponding scenario asserts HTTP 503 and no stored credential, issued token, or active callback claim. It uses synthetic credentials and in-memory storage; it does not claim live GitHub qualification. (worker/test/oauth-browser-binding.test.ts:534, 3be32181ad02)

Likely related people:

  • steipete: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)
  • dwin-gharibi: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)

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.

@steipete
steipete merged commit e423497 into main Sep 22, 2026
35 checks passed
@steipete
steipete deleted the codex/oauth-github-deadline-n852 branch September 22, 2026 11:28
@steipete

Copy link
Copy Markdown
Contributor Author

Landed as e423497 through the normal protected squash path. The fix shares the GitHub deadline owner across OAuth phases while preserving the one-use-code exchange boundary, existing verification retry, encrypted credential reuse, and late-response fencing. The actual merged checkout passed 63 focused tests with one skip, and coordinator deployment succeeded.

The original main CI run encountered a separate GCP HTTPS fixture failure, not a Worker failure. That fixture correction is now landed via #2484. Superseding main CI containing the unchanged OAuth fix succeeded at https://github.com/openclaw/crabbox/actions/runs/35724738623; deployment of that tree succeeded at https://github.com/openclaw/crabbox/actions/runs/35724738659. The real-clock OAuth proof uses native loopback HTTP and synthetic credentials, not live GitHub.

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

Labels

P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant