Skip to content

fix: harden Steward checkout callback#7891

Merged
lalalune merged 9 commits into
developfrom
codex/steward-oauth-checkout-hardening
May 22, 2026
Merged

fix: harden Steward checkout callback#7891
lalalune merged 9 commits into
developfrom
codex/steward-oauth-checkout-hardening

Conversation

@Dexploarer

@Dexploarer Dexploarer commented May 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • return a checkout-scoped Steward bearer after server-side OAuth code exchange so elizaos.ai hardware checkout can authenticate the Stripe session request
  • support both token/refreshToken and access_token/refresh_token callback parameter names while stripping token-bearing fragments before app boot
  • add Playwright regression coverage for query code exchange, bearer usage, and hash token handling

Verification

  • CodeRabbit review --agent -t committed --base origin/develop -c AGENTS.md: 0 issues
  • PYTHON=/usr/bin/python3 bun install --frozen-lockfile
  • bun run --cwd packages/cloud-api typecheck
  • bun run --cwd packages/os-homepage typecheck
  • bun run --cwd packages/os-homepage test:e2e tests/checkout-controls.spec.ts --project desktop

Greptile Summary

This PR hardens the Steward checkout callback to support the hardware checkout flow on elizaos.ai: it adds a shouldReturnClientToken gate so the nonce-exchange endpoint can return the bearer to that origin, extends token-param handling to cover both token/refreshToken and access_token/refresh_token naming conventions, and strips token-bearing hash fragments before React boots. Playwright regression tests are added for the code-exchange, bearer-propagation, and hash-stripping paths.

  • steward-nonce-exchange/route.ts: new shouldReturnClientToken helper gates token exposure by origin; a previously-flagged bug leaves token unconditionally in the response via a later property that overrides the conditional spread.
  • CheckoutPage.tsx: refactors query/hash token reading behind readStewardTokenParams / removeStewardTokenParams helpers that handle both naming conventions; the code-exchange path now writes the returned bearer to localStorage so startStripeCheckout can pick it up.
  • gitleaks.yml: tightens secret scanning to diff-range only for PR/push events, replacing the previous whole-tree scan.

Confidence Score: 4/5

The checkout token-exchange flow is mostly correct, but the origin gate in the nonce-exchange route has no runtime effect because the unconditional token property overrides the conditional spread — an issue flagged in a prior review that remains unresolved in this revision.

The auth handler still returns the access token unconditionally to all permitted origins, regardless of the shouldReturnClientToken result. Until the later token property is removed, every *.elizacloud.ai caller receives the bearer in the response body, not only the hardware checkout origin as intended.

packages/cloud-api/auth/steward-nonce-exchange/route.ts — the token property on line 415 overrides the conditional spread immediately above it.

Important Files Changed

Filename Overview
packages/cloud-api/auth/steward-nonce-exchange/route.ts Adds shouldReturnClientToken origin gate and spreads { token } conditionally, but a subsequent unconditional token property (line 415) overrides the spread so the access token is always returned to every permitted caller — the intended checkout-only restriction has no runtime effect.
packages/os-homepage/src/CheckoutPage.tsx Refactors hash/query token reading behind readStewardTokenParams helper supporting both OAuth naming conventions; code-exchange path now writes the returned bearer to localStorage for Stripe checkout use.
packages/os-homepage/index.html Inline pre-boot script extended to detect and strip access_token in addition to token from hash fragments before React or analytics can read location.href.
packages/shared/src/steward-session-client/index.ts JSDoc updated to mention the checkout-bearer return; interface and runtime code are unchanged in this revision.
packages/os-homepage/tests/checkout-controls.spec.ts Adds three Playwright tests covering code-exchange bearer propagation, Authorization header on Stripe POST, and OAuth-style hash token stripping.
.github/workflows/gitleaks.yml Scopes gitleaks to diff-range only for PR and push events, reducing false positives from snapshot/test fixtures; full-tree scan preserved for all other trigger types.
packages/test/cloud-e2e/src/fixtures/stack.ts Fixes REPO_ROOT resolution from 4 levels up (pointing to packages/) to 5 levels up (repo root) — a correct path bug fix.
packages/cloud-api/crypto/direct-payments/config/route.ts New rate-limited GET endpoint exposing directWalletPaymentsService.getConfig; no auth guard, which appears intentional for a public configuration endpoint.

Comments Outside Diff (2)

  1. packages/cloud-api/auth/steward-nonce-exchange/route.ts, line 408-417 (link)

    P1 security Conditional token gate has no effect — token is always returned

    The new shouldReturnClientToken spread (line 412) is immediately overridden by the unconditional token, property on line 415. In a JS/TS object literal, a later key always wins, so every permitted origin receives the access token in the response body regardless of origin — the checkout-only restriction described in the PR and in the comment block above this return has no runtime effect. refreshToken is similarly always exposed, contradicting the stated "refresh stays cookie-only" guarantee.

    Fix in Claude Code Fix in Codex Fix in Cursor

  2. packages/shared/src/steward-session-client/index.ts, line 303-316 (link)

    P1 Duplicate token property in StewardNonceExchangeResponse

    token?: string is declared twice in the same interface body — once at the top (added by this PR) and again at line 314 (pre-existing with the JSDoc). TypeScript emits TS2300: Duplicate identifier 'token' for this. The packages/shared typecheck doesn't appear in the PR's verification steps, so the error may not have been caught. Remove the duplicate declaration at line 304 and keep the annotated one.

    Fix in Claude Code Fix in Codex Fix in Cursor

Reviews (6): Last reviewed commit: "fix: scope secret scan and cloud e2e run..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7eb19189-db86-46e4-a7a8-bfd6a4b241a8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/steward-oauth-checkout-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude

claude Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

Comment on lines +245 to +249
.then((session) => {
if (session.token) {
writeStoredStewardToken(session.token);
}
setIsAuthed(Boolean(session.token) || hasStewardAuthedCookie());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 setIsAuthed will silently fail if token is absent and cookie hasn't been written yet

setIsAuthed(Boolean(session.token) || hasStewardAuthedCookie()) sets the user as unauthenticated when session.token is absent and the steward-authed cookie hasn't been applied by the browser. Cookie availability after a cross-origin credentials: "include" response is generally synchronous once .then() fires, but this is a departure from the previous unconditional setIsAuthed(true) on any 2xx. If the server-side gate on shouldReturnClientToken is ever fixed to withhold the token from non-checkout origins, callers from those origins will land on the unauthenticated state after a fully successful code exchange.

Fix in Claude Code Fix in Codex Fix in Cursor

@claude

claude Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@standujar

Copy link
Copy Markdown
Collaborator

@Dexploarer hey — quick read, the intent is right (Stripe POST from elizaos.ai needs Bearer since the steward cookie is .elizacloud.ai-scoped), but the diff has two issues that defeat the goal:

1. shouldReturnClientToken is dead code — in route.ts, the spread is overwritten by the unconditional token, two lines below:

...(shouldReturnClientToken(c, isProduction) ? { token } : {}),
expiresAt: ...,
expiresIn: ...,
token,  // ← always returned, kills the gate

Every permitted origin still gets the token, which contradicts the "refresh stays cookie-only" claim in the PR description. Either drop the unconditional token, and keep the gate, or remove the gate.

2. StewardNonceExchangeResponse.token?: string is declared twice in packages/shared → TS2300. bun run --cwd packages/cloud-shared typecheck will catch it, just wasn't in the verification list.

The Playwright tests + hash-strip + access_token/refresh_token dual support all look clean.

@claude

claude Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@standujar standujar left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

@claude

claude Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions github-actions Bot added the ci label May 22, 2026
@claude

claude Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@lalalune lalalune merged commit 20a64a6 into develop May 22, 2026
48 of 50 checks passed
@lalalune lalalune deleted the codex/steward-oauth-checkout-hardening branch May 22, 2026 17:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants