diff --git a/src/__tests__/scanner.test.ts b/src/__tests__/scanner.test.ts index 96b6185..020007d 100644 --- a/src/__tests__/scanner.test.ts +++ b/src/__tests__/scanner.test.ts @@ -112,6 +112,27 @@ describe('MISSING_TLS rule', () => { const findings = runAuthRule(RuleId.MISSING_TLS, config); expect(findings).toHaveLength(0); }); + + it('does not fire for a secure wss:// URL (INSECURE_TRANSPORT owns WebSocket scope)', () => { + // A wss:// endpoint is TLS-encrypted, but parseConfig leaves transport.tls + // false for non-https schemes. Without the WebSocket guard this would be a + // "plain HTTP" false positive on a perfectly secure server. + const config: ParsedMcpConfig = { + serverUrl: 'wss://secure.example.com/mcp', + transport: { url: 'wss://secure.example.com/mcp', tls: false }, + }; + const findings = runAuthRule(RuleId.MISSING_TLS, config); + expect(findings).toHaveLength(0); + }); + + it('does not fire for a ws:// URL (reported by INSECURE_TRANSPORT instead)', () => { + const config: ParsedMcpConfig = { + serverUrl: 'ws://example.com/mcp', + transport: { url: 'ws://example.com/mcp', tls: false }, + }; + const findings = runAuthRule(RuleId.MISSING_TLS, config); + expect(findings).toHaveLength(0); + }); }); // ─── WILDCARD_CORS ──────────────────────────────────────────────────────────── diff --git a/src/rules/auth-rules.ts b/src/rules/auth-rules.ts index 437ce8d..9dbfc96 100644 --- a/src/rules/auth-rules.ts +++ b/src/rules/auth-rules.ts @@ -67,7 +67,18 @@ export const authRules: Rule[] = [ const url = config.transport?.url ?? config.serverUrl; const isTls = config.transport?.tls; - if (url && (url.startsWith('http://') || (!isTls && !url.startsWith('https://')))) { + // WebSocket transports are out of scope for this rule: ws:// is reported by + // INSECURE_TRANSPORT, and wss:// is already TLS-encrypted. Without this guard a + // secure wss:// endpoint is wrongly flagged as "plain HTTP" because it neither + // starts with https:// nor sets transport.tls. + const isWebSocket = + !!url && (url.startsWith('ws://') || url.startsWith('wss://')); + + if ( + url && + !isWebSocket && + (url.startsWith('http://') || (!isTls && !url.startsWith('https://'))) + ) { return [ { ruleId: RuleId.MISSING_TLS,