diff --git a/AUDIT.md b/AUDIT.md index 6a13fb0..8c64764 100644 --- a/AUDIT.md +++ b/AUDIT.md @@ -11,19 +11,20 @@ Status key: - ๐Ÿšซ not verified yet (needs hardware or CI, not a known code gap); - โ€” planned, not started. -*Last updated: Milestone 41, site isolation (2026-10-22).* +*Last updated: first CI runs and the libFuzzer fixes they found (2026-10-23).* ## Headline numbers | Check | Value | How to reproduce | |---|---|---| -| Tests | **1,209 passed, 0 failed** (1,195 before M41; the gap pass's tests had not been counted here) | `cargo test --workspace --release` | -| Fuzz suite | 25 targets; M41 added six `ipc_decode` seeds (`21`โ€“`26`: storage requests and answers served by the browser, an unknown operation, a value claiming 2 GiB); since M40b `parse_document` also moves the tree into a `Document` and checks every interned name reads back (seed `14_invented_names.html`); the newest target is M40's `webidl_parse` (the binding declarations, malformed and deeply nested input), and `js_script/07_generated_bindings` calls prototype members on the wrong `this`, tears accessors off and forges node ids; earlier seeds cover the M38b renderer protocol (`ipc_decode`) and the M39 tree mutation API (`js_script/06_tree_mutations`); `layout_document` also checks M39b's pruned hit testing against visiting every box. Replay, structural stress and deterministic mutation all clean. | `cargo test --manifest-path fuzz/Cargo.toml --no-default-features` | +| Tests | **1,213 passed, 0 failed** (1,209 at M41; four regression tests for the libFuzzer findings since) | `cargo test --workspace --release` | +| Fuzz suite | 25 targets; M41 added six `ipc_decode` seeds (`21`โ€“`26`: storage requests and answers served by the browser, an unknown operation, a value claiming 2 GiB); since M40b `parse_document` also moves the tree into a `Document` and checks every interned name reads back (seed `14_invented_names.html`); the newest target is M40's `webidl_parse` (the binding declarations, malformed and deeply nested input), and `js_script/07_generated_bindings` calls prototype members on the wrong `this`, tears accessors off and forges node ids; earlier seeds cover the M38b renderer protocol (`ipc_decode`) and the M39 tree mutation API (`js_script/06_tree_mutations`); `layout_document` also checks M39b's pruned hit testing against visiting every box. Replay, structural stress and deterministic mutation all clean. The first libFuzzer runs (CI `fuzz-smoke`, 2026-10-22) found five bugs, all fixed with regression tests and seeds: a PNG header declaring billions of pixels allocated its ~51 GB output buffer (`png`'s limits don't cover it; the shared pixel cap now applies); a `ttcf` header claiming 2^32โˆ’1 fonts tripped a `ttf-parser` debug-assert (refused before parsing); CSS nesting under an unclosed `(` multiplied selector text (5 KB โ†’ ~800 MB; selectors with unclosed brackets are now invalid, only valid ones are nested further, and nested selector text is capped at 64 KiB); a `zune-jpeg` 0.4 debug-assert on a truncated bitstream (upgraded to 0.5); and `js_script` reported Boa's lazy GC as a leak (the target now collects after each input, as a page thread does). | `cargo test --manifest-path fuzz/Cargo.toml --no-default-features` | | AddressSanitizer | 0 reports: `js`, `engine` and display-list suites, 3 runs (the CI `sanitizer` job's command); M34 re-ran them with `gpu_backend` (the GPU tests on hardware included), 2 runs; M35 with `text` too; M36 with `browser_core`, `capability` and `storage`, 1 run; M37 with `network`, `filter` and `privacy`, 1 run; M38a re-ran the CI command on Boa 0.22, 3 runs; M38b, 3 runs; M39 with `dom` added, 3 runs; M39b with `layout`, `rendering`, `text`, `style` and `dom`, 3 runs; M39c the same, 3 runs; M40 with `script` (now in the CI job) and `dom`, 4 runs; M40b with `names`, `dom`, `html_parser`, `css_parser` and `style`, 3 runs; M41 with `storage` and `capability` (site isolation, browser-served storage, a fake compromised renderer), 3 runs. | `.github/workflows/ci.yml`, job `sanitizer` (nightly) | | Clippy | 28 warnings (a ratchet; it may not grow โ€” 43 until M40 removed the hand-written bindings; M40b and M41 added none) | `scripts/clippy-ratchet.sh` | | `unsafe` | None. `forbid(unsafe_code)` in every crate, and the exceptions list is empty. | `scripts/check-forbid-unsafe.sh` | | Guardrails | All pass | `check-dependency-direction.sh`, `check-host-boundary.sh`, `check-panic-strategy.sh`, `check-gc-handles.sh`, `check-core-capabilities.sh`, `check-capabilities.sh` (M36), `check-dom-encapsulation.sh` (M39), `check-advisory-exceptions.sh`, `check-psl-freshness.sh` | -| `cargo audit` | 2 documented exceptions, both unmaintained-crate notices: `paste` (build-time, via wgpu's `metal`) and `ttf-parser` (the font parser, ADR 009). The two Boa 0.17 advisories are gone with Boa 0.22 (M38a). | `deny.toml`, `scripts/check-advisory-exceptions.sh` | +| `cargo audit` | 3 documented exceptions, all unmaintained-crate notices: `paste` (build-time, via wgpu's `metal`), `ttf-parser` (the font parser, ADR 009) and `rustybuzz` (the shaper, which sits on `ttf-parser`; RUSTSEC-2026-0206, accepted 2026-10-22 on the same grounds). The two Boa 0.17 advisories are gone with Boa 0.22 (M38a); the rustls/webpki advisories of October 2026 are patched (rustls 0.23.46, rustls-webpki 0.103.16). | `deny.toml`, `scripts/check-advisory-exceptions.sh` | +| CI | Every job of `ci.yml` passes on PR #1 (merged 2026-10-23 as `a7983ad`), `fuzz-smoke` included โ€” reported from the GitHub Actions UI; not checked from this machine (no `gh`). The nightly `fuzz-nightly.yml` has not passed yet: its only run so far (03:17 UTC, 2026-10-23) was before the merge, so it ran the initial commit's `fuzz-smoke.sh`, which lacked the host-triple `--target` pin, and every target failed to build for static musl under ASan. | `.github/workflows/` | | Renderer sandbox | Active on this machine (Linux 7.2): Landlock closes the filesystem; seccomp refuses sockets, exec and ptrace. Seen in `/proc` of a running renderer: seccomp filter, `NoNewPrivs`, no sockets, empty environment. | `browser-renderer --sandbox-self-test`; `crates/renderer/tests/real_processes.rs` | | Real-site corpus | Last replayed at M40b (its recordings are local-only and were not on the machine M41 was built on; it replays in thread mode, which M41 did not change): 56 of 56 pages render, with 0 crashes and 0 timeouts. All are deterministic and match their goldens (re-blessed in M34 after a per-pixel comparison: 43 identical, 13 differing in 1โ€“595 pixels, each explained). | `cargo run -p conformance --release -- run` (the corpus is local-only, not in the repository) | @@ -39,7 +40,7 @@ each violation's detail and the work item that fixes it. | GPU-first | ๐ŸŸข | Everything on screen is drawn by the GPU, through `wgpu` and a display list with clip, transform and group markers (ADR 017); the CPU rasterizer is only for `conformance` PNGs and tests. Clips and transforms per instance on the GPU, opacity groups as offscreen layers, a kept back buffer redrawn by damage, image atlas pages with LRU eviction, batching across non-overlapping items. Transform animations and container scrolls are compositor updates; after an incremental layout the page's display list is updated in part (M39b), and patched in place when the changed element keeps its size (M39c, ADR 022). The GPU path is checked against the CPU rasterizer on hardware (`gpu_matches_raster.rs`). | | Thread-safe | ๐ŸŸข | One OS thread per page (in its own process by default, ADR 016) owns its non-thread-safe state (`Rc`/`RefCell` only in `engine` and `js`). There's a GPU thread and a thread-safe mailbox, checked at compile time. Global state is limited to atomics, read-only tables built once, and the bounded fetch pool's queue (one lock). `rayon` runs style (per-worker result shards), intrinsic sizing (sharded) and image decoding, including a page's network images โ€” which decode off the page thread (M34). | | Capability-based | ๐ŸŸข | Script reaches the network and storage only through capability handles the profile grants each page (ADR 010, M36): no grant, no `fetch`/XHR/`localStorage` โ€” a capability error, checked in thread and process mode. Grants are scoped (storage to the document's own storage key) and revocable (`PageCommand::Revoke`, in-flight requests included). APIs not built have capability types that cannot be constructed. Minting only in Browser Core and the renderer, and no global mutable state in the bindings (`js` and, since M40, `script`) โ€” `check-capabilities.sh`. Only `network` opens connections, in the browser process; file access (`FileAccess`, denied to pages), cookie and profile files, and host-granted fonts are the filesystem's only users; no core crate reads the environment or starts processes (`check-core-capabilities.sh`). Since M41 the browser process enforces the grants too (ADR 025): without the network grant it makes only the document loader's requests, whatever the renderer labels them, and of a cross-origin response gives a renderer only what the request's stated purpose can use; `localStorage` is served by it, through the page's own grant. | -| Zero-trust | ๐ŸŸก | Every page runs in its own sandboxed renderer process (ADR 016), which holds documents of one site: a navigation that lands on another site moves the page to another renderer (ADR 025, M41); the browser decodes what it sends with a bounded, fuzzed codec and makes its network requests and storage operations only for origins of that site it actually loaded. Parsers are bounded and fuzzed, page commands are validated, and redirects to non-web schemes are refused. GPU resources are bounded: a capped glyph atlas that compacts, every texture checked against device limits, an image-texture budget kept by LRU eviction (M34), and a GPU error handler instead of a panic (P1โ€“P3, fixed and verified on hardware; `milestones/principles-audit-fixes.md`). **Gap:** no CI or libFuzzer run has been observed (a GitHub remote exists; its Actions history was not checked, and there is no `g++` locally). | +| Zero-trust | ๐ŸŸก | Every page runs in its own sandboxed renderer process (ADR 016), which holds documents of one site: a navigation that lands on another site moves the page to another renderer (ADR 025, M41); the browser decodes what it sends with a bounded, fuzzed codec and makes its network requests and storage operations only for origins of that site it actually loaded. Parsers are bounded and fuzzed, page commands are validated, and redirects to non-web schemes are refused. GPU resources are bounded: a capped glyph atlas that compacts, every texture checked against device limits, an image-texture budget kept by LRU eviction (M34), and a GPU error handler instead of a panic (P1โ€“P3, fixed and verified on hardware; `milestones/principles-audit-fixes.md`). CI's libFuzzer smoke run passes (60 s per target, 2026-10-22, after fixing the five bugs it found). **Gap:** no long (nightly) libFuzzer run has passed yet. | | Privacy-first | ๐ŸŸข | Every row of the ยง6 boundary has a named policy and a test (table in `milestones/milestone-37-privacy-layer.md`): routes that fail closed with a fixed precedence (ADR 011); DNS over HTTPS with no default vendor (ADR 012); third-party cookies blocked and storage keyed by (top-level site, origin) (ADR 019); a filter engine on every hop in the browser process; a fingerprint policy for `Date`, rAF and fonts, through Boa's host hooks (P5); frozen headers; no `navigator`, `screen` or `performance`; no phone-home. | | Vendor-independent | ๐ŸŸข | No Blink, Gecko or WebKit; `cargo deny` bans encode it, and no service is hard-coded: no default DoH resolver, no bundled third-party filter list. TLS trusts the bundled Mozilla roots plus any CA the user adds (P8, ADR 018). | | Secondary: power budget, minimal-dependency | ๐ŸŸก | A runaway page in a renderer process is ended within 10 s (ADR 013, M38a); pages on threads are bounded only by Boa's loop and recursion limits: the runtime interface has an interrupt (M40, ADR 023 ยง6), but Boa 0.22 offers none. Pages are woken by finished work, never polled; hidden pages do no animation work (a hidden renderer measured at 0 CPU ticks in 2 s); an animating page that can't keep up lowers its frame rate and input is not delayed (ADR 020, M38b). Off-screen `background-color` transitions are skipped (gap pass); other off-screen animations are not; a covered or minimised window hides its active tab (gap pass). `engine`'s exact `rayon` pin is relaxed (P11), but `style` and `images` still pin `rayon = "=1.10.0"`. | @@ -73,7 +74,7 @@ each violation's detail and the work item that fixes it. | 23 | Rich input & editing | ๐ŸŸก | Selection, drag-and-drop, context menus and the file picker are in the backlog. | | 24 | Accessibility tree | ๐ŸŸก | The OS screen-reader bridge is in the backlog. | | โ€” | Hardening pass | ๐ŸŸข | Items not fixed there were taken up by M34โ€“M38 (as 38a and 38b). | -| 25 | Guardrails & CI | ๐ŸŸก | CI workflows and libFuzzer jobs are written; no run observed: the repository has a GitHub remote (`origin`), but its Actions history was not checked ๐Ÿšซ. | +| 25 | Guardrails & CI | ๐ŸŸก | Every PR job passes on GitHub Actions (PR #1, 2026-10-23), `fuzz-smoke` included; its first runs found five bugs, all fixed. **Gap:** the nightly fuzz job has not passed yet โ€” its one run predated the `--target` fix ๐Ÿšซ. | | 26 | Shared network context | ๐ŸŸข | | | 27 | Observability | ๐ŸŸข | Live-window `--trace` and the `gpu_render` span exercised on hardware in M34 (per-frame spans, including `compositor_update`). | | 28 | Origin & site model | ๐ŸŸข | | @@ -115,7 +116,7 @@ each violation's detail and the work item that fixes it. | Video (`