Skip to content

Make a resource budget's disk limit optional (contract 2.6) - #159

Open
M3gA-Mind wants to merge 9 commits into
tinyhumansai:mainfrom
M3gA-Mind:feat/p4-optional-disk-bytes
Open

M3gA-Mind wants to merge 9 commits into
tinyhumansai:mainfrom
M3gA-Mind:feat/p4-optional-disk-bytes

Conversation

@M3gA-Mind

Copy link
Copy Markdown
Collaborator

Summary

ResourceLimits.disk_bytes was a required u64 defaulting to 8 GiB. No backend enforces a writable disk budget, so on Create the value was never applied. On a native jail, an explicit resources budget made all four fields hard requirements, Disk included, and every backend refused. So every StartJailedExec request carrying resources was refused, whatever its CPU, memory and pids.

Contract 2.6 makes disk_bytes an Option<u64>: None by default, and omitted on the wire when None.

  • A native jail asks for Disk only when disk_bytes is set, so a CPU/memory/pids budget is judged on what it asks for.
  • A box created without one keeps the soft core default, as before.

Stacked on #158 (contract 2.5); review only the last commit.

Related issue

Part of #27 (enforcement gaps: the mandatory disk budget).

API or behavior changes

  • tinybox_bus::ResourceLimits::disk_bytes changes from u64 to Option<u64>, and ResourceLimits::default() now has None. This is source-breaking for code that builds the struct literally or reads the field.
  • CONTRACT_VERSION = (2, 6).
  • Host rule: a module before 2.6 cannot decode a ResourceLimits without disk_bytes. A host must have bound a 2.6 module, which is_compatible already requires of a 2.6 host, before it sends one. A host built on 2.6 that talks to older modules must keep setting disk_bytes.
  • A native jail budget without disk_bytes is no longer refused for Disk. It is still refused by any backend that cannot enforce CPU, memory and pids.
  • Core Resources and stored box records are unchanged.

Validation

On our Linux CI runner, at 121446c7a9. The head is the same commit rebased onto #158's final formatting fix, and cargo fmt --check on the head is clean:

  • cargo clippy --all-targets --all-features -- -D warnings: 0 errors, same as main.
  • cargo build --all-targets --all-features and RUSTDOCFLAGS="-D warnings" cargo doc: ok.
  • cargo test --locked --all-features --workspace --no-fail-fast: 1589 passed, 0 failed, with no failures unique to this branch (main: 1575 passed). The version tests pin (2, 6).
  • Red controls (on the first build of this change), each restored afterwards:
    • always requiring Disk fails a_native_jail_budget_requires_disk_only_when_one_is_set;
    • dropping the serde attributes fails disk_bytes_is_optional_and_absent_by_default, because None then serializes as null.

Tests

  • disk_bytes_is_optional_and_absent_by_default:
    • a 2.5-shaped budget with disk_bytes decodes as Some and re-encodes unchanged;
    • one without it decodes as None;
    • the default omits it.
  • a_native_jail_budget_requires_disk_only_when_one_is_set: Cpu, Memory and Pids are always required; Disk only when set.
  • an_unset_disk_budget_keeps_the_soft_core_default: a Create without a disk budget keeps Resources::DEFAULT.disk_bytes and adds no hard requirement.
  • The version tests now pin (2, 6) and refuse a 2.5 module.
  • Red controls: always requiring Disk fails the jail test, and dropping the serde attributes fails the wire test.

Documentation

docs/specs/contract-2-6.md, the contract version doc in tinybox-bus/src/lib.rs, and rustdoc on the field.

Checklist

  • The change is focused on one logical change
  • No new #[allow(...)], #[ignore], or relaxed lints
  • No secrets, tokens, or .env contents in the diff or the description

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
ResourceLimits.disk_bytes was a required u64 defaulting to 8 GiB, and an
explicit native jail budget turned it into a hard Disk requirement that no
backend enforces, so every legacy jail request with resources was refused.

disk_bytes is now Option<u64>, absent by default and on the wire when unset.
A native jail requires Disk only when the caller sets one; a box keeps the
soft core default. Contract 2.6, since a module before it cannot decode a
budget without the field.
@coderabbitai

coderabbitai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e34a9864-2c9c-45fe-89e6-47c9d8e46ba9
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Stacked on #158 (its first three commits are #158's). Please merge #158 first; this PR's diff then reduces to the optional disk_bytes commit.

@tinysweeper

tinysweeper Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Reviewed head: fa277359ff92f2882973f6566883231e09b3f02c

Verdict: Ready for maintainer review · Priority: none
Coverage: Reviewed 25 of 25 files.
Pull request purpose (title): Make a resource budg…

Diagnostics and supporting details

Full summary

Pull request purpose (title): Make a resource budget's disk limit optional (contract 2.6). Under contract 2.6, `ResourceLimits::disk_bytes` becomes `Option<u64>` (default `None`, omitted on the wire), so a native jail asks for `Disk` only when a caller set one; an explicit CPU/memory/pids budget without a disk value now reaches backends able to hold those three constraints instead of being refused for a disk limit nothing enforces. `Create` keeps the soft core `Resources::DEFAULT.disk_bytes` when the field is unset, and the contract version moves to (2,6), which is what makes the omission decodable only against a 2.6 module. The diff also carries the stacked 2.5 `deny_read_names` work, which the description says to review separately.

Review snapshot

Change surface Files Review signal Count
Production 9 Active findings 0
Tests 14 Noted findings 0
Documentation 2 Resolved findings 0
Configuration 0 Pending checks/questions 0

Completeness: Complete
Test assessment: Test coverage is assessed from changed tests and lane evidence; execution is not claimed without trusted check data.

What changed

25 files changed, 567 additions, 32 deletions. `ResourceLimits::disk_bytes` becomes `Option<u64>` with `None` default and `skip_serializing_if`; `CONTRACT_VERSION` becomes (2,6); native jail `required_resource_constraints` adds `Disk` only when `disk_bytes` is `Some`; `make_spec` and `configure_resources` fall back to `Resources::DEFAULT.disk_bytes` when unset; version tests pin (2,6) and reject (2,4)/(2,5); new `docs/specs/contract-2-6.md`; new tests for the wire shape, the disk-constraint gating, and the soft core default. The stacked 2.5 material (`deny_read_names`, `NativeJailConfig::default()`, Seatbelt carve-out and name-denial regex, boxed `ShellHost::Jailed`) is also present.

Features

  • Modified — Optional disk limit in ResourceLimits: `disk_bytes` changes from `u64` to `Option<u64>`, defaults to `None`, and is omitted on the wire when `None`. A value from an older host still decodes as `Some`; code building the struct literally or reading the field must adapt. (crates/tinybox-bus/src/lib.rs pub struct ResourceLimits {, crates/tinybox-bus/src/lib.rs impl Default for ResourceLimits {)
  • Modified — Contract version 2.6: `CONTRACT_VERSION` is (2,6), and `is_compatible` rejects (2,4) and (2,5), so a 2.6 host only talks to modules able to decode a `ResourceLimits` with no `disk_bytes`. (crates/tinybox-bus/src/lib_tests.rs fn version_and_capability_wire_snapshot_preserve_contract_compatibility(), crates/tinybox-bus/src/event_tests.rs fn process_events_keep_their_correlation_and_signal_wire_shape() -> Result<(), s)
  • Modified — Native jail requires Disk only when set: `required_resource_constraints` adds `Constraint::Disk` only when `limits.disk_bytes.is_some()`, so a CPU/memory/pids budget is judged on what it asks for rather than being refused for an unconditional disk limit, and `configure_resources` falls back to the core default when unset. (crates/tinybox-module/src/tinybus_module/native_jail.rs fn configure_resources(jail: &mut Jail, config: &NativeJailConfig) -> Result<()>, crates/tinybox-module/src/tinybus_module/native_jail_tests.rs fn requested_native_resource_limits_are_refused_before_spawn())
  • Modified — Create spec keeps soft core default when disk_bytes unset: `make_spec` uses `Resources::DEFAULT.disk_bytes` when the request has no disk budget, keeping the value soft and adding no hard requirement. (crates/tinybox-module/src/tinybus_module/resources.rs fn make_spec(request: &CreateRequest, host: &str) -> Result<BoxSpec> {, crates/tinybox-module/src/tinybus_module/resources_tests.rs fn sandbox_selection_and_spec_mapping_preserve_host_policy() -> Result<()> {)
  • Added — Contract 2.6 specification document: New `docs/specs/contract-2-6.md` documents the optional field, the soft core default on `Create`, the Disk-only-when-set native jail behavior, and the host rule requiring a bound 2.6 module before the field is omitted. (docs/specs/contract-2-6.md)

Tests

  • test — Covers the disk-limit wire shape: a 2.5-shaped budget with `disk_bytes` decodes as `Some` and re-encodes unchanged, one without it decodes as `None`, and the default omits it from serialization.: added (crates/tinybox-bus/src/lib_tests.rs fn version_and_capability_wire_snapshot_preserve_contract_compatibility())
  • test — Covers the native jail constraint list: Cpu, Memory and Pids are always required and Disk is added only when `disk_bytes` is set; `None` resources yield no constraints.: added (crates/tinybox-module/src/tinybus_module/native_jail_tests.rs fn requested_native_resource_limits_are_refused_before_spawn())
  • test — Covers the Create path: a request without a disk budget keeps `Resources::DEFAULT.disk_bytes` and adds no hard requirement.: added (crates/tinybox-module/src/tinybus_module/resources_tests.rs fn sandbox_selection_and_spec_mapping_preserve_host_policy() -> Result<()> {)
  • test — Version assertions now pin (2,6) and reject (2,4) and (2,5) across the contract snapshot and event-shape tests.: modified (crates/tinybox-bus/src/lib_tests.rs fn no_workspace_docker_operation_is_present_in_the_contract() {, crates/tinybox-bus/src/event_tests.rs fn process_events_keep_their_correlation_and_signal_wire_shape() -> Result<(), s)

Findings

No active actionable findings.

Before merge

None.

How this fits together

flowchart LR
  n0["NativeJailConfig<br/>changed"]:::changed
  n1["...ubset_is_additive_and_keeps_disk_explicit<br/>changed"]:::changed
  n2["..._limits_are_optional_typed_and_round_trip<br/>changed"]:::changed
  n3["ResourceLimits<br/>changed"]:::changed
  n0 -->|uses| n3
  n1 -->|uses| n0
  n2 -->|uses| n0
  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
Loading
Agent review details

critique

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The critique lane answered and found no defect anchored to a concrete failing input, confirming the new tests line up with `required_resource_constraints`, the `denies_by_name` fixture, and the `check_name_denials` refusal message.
  • Lane summary: The new tests in this file match the production contracts I could check: `required_resource_constraints` does omit `Disk` when `disk_bytes` is `None`, the fixture `Backend` now reports `denies_by_name` only for the seatbelt name, and the refusal message the test asserts on is the one `check_name_denials` builds. I found no defect in the added or edited lines that I can anchor to a concrete failing input. _The code index is behind this pull request (indexed at `11db5690c21e`), so retrieved context may be out of date._

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The security lane answered with no reportable findings, noting the native-jail fixture changes are deterministic, use temporary directories, and add no untrusted-input, network, or secrets path.
  • Lane summary: The change is confined to the module's native-jail test fixture: it adds the new `deny_read_names` field to `config()`, follows the `disk_bytes` type change to `Option<u64>`, mirrors `denies_by_name` for the seatbelt fixture, and adds focused tests for the new name-denial preflight. The tests are deterministic, use temporary directories, and introduce no untrusted-input, network, or secrets path; the fixture strings like `a"b` are inputs to a rejection test. Nothing reportable. _The code index is behind this pull request (indexed at `11db5690c21e`), so retrieved context may be out of date._

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The tests lane answered and reported no reportable defects, noting the new behavior is covered by assertion-bearing tests for the constraint gating, the wire round-trip, and the name-denial validation.
  • Lane summary: This revision adds `deny_read_names` plus `NativeJailConfig::default()`, makes `ResourceLimits::disk_bytes` optional, and bumps the contract to 2.6; the new assertion-bearing tests cover each new branch (backend-without-name-denial refusal, name validation, wire additivity and omission, disk-budget constraint gating), so the change looks sound and safe to merge. _The code index is behind this pull request (indexed at `11db5690c21e`), so retrieved context may be out of date._

commits

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The commits lane answered and found no sensitive content in committed files.
  • Lane summary: No sensitive content in committed files. _The code index is behind this pull request (indexed at `11db5690c21e`), so retrieved context may be out of date._

description

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The description lane answered and found the description matches the 2.6 change, including the public API break, the host rule, the tests, and the red controls.
  • Lane summary: The commit under review adds test coverage only: unit tests for the disk-only-when-set constraint list, name-denial admission/refusal on both kinds of backend, and plain-component validation, plus fixture updates for the new `Option<u64>` and `deny_read_names` fields. Nothing here changes behaviour, and the new tests match the code the diff calls into (`denies_by_name` defaults to false, Seatbelt returns true). I found no reportable defect in these commits. _The code index is behind this pull request (indexed at `11db5690c21e`), so retrieved context may be out of date._
Evidence and run details
  • Models: ladder/vectors-oai3, deepseek-v4.1-flash
  • Spend: $0.003150
  • Tokens: 378780 input · 4834 output · 129024 cached · 799 embedding
  • Updated: 2026-11-12T08:34:58Z
Head State Pass summary
4c95935ebe96 incomplete 0 active finding(s), 0 resolved finding(s) (at 2026-11-11T12:15:53Z)
e958ff8bc678 incomplete 0 active finding(s), 0 resolved finding(s) (at 2026-11-11T14:17:29Z)
b2b762f5d6f4 ready for maintainer review 0 active finding(s), 0 resolved finding(s) (at 2026-11-12T07:30:45Z)
fa277359ff92 ready for maintainer review 0 active finding(s), 0 resolved finding(s) (at 2026-11-12T08:34:58Z)

tinysweeper 0.1.2

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

tinysweeper could not complete coverage (19 unanswered item(s)), so this is not an approval.

Diagnostics

Total spend: $0.0381

@tinysweeper tinysweeper Bot added the priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later. label Nov 11, 2026
@tinysweeper tinysweeper Bot added priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. and removed priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later. labels Nov 11, 2026
# Conflicts:
#	crates/tinybox-module/src/tinybus_module/native_jail_tests.rs

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

tinysweeper found nothing blocking. Approving.

Diagnostics

Total spend: $0.0087

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

Labels

priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant