refactor: use fixed localhost-only CORS policy, make auth mandatory

This commit is contained in:
2025-10-25 21:15:11 -04:00
parent 5ce7488597
commit f34dd31ed3
5 changed files with 467 additions and 68 deletions

View File

@@ -8,52 +8,51 @@ export function setupMiddleware(app: Express, settings: MCPServerSettings, creat
// Parse JSON bodies
app.use(express.json());
// CORS configuration
if (settings.enableCORS) {
const corsOptions = {
origin: (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => {
// Allow requests with no origin (like mobile apps or curl requests)
if (!origin) return callback(null, true);
if (settings.allowedOrigins.includes('*') ||
settings.allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true
};
app.use(cors(corsOptions));
}
// CORS configuration - Always enabled with fixed localhost-only policy
const corsOptions = {
origin: (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => {
// Allow requests with no origin (like CLI clients, curl, MCP SDKs)
if (!origin) {
return callback(null, true);
}
// Authentication middleware
if (settings.enableAuth) {
app.use((req: Request, res: Response, next: any) => {
// Defensive check: if auth is enabled but no API key is set, reject all requests
if (!settings.apiKey || settings.apiKey.trim() === '') {
return res.status(500).json(createErrorResponse(null, ErrorCodes.InternalError, 'Server misconfigured: Authentication enabled but no API key set'));
// Allow localhost and 127.0.0.1 on any port, both HTTP and HTTPS
const localhostRegex = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
if (localhostRegex.test(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
const authHeader = req.headers.authorization;
const apiKey = authHeader?.replace('Bearer ', '');
if (apiKey !== settings.apiKey) {
return res.status(401).json(createErrorResponse(null, ErrorCodes.InvalidRequest, 'Unauthorized'));
}
next();
});
}
},
credentials: true
};
app.use(cors(corsOptions));
// Authentication middleware - Always enabled
app.use((req: Request, res: Response, next: any) => {
// Defensive check: if no API key is set, reject all requests
if (!settings.apiKey || settings.apiKey.trim() === '') {
return res.status(500).json(createErrorResponse(null, ErrorCodes.InternalError, 'Server misconfigured: No API key set'));
}
const authHeader = req.headers.authorization;
const providedKey = authHeader?.replace('Bearer ', '');
if (providedKey !== settings.apiKey) {
return res.status(401).json(createErrorResponse(null, ErrorCodes.InvalidRequest, 'Unauthorized'));
}
next();
});
// Origin validation for security (DNS rebinding protection)
app.use((req: Request, res: Response, next: any) => {
const host = req.headers.host;
// Only allow localhost connections
if (host && !host.startsWith('localhost') && !host.startsWith('127.0.0.1')) {
return res.status(403).json(createErrorResponse(null, ErrorCodes.InvalidRequest, 'Only localhost connections allowed'));
}
next();
});
}

View File

@@ -57,38 +57,6 @@ export class MCPServerSettingTab extends PluginSettingTab {
}
}));
// CORS setting
new Setting(containerEl)
.setName('Enable CORS')
.setDesc('Enable Cross-Origin Resource Sharing (requires restart)')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enableCORS)
.onChange(async (value) => {
this.plugin.settings.enableCORS = value;
await this.plugin.saveSettings();
if (this.plugin.mcpServer?.isRunning()) {
new Notice('⚠️ Server restart required for CORS changes to take effect');
}
}));
// Allowed origins
new Setting(containerEl)
.setName('Allowed origins')
.setDesc('Comma-separated list of allowed origins (* for all, requires restart)')
.addText(text => text
.setPlaceholder('*')
.setValue(this.plugin.settings.allowedOrigins.join(', '))
.onChange(async (value) => {
this.plugin.settings.allowedOrigins = value
.split(',')
.map(s => s.trim())
.filter(s => s.length > 0);
await this.plugin.saveSettings();
if (this.plugin.mcpServer?.isRunning()) {
new Notice('⚠️ Server restart required for origin changes to take effect');
}
}));
// Authentication
new Setting(containerEl)
.setName('Enable authentication')