Skip to content

fix(xaa): make the issuer-mismatch abort visible and actionable#3042

Merged
chelojimenez merged 1 commit into
mainfrom
fix/xaa-issuer-mismatch-dx
Jul 7, 2026
Merged

fix(xaa): make the issuer-mismatch abort visible and actionable#3042
chelojimenez merged 1 commit into
mainfrom
fix/xaa-issuer-mismatch-dx

Conversation

@chelojimenez

@chelojimenez chelojimenez commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What

When a server-backed XAA run (stored client secret) re-discovers the authorization server, a mismatch between the stored issuer and the metadata's advertised issuer triggers a deliberate fail-closed abort — the secret is never posted. Two DX bugs made that abort indistinguishable from an outage:

  1. It returned 502. Cloudflare (in front of app.mcpjam.com) replaces origin 502/504 bodies with its branded Bad Gateway HTML page, so the diagnostic JSON never reached the user. A dev reported exactly this in the field: "this endpoint is returning 502 (a cloudflare 502 bad gateway html)" — with WAF/LB logs open, he still couldn't tell it was a config mismatch on his side.
  2. The message named neither issuer. The guard computes requested vs advertised (including the scheme-only http-behind-https-proxy case) and threw both away.

Changes

  • The issuer-mismatch abort is now 409 VALIDATION_ERROR instead of 502 SERVER_UNREACHABLE — a 4xx passes through CDN edges untouched, and this is a config conflict, not an unreachable upstream (genuine unreachable/timeout errors keep their 502/504).
  • The message now names both issuers and tells the user what to do: set the server's Authorization Server issuer to the advertised value; the scheme-only case gets the TLS-proxy hint instead.
  • requestedIssuer / advertisedIssuer / schemeOnly ride in details, and the /proxy/token + /negative-tests WebRouteError handlers now forward details onto the JSON response.

Security

No change to the fail-closed behavior itself: the secret is still never posted on mismatch (regression test asserts no token POST occurs). The advertised issuer included in the error comes from metadata the caller could already fetch themselves — no new information exposure. Connect-path consumers of the same guard log-and-rethrow without status branching, so the 409 is safe there too.

Tests

  • New regression test: mismatch → 409 VALIDATION_ERROR, message contains both issuers, details carries the structured values, and the stored secret is never posted.
  • All 50 XAA suite tests pass (xaa-server-target, xaa, xaa-mint, xaa-discovery).

🤖 Generated with Claude Code


Note

Cursor Bugbot is generating a summary for commit f1a7bff. Configure here.


Summary by cubic

Make issuer-mismatch errors in server-backed XAA discovery visible and actionable by returning 409 and including both issuers, so users see a clear config conflict instead of a Cloudflare 502 page. The secret is still never posted on mismatch.

  • Bug Fixes
    • Return 409 VALIDATION_ERROR for issuer mismatch; real upstream outages keep 502/504.
    • Error message names the requested and advertised issuers and adds a TLS-proxy hint when only the scheme differs.
    • Add structured details (requestedIssuer, advertisedIssuer, schemeOnly) and forward them in /proxy/token and /negative-tests; regression test asserts 409, message contents, details, and no token POST.

Written for commit f1a7bff. Summary will update on new commits.

Review in cubic

The fail-closed issuer check in server-side XAA discovery returned 502,
which Cloudflare replaces with its branded Bad Gateway HTML in hosted —
users saw a fake infrastructure outage instead of the diagnostic (the
exact report we got from the field). Switch the deliberate abort to
409 VALIDATION_ERROR so the body survives the edge, name both the
requested and advertised issuers in the message (plus the schemeOnly
proxy hint), and forward WebRouteError details through the /proxy/token
and /negative-tests error responses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. bug Something isn't working labels Jul 7, 2026
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_6d90e03d-4e92-471f-a420-a42cdc150c73)

@chelojimenez

Copy link
Copy Markdown
Contributor Author

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Internal preview

Preview URL: https://mcp-inspector-pr-3042.up.railway.app
Deployed commit: 94e2194
PR head commit: f1a7bff
Backend target: staging fallback.
Health: ✅ Convex reachable
Access is employee-only in non-production environments.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 3 files

Re-trigger cubic

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ea9389cd-d337-4c53-ad98-0f83a633ebb9

📥 Commits

Reviewing files that changed from the base of the PR and between 8f27804 and f1a7bff.

📒 Files selected for processing (3)
  • mcpjam-inspector/server/routes/mcp/__tests__/xaa-server-target.test.ts
  • mcpjam-inspector/server/routes/mcp/xaa.ts
  • mcpjam-inspector/server/services/xaa-mint.ts

Walkthrough

This change alters how issuer mismatches are handled during server-target token discovery, replacing a 502 SERVER_UNREACHABLE error with a 409 VALIDATION_ERROR that includes structured details (requestedIssuer, advertisedIssuer, schemeOnly) and an expanded error message. Two WebRouteError catch blocks in the routes file now forward the details field into JSON error responses. A new test verifies the fail-closed behavior, confirming the correct status code, error code, message content, details payload, and that no token request is submitted when issuers mismatch.

Changes

Area Change
xaa-mint.ts Issuer mismatch now throws 409 VALIDATION_ERROR with structured details instead of 502 SERVER_UNREACHABLE
xaa.ts /proxy/token and /negative-tests error responses now include error.details
Tests New test validates fail-closed 409 response and blocked token submission on issuer mismatch

Related PRs: None identified.

Suggested labels: bug, security, tests

Suggested reviewers: None identified.

Poem
A rabbit hops where issuers clash,
No more 502's confusing flash —
Now 409 stands firm and clear,
With details spelled out, crisp and near.
No token slips through mismatched gate,
This bunny's proud of fail-closed fate. 🐰🔒

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

mcpjam-inspector/server/routes/mcp/__tests__/xaa-server-target.test.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: ESLint configuration in --config is invalid:

  • Unexpected top-level property "__esModule".

    at ConfigValidator.validateConfigSchema (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2177:19)
    at ConfigArrayFactory._normalizeConfigData (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3019:19)
    at ConfigArrayFactory._loadConfigData (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:21)
    at ConfigArrayFactory.loadFile (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
    at createCLIConfigArray (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)
    at new CascadingConfigArrayFactory (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3735:29)
    at new CLIEngine (/soundcheck/node_modules/eslint/lib/cli-engine/cli-engine.js:617:36)
    at new ESLint (/soundcheck/node_modules/eslint/lib/eslint/eslint.js:430:27)
    at Object.execute (/soundcheck/node_modules/eslint/lib/cli.js:410:24)
    at async main (/soundcheck/node_modules/eslint/bin/eslint.js:152:22)

mcpjam-inspector/server/routes/mcp/xaa.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: ESLint configuration in --config is invalid:

  • Unexpected top-level property "__esModule".

    at ConfigValidator.validateConfigSchema (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2177:19)
    at ConfigArrayFactory._normalizeConfigData (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3019:19)
    at ConfigArrayFactory._loadConfigData (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:21)
    at ConfigArrayFactory.loadFile (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
    at createCLIConfigArray (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)
    at new CascadingConfigArrayFactory (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3735:29)
    at new CLIEngine (/soundcheck/node_modules/eslint/lib/cli-engine/cli-engine.js:617:36)
    at new ESLint (/soundcheck/node_modules/eslint/lib/eslint/eslint.js:430:27)
    at Object.execute (/soundcheck/node_modules/eslint/lib/cli.js:410:24)
    at async main (/soundcheck/node_modules/eslint/bin/eslint.js:152:22)

mcpjam-inspector/server/services/xaa-mint.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: ESLint configuration in --config is invalid:

  • Unexpected top-level property "__esModule".

    at ConfigValidator.validateConfigSchema (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2177:19)
    at ConfigArrayFactory._normalizeConfigData (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3019:19)
    at ConfigArrayFactory._loadConfigData (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:21)
    at ConfigArrayFactory.loadFile (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
    at createCLIConfigArray (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)
    at new CascadingConfigArrayFactory (/soundcheck/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3735:29)
    at new CLIEngine (/soundcheck/node_modules/eslint/lib/cli-engine/cli-engine.js:617:36)
    at new ESLint (/soundcheck/node_modules/eslint/lib/eslint/eslint.js:430:27)
    at Object.execute (/soundcheck/node_modules/eslint/lib/cli.js:410:24)
    at async main (/soundcheck/node_modules/eslint/bin/eslint.js:152:22)


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.

@chelojimenez chelojimenez merged commit a2cf82f into main Jul 7, 2026
14 checks passed
@chelojimenez chelojimenez deleted the fix/xaa-issuer-mismatch-dx branch July 7, 2026 06:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant