Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Sentinel Journal

## 2025-05-23 - Localhost Origin Validation Bypass
**Vulnerability:** The HTTP transport middleware explicitly skips Origin header validation when the request target is `localhost` or `127.0.0.1`.
**Learning:** Developers often assume `localhost` is safe or want to avoid CORS issues during local development, but this allows any website (e.g., via a victim's browser) to make cross-origin requests to the local server (CSRF/interaction), bypassing security controls.
**Prevention:** Always validate the `Origin` header if present, even for localhost requests. Allowlists should include `localhost` explicitly rather than skipping the check entirely based on the `Host` header.
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 13 additions & 2 deletions src/http-transport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,20 @@ describeIfListen('HttpTransport', () => {
expect(response.status).not.toBe(403);
});

it('should validate Origin header for localhost requests (CSRF protection)', async () => {
const response = await request(app)
.post('/mcp')
.set('Accept', 'application/json, text/event-stream')
.set('Origin', 'https://evil.com')
.set('Host', 'localhost')
.send({ jsonrpc: '2.0', id: 1, method: 'initialize' });

expect(response.status).toBe(403);
expect(response.body).toHaveProperty('error', 'Forbidden');
});

it('should validate Origin header for non-localhost requests', async () => {
// Skip Origin check is only for localhost hostname
// For other hostnames, Origin validation applies
// Origin validation applies for all requests
const response = await request(app)
.post('/mcp')
.set('Accept', 'application/json, text/event-stream')
Expand Down
9 changes: 3 additions & 6 deletions src/http-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,9 @@ export class HttpTransport {
this.hasWarnedAboutBinding = true;
}

// Skip Origin check for localhost
if (req.hostname === 'localhost' || req.hostname === '127.0.0.1') {
return next();
}

// Validate Origin header for non-localhost
// Validate Origin header if present
// We explicitly check localhost origins too to prevent CSRF from malicious sites
// (browsers send Origin: null or the site's origin)
if (origin && !this.isAllowedOrigin(origin)) {
this.logger.warn('Rejected request from disallowed origin', { origin, ip: req.ip });
return res.status(HTTP_STATUS.FORBIDDEN).json({
Expand Down
Loading