Tags: gominimal/minimal
Tags
docs: update readme banner to current brand card (#1206) * docs: update readme banner to current brand card Marketing asked to retire the "now open source" framing from the README hero, so the banner is now the plain Minimal brand card carrying the "Build software you can trust" tagline. The alt text is updated to match the new artwork. The card is encoded as lossless WebP at the existing path, so every reference to docs/public/readme-banner.webp keeps resolving. It is 1280x640 where the previous artwork was 2560x1280; the 2:1 aspect ratio is unchanged, so the width="880" render is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: export the banner at 2x for hi-dpi crispness The previous banner shipped 2560x1280; the replacement was a 1x 1280x640 export, which renders soft on retina at the README's width=880. Re-export at 2x and encode lossy WebP q90 (92KB, matching the old file's footprint) instead of lossless (433KB). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1rSh3SxQHyeraReefJ2n3 --------- Co-authored-by: Karl Godard <karl@minimal.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(common): contain jaq parser panics in jq::parse_file, and wire th… …e fuzz dictionaries (#1209) * fix(common): contain jaq parser panics in jq::parse_file jaq/hifijson can panic on adversarial JSON — a fuzzer found an Ord total-order violation in std's sort on numbers that overflow f64 to ±inf, aborting the process. parse_file's input is supply-chain-influenced (upstream project data files, via decode::stacks), and the module already contracts to return Err on bad input, never to abort. Wrap both the JSON and TOML parse branches in catch_unwind and convert a caught unwind to JqError. Proven by parse_file_contains_jaq_panic, an ordinary #[test]: the jq_parse_json fuzz target builds panic=abort (libfuzzer-sys installs an abort hook) and so cannot observe the containment. Minimized crash fixture checked in. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(fuzz): add libfuzzer dictionaries + jq crash regression seeds Dictionaries for the structured targets — graph wire tag bytes + JSON keys, minimal.toml section/key vocabulary, jq JSON tokens, and the ArgSchema grammar — steer the mutator toward structurally valid inputs instead of rediscovering framing from scratch. Measured overnight: graph_from_bytes edge coverage rose 6.5k → 8.3k with graph.dict live, and arg_schema_parse moved off a multi-billion-exec plateau. Seeds preserve both jq_parse_json Ord-violation crash variants for regression, alongside the checked-in minimized fixture. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * build(fuzz): load the dictionaries libFuzzer would otherwise ignore The dictionaries landed as files nothing read. libFuzzer does not discover a dictionary on its own — it has to be handed `-dict=` — and neither the `just fuzz` recipe nor the docs passed one, so all four were inert. That is the same failure mode as a fuzz target that no longer compiles: it reads as coverage that is not there. Name each dict after the target it feeds (`args.dict` -> `arg_schema_parse.dict`, and so on) so the recipe can find it by convention, and pass `-dict=` when `fuzz/<target>.dict` exists. A target without one is unaffected; a dictionary only biases mutation. Verified: `just fuzz common jq_parse_json` now reports `Dictionary: 14 entries` where it previously reported none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
feat(min): add experimental command to setup zed connection
test(fuzz): campaign 2 — tarball/xz/path/redact hardening + minimald … …remote-write fix (#1193) * fix(fuzz): mirror the root workspace patch into each fuzz workspace `just fuzz-check` has been failing on main since the topiary patch landed. Every `fuzz/` dir declares its own `[workspace]` — that isolation is what keeps the nightly/sanitizer build off the main workspace — and `[patch]` does not cross a workspace boundary, so the root's entry never applied. `js-sys` then had no resolvable version and four of the six workspaces stopped building. Nothing caught it because `just ci` does not run `fuzz-check`; this is the exact silent rot the "Keeping the targets alive" section warns about. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(fuzz): give archive_extract an inode oracle, retarget path_invariants `assert_contained` is a path oracle and structurally cannot see an inode escape: a hardlink's path really is inside the tree and `canonicalize` agrees, because a hardlink has no target to resolve — it is a second name for one inode. The escape is by identity, not location. That blind spot is why the CWD-relative hardlink hole in `Entry::unpack` had to be found by reading rather than by fuzzing. Adds a sentinel outside the destination and asserts nothing extracted shares its inode. The sandbox moves under `fuzz/target` rather than `/tmp`: hard links cannot cross devices, and the CWD a tar hardlink resolves against is the crate root, so a `/tmp` destination turns every inode escape into a silent EXDEV. `path_invariants` is retargeted at the `EitherPath::try_new` redesign, which resolved the forged-`RelPath` bug by routing through both validating constructors rather than by weakening the variant. The property is now that `try_new` succeeds exactly when the applicable per-variant constructor does — asserting the routing claim its docs make. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(diagnostics): fuzz redact for leaks, not just panics `redact` states its own asymmetry — "false positives (masking a harmless value) are acceptable, false negatives (leaking a secret) are not" — and its output leaves the machine in support bundles. That is a property, so the target asserts it: after redaction no leaf reachable under a sensitive key or an env table may still hold its original value, across both the JSON and TOML paths. Also covers the `.expect()` in `redact_toml`, which assumes re-serializing a just-parsed document is infallible on input that is a user config file. Deliberately monotonic rather than idempotent. A second pass re-masks the placeholder and records *its* length, which is key-based redaction working as intended: the key is sensitive, so the value is masked whatever it holds. Asserting idempotence flagged that immediately and wrongly — what must hold is that a second pass never unmasks. First run: 18.1M execs, cov 4559, no findings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(common): fuzz normalize_within_root directly It is no longer just tar's containment gate: `op::materialize` uses it for raw-file outputs and `minimald` for the client's uploaded workspace tarball. Three crates now trust the same contract and none re-checks the result, so a gap here is a gap in all three. Reaching it only through `archive_extract` means every probe pays for a tempdir and a full extraction. Fuzzed directly it is a pure function — 150k exec/s against 1.7k — and can explore path shapes a ustar header cannot encode. Asserts what the callers actually rely on: the result is relative, carries no `..`, stays contained when joined onto roots of several shapes (including `/srv/work` against `/srv/workbench`, where a string-prefix check would wrongly pass), and is a fixpoint. 96M executions, no findings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(minimald): validate the uploaded workspace tarball entry by entry `unpack_workspace_files` handed the client's tarball to `async_tar::Archive::unpack`, which writes link targets verbatim. The SFTP subsystem is scoped to the same directory, so a client could upload `link -> /` and then write through it — a remote arbitrary write built from two client-supplied halves. `unpack_workspace_patches`, twenty lines below, already iterates per-entry for exactly this reason: "the client is untrusted so we re-check the wire form here". Both read the same upload channel; only one re-checked. Adds `unpack_validated`: entry paths that escape are fatal, links whose targets escape are skipped with a warning so legitimate tarballs still extract. The rules come from `common::archive`, now via `normalize_link_target`, which returns the resolved target rather than a bool because the hardlink path needs it — `tar::Entry::unpack` resolves a relative hardlink target against the process CWD and cannot be trusted with one. `extract_tar_impl` is refactored onto the same helper, so the symlink-vs-hardlink base rule (symlink targets resolve against the link's own directory, hardlink targets against the destination root) has one definition rather than two. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(fuzzing): record reading the prose, and two oracle-design corollaries The most serious finding of the campaign came from grepping doc comments, not from a target. Records the two greps and why the threat-explaining one yields more: a good security comment marks where an author's attention was, and where it stopped. Both corollaries were learned by getting them wrong — a path oracle blind to inode escapes, and an idempotence assertion on `redact` that fired immediately against behaviour the module never promised. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(common): contain lzma_rs panics on malformed xz streams `extract_compressed_tar` promises a malformed archive yields an `ArchiveError` and never panics. `lzma_rs` breaks that: `backward_size + 1` in its xz footer check overflows on `u32::MAX` (decode/xz.rs). The shipped release profile has overflow-checks off so that one wraps harmlessly, but any build with them on — tests, dev, the fuzz target — aborts. Of the five compression arms only xz runs a pure-Rust decoder; the rest bind long-hardened C libraries, and lzma-rs has had no functional commit since May 2024. Contain it here rather than trusting every future edge case. `catch_unwind` cannot be proved by the fuzz target: `libfuzzer-sys` installs a panic hook that aborts before unwinding, so the guard never runs there. `extract_xz_panic_is_contained` proves it instead, against the exact 589-byte stream the fuzzer produced, committed as a fixture. The fuzz target skips xz for the same reason, with the loss recorded in a comment. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(diagnostics): reject redacted TOML that will not parse back `redact_toml` re-serializes with `to_string_pretty` and returned the result unchecked. The round trip is not guaranteed: a document of deeply nested inline tables parses at one depth and re-serializes into a form that trips the parser's recursion limit, so the call succeeds and the output is unreadable. Found by the `redact_roundtrip` fuzz target. Parse the rendered document back and surface the failure. That keeps the contract consistent — unparseable input is already an error precisely so callers withhold the file rather than pass it through, and a bundle carrying TOML no consumer can read is the same failure one step later. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style(common): rustfmt the xz-panic test (fixup for the lzma containment commit) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: tighten campaign comments; drop the inaccurate sftp-arbitrary-write claim (review) Tom's review: the sftp path already contains a write through a planted symlink (SftpSession::contained canonicalizes + rejects). Reframe the minimald uploader comments from 'remote arbitrary write' to defense-in-depth (don't plant escaping links for the workspace's other consumers; reject tar-slip), and compact the duplicated lzma-overflow prose across the code arm and its test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * revert(minimald): drop unpack_validated — Archive::unpack already contains it (review) Tom's review: async_tar::Archive::unpack (via unpack_in) passes target_base=Some(dst), so it validates hardlink targets and rejects writing through a planted symlink (validate_inside_dst); and a workspace symlink to /bin is legitimate — it resolves against the sandbox rootfs at runtime, so skipping such entries corrupts valid uploads. Revert unpack_workspace_files to Archive::unpack. The commit's shared normalize_link_target was a behavior-preserving refactor of extract_tar_impl's existing check, its only external caller being the now-reverted minimald path; extract_tar_impl's inline check (which genuinely needs it — its per-entry tar::Entry::unpack uses the unvalidated target_base=None arm) is restored verbatim. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
chore(remote-proto): ignore false-positive machete build-deps (#1169) prost-build and tonic-prost-build are build-time codegen deps invoked from build.rs; cargo-machete's source scan flags them as unused even though they are required. Add them to the [package.metadata.cargo-machete] ignored list alongside the runtime deps. Co-authored-by: gominimal-aw-bot[bot] <281738952+gominimal-aw-bot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Tom <twitchyliquid64@users.noreply.github.com>
fix(sandbox2): make rootfs a BTreeSet so assembly order is deterministic Sandbox::new hardlinks Config::rootfs entries first-writer-wins by iterating the collection directly, so a HashSet let a per-process RandomState seed decide which entry provided a contended path. Make the collection itself ordered: SandboxMapped gets an Ord mirroring its manual Eq/Hash key (variant tag, path) and Config::rootfs becomes a BTreeSet. This fixes every rootfs producer at once; the op-layer sort/dedup this replaces was only treating one call site's symptom. The standalone-test path keeps the plain HashSet->BTreeSet swap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(minimald): support arbitrary session exec
PreviousNext