Skip to content

Add a 'deny-write' option - #51

Open
jaredly wants to merge 2 commits into
agentic-dev3o:mainfrom
jaredly:deny-write
Open

Add a 'deny-write' option#51
jaredly wants to merge 2 commits into
agentic-dev3o:mainfrom
jaredly:deny-write

Conversation

@jaredly

@jaredly jaredly commented Sep 1, 2026

Copy link
Copy Markdown

In my applicaiton, I want to run commands without granting write access to the working directory, which there didn't seem to be an option to make that work. I thought about doing a --dont-auto-allow-cwd or something like that, but I think --deny-write is more generally applicable, as it also works for when you want to exclude writes to a subset of an explicitly allowed file tree.

In my applicaiton, I want to run commands without granting write access to the working directory, which there didn't seem to be an option to make that work. I thought about doing a `--dont-auto-allow-cwd` or something like that, but I think `--deny-write` is more generally applicable, as it also works for when you want to exclude writes to a subset of an explicitly allowed file tree.
@Pierozi

Pierozi commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Hi @jaredly, thanks a lot for this PR! 🙏 --deny-write is a very welcome addition — it's the natural counterpart to --deny-read, and you wired it through every layer exactly the way the codebase expects (CLI args, config schema, profile composition, merge, shell completions, --init template, README). Really appreciate the thoroughness.

That said, there is one critical issue we need to fix before merging, plus a few smaller gaps.

1. Rule ordering makes the deny ineffective (blocking)

Seatbelt uses last-match-wins semantics: a later allow overrides an earlier deny for overlapping paths. The new deny file-write* block is emitted in generate_seatbelt_profile before both:

  • the working-directory rule (allow file* (subpath <cwd>)), and
  • the allow_write block.

So for both use cases described in the PR:

  1. sx --deny-write "$PWD" (running without write access to the working directory) → the working-dir allow file* comes later and wins, writes are still permitted.
  2. Denying a subpath of an --allow-write tree → the allow_write rules come later and win for nested paths.

The only paths where the deny currently "works" are ones that were never write-allowed anyway (the sandbox is deny-by-default), so as written the feature is effectively a no-op. This is exactly the invariant encoded in test_deny_rules_come_after_allow_read for the read side.

Fix: move the deny_write block to after the allow_write block in src/sandbox/seatbelt.rs. That placement also does the right thing semantically: the deny only targets file-write*, so read access inside the working dir is preserved.

2. Tests (blocking)

The project follows TDD, and deny_read has coverage in seatbelt_test.rs, cli_test.rs, and config_test.rs. Please add at minimum:

  • a rule-emission test (deny rule appears in the generated profile), and
  • an ordering test asserting the deny file-write* rules come after the working-dir and allow_write rules (mirroring test_deny_rules_come_after_allow_read) — this test would have caught issue ci: bump actions/github-script from 7 to 8 #1.

3. --explain output

print_explain in src/cli/commands.rs prints "Denied Read Paths" and "Allowed Write Paths" but not the new deny-write list. Please add a "Denied Write Paths" section so users can inspect the effective rules.

4. Nits

  • README table: "Deny write (override allows)" → "overrides allows" to match the --deny-read row.
  • One thing worth verifying on macOS once the ordering is fixed: when an allow_write path is an existing file, the profile emits a regex-filter allow, while deny_write emits a subpath-filter deny — mixed filter types may not follow plain last-match-wins. A quick manual check (or test) for that corner case would be great.

(The secrets-scan CI failure looks like the known gitleaks-on-fork-PR issue, not something in your change.)

Again, thank you — the feature is wanted and the structure is right; once the deny block is moved after the allows and covered by tests, this should be good to go. 🚀

@jaredly

jaredly commented Sep 8, 2026

Copy link
Copy Markdown
Author

@Pierozi thanks for the review! I've made the requested changes

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.

2 participants