Skip to content

docs(seatbelt): correct rule-precedence and listen() rationale - #754

Merged
Soham Das (SohamDas2021) merged 3 commits into
microsoft:mainfrom
caarlos0:seatbelt-docs-precision
Aug 5, 2026
Merged

docs(seatbelt): correct rule-precedence and listen() rationale#754
Soham Das (SohamDas2021) merged 3 commits into
microsoft:mainfrom
caarlos0:seatbelt-docs-precision

Conversation

@caarlos0

@caarlos0 Carlos Alexandro Becker (caarlos0) commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #749, which a post-merge Copilot review correctly flagged for describing Seatbelt's precedence rules inaccurately. Comments and docs only — no behavior change, and the generated profile is byte-for-byte identical.

Three claims were wrong. All three are corrected against measurements, not reasoning.

1. "An allow can never take authority back from an earlier rule"

False, and it contradicts this backend's own design: the shallow-to-deep emission depends on a later filtered allow overriding an earlier filtered deny.

profile result
allow then deny, same path EPERM
deny then allow, same path CONNECT_OK
shallow deny, deeper allow CONNECT_OK

The real reason a read-only path still needs an explicit (deny file-write* network-bind network-outbound …) is narrower: the read-only allow names only file-read*, so it says nothing about write or socket operations and cannot displace a broader grant on its own.

2. "deniedPaths is emitted after the network rules so it also overrides the unfiltered (allow network-outbound)"

The override is real, but it does not depend on that ordering — an unfiltered rule never overrides a path-filtered one, in either direction:

profile result
fs deny, then unfiltered net allow EPERM
unfiltered net allow, then fs deny EPERM
unfiltered net allow, no fs deny CONNECT_OK (control)

The control confirms (allow network-outbound) genuinely does grant AF_UNIX connect(), so the first two rows are the deny winning, not a missing capability.

This one matters beyond accuracy: documenting an ordering constraint that does not exist invites a future change to "fix" the emission order on a false premise.

3. "network-inbound is required in addition to network-bind before the kernel will accept listen()"

Backwards.

profile result
(allow network-inbound (local ip)) alone LISTEN_OK
(allow network-bind (local ip)) alone bind() OK, listen() denied (EPERM)
neither bind() denied (EPERM)

network-inbound is what governs listen(), and it covers the bind() too. It is network-bind that is insufficient on its own. This also explains why allowLocalNetwork works under defaultPolicy: "block", where write_local_network_rules emits network-inbound and nothing else.

Follow-ups in this PR

Two things surfaced after the initial push, both extending claim 2 rather than adding new scope:

The strip site itself (0d5248b). Both review passes on #749 — Copilot and a human — landed on the same line, the read-only network-outbound strip, and asked whether the later unfiltered allow defeats it. Nothing at that site said otherwise; the comment there only claimed "an allow never denies", which is the phrasing that produced the wrong model. It now states the rule where the question actually gets asked, and names the test that pins it.

Re-verified end-to-end with the real mxc-exec-mac against a listener created outside the sandbox, one field of config apart:

readonlyPaths:  ["/private/tmp/sbt7/ro"]  ->  CONNECT_DENIED [Errno 1] Operation not permitted
readwritePaths: ["/private/tmp/sbt7/ro"]  ->  CONNECT_OK

Two tests still enforced the false constraint (f6fd568, from review feedback). Asserting the deny is emitted after the unfiltered (allow network-outbound) encodes exactly the ordering this PR documents as nonexistent.

The order assertions that remain are all against filtered allows, where last-match-wins genuinely applies and the deny must come second — denied_paths_appear_after_allows_to_override and denied_paths_deny_unix_socket_ops_after_allows. Those stay; that constraint is real.

Validation

  • cargo test -p seatbelt_common63 passed, 0 failed
  • cargo clippy -p seatbelt_common --all-targets -- -D warnings → clean
  • cargo fmt --all -- --check → clean

Every table above was produced with sandbox-exec against a live listener on this host (macOS 26.5.2, arm64).

Microsoft Reviewers: Open in CodeFlow

Follow-up to microsoft#749. Comments and docs only; no behavior change, no
generated profile changes.

Three claims were wrong, all measured against sandbox-exec:

1. "An allow can never take authority back from an earlier rule."
   False - a later filtered allow does override an earlier filtered deny,
   which is exactly what the shallow-to-deep emission relies on. The
   actual reason a read-only path needs an explicit
   (deny file-write* network-bind network-outbound ...) is narrower: the
   read-only allow names only file-read*, so it says nothing about write
   or socket operations and cannot displace a broader grant.

2. "deniedPaths is emitted after the network rules so it also overrides
   the unfiltered (allow network-outbound)." The override is real, but it
   does not depend on that ordering - an unfiltered rule never overrides
   a path-filtered one. Stating a constraint that does not exist invites
   someone to "fix" the ordering on a false premise.

3. "network-inbound is required in addition to network-bind before the
   kernel will accept listen()." Backwards.

       network-inbound (local ip) alone -> LISTEN_OK
       network-bind    (local ip) alone -> bind OK, LISTEN_DENIED (EPERM)
       neither                          -> BIND_DENIED (EPERM)

   network-inbound governs listen() and covers the bind(); it is
   network-bind that is insufficient on its own.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 184c815b-d8ac-4bad-8bac-c18ab420e1b7
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 5, 2026 19:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Corrects Seatbelt documentation and code comments without changing generated profiles.

Changes:

  • Clarifies filtered-rule precedence and deniedPaths ordering.
  • Corrects the rationale for read-only deny rules.
  • Documents that network-inbound governs listen().
Show a summary per file
File Description
src/backends/seatbelt/common/src/profile_builder.rs Updates precedence and socket-operation comments.
docs/macos-support/seatbelt-backend.md Corrects filesystem and network policy explanations.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/backends/seatbelt/common/src/profile_builder.rs
…gged

Both review passes on microsoft#749 landed on the same line - the read-only
network-outbound strip - and asked whether the later unfiltered
(allow network-outbound) defeats it. It does not, but nothing at that
site said so; the comment there only claimed "an allow never denies",
which is the phrasing that fed the wrong model in the first place.

Spell it out where the question gets asked: the deny survives because
last-match-wins applies between rules that carry a filter, and an
unfiltered rule does not override a path-filtered one. Points at the
test that pins it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 184c815b-d8ac-4bad-8bac-c18ab420e1b7
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 5, 2026 19:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Review feedback: two tests still enforced the ordering constraint this PR
documents as nonexistent - a path-filtered deny is not overridden by an
unfiltered allow in either direction, so asserting the deny is emitted
after (allow network-outbound) encodes a constraint that does not exist.

- denied_paths_deny_outbound_after_unfiltered_allow ->
  denied_paths_deny_outbound_under_default_allow: keeps the real
  guarantee (a denied subtree still denies network-outbound under
  defaultPolicy: "allow") and drops the order assertion.
- readonly_socket_strip_survives_a_default_allow_outbound: drops the
  same guard, which was added in microsoft#749 for the same wrong reason.

The order assertions that remain are against *filtered* allows, where
last-match-wins genuinely applies and the deny must come second:
denied_paths_appear_after_allows_to_override and
denied_paths_deny_unix_socket_ops_after_allows. Those stay.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 184c815b-d8ac-4bad-8bac-c18ab420e1b7
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 5, 2026 20:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@SohamDas2021
Soham Das (SohamDas2021) merged commit db809fd into microsoft:main Aug 5, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants