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
8 changes: 7 additions & 1 deletion docs-site/src/content/docs/reference/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ awf [options] -- <command>

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `--allow-domains <domains>` | string | — | Comma-separated list of allowed domains (required unless `--allow-domains-file` used) |
| `--allow-domains <domains>` | string | — | Comma-separated list of allowed domains (optional; if not specified, all network access is blocked) |
| `--allow-domains-file <path>` | string | — | Path to file containing allowed domains |
| `--block-domains <domains>` | string | — | Comma-separated list of blocked domains (takes precedence over allowed) |
| `--block-domains-file <path>` | string | — | Path to file containing blocked domains |
Expand Down Expand Up @@ -47,9 +47,15 @@ awf [options] -- <command>

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
Expand Down
3 changes: 2 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
sudo awf [options] <command>

Options:
--allow-domains <domains> Comma-separated list of allowed domains (required)
--allow-domains <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> Path to file containing allowed domains
--block-domains <domains> Comma-separated list of blocked domains
Expand Down
5 changes: 2 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions src/squid-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
149 changes: 149 additions & 0 deletions tests/integration/empty-domains.test.ts
Original file line number Diff line number Diff line change
@@ -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
*/

/// <reference path="../jest-custom-matchers.d.ts" />

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);
});
});