Skip to content

Plan files-only Landlock jails for opted-in operator commands - #68

Closed
senamakel wants to merge 2 commits into
tinyhumansai:mainfrom
senamakel:builder-landlock-files-only
Closed

senamakel wants to merge 2 commits into
tinyhumansai:mainfrom
senamakel:builder-landlock-files-only

Conversation

@senamakel

Copy link
Copy Markdown
Member

Summary

Adds SandboxPlanOptions2.files_only, an explicit per-request opt-in for running the operator's own command under a jail that confines only the filesystem, on the host's network. This is Linux Landlock where nothing stronger can start, for example inside a Docker container whose default seccomp profile blocks user namespaces, so bubblewrap fails and Docker is unavailable.

Today requirements() always requires Filesystem and Network enforcement, and the trusted-folder pass rejects any executor with isolation: none. Landlock declares only the filesystem and no isolation, so pick() returns unsupported and every sandboxed shell in such a container is refused, even though the shell already asks for the host's network and gets nothing from a network rule.

With files_only set:

  • a request for NetworkPolicy::Host requires only the filesystem (plus any requested bounds); any other network policy keeps the network requirement;
  • on the trusted-folder pass, an executor with no process isolation qualifies only when it enforces the filesystem exactly (a best-effort filesystem never does);
  • a kernel executor that meets the floor still wins;
  • the artifact states minimum_isolation: process with the filesystem constraint alone.

It is refused (floor) without accept_process_isolation, for any source other than trusted_host, and for SaaS or unlabelled deployments and origins. validate/ConsumeSandboxPlan2 re-derive the plan, so a request that does not set the option cannot reuse a files-only artifact.

Related issue

None. Host side: tinyhumansai/openhuman (agent-definition opt-in sandbox_profile = "files-only"), PR to follow.

API or behavior changes

Additive contract field SandboxPlanOptions2.files_only (default off, absent from the wire when off, so existing fingerprints and hosts are unchanged). A module older than this field rejects a request that sets it (deny_unknown_fields), so an opted-in host fails closed against an old module. No behaviour change for any request that does not set it. CONTRACT_VERSION is unchanged on purpose: bumping it would make every host built on this contract refuse the released module.

Validation

Commands actually run, with their outcome:

  • cargo fmt --all -- --check: clean
  • cargo clippy --all-targets --all-features -- -D warnings: clean
  • cargo build --all-targets --all-features: ok
  • cargo test --all-features: 1380 passed, 0 failed
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features: ok

TDD: cargo test -p tinysecurity-sandbox files_only first failed 3 of 7. The Landlock plan was Some(Unsupported) instead of an artifact, and the two floor cases were Some(Unsupported) instead of Some(Floor). After the engine change all 7 pass.

Tests

  • crates/tinysecurity-sandbox/src/files_only_tests.rs (new): an opted-in operator host-network request plans onto Landlock, gets minimum_isolation: process and [filesystem], stages, activates and validates, and does not validate for a request without the option. The same request without the option is unsupported. A request for no network is still unsupported. A best-effort filesystem with no isolation is unsupported. Without accept_process_isolation the result is floor, as it is for untrusted, SaaS and unlabelled plans. A kernel executor still wins.
  • crates/tinysecurity-bus/src/sandbox_tests.rs: the pinned wire form of SandboxPlanOptions2 now includes files_only.

Documentation

MODULE.md (executor choice section) and the new docs/specs/sandbox-plan-files-only.md.

Checklist

  • The change is focused on one logical change
  • No new #[allow(...)], #[ignore], or relaxed lints: one targeted #[allow(clippy::struct_excessive_bools)] on SandboxPlanOptions2. It is commented and matches the existing one on the wire-flag struct in policy.rs:545. The options are independent wire flags, and merging them into an enum would break the wire form.
  • No secrets, tokens, or .env contents in the diff or the description

senamakel and others added 2 commits November 12, 2026 07:14
Add a test module covering the files-only sandbox mode, exercising its behaviour so the restriction can be verified.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
SandboxPlanOptions2.files_only lets the operator's own host-network command
accept a jail that enforces only the filesystem: the plan drops the network
requirement for a host-network request, and on the trusted-folder pass an
executor with exact filesystem enforcement qualifies without process
isolation. Refused (floor) for untrusted, SaaS or unlabelled plans and
without accept_process_isolation.

Co-authored-by: Medulla <medulla@tinyhumans.ai>
@coderabbitai

coderabbitai Bot commented Nov 12, 2026

Copy link
Copy Markdown

Warning

Review limit reached

  • Run on-demand review

This review includes 6 billable files and costs up to $1.50.

Or wait 58 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

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: 1b2c9a6d-2e8e-4724-8616-86a4499afb4a
Commits

Reviewing files that changed from the base of the PR and between dd1efe0 and 310ef8d.

Files selected for processing: 6

  • MODULE.md
  • crates/tinysecurity-bus/src/sandbox.rs
  • crates/tinysecurity-bus/src/sandbox_tests.rs
  • crates/tinysecurity-sandbox/src/engine.rs
  • crates/tinysecurity-sandbox/src/files_only_tests.rs
  • docs/specs/sandbox-plan-files-only.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@tinysweeper

tinysweeper Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Reviewed head: 310ef8d66384f384d33f5c716c9cdb9e8935a4e7

Verdict: Ready for maintainer review · Priority: none
Coverage: Reviewed 6 of 6 files.
Pull request purpose (title): Plan files-only Land…

Diagnostics and supporting details

Full summary

Pull request purpose (title): Plan files-only Landlock jails for opted-in operator commands. Tiny Sweeper completed its review; deterministic results follow.

Review snapshot

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

Completeness: Complete
Test assessment: No supported feature-to-test mapping was available; this does not mean tests are absent or passed.

Features

  • Added — `files_only` option on `SandboxPlanOptions2`: Adds an explicit per-request opt-in wire flag, default off and omitted from the wire when off, so existing hosts and fingerprints are unchanged; an older module rejects a request that sets it (`deny_unknown_fields`), so an opted-in host fails closed. (crates/tinysecurity-bus/src/sandbox.rs pub struct SandboxPlanOptions2 {, crates/tinysecurity-bus/src/sandbox.rs impl std::fmt::Debug for SandboxPlanRequest2 {)
  • Modified — Network requirement dropped for files-only host-network requests: `requirements()` now requires only the filesystem for a request with `files_only` and `NetworkPolicy::Host`; any other network policy keeps the network requirement. (crates/tinysecurity-sandbox/src/engine.rs fn minimum_isolation(, crates/tinysecurity-sandbox/src/engine.rs impl Engine {)
  • Modified — Trusted-folder executor qualification via `qualifies_for_trusted_folder`: On the trusted-folder pass an executor with no process isolation qualifies only when the request set `files_only` and the executor enforces the filesystem exactly; a best-effort filesystem without isolation never qualifies. Artifact validation uses the same predicate. (crates/tinysecurity-sandbox/src/engine.rs fn pick<'a>(, crates/tinysecurity-sandbox/src/engine.rs impl Engine {)
  • Modified — Floor refusal for files-only requests outside the trusted-operator case: A request setting `files_only` is denied `floor` when isolation is mandatory or `accept_process_isolation` is not set; the source/deployment/origin restrictions follow from the existing floor logic. (crates/tinysecurity-sandbox/src/engine.rs pub(crate) fn denied(generation: u64, reason: SandboxDenial2) -> SandboxPlanResp, crates/tinysecurity-sandbox/src/engine.rs impl Engine {)
  • Added — Files-only documentation: Documents the option's semantics, refusals, compatibility, and the files-only subset of the executor-choice contract. (MODULE.md refuses a 1.21 module, and a 1.21 module rejects a request that sets an option)

Tests

No supported feature-to-test mapping was produced. Test execution is not inferred.

Findings

No active actionable findings.

Before merge

None.

How this fits together

flowchart LR
  n0["SandboxPlanOptions2<br/>changed"]:::changed
  n1["SandboxPlanRequest2<br/>changed"]:::changed
  n2["plan_options_have_a_pinned_wire_form<br/>changed"]:::changed
  n3["minimum_isolation<br/>changed"]:::changed
  n4["SandboxScope2"]:::impacted
  n5["SandboxArtifact2"]:::impacted
  n6["SandboxExecutor2"]:::impacted
  n7["resolve"]:::impacted
  n8["required"]:::impacted
  n9["pick"]:::impacted
  n1 -->|uses| n0
  n2 -->|uses| n0
  n2 -->|calls| n8
  n2 -->|tests| n8
  n3 -->|uses| n6
  n4 -->|uses| n6
  n7 -->|uses| n1
  n7 -->|calls| n3
  n7 -->|uses| n5
  n9 -->|uses| n1
  n9 -->|uses| n4
  n9 -->|uses| n6
  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
  • Lane summary: Reviewed 6 files; 0 findings. _The code index is behind this pull request (indexed at `0b4c801c4669`), so retrieved context may be out of date._

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: Security lane answered with 0 findings across the 4 code files; the two prose files were not security-reviewed by that lane.
  • Lane summary: Reviewed 4 files; 0 findings. 2 files were not security-reviewed: MODULE.md (prose or tabular data), docs/specs/sandbox-plan-files-only.md (prose or tabular data). _The code index is behind this pull request (indexed at `0b4c801c4669`), so retrieved context may be out of date._

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: Tests lane answered: the wire field is pinned and defaulted, and the engine's new branches are exercised.
  • Lane summary: The change is well covered: the new `files_only` wire field gets a pinned serde-shape assertion and a default assertion in `sandbox_tests.rs`, and `files_only_tests.rs` exercises the new engine branches (network requirement dropped only for host network, best-effort filesystem rejected, floor refusals for non-trusting requests/SaaS/unlabelled sources, kernel executor preferred, and `validate` rejecting a request that drops the option). One assertion, `planned.grants == request.grants`, cannot fail; it is noise rather than a mispinned contract, so it does not rise to a reportable defect. _The code index is behind this pull request (indexed at `0b4c801c4669`), so retrieved context may be out of date._

commits

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

description

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: Description lane answered: the description matches the diff, naming the flag, relaxed network requirement, qualification rule, floor refusals, docs and tests, and honestly disclosing the new `#[allow(clippy::struct_excessive_bools)]`.
  • Lane summary: The description matches the diff: it names the additive `files_only` wire flag, the relaxed network requirement, the trusted-folder qualification rule, the floor refusals, and the docs/tests added, and the checklist honestly notes the new `#[allow(clippy::struct_excessive_bools)]`. Nothing significant in the diff is unmentioned. I found no description defect. _The code index is behind this pull request (indexed at `0b4c801c4669`), so retrieved context may be out of date._
Evidence and run details
  • Models: ladder/vectors-oai3, deepseek-v4.1-flash
  • Spend: $0.003718
  • Tokens: 413663 input · 6171 output · 143104 cached · 618 embedding
  • Updated: 2026-11-12T07:22:04Z
Head State Pass summary
310ef8d66384 ready for maintainer review 0 active finding(s), 0 resolved finding(s) (at 2026-11-12T07:22:04Z)

tinysweeper 0.1.2

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-12T07:30:57.580320Z 310ef8d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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.0037

@senamakel

Copy link
Copy Markdown
Member Author

Duplicate of #67, which covers the same files-only plan for opted-in operator commands and is further along with its openhuman integration. Closing this one so only one design lands.

@senamakel senamakel closed this Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant