mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 21:02:12 +03:00
- MitmTarget/AgentId types + MitmTargetSchema (Zod) in src/mitm/types.ts - maskSecret() with pre-compiled BEARER/SK_KEY/LONG_TOKEN patterns - sanitizeHeaders() using isForbiddenUpstreamHeaderName denylist + masking - shouldBypass()/globMatch() — ReDoS-safe string split (no runtime RegExp) - configureUpstreamCa() sets undici global dispatcher; safe error message (Hard Rule #12)
77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
/**
|
|
* Passthrough / bypass logic for the MITM server.
|
|
* Determines which hostnames should be tunneled without TLS decryption.
|
|
*
|
|
* Precedence: bypass list > target match > passthrough default.
|
|
* Source: plano 11 §4.6 (origin: llm-interceptor filters.py::ignore_hosts)
|
|
*/
|
|
|
|
/**
|
|
* Built-in bypass patterns — hosts that must NEVER be TLS-decrypted.
|
|
* Banks, government sites, and corporate SSO providers.
|
|
*/
|
|
export const DEFAULT_BYPASS_PATTERNS: RegExp[] = [
|
|
/\.bank\./i,
|
|
/(^|\.)gov(\.|$)/i,
|
|
/(^|\.)okta\.com$/i,
|
|
/(^|\.)auth0\.com$/i,
|
|
];
|
|
|
|
/**
|
|
* Match a hostname against a simple glob pattern (only * as wildcard, no ** or ?).
|
|
* Implemented without RegExp to avoid ReDoS on user-supplied patterns (CWE-1333).
|
|
* Uses a linear split-and-check algorithm: split by '*', verify each segment appears
|
|
* in order within the lowercase hostname.
|
|
*/
|
|
export function globMatch(hostname: string, pattern: string): boolean {
|
|
// Guard: reject patterns with more than 8 segments (after split) to bound complexity
|
|
const segments = pattern.toLowerCase().split("*");
|
|
if (segments.length > 9) return false;
|
|
|
|
const h = hostname.toLowerCase();
|
|
|
|
// No wildcard — exact match
|
|
if (segments.length === 1) return h === segments[0];
|
|
|
|
// Must start with the first segment (if non-empty)
|
|
const first = segments[0];
|
|
if (first && !h.startsWith(first)) return false;
|
|
|
|
// Must end with the last segment (if non-empty)
|
|
const last = segments[segments.length - 1];
|
|
if (last && !h.endsWith(last)) return false;
|
|
|
|
// Walk through middle segments verifying each appears after the previous match
|
|
let pos = first.length;
|
|
for (let i = 1; i < segments.length - 1; i++) {
|
|
const seg = segments[i];
|
|
if (seg === "") continue; // consecutive wildcards — skip
|
|
const idx = h.indexOf(seg, pos);
|
|
if (idx === -1) return false;
|
|
pos = idx + seg.length;
|
|
}
|
|
|
|
// Ensure the last fixed segment doesn't overlap with middle matches
|
|
if (last) {
|
|
const minEnd = pos + last.length;
|
|
if (minEnd > h.length) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Determine if a hostname should be bypassed (tunneled without TLS decryption).
|
|
*
|
|
* @param hostname - The target hostname (SNI or Host header value)
|
|
* @param userBypass - User-configured bypass patterns (glob strings or regexes)
|
|
* @returns true if the hostname should be tunneled without inspection
|
|
*/
|
|
export function shouldBypass(hostname: string, userBypass: string[]): boolean {
|
|
// Default bypass patterns take precedence
|
|
if (DEFAULT_BYPASS_PATTERNS.some((re) => re.test(hostname))) return true;
|
|
|
|
// User-defined bypass patterns (glob strings)
|
|
return userBypass.some((p) => globMatch(hostname, p));
|
|
}
|