-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinit-firewall.sh
More file actions
executable file
·109 lines (99 loc) · 3.96 KB
/
Copy pathinit-firewall.sh
File metadata and controls
executable file
·109 lines (99 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#!/bin/bash
# Default-deny egress firewall for autonomous OpenCode sessions.
#
# Builds an allowlist of the IPs OpenCode needs (its model catalog, the model
# providers you use, GitHub, the npm registry) and drops every other outbound
# connection with nftables. A prompt-injected or misbehaving agent inside the
# container cannot reach arbitrary hosts.
#
# Runs as root (via the single sudoers rule baked into the image) when the
# container starts with FIREWALL=1. Requires the NET_ADMIN capability.
#
# Extend the allowlist per project without editing this file:
# FIREWALL_ALLOW_DOMAINS="pypi.org files.pythonhosted.org"
#
# Honest limits: this is blast-radius reduction, not a guarantee. DNS (port 53)
# stays open so the allowlist can resolve, which leaves DNS tunneling as an exfil
# channel, and allowlisted hosts (e.g. github.com) are themselves reachable.
set -euo pipefail
# What OpenCode itself talks to:
# - models.dev: the provider/model catalog OpenCode loads at startup.
# - opencode.ai: OpenCode Zen models (including the free ones) and session sharing.
# - registry.npmjs.org: provider SDK packages and npx-launched MCP servers are
# fetched on first use.
# Model providers below are the common direct ones; add yours via
# FIREWALL_ALLOW_DOMAINS if it is not in this list.
ALLOWED_DOMAINS=(
models.dev
opencode.ai
api.anthropic.com
api.openai.com
openrouter.ai
generativelanguage.googleapis.com
registry.npmjs.org
)
read -ra EXTRA_DOMAINS <<< "${FIREWALL_ALLOW_DOMAINS:-}"
# --- Resolve the allowlist (network is still open at this point) -------------
elements=()
echo "[firewall] adding GitHub IP ranges (api.github.com/meta)..."
gh_meta=$(curl -fsSL --max-time 20 https://api.github.com/meta)
while read -r cidr; do
[[ "$cidr" == *:* ]] && continue # IPv4 only; IPv6 is dropped wholesale below
elements+=("$cidr")
done < <(jq -r '[.web[], .api[], .git[], .packages[]?] | unique | .[]' <<< "$gh_meta")
for domain in "${ALLOWED_DOMAINS[@]}" ${EXTRA_DOMAINS[@]+"${EXTRA_DOMAINS[@]}"}; do
ips=$(dig +short A "$domain" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' || true)
if [ -z "$ips" ]; then
echo "[firewall] WARNING: could not resolve $domain; it will be blocked" >&2
continue
fi
while read -r ip; do
elements+=("$ip")
done <<< "$ips"
echo "[firewall] allowed $domain"
done
# --- Apply default-deny with nftables ----------------------------------------
set_elements=$(IFS=,; echo "${elements[*]}")
nft -f - <<NFT
table inet opencode_fw
delete table inet opencode_fw
table inet opencode_fw {
set allowed {
type ipv4_addr
flags interval
auto-merge
elements = { $set_elements }
}
chain input {
type filter hook input priority 0; policy drop;
iifname "lo" accept
ct state established,related accept
}
chain output {
type filter hook output priority 0; policy drop;
oifname "lo" accept
ct state established,related accept
# DNS stays open so the allowlist can resolve (Docker's embedded resolver
# forwards upstream). The set is a startup snapshot: if a CDN rotates an
# allowlisted domain to a new IP mid-session, that connection drops until
# the firewall re-runs (restart the container).
udp dport 53 accept
tcp dport 53 accept
ip daddr @allowed accept
# No IPv6 allowlist: everything v6 falls through to the drop policy.
}
chain forward {
type filter hook forward priority 0; policy drop;
}
}
NFT
# --- Verify ------------------------------------------------------------------
if curl -s -o /dev/null --max-time 5 https://example.com; then
echo "[firewall] VERIFICATION FAILED: example.com is reachable" >&2
exit 1
fi
if ! curl -s -o /dev/null --max-time 10 https://models.dev; then
echo "[firewall] VERIFICATION FAILED: models.dev is NOT reachable" >&2
exit 1
fi
echo "[firewall] active: example.com blocked, models.dev reachable"