Repository navigation
Seatbelt: allowedHosts under defaultPolicy 'block' degrades to allow-all outbound, inverting the requested policy #946
Description
Relevant area(s)
macOS (Seatbelt backend)
Brief description of your issue
On the Seatbelt backend, adding allowedHosts to a defaultPolicy: "block"
policy makes the sandbox more permissive, not less: the emitted profile
degrades to unrestricted outbound.
src/backends/seatbelt/common/src/profile_builder.rs:287-308 matches on
(allow_outbound, has_allowed_hosts):
defaultPolicy |
allowedHosts |
Emitted profile |
|---|---|---|
block |
(none) | deny — no outbound rules emitted |
block |
["api.github.com"] |
(allow network-outbound) — all outbound |
allow |
anything | (allow network-outbound) |
The third match arm is (_, true), so a present allowlist wins over
defaultPolicy: "block" and emits write_outbound_allow_rules. A caller asking
for "deny everything except GitHub" gets unrestricted egress, and the run exits
0 with no error.
The degrade is deliberate — Seatbelt's (remote ...) filters only accept *
and localhost, so per-host filtering genuinely isn't expressible — but the
rationale is a comment inside the generated profile that the caller never sees,
and the resulting posture is the opposite of what was requested.
This applies only when no proxy is configured. With network.proxy set,
UnixProxyCoordinator enforces the host lists cooperatively
(seatbelt_runner.rs:140-152), and the parser separately rejects a remote proxy
under defaultPolicy: "block".
The inconsistency that makes this a bug rather than a documented limitation:
blockedHosts is already rejected at seatbelt_runner.rs:111-119 with exactly
the right reasoning —
"Seatbelt cannot filter network by hostname — reject blockedHosts rather than
silently allowing traffic the user expects to be denied."
allowedHosts hits the identical limitation and is the stronger control, yet
gets the opposite treatment.
Secondary: network.enforcementMode is never read anywhere under
src/backends/seatbelt/ — zero occurrences — so firewall / both are
silently ignored. config_parser.rs:1221-1233 rejects them only when combined
with network.proxy. This is the same fail-open class as #755, though inert in
practice: Seatbelt would not have filtered by host under any mode.
Test-coverage note: the current behavior is pinned by a test whose name
asserts the opposite of what it checks —
profile_builder.rs:753 block_with_allowed_hosts_emits_allowlist asserts
(allow network-outbound) and !p.contains("(remote"), i.e. that there is no
allowlist. Rename it as part of the fix.
Steps to reproduce
- On macOS, build a config with
containment: "seatbelt", nonetwork.proxy,
and:"network": { "defaultPolicy": "block", "allowedHosts": ["api.github.com"] }
- Run a workload that reaches a host outside the allowlist, e.g.
curl -sS https://example.com. - Compare against the same config with
allowedHostsremoved.
Expected behavior
A policy that expresses a restriction Seatbelt cannot enforce should fail rather
than run more permissive than requested — the same principle already applied to
blockedHosts on this backend, and to unenforceable network policy on
Bubblewrap at schema 0.8+ (#943).
Reject allowedHosts on Seatbelt when no proxy is configured, directing callers
to network.proxy for cooperative host filtering. If the rejection is judged too
breaking for existing callers, gate it to schema 0.8+ and keep the degrade with a
loud warning below that, mirroring the Bubblewrap approach.
Separately, enforcementMode: "firewall" / "both" should be rejected on
Seatbelt rather than silently ignored.
Actual behavior
Step 2 succeeds — example.com is reachable despite being outside the
allowlist. Step 3 (no allowedHosts) correctly denies all outbound, so adding
the allowlist is what removed the restriction. Neither run reports an error.
Related: #937 covers proxy enforcement strength on Seatbelt/WSLc (cooperative
vs enforced); this is the no-proxy path, where no enforcement happens at all.
#755 / #896 for the equivalent unattached-chain fail-open on Bubblewrap.
Activity
github-actions commentedon Aug 19, 2026
Triage applied:
Container-Process,Area-SDK-Policy(existingOS-MacOSandIssue-Bugretained)@richiemsft(macOS / Seatbelt)Needs-TriageremovedThe bug spans two related gaps in the Seatbelt backend:
allowedHostsunderdefaultPolicy: "block"(no proxy) silently emits(allow network-outbound)— the opposite of what was requested. TheblockedHostspath already does the right thing by rejecting with an explicit error; this path should too.enforcementMode: "firewall"/"both"is never read in the Seatbelt backend — it should be rejected rather than silently ignored.Maintainers: Comment
/investigateto check whether this issue or bug is valid against the most current code. Copilot will also produce a report with the changes that will be needed. For a small, unambiguous documentation or test fix, it may also create one draft PR.theelliotm commentedon Aug 31, 2026
Primary has been fixed in #1033. Secondary won't be fixed.
enforcementModeis dropped in 0.8.