Repository navigation
Let a jail's scratch live under a denied path, and deny paths by name (contract 2.5) - #158
Conversation
render_profile emitted the deny_read block as the profile's last rule, so it overrode every write grant. The native shell adds its per-call scratch (TMPDIR) to read_write after admission, so a host that denied the folder holding its state directory, such as ~/.openhuman, left every jailed command without a usable TMPDIR. After the deny block, re-allow reads and writes for each writable grant that sits strictly inside a denied path. Seatbelt applies the last matching rule, which a macOS probe confirmed: an allow after a deny re-opens the subpath and leaves the rest denied. Admission already refuses a host's writable grant under a denial, so in practice this carves out only the scratch. Refs tinyhumansai#89
deny_read takes exact paths, so a host could not deny a credential file name wherever it appears, such as a project's .npmrc (tinyhumansai#89). - NativeJailConfig gains deny_read_names (contract 2.5, empty and omitted by default): one path component each, validated at admission and counted against MAX_NATIVE_JAIL_GRANTS. - JailBackend gains denies_by_name (default false). Seatbelt returns true and renders each name as a regex deny of /<name> and everything below it, as the profile's last rule, after the scratch carve-out. A macOS probe confirmed sandbox-exec honours a regex denial at any depth. - Every other backend refuses such a jail instead of ignoring it, both in the module's admission (with the reason) and in the tinybox_jail spawn helpers. - NativeJailConfig gets a most-restrictive Default, so a host building requests with ..NativeJailConfig::default() keeps compiling when the contract grows a field. Every literal in this workspace gains the new field. Closes tinyhumansai#89
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
Tiny Sweeper reviewReviewed head: Verdict: Ready for maintainer review · Priority: none Diagnostics and supporting detailsFull summaryPull request purpose (title): Let a jail's scratch live under a denied path, and deny paths by name (contract 2.5). The change closes #89 by addressing two limits on macOS: the native shell's per-call scratch could not sit under a denied path because the deny block was the profile's last rule, and denials were exact paths only. It ships as a contract minor bump from 2.4 to 2.5: `NativeJailConfig` gains `deny_read_names` plus a most-restrictive `Default`, `JailBackend` gains `denies_by_name`, Seatbelt re-allows writable grants strictly inside a denied path after the deny block and emits escaped regex name denials as the final rule, and every other backend refuses a request carrying names instead of ignoring it. The latest-push delta is unavailable: the review state has the prior reviewed head and rendered evidence, but no trusted base-to-head diff comparison for the current head. Review snapshot
Completeness: Complete What changedContract 2.5 adds `NativeJailConfig.deny_read_names: Vec<String>` (serde default, omitted when empty) so 2.4 requests decode unchanged, plus a most-restrictive `Default` for `NativeJailConfig` so hosts can build with `..NativeJailConfig::default()`. Module admission refuses a name denial on a backend without name support with `UNSUPPORTED_OPERATION`, and refuses names that are not one plain path component (`""`, `.`, `..`, over `MAX_FILE_PATH_BYTES`, or containing `/`, `\`, `"` or NUL) with `INVALID_FILE_PATH`; names also count against `MAX_NATIVE_JAIL_GRANTS`. The `tinybox_jail` spawn helpers refuse a jail carrying names on a backend whose `denies_by_name` is false. On macOS, `render_profile` re-allows writable grants strictly inside a denied path after the deny block, and emits name denials as the profile's final rule using escaped regexes. A separate change boxes `ShellHost::Jailed`, judged a mechanical size fix that does not change behaviour. The change is documented in a new contract 2.5 spec. Features
Tests
FindingsNo active actionable findings. Before mergeNone. How this fits togetherflowchart LR
n0["NativeJailConfig<br/>changed"]:::changed
n1["...ubset_is_additive_and_keeps_disk_explicit<br/>changed"]:::changed
n2["..._vocabulary_is_typed_bounded_and_additive<br/>changed"]:::changed
n3["...wire_shape_contains_only_structural_facts<br/>changed"]:::changed
n4["clone"]:::impacted
n5["tempdir"]:::impacted
n1 -->|uses| n0
n1 -->|calls| n4
n1 -->|tests| n4
n3 -->|calls| n4
n3 -->|tests| n4
n5 -->|calls| n4
classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Agent review detailscritique
security
tests
commits
description
Evidence and run details
|
# Conflicts: # crates/tinybox-module/src/tinybus_module/native_jail_tests.rs
Summary
Two gaps stopped a host from denying every credential file it knows about on macOS (#89):
TMPDIR) toread_writeafter admission, andrender_profileemitted thedeny_readblock as the profile's last rule, overriding every write grant. A host that denied the folder holding its state directory (OpenHuman's~/.openhuman) left every jailed command without a usableTMPDIR. Now each writable grant that sits strictly inside a denied path is re-allowed after the deny block. Admission already refuses a host's writable grant under a denial, so in practice only the scratch is carved out.NativeJailConfig.deny_read_namesdenies a file or directory name wherever it appears (a project's.npmrc). Seatbelt enforces it with a regex rule, placed after the carve-out. Every other backend refuses a request carrying names, with the reason, rather than silently ignoring them.Related issue
Closes #89. Host side: tinyhumansai/openhuman#7917 / #7824.
API or behavior changes
Contract minor bump: 2.4 → 2.5. A host built on this bus requires a module at 2.5, and a 2.5 host refuses a 2.4 module.
NativeJailConfig.deny_read_names: Vec<String>(serde default, omitted when empty), so 2.4 requests decode unchanged. Seedocs/specs/contract-2-5.md.impl Default for NativeJailConfig: the most restrictive request. Hosts that build with..NativeJailConfig::default()keep compiling when the contract adds a field. Hosts building it as a full literal (tinymcptransport/stdio/jailed.rs, OpenHuman) need the new field on their next bump.JailBackend::denies_by_name(), defaultfalse; Seatbelt returnstrue.Jail.deny_read_names.UNSUPPORTED_OPERATIONon bubblewrap, Landlock and AppContainer, and withINVALID_FILE_PATHwhen a name isn't one plain path component.Validation
macOS behaviour was established first with a throwaway probe on our fork (run https://github.com/M3gA-Mind/tinybox/actions/runs/43003256907,
macos-latest, macOS 26.7.2). Each case got the expected result:require-notcarves a subpath out of a deny;regexdenial blocks a name at any depth and allows siblings;literaldenies an exact file.On our Linux CI runner, at 6e7d4e2879 (the head differs only by applying
cargo fmt's one-hunk patch):cargo clippy --all-targets --all-features -- -D warnings: 0 errors, same as main.cargo build --all-targets --all-featuresandRUSTDOCFLAGS="-D warnings" cargo doc: ok.cargo test -p tinybox-bus -p tinybox-jail -p tinybox-module --no-fail-fast: 538 passed, 0 failed, with no failures unique to this branch. All nine new tests ran, including the profile-rendering ones, which run on every host.The live Seatbelt tests (
a_scratch_inside_a_denied_folder_stays_usable_while_the_folder_is_denied,a_denied_name_is_unreadable_wherever_it_appears) run on this PR's macOS job.Tests
"",.,..and names with a separator, quote or NUL are refused as invalid paths.tinybox_jailspawn helpers refuse names on the no-op backend.Defaultis the most restrictive.Documentation
docs/specs/contract-2-5.md, plus rustdoc on every new item.Checklist
#[allow(...)],#[ignore], or relaxed lints.envcontents in the diff or the description