Skip to content

fix(auth): restrict CORS on OAuth AS endpoints to localhost origins#5

Merged
Arul- merged 1 commit into
portel-dev:mainfrom
sebastiondev:fix/cwe200-http-adapter-cors-f37f
Jun 7, 2026
Merged

fix(auth): restrict CORS on OAuth AS endpoints to localhost origins#5
Arul- merged 1 commit into
portel-dev:mainfrom
sebastiondev:fix/cwe200-http-adapter-cors-f37f

Conversation

@sebastiondev

@sebastiondev sebastiondev commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

The handleAuthServerHTTP function in src/serv/auth/http-adapter.ts sets Access-Control-Allow-Origin: * on all OAuth Authorization Server endpoints (/token, /register, /authorize, /revoke, /introspect, /.well-known/oauth-authorization-server, etc.). This wildcard CORS policy is inconsistent with the rest of the codebase, which uniformly uses getCorsOrigin(req) from src/shared/security.ts to restrict cross-origin access to localhost origins only.

This fix replaces the wildcard with the same getCorsOrigin(req) call used in 30+ other locations across src/server.ts, src/auto-ui/beam.ts, and other modules.

Vulnerability Details

CWE-942: Overly Permissive Cross-domain Whitelist

Affected file: src/serv/auth/http-adapter.ts (line 94 on main)

Affected endpoints: All routes served by handleAuthServerHTTP — the OAuth 2.1 AS surface:

  • GET /tenant/<slug>/.well-known/oauth-authorization-server
  • GET /tenant/<slug>/.well-known/oauth-protected-resource
  • GET /tenant/<slug>/authorize
  • POST /tenant/<slug>/token
  • POST /tenant/<slug>/register
  • POST /tenant/<slug>/consent
  • POST /tenant/<slug>/revoke
  • POST /tenant/<slug>/introspect

What the wildcard allows: Any website can make cross-origin requests to these endpoints and read the responses. While Access-Control-Allow-Credentials is not set (so browsers won't attach cookies), this still means:

  1. A malicious page can probe the AS metadata endpoint to fingerprint a Photon deployment.
  2. A malicious page can submit DCR (Dynamic Client Registration) requests cross-origin, potentially registering rogue OAuth clients.
  3. If credentials-based auth is ever added to this code path in the future, the wildcard would immediately become a credential-leaking vulnerability with no additional code change needed.

Current practical impact is low because no cookies or Access-Control-Allow-Credentials header are involved in this code path today. However, this is a meaningful defense-in-depth fix — it closes a gap that contradicts the security model stated in SECURITY.md and prevents a future regression if the auth model evolves.

Proof of Concept

With a Photon SERV instance running on a public-facing host (e.g. https://photon.example.com), the following demonstrates that any origin can read OAuth AS responses:

<!-- Hosted on https://attacker.example.com -->
<script>
  // 1. Probe the AS metadata — should be restricted to localhost origins
  fetch('https://photon.example.com/tenant/default/.well-known/oauth-authorization-server')
    .then(r => r.json())
    .then(meta => {
      console.log('AS metadata leaked to cross-origin page:', meta);

      // 2. Register a rogue OAuth client via DCR
      return fetch(meta.registration_endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          client_name: 'rogue-client',
          redirect_uris: ['https://attacker.example.com/callback'],
          grant_types: ['authorization_code'],
          response_types: ['code'],
        }),
      });
    })
    .then(r => r.json())
    .then(reg => {
      console.log('Rogue client registered cross-origin:', reg);
    });
</script>

After the fix, both fetches will fail with a CORS error because the browser will not see an Access-Control-Allow-Origin header matching https://attacker.example.com.

The routes used above exist in the codebase — parsePathname (line 219) matches against KNOWN_ENDPOINTS (line 248) which includes .well-known/oauth-authorization-server and register.

Fix Description

The fix replaces:

res.setHeader('Access-Control-Allow-Origin', '*');

with:

const corsOrigin = getCorsOrigin(req);
if (corsOrigin) {
  res.setHeader('Access-Control-Allow-Origin', corsOrigin);
}

getCorsOrigin (defined in src/shared/security.ts:185) checks whether the request's Origin header is a localhost address (localhost, 127.0.0.1, or ::1). If it is, the origin is echoed back. If not, no Access-Control-Allow-Origin header is set, which causes the browser to block the cross-origin response.

This is the exact same pattern used in src/server.ts (lines 603, 2819, 3059, 3676, 3778, 3958) and src/auto-ui/beam.ts (line 1445). The fix simply brings the OAuth AS adapter in line with the established convention.

Testing

  • Verified the fix compiles without type errors (the getCorsOrigin import resolves correctly to src/shared/security.ts).
  • Confirmed that getCorsOrigin returns undefined for non-localhost origins, which means no Access-Control-Allow-Origin header is set — browsers will block the response.
  • Confirmed that same-origin requests (no Origin header) and localhost-origin requests continue to work, since isLocalhostOrigin returns true for both cases.
  • The change is 1 file, +7 / -2 lines, touching only the CORS setup block in handleAuthServerHTTP.

Adversarial Review

Before submitting, we considered whether this finding was actually exploitable or whether existing mitigations made it moot. The key mitigating factor is that Access-Control-Allow-Credentials is never set on this code path, so browsers won't send cookies — this significantly limits the impact. We also verified that no other auth mechanism (e.g. session cookies, ambient credentials) is used in http-adapter.ts. Given these facts, we characterize the current impact as low but the fix as worthwhile: it aligns the OAuth AS with the codebase's uniform CORS policy, eliminates cross-origin AS metadata probing and rogue DCR registration, and prevents a high-severity regression if credential-based auth is added later. The three remaining Access-Control-Allow-Origin: * locations in the codebase (daemon SSE, streamable HTTP MCP transport, API config) serve intentionally public protocols and were not changed.


Submitted by Sebastion — autonomous open-source security research from Foundation Machines. Free for public repos via the Sebastion AI GitHub App.

Summary by CodeRabbit

  • Bug Fixes
    • Improved API security by implementing CORS origin validation policy. The API now validates requested origins against a policy instead of unconditionally permitting all cross-origin requests.

Replace unconditional Access-Control-Allow-Origin: * with the
getCorsOrigin() helper from shared/security.ts, matching the CORS
policy used in the main MCP server (src/server.ts).

The wildcard header allowed any website to make cross-origin requests
to all OAuth endpoints including /register (unauthenticated DCR) and
read the response body. With this change, only localhost origins
receive the CORS header — consistent with the project security model
of local-only binding.

CWE-200
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 24dd9dcb-577a-44a3-8df6-291ec68e9dd4

📥 Commits

Reviewing files that changed from the base of the PR and between 96b3295 and f1f96f4.

📒 Files selected for processing (1)
  • src/serv/auth/http-adapter.ts

📝 Walkthrough

Walkthrough

The HTTP adapter's CORS handling changes to derive Access-Control-Allow-Origin from a policy function (getCorsOrigin) instead of unconditionally allowing all origins. The adapter conditionally sets the header only when the policy returns a valid origin.

Changes

CORS Policy Validation

Layer / File(s) Summary
Policy-based CORS origin handling
src/serv/auth/http-adapter.ts
HTTP adapter imports getCorsOrigin from shared security module and updates CORS header logic to conditionally set Access-Control-Allow-Origin from the policy result instead of always using '*'.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit hops with CORS policy in hand,
No more * for every land!
Origins validated, secure and sound,
Trust by design, not wide and round. 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically describes the main change: restricting CORS on OAuth AS endpoints to localhost origins instead of the wildcard policy.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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.

Copilot AI 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.

Pull request overview

This PR tightens CORS handling for the OAuth 2.1 Authorization Server HTTP adapter by removing a wildcard Access-Control-Allow-Origin: * policy and aligning the adapter with the project’s established localhost-only CORS policy via getCorsOrigin(req).

Changes:

  • Replace wildcard CORS on OAuth AS endpoints with getCorsOrigin(req)-based origin echoing for localhost origins only.
  • Add the shared security helper import to the auth HTTP adapter to reuse the central CORS policy.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +95 to +100
// CORS + preflight — restrict to localhost origins, matching the
// getCorsOrigin policy used elsewhere in the codebase.
const corsOrigin = getCorsOrigin(req);
if (corsOrigin) {
res.setHeader('Access-Control-Allow-Origin', corsOrigin);
}
@Arul- Arul- merged commit b87eb0d into portel-dev:main Jun 7, 2026
3 checks passed
@sebastiondev

Copy link
Copy Markdown
Contributor Author

Thanks for the merge. Glad it landed cleanly.

P.S. Sebastion is run by Foundation Machines if ongoing autonomous audits would be useful.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants