Skip to content

Commit 183e451

Browse files
Mossakaclaude
andauthored
fix: enable Squid intercept mode for NAT-redirected traffic (#520)
* fix: enable Squid intercept mode for NAT-redirected traffic When traffic is NAT'd (DNAT) to Squid proxy, clients send relative URLs (GET /path) because they don't know they're talking to a proxy. Squid's normal proxy mode requires absolute URLs (GET http://example.com/path), causing "Invalid URL - Missing hostname" errors. This fix: - Adds interceptPort to SquidConfig for transparent proxy traffic - Configures Squid with `http_port 3129 intercept` for NAT'd traffic - Updates iptables rules to redirect to intercept port (3129) not regular port (3128) - Keeps regular port (3128) for explicit proxy usage via HTTP_PROXY This fixes Codex/rmcp OAuth discovery timeouts - requests now reach the MCP gateway instead of being blocked by Squid. Fixes #519 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add intercept port to SSL Bump mode and update healthcheck 1. Add intercept port (3129) to SSL Bump section: When SSL Bump was enabled, the generateSslBumpSection() function generated its own port config but did not include the intercept port needed for NAT-redirected transparent proxy traffic. 2. Update healthcheck to verify both ports: The Docker healthcheck only verified port 3128, not ensuring port 3129 was also working. Now checks both ports to ensure complete Squid proxy functionality. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a4c7b7c commit 183e451

5 files changed

Lines changed: 69 additions & 15 deletions

File tree

containers/agent/setup-iptables.sh

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ fi
3232
# Get Squid proxy configuration from environment
3333
SQUID_HOST="${SQUID_PROXY_HOST:-squid-proxy}"
3434
SQUID_PORT="${SQUID_PROXY_PORT:-3128}"
35+
# Intercept port for NAT-redirected transparent proxy traffic
36+
# Squid's "intercept" mode handles relative URLs by extracting destination from Host header
37+
SQUID_INTERCEPT_PORT="${SQUID_INTERCEPT_PORT:-3129}"
3538

36-
echo "[iptables] Squid proxy: ${SQUID_HOST}:${SQUID_PORT}"
39+
echo "[iptables] Squid proxy: ${SQUID_HOST}:${SQUID_PORT} (intercept: ${SQUID_INTERCEPT_PORT})"
3740

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

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

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

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

174180
if [[ $port_spec == *"-"* ]]; then
175181
# Port range (e.g., "3000-3010")
176-
echo "[iptables] Redirect port range $port_spec to Squid..."
182+
echo "[iptables] Redirect port range $port_spec to Squid intercept port..."
177183
# For port ranges, use --dport with range syntax (without multiport)
178-
iptables -t nat -A OUTPUT -p tcp --dport "$port_spec" -j DNAT --to-destination "${SQUID_IP}:${SQUID_PORT}"
184+
iptables -t nat -A OUTPUT -p tcp --dport "$port_spec" -j DNAT --to-destination "${SQUID_IP}:${SQUID_INTERCEPT_PORT}"
179185
else
180186
# Single port (e.g., "3000")
181-
echo "[iptables] Redirect port $port_spec to Squid..."
182-
iptables -t nat -A OUTPUT -p tcp --dport "$port_spec" -j DNAT --to-destination "${SQUID_IP}:${SQUID_PORT}"
187+
echo "[iptables] Redirect port $port_spec to Squid intercept port..."
188+
iptables -t nat -A OUTPUT -p tcp --dport "$port_spec" -j DNAT --to-destination "${SQUID_IP}:${SQUID_INTERCEPT_PORT}"
183189
fi
184190
done
185191
else

src/docker-manager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ export function generateDockerCompose(
267267
},
268268
volumes: squidVolumes,
269269
healthcheck: {
270-
test: ['CMD', 'nc', '-z', 'localhost', '3128'],
270+
test: ['CMD-SHELL', 'nc -z localhost 3128 && nc -z localhost 3129'],
271271
interval: '5s',
272272
timeout: '3s',
273273
retries: 5,
@@ -713,6 +713,7 @@ export async function writeConfigs(config: WrapperConfig): Promise<void> {
713713
domains: config.allowedDomains,
714714
blockedDomains: config.blockedDomains,
715715
port: SQUID_PORT,
716+
interceptPort: SQUID_INTERCEPT_PORT,
716717
sslBump: config.sslBump,
717718
caFiles: sslConfig?.caFiles,
718719
sslDbPath: sslConfig ? '/var/spool/squid_ssl_db' : undefined,

src/squid-config.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,27 @@ describe('generateSquidConfig', () => {
11201120
expect(result).toContain('http_port 3128');
11211121
expect(result).not.toContain('https_port');
11221122
});
1123+
1124+
it('should add intercept port when specified', () => {
1125+
const config: SquidConfig = {
1126+
domains: ['github.com'],
1127+
port: 3128,
1128+
interceptPort: 3129,
1129+
};
1130+
const result = generateSquidConfig(config);
1131+
expect(result).toContain('http_port 3128');
1132+
expect(result).toContain('http_port 3129 intercept');
1133+
});
1134+
1135+
it('should not add intercept port when not specified', () => {
1136+
const config: SquidConfig = {
1137+
domains: ['github.com'],
1138+
port: 3128,
1139+
};
1140+
const result = generateSquidConfig(config);
1141+
expect(result).toContain('http_port 3128');
1142+
expect(result).not.toContain('intercept');
1143+
});
11231144
});
11241145
});
11251146

src/squid-config.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ http_port 3128 ssl-bump \\
155155
dynamic_cert_mem_cache_size=16MB \\
156156
options=NO_SSLv3,NO_TLSv1,NO_TLSv1_1
157157
158+
# Intercept port for NAT-redirected transparent proxy traffic
159+
# Traffic is DNAT'd here by iptables. Squid uses "intercept" mode to handle
160+
# relative URLs (GET /path) by extracting the destination from the Host header.
161+
http_port 3129 intercept
162+
158163
# SSL certificate database for dynamic certificate generation
159164
# Using 16MB for certificate cache (sufficient for typical AI agent sessions)
160165
sslcrtd_program /usr/lib/squid/security_file_certgen -s ${sslDbPath} -M 16MB
@@ -205,7 +210,7 @@ ${urlAclSection}${urlAccessRules}`;
205210
* // Blocked: internal.example.com -> acl blocked_domains dstdomain .internal.example.com
206211
*/
207212
export function generateSquidConfig(config: SquidConfig): string {
208-
const { domains, blockedDomains, port, sslBump, caFiles, sslDbPath, urlPatterns, enableHostAccess, allowHostPorts } = config;
213+
const { domains, blockedDomains, port, interceptPort, sslBump, caFiles, sslDbPath, urlPatterns, enableHostAccess, allowHostPorts } = config;
209214

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

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

416427
// For SSL Bump, we need to check hasPlainDomains and hasPatterns for the 'both' protocol domains
417428
// since those are the ones that go into allowed_domains / allowed_domains_regex ACLs

src/types.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,21 @@ export interface SquidConfig {
474474
* @example "3000-3010,8000-8090"
475475
*/
476476
allowHostPorts?: string;
477+
478+
/**
479+
* Port number for transparently intercepted traffic (NAT/DNAT redirected)
480+
*
481+
* When traffic is redirected via iptables NAT (DNAT) to Squid, it arrives
482+
* as "transparent" traffic - the client doesn't know it's talking to a proxy.
483+
* This requires Squid's "intercept" mode which handles relative URLs and
484+
* reconstructs the original destination from the Host header.
485+
*
486+
* This port should be used for NAT-redirected traffic, while the regular
487+
* `port` is used for explicit proxy connections (HTTP_PROXY env var).
488+
*
489+
* @default 3129
490+
*/
491+
interceptPort?: number;
477492
}
478493

479494
/**

0 commit comments

Comments
 (0)