diff --git a/docs-site/src/content/docs/reference/cli-reference.md b/docs-site/src/content/docs/reference/cli-reference.md index d5e0db21..47fd36aa 100644 --- a/docs-site/src/content/docs/reference/cli-reference.md +++ b/docs-site/src/content/docs/reference/cli-reference.md @@ -19,7 +19,7 @@ awf [options] -- | Option | Type | Default | Description | |--------|------|---------|-------------| -| `--allow-domains ` | string | — | Comma-separated list of allowed domains (required unless `--allow-domains-file` used) | +| `--allow-domains ` | string | — | Comma-separated list of allowed domains (optional; if not specified, all network access is blocked) | | `--allow-domains-file ` | string | — | Path to file containing allowed domains | | `--block-domains ` | string | — | Comma-separated list of blocked domains (takes precedence over allowed) | | `--block-domains-file ` | string | — | Path to file containing blocked domains | @@ -47,9 +47,15 @@ awf [options] -- Comma-separated list of allowed domains. Domains automatically match all subdomains. Supports wildcard patterns and protocol-specific filtering. +**If no domains are specified, all network access is blocked.** This is useful for running commands that should have no network access. + ```bash +# Allow specific domains --allow-domains github.com,npmjs.org --allow-domains '*.github.com,api-*.example.com' + +# No network access (empty or omitted) +awf -- echo "offline command" ``` #### Protocol-Specific Filtering diff --git a/docs/usage.md b/docs/usage.md index bf6c59ad..a029a778 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -6,7 +6,8 @@ sudo awf [options] Options: - --allow-domains Comma-separated list of allowed domains (required) + --allow-domains Comma-separated list of allowed domains (optional) + If not specified, all network access is blocked Example: github.com,api.github.com,arxiv.org --allow-domains-file Path to file containing allowed domains --block-domains Comma-separated list of blocked domains diff --git a/src/cli.ts b/src/cli.ts index 2ddc7aec..17f1a62c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -631,10 +631,9 @@ program } } - // Ensure at least one domain is specified + // Log when no domains are specified (all network access will be blocked) if (allowedDomains.length === 0) { - logger.error('At least one domain must be specified with --allow-domains or --allow-domains-file'); - process.exit(1); + logger.debug('No allowed domains specified - all network access will be blocked'); } // Remove duplicates (in case domains appear in both sources) diff --git a/src/squid-config.test.ts b/src/squid-config.test.ts index fdd4cde7..333182a7 100644 --- a/src/squid-config.test.ts +++ b/src/squid-config.test.ts @@ -1374,3 +1374,21 @@ describe('Dangerous ports blocklist in generateSquidConfig', () => { }).not.toThrow(); }); }); + +describe('Empty Domain List', () => { + it('should generate config that denies all traffic when no domains are specified', () => { + const config = { + domains: [], + port: 3128, + }; + const result = generateSquidConfig(config); + // Should deny all traffic when no domains are allowed + expect(result).toContain('http_access deny all'); + // Should have a comment indicating no domains configured + expect(result).toContain('# No domains configured'); + // Should not have any allowed_domains ACL + expect(result).not.toContain('acl allowed_domains'); + expect(result).not.toContain('acl allowed_http_only'); + expect(result).not.toContain('acl allowed_https_only'); + }); +}); diff --git a/tests/integration/empty-domains.test.ts b/tests/integration/empty-domains.test.ts new file mode 100644 index 00000000..4c1fce5a --- /dev/null +++ b/tests/integration/empty-domains.test.ts @@ -0,0 +1,149 @@ +/** + * Empty Domains Tests + * + * These tests verify the behavior when no domains are allowed: + * - All network access should be blocked + * - Commands that don't require network should still work + * - Debug logs should indicate no domains are configured + */ + +/// + +import { describe, test, expect, beforeAll, afterAll } from '@jest/globals'; +import { createRunner, AwfRunner } from '../fixtures/awf-runner'; +import { cleanup } from '../fixtures/cleanup'; + +describe('Empty Domains (No Network Access)', () => { + let runner: AwfRunner; + + beforeAll(async () => { + await cleanup(false); + runner = createRunner(); + }); + + afterAll(async () => { + await cleanup(false); + }); + + describe('Network Blocking', () => { + test('should block all network access when no domains are specified', async () => { + // Try to access a website without any allowed domains + const result = await runner.runWithSudo( + 'curl -f --max-time 5 https://example.com', + { + allowDomains: [], // Empty domains list + logLevel: 'debug', + timeout: 60000, + } + ); + + // Request should fail because no domains are allowed + expect(result).toFail(); + }, 120000); + + test('should block HTTPS traffic when no domains are specified', async () => { + const result = await runner.runWithSudo( + 'curl -f --max-time 5 https://api.github.com/zen', + { + allowDomains: [], + logLevel: 'debug', + timeout: 60000, + } + ); + + expect(result).toFail(); + }, 120000); + + test('should block HTTP traffic when no domains are specified', async () => { + const result = await runner.runWithSudo( + 'curl -f --max-time 5 http://httpbin.org/get', + { + allowDomains: [], + logLevel: 'debug', + timeout: 60000, + } + ); + + expect(result).toFail(); + }, 120000); + }); + + describe('Offline Commands', () => { + test('should allow commands that do not require network access', async () => { + const result = await runner.runWithSudo( + 'echo "Hello, offline world!"', + { + allowDomains: [], + logLevel: 'debug', + timeout: 60000, + } + ); + + expect(result).toSucceed(); + expect(result.stdout).toContain('Hello, offline world!'); + }, 120000); + + test('should allow file system operations without network', async () => { + const result = await runner.runWithSudo( + 'bash -c "echo test > /tmp/test.txt && cat /tmp/test.txt && rm /tmp/test.txt"', + { + allowDomains: [], + logLevel: 'debug', + timeout: 60000, + } + ); + + expect(result).toSucceed(); + expect(result.stdout).toContain('test'); + }, 120000); + + test('should allow local computations without network', async () => { + const result = await runner.runWithSudo( + 'bash -c "expr 2 + 2"', + { + allowDomains: [], + logLevel: 'debug', + timeout: 60000, + } + ); + + expect(result).toSucceed(); + expect(result.stdout).toContain('4'); + }, 120000); + }); + + describe('Debug Output', () => { + test('should indicate no domains are configured in debug output', async () => { + const result = await runner.runWithSudo( + 'echo "test"', + { + allowDomains: [], + logLevel: 'debug', + timeout: 60000, + } + ); + + expect(result).toSucceed(); + // Should show debug message about no domains + expect(result.stderr).toMatch(/No allowed domains specified|all network access will be blocked/i); + }, 120000); + }); + + describe('DNS Behavior', () => { + test('should block network access even when DNS resolution succeeds', async () => { + // DNS lookups should work (we allow DNS traffic), but connecting should fail + // because the domain isn't in the allowlist + const result = await runner.runWithSudo( + 'bash -c "host example.com > /dev/null 2>&1 && curl -f --max-time 5 https://example.com || echo network_blocked"', + { + allowDomains: [], + logLevel: 'debug', + timeout: 60000, + } + ); + + // The network request should be blocked + expect(result.stdout).toContain('network_blocked'); + }, 120000); + }); +});