fix: restrict CORS to localhost origins only
Prevents cross-origin attacks from malicious websites by restricting CORS to only allow: - Requests without Origin header (hooks, curl, CLI tools) - Requests from localhost / 127.0.0.1 origins Previously, CORS was completely open (cors() without configuration), allowing any website to access the local API and read session data.
This commit is contained in:
committed by
Alex Newman
parent
2aab998b62
commit
86b1d7fad9
@@ -24,8 +24,21 @@ export function createMiddleware(
|
|||||||
// JSON parsing with 50mb limit
|
// JSON parsing with 50mb limit
|
||||||
middlewares.push(express.json({ limit: '50mb' }));
|
middlewares.push(express.json({ limit: '50mb' }));
|
||||||
|
|
||||||
// CORS
|
// CORS - restrict to localhost origins only
|
||||||
middlewares.push(cors());
|
middlewares.push(cors({
|
||||||
|
origin: (origin, callback) => {
|
||||||
|
// Allow: requests without Origin header (hooks, curl, CLI tools)
|
||||||
|
// Allow: localhost and 127.0.0.1 origins
|
||||||
|
if (!origin ||
|
||||||
|
origin.startsWith('http://localhost:') ||
|
||||||
|
origin.startsWith('http://127.0.0.1:')) {
|
||||||
|
callback(null, true);
|
||||||
|
} else {
|
||||||
|
callback(new Error('CORS not allowed'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
credentials: false
|
||||||
|
}));
|
||||||
|
|
||||||
// HTTP request/response logging
|
// HTTP request/response logging
|
||||||
middlewares.push((req: Request, res: Response, next: NextFunction) => {
|
middlewares.push((req: Request, res: Response, next: NextFunction) => {
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* CORS Restriction Tests
|
||||||
|
*
|
||||||
|
* Verifies that CORS is properly restricted to localhost origins only.
|
||||||
|
* This prevents cross-origin attacks from malicious websites.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'bun:test';
|
||||||
|
|
||||||
|
// Test the CORS origin validation logic directly
|
||||||
|
function isAllowedOrigin(origin: string | undefined): boolean {
|
||||||
|
if (!origin) return true; // No origin = hooks, curl, CLI
|
||||||
|
if (origin.startsWith('http://localhost:')) return true;
|
||||||
|
if (origin.startsWith('http://127.0.0.1:')) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('CORS Restriction', () => {
|
||||||
|
describe('allowed origins', () => {
|
||||||
|
it('allows requests without Origin header (hooks, curl, CLI)', () => {
|
||||||
|
expect(isAllowedOrigin(undefined)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows localhost with port', () => {
|
||||||
|
expect(isAllowedOrigin('http://localhost:37777')).toBe(true);
|
||||||
|
expect(isAllowedOrigin('http://localhost:3000')).toBe(true);
|
||||||
|
expect(isAllowedOrigin('http://localhost:8080')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows 127.0.0.1 with port', () => {
|
||||||
|
expect(isAllowedOrigin('http://127.0.0.1:37777')).toBe(true);
|
||||||
|
expect(isAllowedOrigin('http://127.0.0.1:3000')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('blocked origins', () => {
|
||||||
|
it('blocks external domains', () => {
|
||||||
|
expect(isAllowedOrigin('http://evil.com')).toBe(false);
|
||||||
|
expect(isAllowedOrigin('https://attacker.io')).toBe(false);
|
||||||
|
expect(isAllowedOrigin('http://malicious-site.net:8080')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('blocks HTTPS localhost (not typically used for local dev)', () => {
|
||||||
|
// HTTPS localhost is unusual and could indicate a proxy attack
|
||||||
|
expect(isAllowedOrigin('https://localhost:37777')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('blocks localhost-like domains (subdomain attacks)', () => {
|
||||||
|
expect(isAllowedOrigin('http://localhost.evil.com')).toBe(false);
|
||||||
|
expect(isAllowedOrigin('http://localhost.attacker.io:8080')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('blocks file:// origins', () => {
|
||||||
|
expect(isAllowedOrigin('file://')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('blocks null origin', () => {
|
||||||
|
// null origin can come from sandboxed iframes
|
||||||
|
expect(isAllowedOrigin('null')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user