Skip to content

Commit 073575b

Browse files
authored
Merge pull request #125 from SocketDev/kapravel/improved-origin-checks
Enhance CORS origin validation to support localhost requests and improve logging for rejected origins
2 parents e662f6f + c275460 commit 073575b

File tree

2 files changed

+37
-10
lines changed

2 files changed

+37
-10
lines changed

index.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -282,17 +282,39 @@ if (useHttp) {
282282

283283
// Validate Origin header as required by MCP spec (for non-health endpoints)
284284
const origin = req.headers.origin
285+
286+
// Check if origin is from localhost (any port) - safe for local development
287+
const isLocalhostOrigin = (originUrl: string): boolean => {
288+
try {
289+
const url = new URL(originUrl)
290+
return url.hostname === 'localhost' || url.hostname === '127.0.0.1'
291+
} catch {
292+
return false
293+
}
294+
}
295+
285296
const allowedOrigins = [
286-
'http://localhost:3000',
287-
'http://127.0.0.1:3000',
288297
'https://mcp.socket.dev',
289298
'https://mcp.socket-staging.dev'
290299
]
291300

292-
const isValidOrigin = origin && allowedOrigins.includes(origin)
301+
// Check if request is from localhost (for same-origin requests that don't send Origin header)
302+
// Use strict matching to prevent spoofing via subdomains like "malicious-localhost.evil.com"
303+
const host = req.headers.host || ''
304+
const isLocalhostHost = host === `localhost:${port}` ||
305+
host === `127.0.0.1:${port}` ||
306+
host === 'localhost' ||
307+
host === '127.0.0.1'
308+
309+
// Allow requests:
310+
// 1. With Origin header from localhost (any port) or production domains
311+
// 2. Without Origin header if they're from localhost (same-origin requests)
312+
const isValidOrigin = origin
313+
? (isLocalhostOrigin(origin) || allowedOrigins.includes(origin))
314+
: isLocalhostHost
293315

294316
if (!isValidOrigin) {
295-
logger.warn(`Rejected request from invalid origin: ${origin}`)
317+
logger.warn(`Rejected request from invalid origin: ${origin || 'missing'} (host: ${host})`)
296318
res.writeHead(403, { 'Content-Type': 'application/json' })
297319
res.end(JSON.stringify({
298320
jsonrpc: '2.0',
@@ -302,11 +324,13 @@ if (useHttp) {
302324
return
303325
}
304326

305-
// Set CORS headers for valid origins
306-
// Note: origin is guaranteed to be truthy here because isValidOrigin === true
307-
res.setHeader('Access-Control-Allow-Origin', origin!)
308-
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS')
309-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept')
327+
// Set CORS headers for valid origins (only needed for cross-origin requests)
328+
// Same-origin requests don't send Origin header and don't need CORS headers
329+
if (origin) {
330+
res.setHeader('Access-Control-Allow-Origin', origin)
331+
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS')
332+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept')
333+
}
310334

311335
if (req.method === 'OPTIONS') {
312336
res.writeHead(200)
@@ -325,6 +349,9 @@ if (useHttp) {
325349

326350
// If this is an initialize, reset the singleton transport so clients can (re)initialize cleanly
327351
if (jsonData && jsonData.method === 'initialize') {
352+
const clientInfo = jsonData.params?.clientInfo
353+
logger.info(`Client connected: ${clientInfo?.name || 'unknown'} v${clientInfo?.version || 'unknown'} from ${origin || host}`)
354+
328355
if (httpTransport) {
329356
try { httpTransport.close() } catch {}
330357
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@socketsecurity/mcp",
3-
"version": "0.0.14",
3+
"version": "0.0.15",
44
"type": "module",
55
"main": "./index.js",
66
"bin": {

0 commit comments

Comments
 (0)