Skip to content

fix(sandbox): stop claiming strict mode is isolation; gate clippy in CI - #13

Merged
ForkedInTime merged 4 commits into
mainfrom
fix/honest-sandbox-and-clippy-gate
Aug 3, 2026
Merged

fix(sandbox): stop claiming strict mode is isolation; gate clippy in CI#13
ForkedInTime merged 4 commits into
mainfrom
fix/honest-sandbox-and-clippy-gate

Conversation

@ForkedInTime

Copy link
Copy Markdown
Owner

Two changes: the P0 honesty fix for the sandbox, and the CI gate that stops this
class of thing regressing.

strict mode overclaimed what it does

best_available_mode() falls back to strict when neither bwrap nor firejail is
present — which is always on macOS and Windows, since both are Linux-only. So on
two of three supported platforms, "sandbox ENABLED" meant a lowercase substring
denylist and nothing else.

The UI said otherwise. /sandbox claimed strict "blocks fork-bombs, disk overwrites,
and other catastrophic patterns"
, and listed the three modes as peers. For a product
sold to enterprise the false claim is the bigger liability: a reviewer who reads the
code after trusting that sentence has found a credibility problem, not just a missing
feature.

What changed:

  • Modes split into isolation (bwrap, firejail — kernel-enforced) and best-effort
    denylist
    (strict — no enforcement), labelled that way wherever shown.
  • /sandbox prints the platform's actual ceiling — on a machine with no backend it
    says so, and notes bubblewrap/firejail are Linux-only.
  • Enabling a non-enforcing mode warns at the moment the user forms a belief about how
    protected they are, rather than burying it in status output.
  • strict_check's doc now lists the forms that walk past it and states plainly that
    denylists cannot be completed — so nobody "fixes" it by adding literals and
    believing the gap is closed.

No change to what is or isn't blocked. This is about not telling users they are
protected when they are not. Real isolation on macOS/Windows (sandbox-exec,
AppContainer) is a separate piece of work worth scoping properly.

CI now gates clippy

Clippy was clean but ungated — nothing stopped it drifting back one PR at a time,
which is exactly the re-surfacing problem. cargo clippy runs the full cargo check
pass plus lints, so this replaces the bare cargo check rather than adding a
step: same build, more coverage, no extra CI time.

cargo test also now runs --all-features, matching what is verified locally.

Verified the gate bites — introducing a len() == 0 comparison turns a warning into
error: length comparison to zero and fails the build (exit 101).

Tests

5 new tests pinning the honesty guarantees, including one asserting the old overclaim
string cannot return, and one pinning the known bypasses so docs and code cannot drift
apart.

One of those tests was initially wrong: X="rm -rf /"; $X is caught, because the
literal appears in the assignment. Indirection only evades when the string is never
spelled out (A=rm; B=-rf; $A $B /). Corrected rather than weakened.

560 tests pass, 0 failures. Clippy clean under -D warnings.

ForkedInTime and others added 4 commits August 2, 2026 16:12
Two things: an honesty fix for the sandbox, and the CI gate that stops any of
this regressing.

## strict mode overclaimed what it does

`best_available_mode()` falls back to `strict` when neither bwrap nor firejail
is present — which is *always* on macOS and Windows, since both are Linux-only.
So on two of three supported platforms, "sandbox ENABLED" meant a lowercase
substring denylist and nothing else.

The UI said otherwise. `/sandbox` claimed strict "blocks fork-bombs, disk
overwrites, and other catastrophic patterns", and listed the three modes as
peers. For a product sold to enterprise the false claim is a bigger liability
than the gap: a reviewer who reads the code after trusting that sentence has
found a credibility problem, not just a missing feature.

Now:
  - Modes are split into *isolation* (bwrap, firejail — kernel-enforced) and
    *best-effort denylist* (strict — no enforcement), and labelled that way
    wherever they are shown.
  - `/sandbox` prints the platform's actual ceiling: on a machine with no
    backend it says so, and says bubblewrap/firejail are Linux-only.
  - Enabling a non-enforcing mode warns at the moment the user forms a belief
    about how protected they are, not buried in status output.
  - `strict_check`'s doc lists the forms that walk past it (flag order, extra
    whitespace, long flags, command substitution, split indirection) and says
    plainly that denylists cannot be completed — so nobody "fixes" it by adding
    literals and believing the gap is closed.

No behaviour change to what is or isn't blocked. This is about not telling
users they are protected when they are not. Real isolation on macOS/Windows
(sandbox-exec, AppContainer) is a separate piece of work.

## CI now gates clippy

Clippy was clean but *ungated* — nothing stopped it drifting back one PR at a
time, which is exactly the "bugs re-surfacing" problem. `cargo clippy` performs
the full `cargo check` pass plus lints, so this replaces the bare `cargo check`
rather than adding a step: same build, more coverage, no extra CI time.

`cargo test` also now runs `--all-features` to match what is verified locally.

Verified the gate bites: introducing a `len() == 0` comparison turns a warning
into `error: length comparison to zero` and fails the build (exit 101).

Adds 5 tests pinning the honesty guarantees — including one asserting the old
overclaim string cannot come back, and one pinning the known bypasses so the
docs and the code cannot drift apart.

One of those tests was initially wrong: `X="rm -rf /"; $X` *is* caught, because
the literal appears in the assignment. Indirection only evades when the string
is never spelled out (`A=rm; B=-rf; $A $B /`). Corrected rather than weakened.

Suite: 560 passed, 0 failed. Clippy clean under -D warnings.

Co-Authored-By: Arch Linux <noreply@archlinux.org>
…e direction

The clippy gate added in the previous commit failed on all three platforms —
working exactly as intended. It surfaced 8 pre-existing lints in code this
branch never touched, none of which my local run caught.

Why local was clean and CI was not: local clippy was 1.94 (2026-03-02) while
CI uses `dtolnay/rust-toolchain@stable`, now 1.97. Five months of new lints.
"Clippy clean locally" was not a meaningful claim; local toolchain updated so
it is.

Lints resolved (all mechanical, no behaviour change):
  - 5x unnecessary_sort_by  → sort_by_key(Reverse(..)) in autocommit, glob,
    tool_search, sdk, session. Same key, same direction, both stable sorts.
  - 2x manual_checked_div   → checked_div().unwrap_or(0) in commands. The
    `if limit > 0` guard was dead (limit is a non-zero literal) but the intent
    is preserved rather than deleted.
  - 1x redundant reference in format! → autofixed in worktree.
  - 1x unused_mut exposed by the sort refactor.

One of those sort sites was in `prune_old_refs`, which decides **which session
snapshots get deleted**. Inverting it would delete the newest sessions instead
of the oldest — silent, unrecoverable loss of exactly the history a user would
reach for with /undo.

The existing integration test could not have caught that. It asserts only
counts (5 deleted, 10 remain), never identity, and it cannot be strengthened
in place: `%(committerdate:unix)` has one-second granularity, so 15 sessions
created in a loop all tie and the surviving set is decided by git's output
order rather than recency.

So the selection is now a separate `select_refs_to_delete`, with unit tests
that pin the direction against synthetic distinct timestamps. Verified:
inverting the sort fails `keeps_the_newest_and_deletes_the_oldest`.

That extraction also documents a real latent bug rather than hiding it — with
tied timestamps, prune keeps refs by alphabetical refname, not recency. A test
pins the current behaviour so it is visible; fixing it needs a tie-breaker on
the ref's own ordering and is filed in the tracker, not smuggled in here.

Suite: 564 passed, 0 failed. Gate passes under the CI toolchain.

Co-Authored-By: Arch Linux <noreply@archlinux.org>
Third round from the new gate. Ubuntu went green last commit; macOS and
Windows each had lints that only exist on their platform — which is precisely
why the gate runs on the full matrix rather than one runner.

  - deeplink.rs (macOS + Windows): `return Ok(())` was the last statement of
    the `#[cfg(not(linux))]` block, so on non-Linux builds it is a needless
    return. Split into two per-platform definitions — the idiomatic form, and
    it removes the lint rather than suppressing it.
  - settings.rs (Windows): `mut parsed` is only mutated in the unix
    ownership/permission branch, so `mut` is unused on Windows.
    `#[cfg_attr(not(unix), allow(unused_mut))]` scopes the allow to the
    platform that needs it instead of blanket-allowing it everywhere.
  - hooks.rs (Windows): `cfg_post` is mine — its only caller is the
    signal-termination test I gated to unix in the previous PR, leaving the
    helper dead on Windows. Gated to match.

Verification note: I could not check the non-Linux paths locally.
`cargo clippy --target x86_64-pc-windows-msvc` fails because the tree-sitter
crates need a C cross-compiler that is not installed. The deeplink stub was
instead verified by temporarily swapping the cfg attributes so it compiles as
the active definition on Linux — clean. The other two are single-line and
platform-obvious. Everything else rests on CI.

Suite: 564 passed, 0 failed. Gate clean on Linux.

Co-Authored-By: Arch Linux <noreply@archlinux.org>
Windows CI failure. `config_dir_honours_the_env_override` and
`no_subprocess_when_no_profile_directory_exists` both called
`std::env::set_var("ANTHROPIC_CONFIG_DIR", ...)`. Env is process-global and
cargo runs tests in parallel threads, so the two raced each other — one
clobbering the other's value mid-assertion. It happened to pass on Linux and
macOS and lost the race on Windows.

This is self-inflicted: `auth.rs` already has an `AuthEnv` injection seam
whose entire purpose is testing resolution without touching process env, and
these two tests bypassed it.

Fixed by making the check pure — `profile_dir_exists_at(Option<&Path>)` takes
the directory instead of reading it — so both tests operate on a tempdir with
no global state. The env-reading wrapper is a one-liner and needs no test.

No production behaviour change.

Suite: 564 passed, 0 failed. Gate clean.

Co-Authored-By: Arch Linux <noreply@archlinux.org>
@ForkedInTime
ForkedInTime merged commit 0f49d90 into main Aug 3, 2026
5 checks passed
@ForkedInTime
ForkedInTime deleted the fix/honest-sandbox-and-clippy-gate branch August 3, 2026 00:05
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.

1 participant