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
11 changes: 9 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,20 @@ runs:
shell: bash
env:
INPUT_VERSION: ${{ inputs.version }}
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail

REPO="github/gh-aw-firewall"
BINARY_NAME="awf-linux-x64"
INSTALL_DIR="${RUNNER_TEMP}/awf-bin"

# Build auth header for GitHub API to avoid rate limits
AUTH_HEADER=()
if [ -n "${GITHUB_TOKEN:-}" ]; then
AUTH_HEADER=(-H "Authorization: token ${GITHUB_TOKEN}")
fi

# Create install directory
mkdir -p "$INSTALL_DIR"

Expand All @@ -61,9 +68,9 @@ runs:
echo "Fetching latest release version..."
# Use jq if available, fallback to grep/sed
if command -v jq &> /dev/null; then
VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | jq -r '.tag_name')
VERSION=$(curl -fsSL "${AUTH_HEADER[@]}" "https://api.github.com/repos/${REPO}/releases/latest" | jq -r '.tag_name')
else
VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
VERSION=$(curl -fsSL "${AUTH_HEADER[@]}" "https://api.github.com/repos/${REPO}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
fi
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
echo "::error::Failed to fetch latest version from GitHub API"
Expand Down
33 changes: 11 additions & 22 deletions containers/agent/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,11 @@ if [ "$CURRENT_UID" != "$HOST_UID" ] || [ "$CURRENT_GID" != "$HOST_GID" ]; then
echo "[entrypoint] UID/GID adjustment complete"
fi

# Fix DNS configuration - ensure external DNS works alongside Docker's embedded DNS
# Docker's embedded DNS (127.0.0.11) is used for service name resolution (e.g., squid-proxy)
# Trusted external DNS servers are used for internet domain resolution
# Configure DNS to use only Docker's embedded DNS (127.0.0.11)
# Docker embedded DNS handles all name resolution:
# - Container names (e.g., squid-proxy) → resolved directly
# - External domains → forwarded to upstream DNS servers configured via docker-compose dns: field
# No external DNS servers are listed in resolv.conf to prevent DNS-based data exfiltration
echo "[entrypoint] Configuring DNS..."
if [ -f /etc/resolv.conf ]; then
# Backup original resolv.conf
Expand All @@ -85,30 +87,17 @@ if [ -f /etc/resolv.conf ]; then
} > /etc/resolv.conf
echo "[entrypoint] DNS configured with Docker embedded DNS (127.0.0.11) and DoH proxy ($AWF_DOH_PROXY_IP)"
else
# Traditional DNS mode: use configured DNS servers
# Get DNS servers from environment (default to Google DNS)
DNS_SERVERS="${AWF_DNS_SERVERS:-8.8.8.8,8.8.4.4}"

# Create new resolv.conf with Docker embedded DNS first, then trusted external DNS servers
# Simplified security model: Docker embedded DNS only
# Docker embedded DNS at 127.0.0.11 handles all resolution
# It forwards to upstream servers configured via docker-compose dns: field
# No external DNS servers listed to prevent DNS-based data exfiltration
{
echo "# Generated by awf entrypoint"
echo "# Docker embedded DNS for service name resolution (squid-proxy, etc.)"
echo "# Docker embedded DNS handles all resolution (forwards to upstream via docker-compose dns: config)"
echo "nameserver 127.0.0.11"
echo "# Trusted external DNS servers for internet domain resolution"

# Add each trusted DNS server
IFS=',' read -ra DNS_ARRAY <<< "$DNS_SERVERS"
for dns_server in "${DNS_ARRAY[@]}"; do
dns_server=$(echo "$dns_server" | tr -d ' ')
if [ -n "$dns_server" ]; then
echo "nameserver $dns_server"
fi
done

echo "options ndots:0"
} > /etc/resolv.conf

echo "[entrypoint] DNS configured with Docker embedded DNS (127.0.0.11) and trusted servers: $DNS_SERVERS"
echo "[entrypoint] DNS configured with Docker embedded DNS (127.0.0.11) only"
fi
fi

Expand Down
114 changes: 52 additions & 62 deletions containers/agent/setup-iptables.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,30 @@ if [ -z "$SQUID_IP" ]; then
fi
echo "[iptables] Squid IP resolved to: $SQUID_IP"

# Save Docker's embedded DNS DNAT rules before flushing.
# Docker adds DNAT rules to redirect 127.0.0.11:53 to its internal DNS server
# on a random high port. Flushing the NAT chain destroys these rules, breaking
# DNS resolution via Docker embedded DNS.
DOCKER_DNS_RULES=$(iptables-save -t nat 2>/dev/null | grep -- "-A OUTPUT.*127.0.0.11" || true)

# Clear existing NAT rules (both IPv4 and IPv6)
iptables -t nat -F OUTPUT 2>/dev/null || true
if [ "$IP6TABLES_AVAILABLE" = true ]; then
ip6tables -t nat -F OUTPUT 2>/dev/null || true
fi

# Restore Docker's embedded DNS DNAT rules (must come before localhost RETURN rules
# so that DNS queries to 127.0.0.11:53 are properly redirected to Docker's DNS server)
if [ -n "$DOCKER_DNS_RULES" ]; then
echo "[iptables] Restoring Docker embedded DNS DNAT rules..."
while IFS= read -r rule; do
if [ -n "$rule" ]; then
# iptables-save outputs rules like "-A OUTPUT -d 127.0.0.11/32 -p udp -m udp --dport 53 -j DNAT --to-destination 127.0.0.11:XXXXX"
iptables -t nat $rule 2>/dev/null || true
fi
done <<< "$DOCKER_DNS_RULES"
fi

# Allow localhost traffic (for stdio MCP servers and test frameworks)
echo "[iptables] Allow localhost traffic..."
iptables -t nat -A OUTPUT -o lo -j RETURN
Expand All @@ -79,9 +97,6 @@ if [ -n "$AGENT_IP" ] && is_valid_ipv4 "$AGENT_IP"; then
iptables -A OUTPUT -p tcp -d "$AGENT_IP" -j ACCEPT
fi

# Get DNS servers from environment (default to Google DNS)
DNS_SERVERS="${AWF_DNS_SERVERS:-8.8.8.8,8.8.4.4}"

# Check if DNS-over-HTTPS mode is enabled
if [ "$AWF_DOH_ENABLED" = "true" ] && [ -n "$AWF_DOH_PROXY_IP" ]; then
echo "[iptables] DNS-over-HTTPS mode: routing DNS through DoH proxy at $AWF_DOH_PROXY_IP"
Expand All @@ -97,66 +112,35 @@ if [ "$AWF_DOH_ENABLED" = "true" ] && [ -n "$AWF_DOH_PROXY_IP" ]; then

# Allow return traffic to DoH proxy
iptables -t nat -A OUTPUT -d "$AWF_DOH_PROXY_IP" -j RETURN

# Set variables for OUTPUT filter chain (used later)
IPV4_DNS_SERVERS=()
IPV6_DNS_SERVERS=()
else
echo "[iptables] Configuring DNS rules for trusted servers: $DNS_SERVERS"

# Separate IPv4 and IPv6 DNS servers
IPV4_DNS_SERVERS=()
IPV6_DNS_SERVERS=()
# Simplified DNS model: Docker embedded DNS (127.0.0.11) handles all name resolution.
# The embedded DNS forwards to upstream servers configured via docker-compose dns: field.
# Docker's DNS forwarding may traverse the container's network namespace, so we must
# explicitly allow UDP/TCP port 53 to the configured upstream servers.
# Direct DNS queries to non-configured servers are blocked by the OUTPUT filter chain.
DNS_SERVERS="${AWF_DNS_SERVERS:-8.8.8.8,8.8.4.4}"
echo "[iptables] DNS: Docker embedded DNS forwards to upstream: $DNS_SERVERS"

# Allow DNS queries to configured upstream servers (needed for Docker DNS forwarding)
IFS=',' read -ra DNS_ARRAY <<< "$DNS_SERVERS"
for dns_server in "${DNS_ARRAY[@]}"; do
dns_server=$(echo "$dns_server" | tr -d ' ')
if [ -n "$dns_server" ]; then
if is_ipv6 "$dns_server"; then
IPV6_DNS_SERVERS+=("$dns_server")
if [ "$IP6TABLES_AVAILABLE" = true ]; then
ip6tables -t nat -A OUTPUT -p udp -d "$dns_server" --dport 53 -j RETURN
ip6tables -t nat -A OUTPUT -p tcp -d "$dns_server" --dport 53 -j RETURN
fi
else
IPV4_DNS_SERVERS+=("$dns_server")
iptables -t nat -A OUTPUT -p udp -d "$dns_server" --dport 53 -j RETURN
iptables -t nat -A OUTPUT -p tcp -d "$dns_server" --dport 53 -j RETURN
fi
fi
done

echo "[iptables] IPv4 DNS servers: ${IPV4_DNS_SERVERS[*]:-none}"
echo "[iptables] IPv6 DNS servers: ${IPV6_DNS_SERVERS[*]:-none}"

# Allow DNS queries ONLY to trusted IPv4 DNS servers (prevents DNS exfiltration)
for dns_server in "${IPV4_DNS_SERVERS[@]}"; do
echo "[iptables] Allow DNS to trusted IPv4 server: $dns_server"
iptables -t nat -A OUTPUT -p udp -d "$dns_server" --dport 53 -j RETURN
iptables -t nat -A OUTPUT -p tcp -d "$dns_server" --dport 53 -j RETURN
done

# Allow DNS queries ONLY to trusted IPv6 DNS servers
if [ "$IP6TABLES_AVAILABLE" = true ]; then
for dns_server in "${IPV6_DNS_SERVERS[@]}"; do
echo "[iptables] Allow DNS to trusted IPv6 server: $dns_server"
ip6tables -t nat -A OUTPUT -p udp -d "$dns_server" --dport 53 -j RETURN
ip6tables -t nat -A OUTPUT -p tcp -d "$dns_server" --dport 53 -j RETURN
done
elif [ ${#IPV6_DNS_SERVERS[@]} -gt 0 ]; then
echo "[iptables] WARNING: IPv6 DNS servers configured but ip6tables not available"
fi

# Allow DNS to Docker's embedded DNS server (127.0.0.11) for container name resolution
echo "[iptables] Allow DNS to Docker embedded DNS (127.0.0.11)..."
# Also allow DNS to Docker's embedded DNS server itself
iptables -t nat -A OUTPUT -p udp -d 127.0.0.11 --dport 53 -j RETURN
iptables -t nat -A OUTPUT -p tcp -d 127.0.0.11 --dport 53 -j RETURN

# Allow return traffic to trusted IPv4 DNS servers
echo "[iptables] Allow traffic to trusted DNS servers..."
for dns_server in "${IPV4_DNS_SERVERS[@]}"; do
iptables -t nat -A OUTPUT -d "$dns_server" -j RETURN
done

# Allow return traffic to trusted IPv6 DNS servers
if [ "$IP6TABLES_AVAILABLE" = true ]; then
for dns_server in "${IPV6_DNS_SERVERS[@]}"; do
ip6tables -t nat -A OUTPUT -d "$dns_server" -j RETURN
done
fi
fi

# Allow traffic to Squid proxy itself (prevent redirect loop)
Expand Down Expand Up @@ -290,23 +274,27 @@ fi
# These rules apply AFTER NAT translation
echo "[iptables] Configuring OUTPUT filter chain rules..."

# Allow localhost traffic
# Allow localhost traffic (includes Docker embedded DNS at 127.0.0.11)
iptables -A OUTPUT -o lo -j ACCEPT

# Allow DNS queries to trusted servers (or DoH proxy)
# Allow DNS to DoH proxy or configured upstream servers
if [ "$AWF_DOH_ENABLED" = "true" ] && [ -n "$AWF_DOH_PROXY_IP" ]; then
iptables -A OUTPUT -p udp -d "$AWF_DOH_PROXY_IP" --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp -d "$AWF_DOH_PROXY_IP" --dport 53 -j ACCEPT
else
for dns_server in "${IPV4_DNS_SERVERS[@]}"; do
iptables -A OUTPUT -p udp -d "$dns_server" --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp -d "$dns_server" --dport 53 -j ACCEPT
# Allow DNS to configured upstream servers (needed for Docker DNS forwarding)
for dns_server in "${DNS_ARRAY[@]}"; do
dns_server=$(echo "$dns_server" | tr -d ' ')
if [ -n "$dns_server" ] && ! is_ipv6 "$dns_server"; then
iptables -A OUTPUT -p udp -d "$dns_server" --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp -d "$dns_server" --dport 53 -j ACCEPT
fi
done
fi

# Allow DNS to Docker's embedded DNS server
iptables -A OUTPUT -p udp -d 127.0.0.11 --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp -d 127.0.0.11 --dport 53 -j ACCEPT
# Allow DNS to Docker's embedded DNS server
iptables -A OUTPUT -p udp -d 127.0.0.11 --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp -d 127.0.0.11 --dport 53 -j ACCEPT
fi

# Allow traffic to Squid proxy (after NAT redirection)
iptables -A OUTPUT -p tcp -d "$SQUID_IP" -j ACCEPT
Expand All @@ -316,10 +304,12 @@ if [ -n "$AWF_API_PROXY_IP" ]; then
iptables -A OUTPUT -p tcp -d "$AWF_API_PROXY_IP" -j ACCEPT
fi

# Drop all other TCP traffic (default deny policy)
# This ensures that only explicitly allowed ports can be accessed
echo "[iptables] Drop all non-redirected TCP traffic (default deny)..."
# Drop all other TCP and UDP traffic (default deny policy)
# TCP: ensures only explicitly allowed ports can be accessed
# UDP: prevents DNS exfiltration by blocking direct queries to non-configured DNS servers
echo "[iptables] Drop all non-allowed TCP and UDP traffic (default deny)..."
iptables -A OUTPUT -p tcp -j DROP
iptables -A OUTPUT -p udp -j DROP

echo "[iptables] NAT rules applied successfully"
echo "[iptables] Current IPv4 NAT OUTPUT rules:"
Expand Down
2 changes: 1 addition & 1 deletion src/cli-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ export async function runMainWorkflow(
// Step 0: Setup host-level network and iptables
logger.info('Setting up host-level firewall network and iptables rules...');
const networkConfig = await dependencies.ensureFirewallNetwork();
const dnsServers = config.dnsServers || ['8.8.8.8', '8.8.4.4'];
// When API proxy is enabled, allow agent→sidecar traffic at the host level.
// The sidecar itself routes through Squid, so domain whitelisting is still enforced.
const dnsServers = config.dnsServers || ['8.8.8.8', '8.8.4.4'];
const apiProxyIp = config.enableApiProxy ? networkConfig.proxyIp : undefined;
// When DoH is enabled, the DoH proxy needs direct HTTPS access to the resolver
const dohProxyIp = config.dnsOverHttps ? '172.30.0.40' : undefined;
Expand Down
66 changes: 26 additions & 40 deletions src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@

afterEach(() => {
// Clean up the test directory
if (fs.existsSync(testDir)) {

Check warning on line 51 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / ESLint

Found existsSync from package "fs" with non literal argument at index 0

Check warning on line 51 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 20)

Found existsSync from package "fs" with non literal argument at index 0

Check warning on line 51 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 22)

Found existsSync from package "fs" with non literal argument at index 0
fs.rmSync(testDir, { recursive: true, force: true });
}
});

it('should parse domains from file with one domain per line', () => {
const filePath = path.join(testDir, 'domains.txt');
fs.writeFileSync(filePath, 'github.com\napi.github.com\nnpmjs.org');

Check warning on line 58 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / ESLint

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 58 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 20)

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 58 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 22)

Found writeFileSync from package "fs" with non literal argument at index 0

const result = parseDomainsFile(filePath);

Expand All @@ -64,7 +64,7 @@

it('should parse comma-separated domains from file', () => {
const filePath = path.join(testDir, 'domains.txt');
fs.writeFileSync(filePath, 'github.com, api.github.com, npmjs.org');

Check warning on line 67 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / ESLint

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 67 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 20)

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 67 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 22)

Found writeFileSync from package "fs" with non literal argument at index 0

const result = parseDomainsFile(filePath);

Expand All @@ -73,7 +73,7 @@

it('should handle mixed formats (lines and commas)', () => {
const filePath = path.join(testDir, 'domains.txt');
fs.writeFileSync(filePath, 'github.com\napi.github.com, npmjs.org\nexample.com');

Check warning on line 76 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / ESLint

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 76 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 20)

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 76 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 22)

Found writeFileSync from package "fs" with non literal argument at index 0

const result = parseDomainsFile(filePath);

Expand All @@ -82,7 +82,7 @@

it('should skip empty lines', () => {
const filePath = path.join(testDir, 'domains.txt');
fs.writeFileSync(filePath, 'github.com\n\n\napi.github.com\n\nnpmjs.org');

Check warning on line 85 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / ESLint

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 85 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 20)

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 85 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 22)

Found writeFileSync from package "fs" with non literal argument at index 0

const result = parseDomainsFile(filePath);

Expand All @@ -91,7 +91,7 @@

it('should skip lines with only whitespace', () => {
const filePath = path.join(testDir, 'domains.txt');
fs.writeFileSync(filePath, 'github.com\n \n\t\napi.github.com');

Check warning on line 94 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / ESLint

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 94 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 20)

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 94 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 22)

Found writeFileSync from package "fs" with non literal argument at index 0

const result = parseDomainsFile(filePath);

Expand All @@ -100,7 +100,7 @@

it('should skip comments starting with #', () => {
const filePath = path.join(testDir, 'domains.txt');
fs.writeFileSync(filePath, '# This is a comment\ngithub.com\n# Another comment\napi.github.com');

Check warning on line 103 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / ESLint

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 103 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 20)

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 103 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 22)

Found writeFileSync from package "fs" with non literal argument at index 0

const result = parseDomainsFile(filePath);

Expand All @@ -109,7 +109,7 @@

it('should handle inline comments (after domain)', () => {
const filePath = path.join(testDir, 'domains.txt');
fs.writeFileSync(filePath, 'github.com # GitHub main domain\napi.github.com # API endpoint');

Check warning on line 112 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / ESLint

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 112 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 20)

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 112 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 22)

Found writeFileSync from package "fs" with non literal argument at index 0

const result = parseDomainsFile(filePath);

Expand All @@ -118,7 +118,7 @@

it('should handle domains with inline comments in comma-separated format', () => {
const filePath = path.join(testDir, 'domains.txt');
fs.writeFileSync(filePath, 'github.com, api.github.com # GitHub domains\nnpmjs.org');

Check warning on line 121 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / ESLint

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 121 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 20)

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 121 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 22)

Found writeFileSync from package "fs" with non literal argument at index 0

const result = parseDomainsFile(filePath);

Expand All @@ -133,7 +133,7 @@

it('should return empty array for file with only comments and whitespace', () => {
const filePath = path.join(testDir, 'domains.txt');
fs.writeFileSync(filePath, '# Comment 1\n\n# Comment 2\n \n');

Check warning on line 136 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / ESLint

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 136 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 20)

Found writeFileSync from package "fs" with non literal argument at index 0

Check warning on line 136 in src/cli.test.ts

View workflow job for this annotation

GitHub Actions / Build and Lint (Node 22)

Found writeFileSync from package "fs" with non literal argument at index 0

const result = parseDomainsFile(filePath);

Expand Down Expand Up @@ -347,7 +347,6 @@
program
.option('--log-level <level>', 'Log level', 'info')
.option('--keep-containers', 'Keep containers', false)
.option('--skip-cleanup', 'Skip all cleanup', false)
.option('--build-local', 'Build locally', false)
.option('--env-all', 'Pass all env vars', false);

Expand All @@ -357,22 +356,9 @@

expect(opts.logLevel).toBe('info');
expect(opts.keepContainers).toBe(false);
expect(opts.skipCleanup).toBe(false);
expect(opts.buildLocal).toBe(false);
expect(opts.envAll).toBe(false);
});

it('should parse --skip-cleanup flag when provided', () => {
const program = new Command();

program
.option('--skip-cleanup', 'Skip all cleanup', false);

program.parse(['node', 'awf', '--skip-cleanup'], { from: 'node' });
const opts = program.opts();

expect(opts.skipCleanup).toBe(true);
});
});

describe('argument parsing with variadic args', () => {
Expand Down Expand Up @@ -1334,32 +1320,6 @@
});
});

describe('hasRateLimitOptions', () => {
it('should return false when no rate limit options are set', () => {
expect(hasRateLimitOptions({})).toBe(false);
});

it('should return true when rateLimitRpm is set', () => {
expect(hasRateLimitOptions({ rateLimitRpm: '100' })).toBe(true);
});

it('should return true when rateLimitRph is set', () => {
expect(hasRateLimitOptions({ rateLimitRph: '1000' })).toBe(true);
});

it('should return true when rateLimitBytesPm is set', () => {
expect(hasRateLimitOptions({ rateLimitBytesPm: '50000000' })).toBe(true);
});

it('should return true when rateLimit is explicitly false', () => {
expect(hasRateLimitOptions({ rateLimit: false })).toBe(true);
});

it('should return false when rateLimit is true (default enabling)', () => {
expect(hasRateLimitOptions({ rateLimit: true })).toBe(false);
});
});

describe('validateSkipPullWithBuildLocal', () => {
it('should return valid when both flags are false', () => {
const result = validateSkipPullWithBuildLocal(false, false);
Expand Down Expand Up @@ -2060,4 +2020,30 @@
});
});
});

describe('hasRateLimitOptions', () => {
it('returns false when no rate limit options are set', () => {
expect(hasRateLimitOptions({})).toBe(false);
});

it('returns true when rateLimitRpm is set', () => {
expect(hasRateLimitOptions({ rateLimitRpm: '100' })).toBe(true);
});

it('returns true when rateLimitRph is set', () => {
expect(hasRateLimitOptions({ rateLimitRph: '1000' })).toBe(true);
});

it('returns true when rateLimitBytesPm is set', () => {
expect(hasRateLimitOptions({ rateLimitBytesPm: '1048576' })).toBe(true);
});

it('returns true when rateLimit is explicitly false', () => {
expect(hasRateLimitOptions({ rateLimit: false })).toBe(true);
});

it('returns false when rateLimit is true', () => {
expect(hasRateLimitOptions({ rateLimit: true })).toBe(false);
});
});
});
4 changes: 3 additions & 1 deletion src/docker-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1496,7 +1496,7 @@ describe('docker-manager', () => {
});

describe('dnsServers option', () => {
it('should use custom DNS servers when specified', () => {
it('should use custom DNS servers for Docker embedded DNS forwarding', () => {
const config: WrapperConfig = {
...mockConfig,
dnsServers: ['1.1.1.1', '1.0.0.1'],
Expand All @@ -1506,6 +1506,7 @@ describe('docker-manager', () => {
const env = agent.environment as Record<string, string>;

expect(agent.dns).toEqual(['1.1.1.1', '1.0.0.1']);
// AWF_DNS_SERVERS env var should be set for setup-iptables.sh DNS ACCEPT rules
expect(env.AWF_DNS_SERVERS).toBe('1.1.1.1,1.0.0.1');
});

Expand All @@ -1515,6 +1516,7 @@ describe('docker-manager', () => {
const env = agent.environment as Record<string, string>;

expect(agent.dns).toEqual(['8.8.8.8', '8.8.4.4']);
// AWF_DNS_SERVERS env var should be set for setup-iptables.sh DNS ACCEPT rules
expect(env.AWF_DNS_SERVERS).toBe('8.8.8.8,8.8.4.4');
});
});
Expand Down
Loading
Loading