feat(security): enforce WSS for non-localhost connections

- Add SecurityError class for clear error handling
- Add validateWebSocketSecurity function
- Block ws:// connections to non-localhost hosts
- Add unit tests for security validation logic

Security: Prevents man-in-the-middle attacks on remote connections
by requiring WSS protocol for all non-localhost WebSocket connections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-21 17:27:56 +08:00
parent d266a1435f
commit 32c9f93a7b
2 changed files with 223 additions and 4 deletions

View File

@@ -74,6 +74,35 @@ import {
import type { GatewayConfigSnapshot, GatewayModelChoice } from './gateway-config';
import { installApiMethods } from './gateway-api';
// === Security ===
/**
* Security error for invalid WebSocket connections.
* Thrown when non-localhost URLs use ws:// instead of wss://.
*/
export class SecurityError extends Error {
constructor(message: string) {
super(message);
this.name = 'SecurityError';
}
}
/**
* Validate WebSocket URL security.
* Ensures non-localhost connections use WSS protocol.
*
* @param url - The WebSocket URL to validate
* @throws SecurityError if non-localhost URL uses ws:// instead of wss://
*/
export function validateWebSocketSecurity(url: string): void {
if (!url.startsWith('wss://') && !isLocalhost(url)) {
throw new SecurityError(
'Non-localhost connections must use WSS protocol for security. ' +
`URL: ${url.replace(/:[^:@]+@/, ':****@')}`
);
}
}
function createIdempotencyKey(): string {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
@@ -205,10 +234,8 @@ export class GatewayClient {
return this.connectRest();
}
// Security warning: non-localhost with insecure WebSocket
if (!this.url.startsWith('wss://') && !isLocalhost(this.url)) {
console.warn('[Gateway] Connecting to non-localhost with insecure WebSocket (ws://). Consider using WSS in production.');
}
// Security validation: enforce WSS for non-localhost connections
validateWebSocketSecurity(this.url);
this.autoReconnect = true;
this.setState('connecting');