diff --git a/src/host-iptables-host-access.test.ts b/src/host-iptables-host-access.test.ts index 9bbd5c07..d2f766d2 100644 --- a/src/host-iptables-host-access.test.ts +++ b/src/host-iptables-host-access.test.ts @@ -181,6 +181,38 @@ describe('host-iptables (host access)', () => { ]); }); + it('should skip invalid ports in allowHostServicePorts', async () => { + setupDefaultIptablesMocks(); + + mockedExeca.mockImplementation(((cmd: string, args: string[]) => { + if (cmd === 'docker' && args.includes('bridge')) { + return Promise.resolve({ stdout: '172.17.0.1', stderr: '', exitCode: 0 }); + } + return Promise.resolve({ stdout: '', stderr: '', exitCode: 0 }); + }) as any); + + const hostAccess: HostAccessConfig = { enabled: true, allowHostServicePorts: 'abc,99999,-1' }; + await setupHostIptables('172.30.0.10', 3128, ['8.8.8.8', '8.8.4.4'], undefined, undefined, hostAccess); + + // Verify invalid service ports are NOT added + expect(mockedExeca).not.toHaveBeenCalledWith('iptables', expect.arrayContaining([ + '--dport', 'abc', + ])); + expect(mockedExeca).not.toHaveBeenCalledWith('iptables', expect.arrayContaining([ + '--dport', '99999', + ])); + expect(mockedExeca).not.toHaveBeenCalledWith('iptables', expect.arrayContaining([ + '--dport', '-1', + ])); + + // Default ports should still be present + expect(mockedExeca).toHaveBeenCalledWith('iptables', [ + '-t', 'filter', '-A', 'FW_WRAPPER', + '-p', 'tcp', '-d', '172.30.0.1', '--dport', '80', + '-j', 'ACCEPT', + ]); + }); + it('should skip invalid ports in allowHostPorts', async () => { setupDefaultIptablesMocks(); diff --git a/src/host-iptables-rules.ts b/src/host-iptables-rules.ts index 143d73f7..50a8a762 100644 --- a/src/host-iptables-rules.ts +++ b/src/host-iptables-rules.ts @@ -234,16 +234,18 @@ export async function setupHostIptables(squidIp: string, squidPort: number, dnsS ]); } + const needsGatewayIps = !!cliProxyConfig || !!hostAccess?.enabled; + const dockerBridgeGateway = needsGatewayIps ? await getDockerBridgeGateway() : null; + const gatewayIps = [AWF_NETWORK_GATEWAY]; + if (dockerBridgeGateway) { + gatewayIps.push(dockerBridgeGateway); + } + // 5b2. Allow CLI proxy container to reach host DIFC proxy (when enabled) // The cli-proxy container needs to TCP-tunnel to the external DIFC proxy on the host. // Only the cli-proxy IP is allowed to reach the host gateway on the DIFC port. if (cliProxyConfig) { const { ip: cliProxyIp, difcProxyPort } = cliProxyConfig; - const gatewayIp = await getDockerBridgeGateway(); - const gatewayIps = [AWF_NETWORK_GATEWAY]; - if (gatewayIp) { - gatewayIps.push(gatewayIp); - } for (const gwIp of gatewayIps) { logger.debug(`Allowing CLI proxy (${cliProxyIp}) → host gateway (${gwIp}):${difcProxyPort}`); await execa('iptables', [ @@ -258,12 +260,6 @@ export async function setupHostIptables(squidIp: string, squidPort: number, dnsS // 5c. Allow traffic to host gateway when host access is enabled // This is needed for Playwright localhost testing, MCP servers, etc. if (hostAccess?.enabled) { - const gatewayIp = await getDockerBridgeGateway(); - const gatewayIps = [AWF_NETWORK_GATEWAY]; - if (gatewayIp) { - gatewayIps.push(gatewayIp); - } - // Default: allow HTTP (80) and HTTPS (443) const defaultPorts = ['80', '443']; diff --git a/src/host-iptables-setup.test.ts b/src/host-iptables-setup.test.ts index 4b68f9fc..45c99f23 100644 --- a/src/host-iptables-setup.test.ts +++ b/src/host-iptables-setup.test.ts @@ -376,6 +376,26 @@ describe('host-iptables (setup)', () => { '-j', 'ACCEPT', ]); }); + + it('should resolve Docker bridge gateway once when cliProxyConfig and hostAccess are both enabled', async () => { + setupDefaultIptablesMocks(); + + mockedExeca.mockImplementation(((cmd: string, args: string[]) => { + if (cmd === 'docker' && args.includes('bridge')) { + return Promise.resolve({ stdout: '172.17.0.1', stderr: '', exitCode: 0 }); + } + return Promise.resolve({ stdout: '', stderr: '', exitCode: 0 }); + }) as any); + + const cliProxyConfig = { ip: '172.30.0.50', difcProxyPort: 18443 }; + const hostAccess = { enabled: true }; + await setupHostIptables('172.30.0.10', 3128, ['8.8.8.8', '8.8.4.4'], undefined, undefined, hostAccess, cliProxyConfig); + + const bridgeGatewayCalls = mockedExeca.mock.calls.filter(([cmd, args]) => + cmd === 'docker' && Array.isArray(args) && args.includes('bridge') + ); + expect(bridgeGatewayCalls).toHaveLength(1); + }); }); describe('setupHostIptables with IPv6 DNS servers', () => {