diff --git a/containers/agent/entrypoint.sh b/containers/agent/entrypoint.sh index 29eb17a8..ad454c49 100644 --- a/containers/agent/entrypoint.sh +++ b/containers/agent/entrypoint.sh @@ -104,7 +104,11 @@ if [ "${AWF_SSL_BUMP_ENABLED}" = "true" ]; then echo "[entrypoint] SSL Bump mode detected - updating CA certificates..." if [ -f /usr/local/share/ca-certificates/awf-ca.crt ]; then update-ca-certificates 2>/dev/null + # Set NODE_EXTRA_CA_CERTS so Node.js tools (Yarn 4, Corepack, npm) trust the AWF CA. + # Node.js uses its own CA bundle, not the system CA store updated by update-ca-certificates. + export NODE_EXTRA_CA_CERTS="/usr/local/share/ca-certificates/awf-ca.crt" echo "[entrypoint] CA certificates updated for SSL Bump" + echo "[entrypoint] NODE_EXTRA_CA_CERTS set to $NODE_EXTRA_CA_CERTS" echo "[entrypoint] ⚠️ WARNING: HTTPS traffic will be intercepted for URL inspection" else echo "[entrypoint][WARN] SSL Bump enabled but CA certificate not found" diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index d334c308..cab20595 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -490,10 +490,49 @@ describe('docker-manager', () => { expect(env.HTTP_PROXY).toBe('http://172.30.0.10:3128'); expect(env.HTTPS_PROXY).toBe('http://172.30.0.10:3128'); + expect(env.https_proxy).toBe('http://172.30.0.10:3128'); expect(env.SQUID_PROXY_HOST).toBe('squid-proxy'); expect(env.SQUID_PROXY_PORT).toBe('3128'); }); + it('should set lowercase https_proxy for Yarn 4 and Corepack compatibility', () => { + const result = generateDockerCompose(mockConfig, mockNetworkConfig); + const agent = result.services.agent; + const env = agent.environment as Record; + + // Yarn 4 (undici), Corepack, and some Node.js HTTP clients only check lowercase + expect(env.https_proxy).toBe(env.HTTPS_PROXY); + // http_proxy is intentionally NOT set - see comment in docker-manager.ts + expect(env.http_proxy).toBeUndefined(); + }); + + it('should set NODE_EXTRA_CA_CERTS when SSL Bump is enabled', () => { + const sslBumpConfig = { ...mockConfig, sslBump: true }; + const ssl = { + caFiles: { + certPath: '/tmp/awf-test/ssl/ca-cert.pem', + keyPath: '/tmp/awf-test/ssl/ca-key.pem', + derPath: '/tmp/awf-test/ssl/ca-cert.der', + }, + sslDbPath: '/tmp/awf-test/ssl_db', + }; + const result = generateDockerCompose(sslBumpConfig, mockNetworkConfig, ssl); + const agent = result.services.agent; + const env = agent.environment as Record; + + expect(env.NODE_EXTRA_CA_CERTS).toBe('/usr/local/share/ca-certificates/awf-ca.crt'); + expect(env.AWF_SSL_BUMP_ENABLED).toBe('true'); + }); + + it('should not set NODE_EXTRA_CA_CERTS when SSL Bump is disabled', () => { + const result = generateDockerCompose(mockConfig, mockNetworkConfig); + const agent = result.services.agent; + const env = agent.environment as Record; + + expect(env.NODE_EXTRA_CA_CERTS).toBeUndefined(); + expect(env.AWF_SSL_BUMP_ENABLED).toBeUndefined(); + }); + it('should set NO_COLOR=1 to disable ANSI color output from CLI tools', () => { const result = generateDockerCompose(mockConfig, mockNetworkConfig); const agent = result.services.agent; diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 9c7d1338..c2d4aced 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -343,6 +343,13 @@ export function generateDockerCompose( const environment: Record = { HTTP_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`, HTTPS_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`, + // Lowercase https_proxy for tools that only check lowercase (e.g., Yarn 4/undici, Corepack). + // NOTE: We intentionally do NOT set lowercase http_proxy. Some curl builds (Ubuntu 22.04) + // ignore uppercase HTTP_PROXY for HTTP URLs (httpoxy mitigation), which means HTTP traffic + // falls through to iptables DNAT interception — the correct behavior for connection-level + // blocking. Setting http_proxy would route HTTP through the forward proxy where Squid's + // 403 error page returns exit code 0, breaking security expectations. + https_proxy: `http://${networkConfig.squidIp}:${SQUID_PORT}`, SQUID_PROXY_HOST: 'squid-proxy', SQUID_PROXY_PORT: SQUID_PORT.toString(), HOME: homeDir, @@ -699,6 +706,10 @@ export function generateDockerCompose( agentVolumes.push(`${sslConfig.caFiles.certPath}:/usr/local/share/ca-certificates/awf-ca.crt:ro`); // Set environment variable to indicate SSL Bump is enabled environment.AWF_SSL_BUMP_ENABLED = 'true'; + // Tell Node.js to trust the AWF session CA certificate. + // Without this, Node.js tools (Yarn 4, Corepack, npm) fail with EPROTO + // because Node.js uses its own CA bundle, not the system CA store. + environment.NODE_EXTRA_CA_CERTS = '/usr/local/share/ca-certificates/awf-ca.crt'; } // SECURITY: Selective mounting to prevent credential exfiltration @@ -1015,6 +1026,10 @@ export function generateDockerCompose( // Route through Squid to respect domain whitelisting HTTP_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`, HTTPS_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`, + https_proxy: `http://${networkConfig.squidIp}:${SQUID_PORT}`, + // Prevent curl health check from routing localhost through Squid + NO_PROXY: `localhost,127.0.0.1,::1`, + no_proxy: `localhost,127.0.0.1,::1`, // Rate limiting configuration ...(config.rateLimitConfig && { AWF_RATE_LIMIT_ENABLED: String(config.rateLimitConfig.enabled),