Skip to content

fix(saas): harden the user shell sandbox (#7154 review follow-ups) - #7195

Merged
senamakel merged 4 commits into
tinyhumansai:mainfrom
senamakel:saas-tools-hardening
Oct 9, 2026
Merged

senamakel merged 4 commits into
tinyhumansai:mainfrom
senamakel:saas-tools-hardening

Conversation

@senamakel

@senamakel senamakel commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Summary

These are the round-1 review fixes for #7154 (SaaS host tool groups and the per-user sandbox). #7154 was merged before they were applied.

  • Symlinks resolved before mounting. user_agents::tools::sandbox_policy canonicalizes the action dir and requires it to resolve to exactly <root>/agents/<id>/sandbox. The resolved path is what gets mounted read-write. Before, a sandbox entry that was a symlink passed the textual check and could point the container's writable mount anywhere on the host. A missing sandbox directory is refused too.
  • The host network is refused in any case. The runtime check and the boot guard both use is_host_network, which trims and compares case-insensitively, so HOST or Host can't slip past.
  • CPU limits must be finite. The boot guard now refuses cpu_limit = inf.
  • A refused command is reported as not allowed. When the SaaS sandbox can't be built, the shell returns allowed = false, because nothing ran.

Points I declined, from the same review:

  • Domain gate: SaasToolGroups contain only file and shell tools, which belong to no gated domain family, so the allowlist can't re-enable a disabled domain.
  • BootInputs call sites: every construction site already passes sandbox_available.
  • host_files: those tools are confined by the user's forced policy: autonomy on, workspace_only, action_dir = the user's sandbox, and in SaaS no projects or /tmp/openhuman grants.

Test plan

  • cargo test -p openhuman --lib -- user_agents::tools core::runtime::boot_guard tools::impl::system::shell: 37 passed. New tests:
    • a_symlinked_sandbox_is_refused
    • a_missing_sandbox_is_refused
    • the_host_network_is_refused_in_any_case
    • an_uppercase_host_network_or_an_infinite_cpu_limit_is_refused
  • cargo test -p openhuman-cli --test saas_mode_e2e: 9 passed.
  • cargo clippy -p openhuman --all-targets: no new warnings. The one warning, in harness_assembly_tests.rs, is already on main.

Summary by CodeRabbit

  • Bug Fixes
    • Sandbox checks now reject host networking regardless of capitalization or surrounding whitespace, and reject CPU limits that are infinite.
    • Sandbox access is denied when its location cannot be verified, including when it is missing or resolves outside the expected location.
    • If sandbox policy cannot be resolved, shell access is denied instead of allowed.
    • Sandbox validation now reports violations when multiple limits or settings are invalid.

senamakel and others added 2 commits October 9, 2026 16:23
The shell tool now checks the boot guard before executing commands, so
invocations are rejected while the runtime is still starting up. This
prevents user agents from running shell commands before the environment
is fully initialized.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…ensitive host network, finite CPU, refusal not allowed

Co-authored-by: Medulla <medulla@tinyhumans.ai>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codex Review Summary

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-09T14:06:08.409412Z 31c49c9 New commits
ℹ️ 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.

@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dcc59235-0fec-4586-909f-a95edaa9d754

📥 Commits

Reviewing files that changed from the base of the PR and between 3287489 and 31c49c9.


⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock

📒 Files selected for processing (2)
  • crates/openhuman-core/Cargo.toml
  • crates/openhuman-core/src/user_agents/gateway.rs

 _______________________________
< When in doubt, review it out. >
 -------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7ab52ec6-3b6b-43aa-9d68-980b06d804f2

📥 Commits

Reviewing files that changed from the base of the PR and between d468179 and 3287489.


📒 Files selected for processing (5)
  • crates/openhuman-core/src/core/runtime/boot_guard.rs
  • crates/openhuman-core/src/core/runtime/boot_guard_tests.rs
  • crates/openhuman-core/src/tools/impl/system/shell.rs
  • crates/openhuman-core/src/user_agents/tools.rs
  • crates/openhuman-core/src/user_agents/tools_tests.rs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.



📝 Walkthrough

Walkthrough

Sandbox policy resolution now validates canonical paths and rejects host-network variants. Runtime validation rejects non-finite CPU limits. SaaS policy resolution failures now return a disallowed result.

Changes

Sandbox enforcement

Layer / File(s) Summary
Sandbox policy resolution
crates/openhuman-core/src/user_agents/tools.rs, crates/openhuman-core/src/tools/impl/system/shell.rs, crates/openhuman-core/src/user_agents/tools_tests.rs
The policy recognizes host-network variants and validates canonical sandbox paths. SaaS policy resolution failures return allowed = false. Tests cover temporary layouts, host-network variants, symlinked paths, and missing sandboxes.
Runtime sandbox validation
crates/openhuman-core/src/core/runtime/boot_guard.rs, crates/openhuman-core/src/core/runtime/boot_guard_tests.rs
Runtime validation uses the host-network helper and rejects non-finite or non-positive CPU limits. A test checks uppercase host networking and an infinite CPU limit.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: al629176


Merge Risk

Merge Risk: ⚪ Minimal · up to 32874

The reviewed changes tighten sandbox path, network, and CPU validation and fail closed on policy errors. No supported merge-blocking issue remains.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 32874

The changes strengthen sandbox isolation without expanding shell authority. They reject redirected or missing sandbox paths, unsafe network settings, and invalid CPU limits, and accurately report refused commands. Remaining uncertainty concerns filesystem replacement races and interrupted-execution cleanup, rather than an identified regression.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The shared enforcement change applies to SaaS user-shell commands, while each accepted command retains a single agent sandbox as its writable host workspace. The security-sensitive sink is the Docker host bind mount: accepting a redirected source could expose host data or other agents' data. Canonical path equality narrows that exposure for static redirection.

Trust Boundaries and Controls

  • observed — The inspected identity flow resolves an authenticated gateway user to UserAgentState, whose forced layout supplies the agent sandbox action directory. Effective workspace paths still undergo SaaS policy validation before execution. The policy validates the agent-directory shape and resolved location; it does not itself replace the upstream identity-binding responsibility.
  • inferred — Canonicalization does not atomically pin a directory through Docker mount creation. Earlier source inspection shows the mount builder resolves the path again. Exploiting replacement would require authority to alter the sandbox or its parent on the host; the inspected tenant shell flow did not establish that capability. This residual condition predates the PR, and no increased exposure was established.

Resilience and Maintainability Implications

  • observed — Existing deprovisioning holds the host's open-agent lock and refuses to archive an agent with additional active state references. GatewayScope retains such a reference. This provides lifecycle counterevidence against ordinary deprovisioning replacing a sandbox during active use, although complete dispatch lifetime and interruption cleanup were not established.



🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: hardening the SaaS user shell sandbox. It matches the sandbox validation, networking, resource-limit, and failure-handling updates.
Docstring Coverage Passed Docstring coverage is 86.67% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 5 files.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.


  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

A rabbit checks the sandbox gate,
“HOST” and spaces meet their fate.
The paths resolve, the limits shine,
No endless CPU slips the line.
A safer burrow, neat and fine!

Comment @coderabbitai help to get the list of available commands.

@tinysweeper

tinysweeper Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

This change hardens the SaaS user-shell sandbox: it canonicalizes the sandbox directory to block symlink escapes, refuses host network names in any casing or with whitespace, rejects non-finite CPU limits at boot, and reports a sandbox-refused shell command as not allowed. Unit tests cover each behavior; two earlier findings remain open: the sandbox-refused shell result path is still not driven by any test, and end-to-end jobs are pending.

State: Reviewing pending checks
Priority: medium
Reviewed head: 31c49c9da783
Updated: 1791555236 (Unix time)

Review snapshot

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

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

What changed

The pull request modifies the sandbox policy construction and boot guard checks. In the sandbox policy builder, the action directory is now canonicalized and must resolve exactly to the agent's expected `sandbox/` directory under the agents root, and the resolved path is used as the workspace root, so a symlink pointing elsewhere on the host is refused. Host-network detection moved to a shared helper that trims and compares case-insensitively, since Docker matches the network name case-insensitively, and both the boot guard and the policy builder now use it. The boot guard also rejects non-finite CPU limits (previously only NaN was caught, letting infinities through). In the shell tool, when the SaaS sandbox refuses a command before anything runs, the result flag changed from true to false so it is reported as not allowed. Dependency changes pin hmac to 0.13 against sha2 0.11 via a renamed alias, and the gateway imports were updated accordingly.

Features

  • Added — Symlink-resolved sandbox directory verification: The read-write mount is verified via canonicalize to be the agent's real sandbox directory under the agents root; a symlink resolving outside it is refused and the canonicalized path becomes the workspace_root, preventing a sandboxed command from mounting unrelated host paths read-write. (crates/openhuman-core/src/user_agents/tools.rs#pub fn sandbox_policy_with()
  • Modified — Case- and whitespace-insensitive host network refusal: A new helper compares the trimmed network name against "host" case-insensitively; both the boot guard and sandbox policy builder use it, so configurations like "HOST" or " Host " can no longer bypass the host-network refusal that Docker would treat as the host network. (crates/openhuman-core/src/user_agents/tools.rs#pub fn sandbox_policy_with(, crates/openhuman-core/src/core/runtime/boot_guard.rs#fn sandbox_problems(inputs: &BootInputs<'_>) -> Vec<String> {)
  • Modified — Sandbox-refused shell command reported as not allowed: When the SaaS sandbox refuses a command, the shell tool now returns allowed=false with an error result; the comment notes nothing ran, so downstream consumers no longer see the refusal as an allowed execution. (crates/openhuman-core/src/tools/impl/system/shell.rs#impl ShellTool {)
  • Internal refactor — hmac/sha2 version alignment for the gateway signature: The manifest pins hmac 0.13 against sha2 0.11 under the alias sha2_011 to reuse versions already in the dependency graph, and the SaaS gateway imports were updated to the new hmac API (KeyInit) and the aliased Sha256. (crates/openhuman-core/Cargo.toml#zeroize = "1", crates/openhuman-core/src/user_agents/gateway.rs)

Tests

  • unit — the_sandbox_refuses_anything_but_a_user_sandbox: Unit-level test of sandbox policy refusal behavior in the tools tests. (crates/openhuman-core/src/user_agents/tools_tests.rs#fn the_sandbox_refuses_anything_but_a_user_sandbox() {)
  • unit — the_host_network_is_refused_in_any_case: Parametrizes over "host", "HOST", and " Host " and asserts the sandbox policy builder refuses each, covering the new case-insensitive matching. (crates/openhuman-core/src/user_agents/tools_tests.rs#fn the_sandbox_refuses_anything_but_a_user_sandbox() {)
  • unit — a_symlinked_sandbox_is_refused: Unix-only test replaces the agent's sandbox directory with a symlink to elsewhere and asserts the refusal message mentions "outside", covering the canonicalization check. (crates/openhuman-core/src/user_agents/tools_tests.rs#fn a_named_network_allows_egress() {)
  • unit — a_missing_sandbox_is_refused: Asserts that a sandbox directory that does not exist on disk is refused by the policy builder. (crates/openhuman-core/src/user_agents/tools_tests.rs#fn root() -> PathBuf {)
  • unit — a_named_network_allows_egress: Updated to build a real on-disk root with the agent's sandbox and workspace directories and asserts a named network enables egress. (crates/openhuman-core/src/user_agents/tools_tests.rs#fn a_named_network_allows_egress() {)
  • unit — an_uppercase_host_network_or_an_infinite_cpu_limit_is_refused: Boot guard test using a HOST network and an infinite cpu_limit asserts both violations are reported. (crates/openhuman-core/src/core/runtime/boot_guard_tests.rs#fn a_root_aliasing_the_desktop_install_is_refused() {)

Findings

  • medium · critique · Drive the sandbox refusal through the shell path — This test calls the private boot-guard checker directly and only proves that two `Violation::Sandbox` values are produced. It does not exercise the shell/tool execution path that c (crates/openhuman\-core/src/core/runtime/boot\_guard\_tests\.rs:369)
  • medium · critique · Drive sandbox refusal through an end-to-end test — The refusal is still covered only by the direct `sandbox_policy_with` unit test. That does not verify that the shell execution path propagates this error to its caller and prevents (crates/openhuman\-core/src/user\_agents/tools\.rs:241)
  • medium · critique · Add an end-to-end test for canonicalized workspace_root — The canonicalized `workspace_root` execution path is still not exercised by an end-to-end test, so regressions in the directory normalization and sandbox-policy handoff can go unno (crates/openhuman\-core/src/tools/impl/system/shell\.rs:381)
  • medium · critique · Exercise canonicalized workspace roots end to end — The new canonicalization is asserted only by the policy unit test. No end-to-end test demonstrates that the resulting `workspace_root` is the path actually mounted or used by a com (crates/openhuman\-core/src/user\_agents/tools\.rs:245)
  • medium · critique · Update the lockfile with the new dependency edges — This manifest changes the direct HMAC version and adds a new direct `sha2` alias, but the pull request contains no corresponding `Cargo.lock` change. Even though the lockfile alrea (crates/openhuman\-core/Cargo\.toml:396)
  • medium · security · Drive the sandbox refusal through shell execution — This only proves that `sandbox_policy_with` returns an error; it does not verify that the user-facing shell path propagates the refusal instead of ignoring, translating, or bypassi (crates/openhuman\-core/src/user\_agents/tools\_tests\.rs:159)
  • medium · security · Exercise the canonicalized workspace root end to end — The test checks the value returned by the policy helper, but no end-to-end execution verifies that the canonicalized path is the path actually handed to the container runner and mo (crates/openhuman\-core/src/user\_agents/tools\_tests\.rs:108)
  • medium · security · Drive the sandbox-refused shell result end to end — This only exercises `boot_guard::check` through a unit-test helper and never runs the shell operation that consumes the sandbox refusal. The refusal result could therefore be lost (crates/openhuman\-core/src/core/runtime/boot\_guard\_tests\.rs:376)
  • medium · security · Exercise canonicalized workspace_root end to end — The added test checks only the number of boot violations and does not exercise the runtime path using the canonicalized `workspace_root`. The canonicalization behavior therefore re (crates/openhuman\-core/src/core/runtime/boot\_guard\_tests\.rs:376)
  • medium · tests · Sandbox-refused shell result is still not driven by any test — The Err arm now flips the boolean from true to false, a behavioural change that decides how callers classify the outcome, and no test in this pull request exercises it: no shell-le (crates/openhuman\-core/src/tools/impl/system/shell\.rs:378)
  • medium · description · Drive the canonicalized workspace_root through an end-to-end test — The canonicalization change is well covered by unit tests (a_symlinked_sandbox_is_refused, and the policy asserting workspace_root == action.canonicalize().unwrap()), but the end-t (\(pull request description\))

Resolved this pass

  • Sandbox-refused shell result is not driven end to end
  • Sandbox-refused shell result is still not driven end to end
  • Sandbox-refused shell result is not driven end to end
  • Sandbox-refused shell result is still not driven end to end
  • Canonicalized workspace_root never exercised by an end-to-end test
  • Canonicalized workspace\_root never exercised by an end-to-end test
  • Sandbox-refused shell result is not driven end to end
  • Sandbox-refused shell result is still not driven end to end
  • Canonicalized workspace_root never exercised by an end-to-end test

Pending checks: Rust E2E (mock backend), Build Playwright E2E Artifact, E2E (Playwright / web lane), Desktop E2E (full suite, 3 OS)

Before merge

  • Wait for Rust E2E (mock backend), Build Playwright E2E Artifact, E2E (Playwright / web lane), Desktop E2E (full suite, 3 OS).

How this fits together

flowchart LR
  n0["run_with_security_in_context"]:::impacted
  n1["run_sandboxed"]:::impacted
  n2["execute_in_context"]:::impacted
  n3["runtime_path_for_command"]:::impacted
  n4["Value"]:::impacted
  n5["external_effect_with_args"]:::impacted
  n0 -->|calls| n1
  n0 -->|calls| n3
  n1 -->|calls| n3
  n2 -->|calls| n0
  n2 -->|uses| n4
  n2 -->|calls| n5
  n5 -->|uses| 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
Loading
Agent review details

critique

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Reviewed 7 files; 5 findings. (2 earlier finding(s) still open) _Code retrieval was unavailable (model: ladder embeddings returned 400 Bad Request: {"error":{"message":"unknown ladder vectors; known ladders are flash (also chat-v1, flash-v1), instant (also no-think, instant-v1), reasoning (also deepseek), max-reasoning (also max-reasoning-v1), deepseek-flash (also reasoning-v1, agentic-v1), deep (also luna), scribe, uncensored, vectors-oai3 (also embeddings-oai3-v1), vision (also vision-v1, multimodal-v1), image (also images-v1, image-v1), vi), so this review saw the diff alone._ _Memory was unavailable (model: cortex: v1/recall: error sending request for url (http://cortexdb:3141/v1/recall\)\), so this review ran without it._
  • Evidence: crates/openhuman\-core/src/core/runtime/boot\_guard\_tests\.rs — Drive the sandbox refusal through the shell path
  • Evidence: crates/openhuman\-core/src/user\_agents/tools\.rs — Drive sandbox refusal through an end-to-end test
  • Evidence: crates/openhuman\-core/src/tools/impl/system/shell\.rs — Add an end-to-end test for canonicalized workspace_root
  • Evidence: crates/openhuman\-core/src/user\_agents/tools\.rs — Exercise canonicalized workspace roots end to end
  • Evidence: crates/openhuman\-core/Cargo\.toml — Update the lockfile with the new dependency edges

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: Canonicalizing both the action directory and the agents root before comparing against the expected sandbox path closes a symlink-based escape from the read-write mount.
  • Positive: Host-network matching now mirrors Docker's case-insensitive behavior, closing a bypass where "HOST" would have been accepted by the old exact comparison.
  • Lane summary: Reviewed 7 files; 4 findings. _Code retrieval was unavailable (model: ladder embeddings returned 400 Bad Request: {"error":{"message":"unknown ladder vectors; known ladders are flash (also chat-v1, flash-v1), instant (also no-think, instant-v1), reasoning (also deepseek), max-reasoning (also max-reasoning-v1), deepseek-flash (also reasoning-v1, agentic-v1), deep (also luna), scribe, uncensored, vectors-oai3 (also embeddings-oai3-v1), vision (also vision-v1, multimodal-v1), image (also images-v1, image-v1), vi), so this review saw the diff alone._ _Memory was unavailable (model: cortex: v1/recall: error sending request for url (http://cortexdb:3141/v1/recall\)\), so this review ran without it._
  • Evidence: crates/openhuman\-core/src/user\_agents/tools\_tests\.rs — Drive the sandbox refusal through shell execution
  • Evidence: crates/openhuman\-core/src/user\_agents/tools\_tests\.rs — Exercise the canonicalized workspace root end to end
  • Evidence: crates/openhuman\-core/src/core/runtime/boot\_guard\_tests\.rs — Drive the sandbox-refused shell result end to end
  • Evidence: crates/openhuman\-core/src/core/runtime/boot\_guard\_tests\.rs — Exercise canonicalized workspace_root end to end

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The new tests build real on-disk directory structures (including a tempfile-based fixture with symlink scenarios), which makes the canonicalization checks meaningfully exercised rather than checked against fake paths.
  • Positive: Each new behavior (uppercase host network, infinite CPU limit, symlinked sandbox, missing sandbox) has a dedicated test that would fail on regression.
  • Lane summary: The change pins the sandbox-policy checks (case-insensitive host network, canonicalized sandbox directory, finite cpu_limit) with real tests that fail on regression, and the symlink and missing-directory refusals are now exercised. The earlier canonicalization finding is resolved; the sandbox-refused shell result in tools/impl/system/shell.rs is still only a flag flip with no test driving the Err path, so that finding remains. (3 earlier finding(s) still open) _Code retrieval was unavailable (model: ladder embeddings returned 400 Bad Request: {"error":{"message":"unknown ladder vectors; known ladders are flash (also chat-v1, flash-v1), instant (also no-think, instant-v1), reasoning (also deepseek), max-reasoning (also max-reasoning-v1), deepseek-flash (also reasoning-v1, agentic-v1), deep (also luna), scribe, uncensored, vectors-oai3 (also embeddings-oai3-v1), vision (also vision-v1, multimodal-v1), image (also images-v1, image-v1), vi), so this review saw the diff alone._ _Memory was unavailable (model: cortex: v1/recall: error sending request for url (http://cortexdb:3141/v1/recall\)\), so this review ran without it._
  • Evidence: crates/openhuman\-core/src/tools/impl/system/shell\.rs — Sandbox-refused shell result is still not driven by any test

commits

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: Nothing sensitive found in what this pull request commits.

description

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: The fix matches its description: symlinks are canonicalized and checked against the expected sandbox path, host-network matching is case/whitespace-insensitive in both the runtime check and the boot guard, infinite CPU limits are refused, and a refused sandbox now yields allowed=false, all with unit tests. One earlier concern remains: the canonicalized workspace_root is still only covered at the unit level, with no end-to-end test driving a container through it. (4 earlier finding(s) still open) _Code retrieval was unavailable (model: ladder embeddings returned 400 Bad Request: {"error":{"message":"unknown ladder vectors; known ladders are flash (also chat-v1, flash-v1), instant (also no-think, instant-v1), reasoning (also deepseek), max-reasoning (also max-reasoning-v1), deepseek-flash (also reasoning-v1, agentic-v1), deep (also luna), scribe, uncensored, vectors-oai3 (also embeddings-oai3-v1), vision (also vision-v1, multimodal-v1), image (also images-v1, image-v1), vi), so this review saw the diff alone._ _Memory was unavailable (model: cortex: v1/recall: error sending request for url (http://cortexdb:3141/v1/recall\)\), so this review ran without it._
  • Evidence: \(pull request description\) — Drive the canonicalized workspace_root through an end-to-end test

e2e

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: The change adds case-insensitive host-network refusal, symlink canonicalization of the sandbox mount, a finite-CPU-limit boot check, and reclassifies a refused sandbox as not-allowed. Unit tests cover all of it, but neither behavioural surface this review has flagged in earlier cycles is driven end to end yet, so both stand. Waiting on end-to-end jobs: `Rust E2E (mock backend)`, `Build Playwright E2E Artifact`, `E2E (Playwright / web lane)`, `Desktop E2E (full suite, 3 OS)`. (2 already reported on an earlier push) (2 earlier finding(s) still open)
  • Unresolved questions/checks: Rust E2E (mock backend), Build Playwright E2E Artifact, E2E (Playwright / web lane), Desktop E2E (full suite, 3 OS)
Evidence and run details
  • Models: gpt-5.6-luna, glm-5.3-flash
  • Spend: $0.018936
  • Tokens: 259138 input · 25313 output · 28884 cached · 0 embedding
Head State Pass summary
3287489c1cbb pending 1 active finding(s), 0 resolved finding(s) (at 1791553164)
31c49c9da783 pending 2 active finding(s), 3 resolved finding(s) (at 1791554988)
31c49c9da783 pending 11 active finding(s), 9 resolved finding(s) (at 1791555236)

tinysweeper 0.1.0

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3287489c1c

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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

             $0.0096 · 139,330 in / 9,286 out · 17,668 cached (13%) · gpt-5.6-luna, glm-5.3-flash
critique:    $0.0056 · 68,340 in  / 3,826 out · 10,480 cached (15%) · gpt-5.6-luna
security:    $0.0036 · 45,040 in  / 1,936 out · 5,524 cached (12%)  · gpt-5.6-luna
tests:       $0.0001 · 6,501 in   / 554 out   · 1,536 cached (24%)  · glm-5.3-flash
description: $0.0001 · 6,469 in   / 194 out   · 64 cached (1%)      · glm-5.3-flash
e2e:         $0.0001 · 7,980 in   / 768 out   · 64 cached (1%)      · glm-5.3-flash

@tinysweeper tinysweeper Bot added the priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later. label Oct 9, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Oct 9, 2026
senamakel and others added 2 commits October 9, 2026 17:03
The gateway signature now uses hmac 0.13 with sha2 0.11, the versions
tinychannels-bus already pulls in, so the dependency graph gains no new
packages. The import switches to the aliased sha2_011 crate and adds
KeyInit, which hmac 0.13 requires for key construction.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
@senamakel

Copy link
Copy Markdown
Member Author

Merging with --admin. The only failing lanes are kernel-floor and dep-sim-calibration (flows profile 338 packages / 316 names against a limit of 337/315), and that comes from main: this PR changes no manifest or lockfile, and current main measures the same 338/316. The flows limit was last set by #7197; something merged after it added one crate, and it should be found and justified, or the limit raised, separately.

@senamakel
senamakel merged commit a992704 into tinyhumansai:main Oct 9, 2026
8 of 15 checks passed

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

             $0.0012 · 92,953 in / 8,386 out · 9,164 cached (10%) · gpt-5.6-luna, glm-5.3-flash
critique:    $0.0004 · 30,819 in / 2,905 out · 4,058 cached (13%) · gpt-5.6-luna
security:    $0.0004 · 33,216 in / 2,105 out · 3,570 cached (11%) · gpt-5.6-luna
tests:       $0.0001 · 7,197 in  / 537 out   · 1,536 cached (21%) · glm-5.3-flash
description: $0.0001 · 7,142 in  / 284 out   · 0 cached (0%)      · glm-5.3-flash
e2e:         $0.0001 · 8,799 in  / 888 out   · 0 cached (0%)      · glm-5.3-flash

}
Err(why) => {
tracing::warn!(reason = %why, "[shell] SaaS sandbox refused the command");
// Nothing ran: report it as not allowed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium tests confident

Sandbox-refused shell result is still not driven end to end

The refused-SaaS-sandbox branch changed from returning true to false (false, ToolResult::error(...)), yet no test in this pull request exercises that error path. The comment asserts "Nothing ran: report it as not allowed", but the only thing pinning that claim would be a test, and no diff here contains one — a revert of the false back to true would pass the suite. A test that forces the sandbox construction/invocation to fail and asserts the shell tool reports allowed == false (and an error result) would earn its keep. Severity stays at medium as previously given; the code did not change the substance of the gap, only the flag value.

[RULE] untested-error-path ·

if config.network.trim() == "host" {
// Resolve symlinks: the directory mounted read-write must really be this
// agent's sandbox, not a link from it to somewhere else on the host.
let resolved = action_dir

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium e2e uncertain

Canonicalized workspace_root never exercised by an end-to-end test

The policy now mounts workspace_root: resolved — the canonicalized path — into the container, and refuses sandboxes whose path resolves outside the agent directory. The unit tests cover policy construction, but no end-to-end test launches the sandbox and exercises a command against the mounted workspace, which is what would catch a regression here (e.g. canonicalization diverging from what Docker actually mounts, or the expected-path comparison failing on a legitimate layout). A test would have to start the system with a real agent sandbox directory, run a shell command through the sandboxed tool, and assert the command sees and can write the agent's sandbox.

[RULE] e2e-uncovered ·

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

             $0.0189 · 259,138 in / 25,313 out · 28,884 cached (11%) · gpt-5.6-luna, glm-5.3-flash
critique:    $0.0098 · 111,379 in / 11,262 out · 12,366 cached (11%) · gpt-5.6-luna, glm-5.3-flash
security:    $0.0088 · 109,215 in / 8,961 out  · 9,094 cached (8%)   · gpt-5.6-luna
tests:       $0.0002 · 16,426 in  / 2,119 out  · 4,160 cached (25%)  · glm-5.3-flash
description: $0.0001 · 7,163 in   / 662 out    · 1,408 cached (20%)  · glm-5.3-flash
e2e:         $0.0001 · 8,820 in   / 667 out    · 1,728 cached (20%)  · glm-5.3-flash

}

#[test]
fn an_uppercase_host_network_or_an_infinite_cpu_limit_is_refused() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium critique confident

Drive the sandbox refusal through the shell path

This test calls the private boot-guard checker directly and only proves that two Violation::Sandbox values are produced. It does not exercise the shell/tool execution path that consumes the boot result, so a wiring error could still allow the refused shell operation while this test passes. Add an end-to-end test that configures host_shell, triggers the refusal, and asserts the resulting shell operation is blocked.

[RULE] missing-end-to-end-test ·

));
}
if is_host_network(&config.network) {
return Err("the sandbox cannot use the host network".to_string());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium critique confident

Drive sandbox refusal through an end-to-end test

The refusal is still covered only by the direct sandbox_policy_with unit test. That does not verify that the shell execution path propagates this error to its caller and prevents execution, which was the behavior this guard was added to enforce. Add an end-to-end test that submits a sandbox command with a refused network and asserts the command result is refused rather than executed.

[RULE] missing-end-to-end-coverage ·

// Nothing ran: report it as not allowed.
(
true,
false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium critique confident

Add an end-to-end test for canonicalized workspace_root

The canonicalized workspace_root execution path is still not exercised by an end-to-end test, so regressions in the directory normalization and sandbox-policy handoff can go unnoticed. Add coverage that supplies a path requiring canonicalization and verifies the command is executed or rejected using the canonicalized workspace root. This concern predates the one-line result flag change and is retained from the earlier review.

[RULE] missing-end-to-end-test ·

Ok(SandboxPolicy {
backend: SandboxBackendKind::Docker,
workspace_root: action_dir.to_path_buf(),
workspace_root: resolved,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium critique confident

Exercise canonicalized workspace roots end to end

The new canonicalization is asserted only by the policy unit test. No end-to-end test demonstrates that the resulting workspace_root is the path actually mounted or used by a command, so a downstream consumer that still uses the original path could silently bypass this protection. Add an end-to-end test with a canonicalized sandbox path and verify the command operates in the resolved sandbox.

[RULE] missing-end-to-end-coverage ·

hmac = "0.12"
# The SaaS gateway signature. 0.13 on sha2 0.11, the versions tinychannels-bus
# already brings, so it adds no package to the graph.
hmac = "0.13"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium critique likely

Update the lockfile with the new dependency edges

This manifest changes the direct HMAC version and adds a new direct sha2 alias, but the pull request contains no corresponding Cargo.lock change. Even though the lockfile already contains hmac 0.13.0 and sha2 0.11.0 for other packages, Cargo still needs to record these dependencies for openhuman-core; otherwise commands run with --locked can fail with a lockfile-needs-update error. Regenerate the workspace lockfile and commit the resulting package dependency updates.

[RULE] lockfile-sync ·

network: network.into(),
..SaasSandboxConfig::default()
};
assert!(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium security confident

Drive the sandbox refusal through shell execution

This only proves that sandbox_policy_with returns an error; it does not verify that the user-facing shell path propagates the refusal instead of ignoring, translating, or bypassing it. Add an end-to-end test that invokes the shell operation with a refused sandbox configuration and asserts the returned result is refused.

[RULE] missing-end-to-end-test ·

sandbox_policy_with(&root, &SaasSandboxConfig::default(), &action, &state).unwrap();
assert_eq!(policy.backend, SandboxBackendKind::Docker);
assert_eq!(policy.workspace_root, action);
assert_eq!(policy.workspace_root, action.canonicalize().unwrap());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium security confident

Exercise the canonicalized workspace root end to end

The test checks the value returned by the policy helper, but no end-to-end execution verifies that the canonicalized path is the path actually handed to the container runner and mounted as the workspace. Add coverage through the shell/container execution path that asserts the resolved workspace root is used.

[RULE] missing-end-to-end-test ·

f.config.sandbox.cpu_limit = f64::INFINITY;
let mut i = inputs(&f, &[]);
i.sandbox_available = true;
let found = violations(&i);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium security confident

Drive the sandbox-refused shell result end to end

This only exercises boot_guard::check through a unit-test helper and never runs the shell operation that consumes the sandbox refusal. The refusal result could therefore be lost or bypassed in the real shell path. Add an end-to-end test that invokes the shell flow and asserts the refused result reaches the caller.

[RULE] missing-end-to-end-test ·

f.config.sandbox.cpu_limit = f64::INFINITY;
let mut i = inputs(&f, &[]);
i.sandbox_available = true;
let found = violations(&i);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium security confident

Exercise canonicalized workspace_root end to end

The added test checks only the number of boot violations and does not exercise the runtime path using the canonicalized workspace_root. The canonicalization behavior therefore remains unverified at the integration boundary where an incorrect root could affect sandbox placement or authorization. Add an end-to-end test that supplies the relevant root and verifies the runtime uses the canonicalized path.

[RULE] missing-end-to-end-test ·

@@ -376,8 +376,9 @@ impl ShellTool {
}
Err(why) => {
tracing::warn!(reason = %why, "[shell] SaaS sandbox refused the command");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium tests likely

Sandbox-refused shell result is still not driven by any test

The Err arm now flips the boolean from true to false, a behavioural change that decides how callers classify the outcome, and no test in this pull request exercises it: no shell-level test drives the SaaS-sandbox error path, and no end-to-end test asserts a refused sandbox surfaces as not-allowed. Nothing in the diff would fail if this flag were flipped back. Add a test that forces the sandbox call to fail and asserts the resulting ToolResult/status.

[RULE] untested-behaviour ·

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

Labels

priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant