fix(sandbox): deny WASM fs_write to the audit anchor via a capability deny-list#6196
Merged
Conversation
… deny-list
The WASM sandbox gated fs_write solely on glob capability matching, so a skill
granted a broad FileWrite subtree -- or the universal FileWrite("*") -- could
truncate the audit anchor ([audit].anchor_path, default data_dir/audit.anchor)
and silently break the tamper-evident Merkle chain. host_fs_write had no
knowledge of protected paths.
Add a ToolPolicy::protected_write_paths() method (default empty; the kernel
returns the boot-resolved anchor, resolving relative paths against data_dir in
lockstep with boot.rs). host_fs_write now denies any write whose canonical
target matches a protected path ABOVE the capability check, as defense in
depth, so even FileWrite("*") cannot reach the anchor. The deny-list is
scoped strictly to the anchor file, not all of data_dir, to keep the blast
radius small. Protected paths are canonicalized the same way the write target
is resolved, so symlink and macOS /private aliasing match.
Tests: FileWrite("*") cannot truncate the anchor (content unchanged); a
sibling path under the same dir still writes; a kernel-less guest skips the
check. SECURITY.md updated to document the new sandbox-level protection.
Closes #6182. Supersedes the duplicate #6181.
…nvention CLAUDE.md forbids multi-paragraph docstrings and multi-line comment blocks (one short line max) and prohibits referencing the originating issue number inside inline code comments (those belong in PR/CHANGELOG, not source). - Collapse 13-line `protected_write_paths` trait doc to one line - Collapse 9-line `is_protected_write_target` doc to one line - Collapse 8-line kernel impl comment to one line - Replace 7-line `// SECURITY (#6182):` block with single-line note - Drop 2-3 line test-body comments whose content the function name conveys - Remove `protected_paths` test-struct field doc (name is self-explanatory)
houko
force-pushed
the
feat/6182-audit-anchor-write-denylist
branch
from
June 18, 2026 07:37
6b57fcc to
102e01d
Compare
Exact PathBuf comparison in is_protected_write_target left a bypass on case-insensitive filesystems (macOS APFS default, NTFS): a guest could target .../AUDIT.ANCHOR and the OS would still truncate the on-disk .../audit.anchor, slipping past the deny-list. Once the canonical parents match, compare the leaf case-insensitively so case-variants of the protected filename are refused. Adds a regression test exercising the AUDIT.ANCHOR variant.
houko
enabled auto-merge (squash)
June 19, 2026 04:50
Deploying librefang with
|
| Latest commit: |
4f4b0cd
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b9eb9b37.librefang-7oe.pages.dev |
| Branch Preview URL: | https://feat-6182-audit-anchor-write.librefang-7oe.pages.dev |
GQAdonis
pushed a commit
to GQAdonis/librefang
that referenced
this pull request
Jun 19, 2026
Merge upstream/main into the BossFang fork; origin/main was 15 commits behind. Notable upstream changes: - librefang#6225 scope the compaction-summary banner to the compacted session (adds canonical_sessions.compacted_summary_session_id). - librefang#6196 deny WASM fs_write to the audit anchor via a capability deny-list. - librefang#6226 / librefang#6227 add agent label and exit-reason metrics to the agent loop. - librefang#6215 context-window usage indicator + honest quota-error classification. - librefang#6217 per-instance sidecar secrets so each agent owns its own handle. - librefang#6194 global Auto-Dream on/off switch on the Memory tab. - librefang#6211 / librefang#6214 / librefang#6212 token/context cap fixes; librefang#6208 refuse to delete the active prompt version. - librefang#6218 browser_tools.rs ToolError migration; librefang#6203 launchctl let-binding build fix; librefang#6224 docs dep bumps; librefang#6193 drop five orphaned email deps. Conflict resolution: - deny.toml: keep our RUSTSEC-2025-0141 (bincode 2.0.x) ignore entry (take ours). - deny.toml: ignore three pre-existing unmaintained advisories newly tripped by the RustSec DB (all transitive via the UAR provider chain, none introduced by this merge): RUSTSEC-2024-0384 (instant), RUSTSEC-2024-0436 (paste), RUSTSEC-2025-0119 (number_prefix). BossFang preservation (SurrealDB schema parity for upstream SQLite v46 / librefang#6225): - Add crates/librefang-storage/src/migrations/sql/032_canonical_sessions_compacted_summary_session_id.surql declaring the new field on the SCHEMAFULL canonical_sessions table (SCHEMAFULL silently drops undefined fields on write), registered as version 32 in migrations/mod.rs. - migrate/sqlite_to_surreal.rs: copy the new column so a SQLite to SurrealDB migration preserves the owning-session pointer rather than dropping it. - backends/surreal_session.rs: preserve compacted_summary_session_id across canonical appends, since the upsert replaces the whole record. - Cargo.lock: pick up the workspace version bump (beta.19 to beta.20) the merge introduced. Verification: - cargo check --workspace --lib — clean. - cargo check -p librefang-storage -p librefang-memory -p librefang-uar-spec — clean. - cargo test -p librefang-storage migration — ok (migration ordering / SurrealDB-3 flexible-syntax invariants). - cargo test -p librefang-memory --lib session — 50 passed (incl. upstream's store_llm_summary round-trip test). - cargo clippy -p librefang-storage -p librefang-memory — clean. - python3 scripts/enforce-branding.py --check — clean; Tauri desktop audit and URL-drift scan both clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #6182. The WASM skill sandbox gated
fs_writesolely on glob capability matching. A skill manifest granting a broadFileWritesubtree — or the universalFileWrite("*")— passedcheck_capabilityagainst the audit anchor and could truncate it, silently breaking the tamper-evident audit Merkle chain.host_fs_writehad no knowledge of the anchor path (resolved only kernel-side at boot).SECURITY.mddocuments that the anchor's integrity rests on filesystem permissions; this adds a sandbox-level deny-list so a capability-bounded skill cannot reach it regardless of grant.Changes
crates/librefang-kernel-handle/src/tool_policy.rs— newToolPolicy::protected_write_paths()trait method (defaultvec![], so stub kernels are unaffected).crates/librefang-kernel/src/kernel/handles/tool_policy.rs— kernel impl returns the boot-resolved audit anchor ([audit].anchor_path, defaultdata_dir/audit.anchor), resolving relative paths againstdata_dirin lockstep withboot.rs(absolute wins / relative joinsdata_dir/ unset defaults).crates/librefang-runtime/src/host_functions.rs—host_fs_writedenies any write whose canonical target matches a protected path, above the capability check (defense in depth). Newis_protected_write_targethelper canonicalizes protected paths the same way the write target is resolved (safe_resolve_parent), so symlink resolution and macOS/privatealiasing match on both sides. The deny-list is scoped strictly to the anchor file, not all ofdata_dir.SECURITY.md— documents the new sandbox-level protection under the anchor threat model.CHANGELOG.md— entry under[Unreleased] → Added.Verification
New tests in
host_functions.rs:test_fs_write_denied_protected_audit_anchor_even_with_wildcard—FileWrite("*")cannot truncate the anchor; content stays unchanged.test_fs_write_allows_non_protected_sibling_with_wildcard— a sibling path in the same dir still writes (deny-list isn't adata_dir-wide ban).test_fs_write_no_kernel_skips_protected_check— a kernel-less guest skips the check.Duplicate
#6181 has an identical title, author, and proposed fix (its body carries unrelated copy-paste noise). This PR tracks the work under #6182; I recommend the maintainer close #6181 as a duplicate — I have not closed it.