Skip to content

Commit 75023ce

Browse files
CopilotMossaka
andcommitted
feat: allow empty allowDomains to block all network access (#451)
* Initial plan * feat: allow empty allowDomains to block all network access Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> * test: add integration tests for empty domains (no network access) Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> * test: fix misleading test title for DNS behavior test Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> * ci: re-trigger workflow checks * chore: merge origin/main and fix integration test Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> Co-authored-by: Jiaxiao (mossaka) Zhou <duibao55328@gmail.com>
1 parent 443617a commit 75023ce

5 files changed

Lines changed: 178 additions & 5 deletions

File tree

docs-site/src/content/docs/reference/cli-reference.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ awf [options] -- <command>
1919

2020
| Option | Type | Default | Description |
2121
|--------|------|---------|-------------|
22-
| `--allow-domains <domains>` | string || Comma-separated list of allowed domains (required unless `--allow-domains-file` used) |
22+
| `--allow-domains <domains>` | string || Comma-separated list of allowed domains (optional; if not specified, all network access is blocked) |
2323
| `--allow-domains-file <path>` | string || Path to file containing allowed domains |
2424
| `--block-domains <domains>` | string || Comma-separated list of blocked domains (takes precedence over allowed) |
2525
| `--block-domains-file <path>` | string || Path to file containing blocked domains |
@@ -48,9 +48,15 @@ awf [options] -- <command>
4848

4949
Comma-separated list of allowed domains. Domains automatically match all subdomains. Supports wildcard patterns and protocol-specific filtering.
5050

51+
**If no domains are specified, all network access is blocked.** This is useful for running commands that should have no network access.
52+
5153
```bash
54+
# Allow specific domains
5255
--allow-domains github.com,npmjs.org
5356
--allow-domains '*.github.com,api-*.example.com'
57+
58+
# No network access (empty or omitted)
59+
awf -- echo "offline command"
5460
```
5561

5662
#### Protocol-Specific Filtering

docs/usage.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
sudo awf [options] <command>
77
88
Options:
9-
--allow-domains <domains> Comma-separated list of allowed domains (required)
9+
--allow-domains <domains> Comma-separated list of allowed domains (optional)
10+
If not specified, all network access is blocked
1011
Example: github.com,api.github.com,arxiv.org
1112
--allow-domains-file <path> Path to file containing allowed domains
1213
--block-domains <domains> Comma-separated list of blocked domains

src/cli.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -665,10 +665,9 @@ program
665665
}
666666
}
667667

668-
// Ensure at least one domain is specified
668+
// Log when no domains are specified (all network access will be blocked)
669669
if (allowedDomains.length === 0) {
670-
logger.error('At least one domain must be specified with --allow-domains or --allow-domains-file');
671-
process.exit(1);
670+
logger.debug('No allowed domains specified - all network access will be blocked');
672671
}
673672

674673
// Remove duplicates (in case domains appear in both sources)

src/squid-config.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,3 +1374,21 @@ describe('Dangerous ports blocklist in generateSquidConfig', () => {
13741374
}).not.toThrow();
13751375
});
13761376
});
1377+
1378+
describe('Empty Domain List', () => {
1379+
it('should generate config that denies all traffic when no domains are specified', () => {
1380+
const config = {
1381+
domains: [],
1382+
port: 3128,
1383+
};
1384+
const result = generateSquidConfig(config);
1385+
// Should deny all traffic when no domains are allowed
1386+
expect(result).toContain('http_access deny all');
1387+
// Should have a comment indicating no domains configured
1388+
expect(result).toContain('# No domains configured');
1389+
// Should not have any allowed_domains ACL
1390+
expect(result).not.toContain('acl allowed_domains');
1391+
expect(result).not.toContain('acl allowed_http_only');
1392+
expect(result).not.toContain('acl allowed_https_only');
1393+
});
1394+
});
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/**
2+
* Empty Domains Tests
3+
*
4+
* These tests verify the behavior when no domains are allowed:
5+
* - All network access should be blocked
6+
* - Commands that don't require network should still work
7+
* - Debug logs should indicate no domains are configured
8+
*/
9+
10+
/// <reference path="../jest-custom-matchers.d.ts" />
11+
12+
import { describe, test, expect, beforeAll, afterAll } from '@jest/globals';
13+
import { createRunner, AwfRunner } from '../fixtures/awf-runner';
14+
import { cleanup } from '../fixtures/cleanup';
15+
16+
describe('Empty Domains (No Network Access)', () => {
17+
let runner: AwfRunner;
18+
19+
beforeAll(async () => {
20+
await cleanup(false);
21+
runner = createRunner();
22+
});
23+
24+
afterAll(async () => {
25+
await cleanup(false);
26+
});
27+
28+
describe('Network Blocking', () => {
29+
test('should block all network access when no domains are specified', async () => {
30+
// Try to access a website without any allowed domains
31+
const result = await runner.runWithSudo(
32+
'curl -f --max-time 5 https://example.com',
33+
{
34+
allowDomains: [], // Empty domains list
35+
logLevel: 'debug',
36+
timeout: 60000,
37+
}
38+
);
39+
40+
// Request should fail because no domains are allowed
41+
expect(result).toFail();
42+
}, 120000);
43+
44+
test('should block HTTPS traffic when no domains are specified', async () => {
45+
const result = await runner.runWithSudo(
46+
'curl -f --max-time 5 https://api.github.com/zen',
47+
{
48+
allowDomains: [],
49+
logLevel: 'debug',
50+
timeout: 60000,
51+
}
52+
);
53+
54+
expect(result).toFail();
55+
}, 120000);
56+
57+
test('should block HTTP traffic when no domains are specified', async () => {
58+
const result = await runner.runWithSudo(
59+
'curl -f --max-time 5 http://httpbin.org/get',
60+
{
61+
allowDomains: [],
62+
logLevel: 'debug',
63+
timeout: 60000,
64+
}
65+
);
66+
67+
expect(result).toFail();
68+
}, 120000);
69+
});
70+
71+
describe('Offline Commands', () => {
72+
test('should allow commands that do not require network access', async () => {
73+
const result = await runner.runWithSudo(
74+
'echo "Hello, offline world!"',
75+
{
76+
allowDomains: [],
77+
logLevel: 'debug',
78+
timeout: 60000,
79+
}
80+
);
81+
82+
expect(result).toSucceed();
83+
expect(result.stdout).toContain('Hello, offline world!');
84+
}, 120000);
85+
86+
test('should allow file system operations without network', async () => {
87+
const result = await runner.runWithSudo(
88+
'bash -c "echo test > /tmp/test.txt && cat /tmp/test.txt && rm /tmp/test.txt"',
89+
{
90+
allowDomains: [],
91+
logLevel: 'debug',
92+
timeout: 60000,
93+
}
94+
);
95+
96+
expect(result).toSucceed();
97+
expect(result.stdout).toContain('test');
98+
}, 120000);
99+
100+
test('should allow local computations without network', async () => {
101+
const result = await runner.runWithSudo(
102+
'bash -c "expr 2 + 2"',
103+
{
104+
allowDomains: [],
105+
logLevel: 'debug',
106+
timeout: 60000,
107+
}
108+
);
109+
110+
expect(result).toSucceed();
111+
expect(result.stdout).toContain('4');
112+
}, 120000);
113+
});
114+
115+
describe('Debug Output', () => {
116+
test('should indicate no domains are configured in debug output', async () => {
117+
const result = await runner.runWithSudo(
118+
'echo "test"',
119+
{
120+
allowDomains: [],
121+
logLevel: 'debug',
122+
timeout: 60000,
123+
}
124+
);
125+
126+
expect(result).toSucceed();
127+
// Should show debug message about no domains
128+
expect(result.stderr).toMatch(/No allowed domains specified|all network access will be blocked/i);
129+
}, 120000);
130+
});
131+
132+
describe('DNS Behavior', () => {
133+
test('should block network access even when DNS resolution succeeds', async () => {
134+
// DNS lookups should work (we allow DNS traffic), but connecting should fail
135+
// because the domain isn't in the allowlist
136+
const result = await runner.runWithSudo(
137+
'bash -c "host example.com > /dev/null 2>&1 && curl -f --max-time 5 https://example.com || echo network_blocked"',
138+
{
139+
allowDomains: [],
140+
logLevel: 'debug',
141+
timeout: 60000,
142+
}
143+
);
144+
145+
// The network request should be blocked
146+
expect(result.stdout).toContain('network_blocked');
147+
}, 120000);
148+
});
149+
});

0 commit comments

Comments
 (0)