Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions containers/agent/setup-iptables.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ fi
# Get Squid proxy configuration from environment
SQUID_HOST="${SQUID_PROXY_HOST:-squid-proxy}"
SQUID_PORT="${SQUID_PROXY_PORT:-3128}"
# Intercept port for NAT-redirected transparent proxy traffic
# Squid's "intercept" mode handles relative URLs by extracting destination from Host header
SQUID_INTERCEPT_PORT="${SQUID_INTERCEPT_PORT:-3129}"

echo "[iptables] Squid proxy: ${SQUID_HOST}:${SQUID_PORT}"
echo "[iptables] Squid proxy: ${SQUID_HOST}:${SQUID_PORT} (intercept: ${SQUID_INTERCEPT_PORT})"

# Resolve Squid hostname to IP
# Use awk's NR to get first line to avoid host binary dependency in chroot mode
Expand Down Expand Up @@ -154,15 +157,18 @@ for port in "${DANGEROUS_PORTS[@]}"; do
done
echo "[iptables] NAT blacklist applied for ${#DANGEROUS_PORTS[@]} dangerous ports"

# Redirect standard HTTP/HTTPS ports to Squid
# Redirect standard HTTP/HTTPS ports to Squid's INTERCEPT port
# This provides defense-in-depth: iptables enforces port policy, Squid enforces domain policy
echo "[iptables] Redirect HTTP (80) and HTTPS (443) to Squid..."
iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination "${SQUID_IP}:${SQUID_PORT}"
iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination "${SQUID_IP}:${SQUID_PORT}"
# We use the intercept port (not the regular port) because NAT-redirected traffic is "transparent" -
# clients send relative URLs (GET /path) which require Squid's intercept mode to handle properly.
# The regular port (3128) is for explicit proxy usage via HTTP_PROXY/HTTPS_PROXY env vars.
echo "[iptables] Redirect HTTP (80) and HTTPS (443) to Squid intercept port..."
iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination "${SQUID_IP}:${SQUID_INTERCEPT_PORT}"
iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination "${SQUID_IP}:${SQUID_INTERCEPT_PORT}"

# If user specified additional ports via --allow-host-ports, redirect those too
if [ -n "$AWF_ALLOW_HOST_PORTS" ]; then
echo "[iptables] Redirect user-specified ports to Squid..."
echo "[iptables] Redirect user-specified ports to Squid intercept port..."

# Parse comma-separated port list
IFS=',' read -ra PORTS <<< "$AWF_ALLOW_HOST_PORTS"
Expand All @@ -173,13 +179,13 @@ if [ -n "$AWF_ALLOW_HOST_PORTS" ]; then

if [[ $port_spec == *"-"* ]]; then
# Port range (e.g., "3000-3010")
echo "[iptables] Redirect port range $port_spec to Squid..."
echo "[iptables] Redirect port range $port_spec to Squid intercept port..."
# For port ranges, use --dport with range syntax (without multiport)
iptables -t nat -A OUTPUT -p tcp --dport "$port_spec" -j DNAT --to-destination "${SQUID_IP}:${SQUID_PORT}"
iptables -t nat -A OUTPUT -p tcp --dport "$port_spec" -j DNAT --to-destination "${SQUID_IP}:${SQUID_INTERCEPT_PORT}"
else
# Single port (e.g., "3000")
echo "[iptables] Redirect port $port_spec to Squid..."
iptables -t nat -A OUTPUT -p tcp --dport "$port_spec" -j DNAT --to-destination "${SQUID_IP}:${SQUID_PORT}"
echo "[iptables] Redirect port $port_spec to Squid intercept port..."
iptables -t nat -A OUTPUT -p tcp --dport "$port_spec" -j DNAT --to-destination "${SQUID_IP}:${SQUID_INTERCEPT_PORT}"
fi
done
else
Expand Down
3 changes: 2 additions & 1 deletion src/docker-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ export function generateDockerCompose(
},
volumes: squidVolumes,
healthcheck: {
test: ['CMD', 'nc', '-z', 'localhost', '3128'],
test: ['CMD-SHELL', 'nc -z localhost 3128 && nc -z localhost 3129'],
interval: '5s',
timeout: '3s',
retries: 5,
Expand Down Expand Up @@ -713,6 +713,7 @@ export async function writeConfigs(config: WrapperConfig): Promise<void> {
domains: config.allowedDomains,
blockedDomains: config.blockedDomains,
port: SQUID_PORT,
interceptPort: SQUID_INTERCEPT_PORT,
sslBump: config.sslBump,
caFiles: sslConfig?.caFiles,
sslDbPath: sslConfig ? '/var/spool/squid_ssl_db' : undefined,
Expand Down
21 changes: 21 additions & 0 deletions src/squid-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,27 @@ describe('generateSquidConfig', () => {
expect(result).toContain('http_port 3128');
expect(result).not.toContain('https_port');
});

it('should add intercept port when specified', () => {
const config: SquidConfig = {
domains: ['github.com'],
port: 3128,
interceptPort: 3129,
};
const result = generateSquidConfig(config);
expect(result).toContain('http_port 3128');
expect(result).toContain('http_port 3129 intercept');
});

it('should not add intercept port when not specified', () => {
const config: SquidConfig = {
domains: ['github.com'],
port: 3128,
};
const result = generateSquidConfig(config);
expect(result).toContain('http_port 3128');
expect(result).not.toContain('intercept');
});
Comment on lines +1124 to +1143
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new tests for intercept port are placed inside the "SSL Bump Mode" describe block, but they don't actually test the combination of SSL Bump with intercept port. This misses the critical bug where SSL Bump ignores the intercept port configuration.

Add a test that verifies intercept port works with SSL Bump enabled:

  • Config with sslBump: true, interceptPort: 3129
  • Expected: both "http_port 3128 ssl-bump" and "http_port 3129 ssl-bump intercept" in output

This test would catch the bug where generateSslBumpSection doesn't support intercept port.

Copilot uses AI. Check for mistakes.
});
});

Expand Down
19 changes: 15 additions & 4 deletions src/squid-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ http_port 3128 ssl-bump \\
dynamic_cert_mem_cache_size=16MB \\
options=NO_SSLv3,NO_TLSv1,NO_TLSv1_1

# Intercept port for NAT-redirected transparent proxy traffic
# Traffic is DNAT'd here by iptables. Squid uses "intercept" mode to handle
# relative URLs (GET /path) by extracting the destination from the Host header.
http_port 3129 intercept

# SSL certificate database for dynamic certificate generation
# Using 16MB for certificate cache (sufficient for typical AI agent sessions)
sslcrtd_program /usr/lib/squid/security_file_certgen -s ${sslDbPath} -M 16MB
Expand Down Expand Up @@ -205,7 +210,7 @@ ${urlAclSection}${urlAccessRules}`;
* // Blocked: internal.example.com -> acl blocked_domains dstdomain .internal.example.com
*/
export function generateSquidConfig(config: SquidConfig): string {
const { domains, blockedDomains, port, sslBump, caFiles, sslDbPath, urlPatterns, enableHostAccess, allowHostPorts } = config;
const { domains, blockedDomains, port, interceptPort, sslBump, caFiles, sslDbPath, urlPatterns, enableHostAccess, allowHostPorts } = config;

// Parse domains into plain domains and wildcard patterns
// Note: parseDomainList extracts and preserves protocol info from prefixes (http://, https://)
Expand Down Expand Up @@ -408,10 +413,16 @@ export function generateSquidConfig(config: SquidConfig): string {

// Generate SSL Bump section if enabled
let sslBumpSection = '';
// Port configuration: Use normal proxy mode (not intercept mode)
// With targeted port redirection in iptables, traffic is explicitly redirected
// to Squid on specific ports (80, 443, + user-specified), maintaining defense-in-depth
// Port configuration:
// - Regular port (3128): For explicit proxy usage via HTTP_PROXY/HTTPS_PROXY env vars
// Clients aware of the proxy send absolute URLs (GET http://example.com/path)
// - Intercept port (3129): For NAT-redirected transparent proxy traffic
// Traffic is DNAT'd here by iptables. Squid uses "intercept" mode to handle
// relative URLs (GET /path) by extracting the destination from the Host header.
let portConfig = `http_port ${port}`;
if (interceptPort) {
portConfig += `\nhttp_port ${interceptPort} intercept`;
}
Comment on lines +423 to +425
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When SSL Bump is enabled, the intercept port configuration added here will be ignored. On line 436 (visible in surrounding context), portConfig is set to empty string when sslBump is enabled, and the SSL Bump section uses a hardcoded port configuration.

The generateSslBumpSection function (called on line 428-434) hardcodes "http_port 3128 ssl-bump" and doesn't receive the port or interceptPort parameters. This means NAT-redirected traffic will fail with "Invalid URL - Missing hostname" errors when SSL Bump is enabled, which is the exact issue this PR is trying to fix.

To fix this, generateSslBumpSection needs to be updated to:

  1. Accept port and interceptPort parameters
  2. Generate both "http_port [port] ssl-bump" and "http_port [interceptPort] ssl-bump intercept" configurations when interceptPort is provided

Without this fix, SSL Bump mode will continue to experience the NAT redirect failures described in issue #519.

Copilot uses AI. Check for mistakes.

// For SSL Bump, we need to check hasPlainDomains and hasPatterns for the 'both' protocol domains
// since those are the ones that go into allowed_domains / allowed_domains_regex ACLs
Expand Down
15 changes: 15 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,21 @@ export interface SquidConfig {
* @example "3000-3010,8000-8090"
*/
allowHostPorts?: string;

/**
* Port number for transparently intercepted traffic (NAT/DNAT redirected)
*
* When traffic is redirected via iptables NAT (DNAT) to Squid, it arrives
* as "transparent" traffic - the client doesn't know it's talking to a proxy.
* This requires Squid's "intercept" mode which handles relative URLs and
* reconstructs the original destination from the Host header.
*
* This port should be used for NAT-redirected traffic, while the regular
* `port` is used for explicit proxy connections (HTTP_PROXY env var).
*
* @default 3129
*/
interceptPort?: number;
}

/**
Expand Down
Loading