Skip to content

[stack 7/8] fix(providers): harden MCP OAuth and Codex transports - #1164

Open
sethkarten wants to merge 13 commits into
stack/external-06-windowsfrom
stack/external-07-providers
Open

[stack 7/8] fix(providers): harden MCP OAuth and Codex transports#1164
sethkarten wants to merge 13 commits into
stack/external-06-windowsfrom
stack/external-07-providers

Conversation

@sethkarten

@sethkarten sethkarten commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Stack 7/8 — fix(providers): harden MCP OAuth and Codex transports

Active review snapshot — do not merge yet. The complete stack is open for architecture/design review, while final cumulative audit, CI, Cursor Bug Bot, and Macroscope findings are being remediated. Branches will be force-updated after validation.

Base: stack/external-06-windows
Review order: merge only after the preceding stack layer is accepted. This PR is not intended to merge independently out of order.

Stack navigation

  1. #1158 — ci: harden verification and release compatibility
  2. #1159 — fix(security): harden session and autonomous execution boundaries
  3. #1160 — fix(coding-agent): make persisted state crash-safe
  4. #1161 — fix(daemon): fence worker and supervisor lifecycle state
  5. #1162 — fix(coding-agent): repair queued and archived session lifecycle
  6. #1163 — fix(coding-agent): complete Windows kernel and daemon startup
  7. #1164 — fix(providers): harden MCP OAuth and Codex transports
  8. #1165 — fix(runtime): bound transcript and autonomous recovery

Summary

  • Preserve MCP tool schemas and use the correct Codex discovery client version.
  • Harden Codex SSE/WebSocket framing, cancellation, cleanup, and fallback state.
  • Implement SSRF-safe RFC 9728/8414/8707 OAuth discovery, native MCP challenges, refresh binding, and RFC 6052 NAT64 policy.

Validation

Provenance

  • Authored independently from upstream/main using issue reports and PR descriptions/comments only.
  • No external contributor branch, diff, commit, implementation code, or test code was fetched, inspected, copied, or reused.
  • The implementation and regression tests in this stack are maintainer-owned.

Linked-item disposition

Fixed on merge

Independently superseded pull requests

Reviewer notes

  • Please review this layer against its immediate stack base, not against main, to avoid cumulative duplicate diffs.
  • No merge is requested; the complete stack is being left for human review.

}

/** Follow RFC 9728 resource metadata, then fall back to co-located AS discovery. */
async function discover(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High mcp/oauth.ts:282

Scope selection uses discovery.challengedScope ?? config.scopes ?? discovery.resourceScopes?.join(" ") and never falls back to metadata.scopes_supported. When a server has no protected-resource scopes_supported (including the co-located fallback path) but does advertise authorization-server scopes, the authorization request omits scope entirely. This regresses the previous behavior of using advertised scopes and can yield an unusable or rejected token. Consider adding metadata.scopes_supported?.join(" ") as a final fallback in the scope selection chain.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/ai/src/mcp/oauth.ts around line 282:

Scope selection uses `discovery.challengedScope ?? config.scopes ?? discovery.resourceScopes?.join(" ")` and never falls back to `metadata.scopes_supported`. When a server has no protected-resource `scopes_supported` (including the co-located fallback path) but does advertise authorization-server scopes, the authorization request omits `scope` entirely. This regresses the previous behavior of using advertised scopes and can yield an unusable or rejected token. Consider adding `metadata.scopes_supported?.join(" ")` as a final fallback in the scope selection chain.

if (typeof metadata.registration_endpoint !== "string") throw new Error("Invalid OAuth registration_endpoint");
endpoints.push(metadata.registration_endpoint);
}
for (const endpoint of endpoints) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High mcp/oauth.ts:249

validateAuthorizationServerMetadata rejects any authorization, token, or registration endpoint whose origin differs from the issuer, so a standards-compliant server that hosts endpoints on separate domains is discovered but then rejected, making MCP login impossible. RFC 8414 does not require these endpoints to share the issuer origin. Consider validating each endpoint independently with validateOAuthNetworkUrl instead of enforcing origin equality with the issuer.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/ai/src/mcp/oauth.ts around line 249:

`validateAuthorizationServerMetadata` rejects any authorization, token, or registration endpoint whose origin differs from the issuer, so a standards-compliant server that hosts endpoints on separate domains is discovered but then rejected, making MCP login impossible. RFC 8414 does not require these endpoints to share the issuer origin. Consider validating each endpoint independently with `validateOAuthNetworkUrl` instead of enforcing origin equality with the issuer.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 74a91a6. Configure here.

const nat64Prefixes = config.nat64Prefixes ? [...config.nat64Prefixes] : undefined;
const nat64CacheKey = {};
const resourcePolicy: OAuthFetchPolicy = { ...createOAuthFetchPolicy(config.url), nat64Prefixes, nat64CacheKey };
const oauthPolicy: OAuthFetchPolicy = { nat64Prefixes, nat64CacheKey };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Loopback OAuth AS policy mismatch

Medium Severity

createOAuthFetchPolicy permits HTTP on the configured loopback resource origin, but oauthPolicy omits allowedHttpOrigin. Colocated discovery, authorization-server metadata, DCR, and token exchange against that same loopback origin then fail the HTTPS-only check, so local HTTP MCP OAuth cannot complete.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 74a91a6. Configure here.

@sethkarten
sethkarten force-pushed the stack/external-07-providers branch from 74a91a6 to f4f6308 Compare August 10, 2026 19:23
const nat64Prefixes = config.nat64Prefixes ? [...config.nat64Prefixes] : undefined;
const nat64CacheKey = {};
const resourcePolicy: OAuthFetchPolicy = { ...createOAuthFetchPolicy(config.url), nat64Prefixes, nat64CacheKey };
const oauthPolicy: OAuthFetchPolicy = { nat64Prefixes, nat64CacheKey };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High mcp/oauth.ts:584

oauthPolicy omits the loopback HTTP allowance from createOAuthFetchPolicy(config.url), so OAuth discovery and token requests reject configured local HTTP MCP origins like http://127.0.0.1:3000. resourcePolicy preserves the allowance via allowedHttpOrigin, but oauthPolicy does not, so validateOAuthNetworkUrl throws on the loopback issuer during login and refresh. Spread createOAuthFetchPolicy(config.url) into oauthPolicy so the configured loopback origin is permitted for OAuth endpoints.

Suggested change
const oauthPolicy: OAuthFetchPolicy = { nat64Prefixes, nat64CacheKey };
const oauthPolicy: OAuthFetchPolicy = { ...createOAuthFetchPolicy(config.url), nat64Prefixes, nat64CacheKey };
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/ai/src/mcp/oauth.ts around line 584:

`oauthPolicy` omits the loopback HTTP allowance from `createOAuthFetchPolicy(config.url)`, so OAuth discovery and token requests reject configured local HTTP MCP origins like `http://127.0.0.1:3000`. `resourcePolicy` preserves the allowance via `allowedHttpOrigin`, but `oauthPolicy` does not, so `validateOAuthNetworkUrl` throws on the loopback issuer during login and refresh. Spread `createOAuthFetchPolicy(config.url)` into `oauthPolicy` so the configured loopback origin is permitted for OAuth endpoints.

} else if (kinds.some((kind) => kind !== "public")) {
throw new Error(`OAuth hostname resolves to a non-public address: ${hostname}`);
}
const selected = addresses[0];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High mcp/safe-fetch.ts:335

resolveTarget validates every resolved address but then pins the request to only addresses[0]. If that first address is unreachable while another validated address is healthy, the request fails instead of falling back. The pinnedLookup closure only ever returns target.address, so Undici has no other candidates to try. Consider preserving the full validated address set in the pinned lookup so the HTTP client can fall back to a healthy address.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/ai/src/mcp/safe-fetch.ts around line 335:

`resolveTarget` validates every resolved address but then pins the request to only `addresses[0]`. If that first address is unreachable while another validated address is healthy, the request fails instead of falling back. The `pinnedLookup` closure only ever returns `target.address`, so Undici has no other candidates to try. Consider preserving the full validated address set in the pinned lookup so the HTTP client can fall back to a healthy address.

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

Labels

None yet

Projects

None yet

1 participant