Skip to content

Seatbelt: allowedHosts under defaultPolicy 'block' degrades to allow-all outbound, inverting the requested policy #946

Closed as not planned

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

  1. On macOS, build a config with containment: "seatbelt", no network.proxy,
    and:
    "network": { "defaultPolicy": "block", "allowedHosts": ["api.github.com"] }
  2. Run a workload that reaches a host outside the allowlist, e.g.
    curl -sS https://example.com.
  3. Compare against the same config with allowedHosts removed.

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

  1. github-actions commented on Aug 19, 2026

    @github-actions

    Triage applied:

    • Labels added: Container-Process, Area-SDK-Policy (existing OS-MacOS and Issue-Bug retained)
    • Owner assigned: @richiemsft (macOS / Seatbelt)
    • Needs-Triage removed

    The bug spans two related gaps in the Seatbelt backend:

    1. allowedHosts under defaultPolicy: "block" (no proxy) silently emits (allow network-outbound) — the opposite of what was requested. The blockedHosts path already does the right thing by rejecting with an explicit error; this path should too.
    2. enforcementMode: "firewall"/"both" is never read in the Seatbelt backend — it should be rejected rather than silently ignored.

    Maintainers: Comment /investigate to 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.

    Generated by 🏷️ Issue Triage for #946 · sonnet46 · 27.4 AIC · ⌖ 5.35 AIC · ⊞ 7.5K · ◷

  2. theelliotm commented on Aug 31, 2026

    @theelliotm
    Contributor

    Primary has been fixed in #1033. Secondary won't be fixed. enforcementMode is dropped in 0.8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Backend-ProcessContainerWindows ProcessContainer behavior, including BaseContainer and AppContainer isolation tiers.Issue-BugSomething is not working as intended.OS-MacOSApplies specifically to macOS hosts or macOS behavior.

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions