@@ -5,6 +5,7 @@ import execa from 'execa';
55import { WrapperConfig , BlockedTarget } from './types' ;
66import { logger } from './logger' ;
77import { generateSquidConfig , generatePolicyManifest } from './squid-config' ;
8+ import { parseDomainWithProtocol , isWildcardPattern , wildcardToRegex } from './domain-patterns' ;
89import { generateSessionCa , initSslDb , parseUrlPatterns } from './ssl-bump' ;
910import {
1011 SQUID_PORT ,
@@ -508,6 +509,68 @@ export async function startContainers(workDir: string, allowedDomains: string[],
508509 }
509510}
510511
512+ /**
513+ * Classifies and logs each blocked target, then emits actionable fix suggestions.
514+ * Extracted to avoid duplicating this logic between the startup-error path
515+ * (which uses `logger.error`) and the post-run warning path (which uses `logger.warn`).
516+ *
517+ * @param blockedTargets - Targets that were denied by the firewall
518+ * @param allowedDomains - Domains currently in the allowlist
519+ * @param log - Logging function to use (e.g. `logger.error` or `logger.warn`)
520+ * @returns The categorized lists so callers can decide on further action
521+ */
522+ function reportBlockedDomains (
523+ blockedTargets : BlockedTarget [ ] ,
524+ allowedDomains : string [ ] ,
525+ log : ( msg : string ) => void ,
526+ ) : { missingDomains : string [ ] ; portIssues : BlockedTarget [ ] } {
527+ const uniqueMissingDomains = new Set < string > ( ) ;
528+ const portIssues : BlockedTarget [ ] = [ ] ;
529+
530+ blockedTargets . forEach ( blocked => {
531+ const isAllowed = allowedDomains . some ( allowed => {
532+ // Strip any protocol prefix (e.g. "https://github.com" -> "github.com")
533+ const normalizedAllowed = parseDomainWithProtocol ( allowed ) . domain ;
534+ if ( isWildcardPattern ( normalizedAllowed ) ) {
535+ // Wildcard pattern match (e.g. "*.github.com")
536+ try {
537+ return new RegExp ( wildcardToRegex ( normalizedAllowed ) , 'i' ) . test ( blocked . domain ) ;
538+ } catch {
539+ return false ;
540+ }
541+ }
542+ // Exact match or subdomain match
543+ return blocked . domain === normalizedAllowed || blocked . domain . endsWith ( '.' + normalizedAllowed ) ;
544+ } ) ;
545+
546+ if ( ! isAllowed ) {
547+ // Domain not in allowlist
548+ log ( ` - Blocked: ${ blocked . target } (domain not in allowlist)` ) ;
549+ uniqueMissingDomains . add ( blocked . domain ) ;
550+ } else if ( blocked . port && blocked . port !== '80' && blocked . port !== '443' ) {
551+ // Domain is allowed but port is not
552+ log ( ` - Blocked: ${ blocked . target } (port ${ blocked . port } not allowed, only 80 and 443 are permitted)` ) ;
553+ portIssues . push ( blocked ) ;
554+ } else {
555+ // Other reason (shouldn't happen often)
556+ log ( ` - Blocked: ${ blocked . target } ` ) ;
557+ }
558+ } ) ;
559+
560+ log ( 'Allowed domains:' ) ;
561+ allowedDomains . forEach ( domain => { log ( ` - Allowed: ${ domain } ` ) ; } ) ;
562+
563+ const missingDomains = [ ...uniqueMissingDomains ] ;
564+ if ( missingDomains . length > 0 ) {
565+ log ( `To fix domain issues: --allow-domains "${ [ ...allowedDomains , ...missingDomains ] . join ( ',' ) } "` ) ;
566+ }
567+ if ( portIssues . length > 0 ) {
568+ log ( 'To fix port issues: Use standard ports 80 (HTTP) or 443 (HTTPS)' ) ;
569+ }
570+
571+ return { missingDomains, portIssues } ;
572+ }
573+
511574/**
512575 * Runs the Squid-log diagnostic check and re-throws with a user-friendly message
513576 * when blocked domains are found, or rethrows the original error otherwise.
@@ -524,40 +587,7 @@ async function handleHealthcheckError(
524587
525588 if ( hasDenials ) {
526589 logger . error ( 'Firewall blocked domains during startup:' ) ;
527-
528- const missingDomains : string [ ] = [ ] ;
529- const portIssues : BlockedTarget [ ] = [ ] ;
530-
531- blockedTargets . forEach ( blocked => {
532- const isAllowed = allowedDomains . some ( allowed =>
533- blocked . domain === allowed || blocked . domain . endsWith ( '.' + allowed )
534- ) ;
535-
536- if ( ! isAllowed ) {
537- // Domain not in allowlist
538- logger . error ( ` - Blocked: ${ blocked . target } (domain not in allowlist)` ) ;
539- missingDomains . push ( blocked . domain ) ;
540- } else if ( blocked . port && blocked . port !== '80' && blocked . port !== '443' ) {
541- // Domain is allowed but port is not
542- logger . error ( ` - Blocked: ${ blocked . target } (port ${ blocked . port } not allowed, only 80 and 443 are permitted)` ) ;
543- portIssues . push ( blocked ) ;
544- } else {
545- // Other reason (shouldn't happen often)
546- logger . error ( ` - Blocked: ${ blocked . target } ` ) ;
547- }
548- } ) ;
549-
550- logger . error ( 'Allowed domains:' ) ;
551- allowedDomains . forEach ( domain => {
552- logger . error ( ` - Allowed: ${ domain } ` ) ;
553- } ) ;
554-
555- if ( missingDomains . length > 0 ) {
556- logger . error ( `To fix domain issues: --allow-domains "${ [ ...allowedDomains , ...missingDomains ] . join ( ',' ) } "` ) ;
557- }
558- if ( portIssues . length > 0 ) {
559- logger . error ( 'To fix port issues: Use standard ports 80 (HTTP) or 443 (HTTPS)' ) ;
560- }
590+ reportBlockedDomains ( blockedTargets , allowedDomains , msg => logger . error ( msg ) ) ;
561591
562592 // Create a more user-friendly error
563593 const blockedList = blockedTargets . map ( b => `"${ b . target } "` ) . join ( ', ' ) ;
@@ -644,40 +674,7 @@ export async function runAgentCommand(workDir: string, allowedDomains: string[],
644674 // If command failed (non-zero exit) and domains were blocked, show a warning
645675 if ( exitCode !== 0 && hasDenials ) {
646676 logger . warn ( 'Firewall blocked domains:' ) ;
647-
648- const missingDomains : string [ ] = [ ] ;
649- const portIssues : BlockedTarget [ ] = [ ] ;
650-
651- blockedTargets . forEach ( blocked => {
652- const isAllowed = allowedDomains . some ( allowed =>
653- blocked . domain === allowed || blocked . domain . endsWith ( '.' + allowed )
654- ) ;
655-
656- if ( ! isAllowed ) {
657- // Domain not in allowlist
658- logger . warn ( ` - Blocked: ${ blocked . target } (domain not in allowlist)` ) ;
659- missingDomains . push ( blocked . domain ) ;
660- } else if ( blocked . port && blocked . port !== '80' && blocked . port !== '443' ) {
661- // Domain is allowed but port is not
662- logger . warn ( ` - Blocked: ${ blocked . target } (port ${ blocked . port } not allowed, only 80 and 443 are permitted)` ) ;
663- portIssues . push ( blocked ) ;
664- } else {
665- // Other reason (shouldn't happen often)
666- logger . warn ( ` - Blocked: ${ blocked . target } ` ) ;
667- }
668- } ) ;
669-
670- logger . warn ( 'Allowed domains:' ) ;
671- allowedDomains . forEach ( domain => {
672- logger . warn ( ` - Allowed: ${ domain } ` ) ;
673- } ) ;
674-
675- if ( missingDomains . length > 0 ) {
676- logger . warn ( `To fix domain issues: --allow-domains "${ [ ...allowedDomains , ...missingDomains ] . join ( ',' ) } "` ) ;
677- }
678- if ( portIssues . length > 0 ) {
679- logger . warn ( 'To fix port issues: Use standard ports 80 (HTTP) or 443 (HTTPS)' ) ;
680- }
677+ reportBlockedDomains ( blockedTargets , allowedDomains , msg => logger . warn ( msg ) ) ;
681678 }
682679
683680 return { exitCode, blockedDomains : blockedTargets . map ( b => b . domain ) } ;
0 commit comments