Main @ 958e1ec6110e80e0c22104a7b18ed0fb4c60b723 (merge of #457 ). Prior audit @ 00aa515 (#437 ). Run via /general-audit.
Method
Per lessons #426 + #438 (orchestrator-direct default for small diffs, narrow agents only when justified, pre-fetch existing-issue lists, backfill timed-out concerns):
0 agents launched. 52-file diff right at threshold; orchestrator-direct sweep covered surface w/o context blow-up.
Pre-fetched existing-issue lists for dedup: /tmp/audit-issues.txt (19 entries), /tmp/techdebt-issues.txt (21), /tmp/security-issues.txt (30) → 69 unique titles.
Orchestrator ran cargo-audit directly (lesson Add deep-review skill for iterative code review #5 ). Clean vs CI ignore list (8 RUSTSEC IDs already tracked [DEP-01] rustls-webpki 0.103.10 has 3 open RUSTSEC advisories (name-constraint bypass + CRL panic) #223 [DEP-02] Three concurrent rand major versions + RUSTSEC-2026-0097 across all of them #246 [DEP-03] Workspace uses unmaintained bincode 1.3 for on-wire + on-disk serialization #247 [DEP-08] Unmaintained paste 1.0.15 (RUSTSEC-2024-0436) — not in CI ignore list #316 [DEP-09] Unmaintained proc-macro-error 0.4.12 (RUSTSEC-2024-0370) — not in CI ignore list #317 [DEP-10] Unmaintained atomic-polyfill 1.0.3 (RUSTSEC-2023-0089) — not in CI ignore list #318 ).
Direct main-context sweeps for: unsafe, dbg!/eprintln!/todo!/unimplemented!, Arc<Mutex>/Arc<RwLock> w/o lock-ok, panic!/unwrap in lib prod, js_sys::eval/innerHTML, TODO/FIXME/HACK, anyhow:: in pure libs, vector caps in wire types, let _ = h.<method>().await swallow, swallowed errors in new code, docker pin/USER hardening.
Diff @ 00aa515 ..958e1ec : 37 commits / ~20 PRs / 52 files / +3525 −319. Touches: state caps (SEC-V-07 RotateChannelKey + Event.deps + per-author pending), relay TopicAnnounce caps, agent ReadOnly join-links gate (AUD-2 from general-audit: main @ 00aa515 (2026-04-27) #437 — fixed by fix(agent): readonly scope must deny join_links resource #450 ), state_bridge SAFETY comment (AUD-1 from general-audit: main @ 00aa515 (2026-04-27) #437 — fixed by docs(web): SAFETY comment for DerivedStateActor #445 ), service_worker_bridge extracted from main.rs w/ kind discriminator + module-local stash (issue [SEC-W-06] Service-worker postMessage payload accepted without kind / origin check #244 ), invite topic-format validate, web error toast plumbing in handlers.rs ([GEN-07] All UI action handlers swallow errors with no toast / log #350 ), platform-aware key paths (chore(workers): platform-aware default paths #439 ), docker pin-by-digest + USER drop (DEP-02), workers untouched at root (chore(docker): drop root, run containers as willow user #434 ), in-browser test additions (+457 LOC), static_assets test (+164 LOC).
Concerns audited
Concern
Method
Result
security: input-validation/DoS
direct
strong (caps verified)
security: auth/permissions
direct
strong (AUD-2 closed)
security: web/WASM
direct
strong; F2 found
security: deps/supply-chain
orchestrator (cargo audit)
clean vs ignore; F1 found
tech debt
direct
stable; existing trackers cover (#332 #320 –#330 )
architecture (spec drift)
direct
lock-ok comments still compliant w/ docs/specs/2026-04-26-state-management-model-design.md
general
direct
F2
test coverage
direct sample
new tests landed alongside new code; existing tracker #418 #340 #341 covers gaps
Strong areas (re-verified, no findings)
state crate purity: zero tokio / iroh / std::fs / SystemTime (rg "tokio::|std::fs|SystemTime|iroh::" crates/state/src still clean)
wire deserialize defense-in-depth: MAX_DESER_SIZE = 256 KB + per-variant WireMessage::max_size
event-DAG insert: signature + content-hash + new caps (MAX_EVENT_DEPS = 64, MAX_ENCRYPTED_KEY_BYTES = 128)
pending-buffer cap: 10k total + per-author sub-cap (max_entries / N — landed in 5b78e61)
relay caps: MAX_TOPICS = 10_000, MAX_TOPIC_LEN = 256, MAX_TOPICS_PER_ANNOUNCE = 64, MAX_TOPICS_PER_SIGNER = 100, MAX_CONCURRENT_BOOTSTRAP_CONNECTIONS = 1024, BOOTSTRAP_IO_TIMEOUT = 5s, BOOTSTRAP_DRAIN_BUFFER_CAP = 8 KiB
agent scope gate: TokenScope::ReadOnly | Messaging deny willow://server/join-links (AUD-2 closed via fix(agent): readonly scope must deny join_links resource #450 ; new e2e test asserts filter pipeline matches gate)
state_bridge SAFETY comment: documents Send invariant for DerivedStateActor (AUD-1 closed via docs(web): SAFETY comment for DerivedStateActor #445 )
service worker bridge: kind discriminator + module-local RefCell stash (no global window.__willowLastPush — closes [SEC-W-06] Service-worker postMessage payload accepted without kind / origin check #244 )
invite topic validation: topic_matches_server enforces {server_id}/ prefix + MAX_TOPIC_LEN = 256 + MAX_INVITE_CHANNELS = 1000 (closes earlier topic-confusion concern)
platform-aware default key paths in replay/storage main.rs (dirs::config_dir() w/ /etc/willow fallback)
docker images: digest-pinned rust:1.95-slim-bookworm + nginxinc/nginx-unprivileged:1.27-alpine, all containers USER willow/USER nginx (closes DEP-02 + chore(docker): drop root, run containers as willow user #434 )
lock-ok comments present on every legitimate lib-crate lock; new client/src/{lib.rs,listeners.rs,mutations.rs} sites annotated
no unimplemented!() / todo!() / dbg!() / eprintln!() / FIXME / HACK in lib code
web js_sys::eval sites: 1 user-input-interpolated ([WS-1] Web: js_sys::eval(format!()) for pinned-message scroll uses band-aid sanitization #425 [WS-1] tracked), rest static-string (window.__WILLOW_RELAY_URL read, setTimeout focus shim) — no new XSS surface
Findings (2 child issues)
#
Sev
Title
F1
low
Docker web.Dockerfile installs trunk unpinned — sibling of closed #319 (different scope: docker pipeline, not deploy.yml)
F2
medium
11 web component handlers swallow client mutation errors w/ let _ = h.<method>().await — sibling of closed #350 (different scope: components/, not handlers.rs)
Existing-issue overlap (not re-filed)
Dropped findings (verified false during 2nd-pass grep)
None. Both surviving findings re-grep-verified before file:
F1: grep -n 'cargo install trunk' docker/web.Dockerfile → 1 hit, .github/workflows/deploy.yml already uses pinned taiki-e/install-action@cf525cb...trunk@0.21.14
F2: rg -n "let _ = h\.[a-z_]+\(.*\)\.await" crates/web/src/components/ → 11 hits across roles.rs (4), channel_sidebar.rs (4), settings.rs (2), sync_queue_view.rs (1)
Method change vs #437
Lessons follow-up issue describes outcome.
Main @
958e1ec6110e80e0c22104a7b18ed0fb4c60b723(merge of #457). Prior audit @00aa515(#437). Run via/general-audit.Method
Per lessons #426 + #438 (orchestrator-direct default for small diffs, narrow agents only when justified, pre-fetch existing-issue lists, backfill timed-out concerns):
/tmp/audit-issues.txt(19 entries),/tmp/techdebt-issues.txt(21),/tmp/security-issues.txt(30) → 69 unique titles.randmajor versions + RUSTSEC-2026-0097 across all of them #246 [DEP-03] Workspace uses unmaintainedbincode 1.3for on-wire + on-disk serialization #247 [DEP-08] Unmaintainedpaste 1.0.15(RUSTSEC-2024-0436) — not in CI ignore list #316 [DEP-09] Unmaintainedproc-macro-error 0.4.12(RUSTSEC-2024-0370) — not in CI ignore list #317 [DEP-10] Unmaintainedatomic-polyfill 1.0.3(RUSTSEC-2023-0089) — not in CI ignore list #318).unsafe,dbg!/eprintln!/todo!/unimplemented!,Arc<Mutex>/Arc<RwLock>w/o lock-ok,panic!/unwrapin lib prod,js_sys::eval/innerHTML,TODO/FIXME/HACK,anyhow::in pure libs, vector caps in wire types,let _ = h.<method>().awaitswallow, swallowed errors in new code, docker pin/USER hardening.postMessagepayload accepted withoutkind/ origin check #244), invite topic-format validate, web error toast plumbing in handlers.rs ([GEN-07] All UI action handlers swallow errors with no toast / log #350), platform-aware key paths (chore(workers): platform-aware default paths #439), docker pin-by-digest + USER drop (DEP-02), workers untouched at root (chore(docker): drop root, run containers as willow user #434), in-browser test additions (+457 LOC), static_assets test (+164 LOC).Concerns audited
docs/specs/2026-04-26-state-management-model-design.mdStrong areas (re-verified, no findings)
tokio/iroh/std::fs/SystemTime(rg "tokio::|std::fs|SystemTime|iroh::" crates/state/srcstill clean)MAX_DESER_SIZE = 256 KB+ per-variantWireMessage::max_sizeMAX_EVENT_DEPS = 64,MAX_ENCRYPTED_KEY_BYTES = 128)max_entries / N— landed in5b78e61)MAX_TOPICS = 10_000,MAX_TOPIC_LEN = 256,MAX_TOPICS_PER_ANNOUNCE = 64,MAX_TOPICS_PER_SIGNER = 100,MAX_CONCURRENT_BOOTSTRAP_CONNECTIONS = 1024,BOOTSTRAP_IO_TIMEOUT = 5s,BOOTSTRAP_DRAIN_BUFFER_CAP = 8 KiBTokenScope::ReadOnly | Messagingdenywillow://server/join-links(AUD-2 closed via fix(agent): readonly scope must deny join_links resource #450; new e2e test asserts filter pipeline matches gate)Sendinvariant forDerivedStateActor(AUD-1 closed via docs(web): SAFETY comment for DerivedStateActor #445)kinddiscriminator + module-localRefCellstash (no globalwindow.__willowLastPush— closes [SEC-W-06] Service-workerpostMessagepayload accepted withoutkind/ origin check #244)topic_matches_serverenforces{server_id}/prefix +MAX_TOPIC_LEN = 256+MAX_INVITE_CHANNELS = 1000(closes earlier topic-confusion concern)replay/storagemain.rs(dirs::config_dir()w//etc/willowfallback)rust:1.95-slim-bookworm+nginxinc/nginx-unprivileged:1.27-alpine, all containersUSER willow/USER nginx(closes DEP-02 + chore(docker): drop root, run containers as willow user #434)client/src/{lib.rs,listeners.rs,mutations.rs}sites annotatedunimplemented!()/todo!()/dbg!()/eprintln!()/FIXME/HACKin lib codejs_sys::evalsites: 1 user-input-interpolated ([WS-1] Web: js_sys::eval(format!()) for pinned-message scroll uses band-aid sanitization #425 [WS-1] tracked), rest static-string (window.__WILLOW_RELAY_URLread,setTimeoutfocus shim) — no new XSS surfaceFindings (2 child issues)
web.Dockerfileinstallstrunkunpinned — sibling of closed #319 (different scope: docker pipeline, not deploy.yml)let _ = h.<method>().await— sibling of closed #350 (different scope: components/, not handlers.rs)Existing-issue overlap (not re-filed)
js_sys::eval(format!())for pinned scroll) → [WS-1] Web: js_sys::eval(format!()) for pinned-message scroll uses band-aid sanitization #425anyhowin lib crates) → [TD-14] anyhow used in 8 library crates contradicts CLAUDE.md convention #332TopicAnnounce.topics: Vec<String>has no element-count cap; enables relay CPU amplification + topic-slot exhaustion #235 (landed via 3ee8a98)RotateChannelKey.encrypted_keys+Event.depsvectors have no element caps #236 (landed via 7ee7e0a)rust:latest/rust:slim/nginx:alpinetags #313 (landed via c479670)randmajor versions + RUSTSEC-2026-0097 across all of them #246 [DEP-03] Workspace uses unmaintainedbincode 1.3for on-wire + on-disk serialization #247 [DEP-08] Unmaintainedpaste 1.0.15(RUSTSEC-2024-0436) — not in CI ignore list #316 [DEP-09] Unmaintainedproc-macro-error 0.4.12(RUSTSEC-2024-0370) — not in CI ignore list #317 [DEP-10] Unmaintainedatomic-polyfill 1.0.3(RUSTSEC-2023-0089) — not in CI ignore list #318Dropped findings (verified false during 2nd-pass grep)
None. Both surviving findings re-grep-verified before file:
grep -n 'cargo install trunk' docker/web.Dockerfile→ 1 hit,.github/workflows/deploy.ymlalready uses pinnedtaiki-e/install-action@cf525cb...trunk@0.21.14rg -n "let _ = h\.[a-z_]+\(.*\)\.await" crates/web/src/components/→ 11 hits acrossroles.rs(4),channel_sidebar.rs(4),settings.rs(2),sync_queue_view.rs(1)Method change vs #437
Lessons follow-up issue describes outcome.