Skip to content

fix(fuzz): the crate was invisible to lint, dependabot, audit, deny, and osv - #453

Merged
forkwright merged 6 commits into
mainfrom
fix/fuzz-crate-lint-debt
Aug 21, 2026
Merged

fix(fuzz): the crate was invisible to lint, dependabot, audit, deny, and osv#453
forkwright merged 6 commits into
mainfrom
fix/fuzz-crate-lint-debt

Conversation

@forkwright

Copy link
Copy Markdown
Owner

fuzz/ was invisible to every repo-wide check. Five of them.

I found this while producing lint evidence for #377: a kanon lint . --all at origin/main returned
48 findings, and 15 were in fuzz/ — every one introduced when that crate landed in #428, earlier
tonight. Nothing was ever going to catch them.

Why nothing saw it

The crate declares its own empty [workspace] table so a nightly sanitizer build stays out of the
stable workspace. That is deliberate and correct. It also means every check keyed to the root
manifest resolves a graph that does not contain it:

check why it missed fuzz/
cargo clippy --workspace / the CI gate the root workspace excludes the directory
kanon lint ran, and nobody had looked at its output
dependabot directory: / only
cargo audit reads one lockfile
cargo deny follows the manifest it is given
osv-scanner the step named --lockfile=Cargo.lock explicitly

So libfuzzer-sys, prost, tokio-util and bytes have had no vulnerability scanning and no
dependency updates
since the crate was added. Three separate security jobs, each looking like it
covered the repository.

What changed

The lint debt is clearedkanon lint . --all now reports zero findings under fuzz/, repo
total 48 → 33. Manifest sections follow the canonical order the rule encodes (workspace, package,
bin, dependencies, package.metadata), the single-key [dependencies.kerykeion] block folds into
[dependencies], and a LICENSE sits at this crate's own workspace root — which is what
MANIFEST/license-spdx-match was asking for, since [workspace] makes fuzz/ a root in its own
right and the repository LICENSE one directory up does not reach it.

One fix was worth more than the lint that prompted it. message_parse had an .expect() flagged
as library code; replacing it with panic! traded one rule for another, which is the tell that
neither was the fix. The property wanted is a message this parser produced decodes again to the same
bytes
— one comparison, not a match arm plus an assertion. It is now a single assert_eq! over
Option<&[u8]>, which also states the decode-succeeds half the previous shape left implicit.

The coverage gap is closed at each site: a /fuzz dependabot entry, a second cargo deny run
with --manifest-path fuzz/Cargo.toml, a second cargo audit with --file fuzz/Cargo.lock, and
--lockfile=fuzz/Cargo.lock added to the osv step. Each is a second invocation rather than a widened
first one, because no flag on any of the three spans two workspaces — the comments say so at each
site, since "why is this run twice" is the obvious question and collapsing them is the obvious wrong
answer.

fuzz/Cargo.lock is now committed, because dependabot updates a binary crate by moving its lockfile
and cannot act without one. It also removes the untracked file any local cargo check in that
directory leaves behind.

Verification

  • kanon lint . --all at this branch: zero findings under fuzz/.
  • cargo +nightly check --target x86_64-unknown-linux-gnu in fuzz/: Finished, exit 0 — the
    reordered manifest and rewritten target still build.
  • Both YAML files parse.

pre-push-verify.sh derives crates from the branch diff and finds none: this touches only fuzz/
(outside the workspace) and two CI files. The real verification is the security jobs themselves
reporting on this PR, which is the thing that could not happen before it.

forkwright added 6 commits August 21, 2026 04:48
`kanon lint` reported 15 findings in `fuzz/`, every one introduced when the
crate landed in #428. None of them was ever going to be caught: the crate
carries its own empty `[workspace]` table so a nightly sanitizer build stays
out of the stable workspace, and that same detachment means
`cargo clippy --workspace` and the CI gate never lint it. It is the only
directory in the repo outside both.

Manifest sections now follow the canonical order the rule encodes —
workspace, package, bin, dependencies, package.metadata — and the expanded
`[dependencies.kerykeion]` block folds into `[dependencies]`, where a
single-key sub-table belonged anyway. A LICENSE sits at this crate's own
workspace root, which is what `MANIFEST/license-spdx-match` was asking for: an
`[workspace]` table makes `fuzz/` a root in its own right, so the repository
LICENSE one directory up does not reach it.

`message_parse` had an `.expect()` flagged as library code. Replacing it with
`panic!` traded one rule for another, which is the tell that neither was the
fix: the property wanted is that a message this parser produced decodes again
to the same bytes, and that is one comparison, not a match arm plus an
assertion. It now reads as a single `assert_eq!` over
`Option<&[u8]>`, which also states the decode-succeeds half that the previous
shape left implicit. The unconditional `let _ = ToRadio::decode(data)` became
`drop(..)`, since discarding is the intent and a bare `let _` on a Result
reads as an oversight wherever it appears.

The README's prose findings were em dashes and hedges, rewritten rather than
suppressed.

Verified: `kanon lint . --all` reports zero findings under `fuzz/` (repo total
48 to 33), and `cargo +nightly check --target x86_64-unknown-linux-gnu` still
builds the crate.
`fuzz/` declares its own empty [workspace] table so a nightly sanitizer build
stays out of the stable workspace. That detachment is deliberate and correct,
and it also puts the crate outside every repo-wide check keyed to the root
manifest: `cargo audit`, `cargo deny`, and dependabot's `directory: /` entry
all resolve the root graph and never see it.

So `libfuzzer-sys`, `prost`, `tokio-util` and `bytes` have had no dependency
update coverage at all since the crate landed. A second cargo entry at /fuzz
closes that.

This is the same root cause as the lint debt in the preceding commit: one
detached directory, invisible to four separate repo-wide checks, each of which
looks like it covers the whole repository.
Dependabot updates a binary crate by moving its lockfile, so the /fuzz entry
added in the previous commit needs one to act on. Committing it also makes the
scheduled fuzz run reproducible and removes the untracked file any local
`cargo check` in that directory leaves behind.
cargo-deny, cargo-audit and osv-scanner all ran against the root workspace
only. `fuzz/` declares its own [workspace], so none of the three ever resolved
its graph: cargo-deny follows the manifest it is given, cargo-audit reads one
lockfile, and the osv step named `--lockfile=Cargo.lock` explicitly.

The result was that `libfuzzer-sys`, `prost`, `tokio-util` and `bytes` had no
vulnerability scanning at all -- three independent security checks, each
looking like it covered the repository, and a directory none of them could
see.

Each is extended by a second invocation rather than a widened first one,
because no flag on any of the three spans two workspaces. The comments say so
at each site, since "why is this run twice" is the obvious question and
collapsing them back into one is the obvious wrong answer.

With the preceding commits this closes the whole class for `fuzz/`: kanon lint,
dependabot, cargo audit, cargo deny, and osv-scanner. The detachment that makes
it invisible is deliberate -- a nightly sanitizer build must stay out of a
stable workspace -- so the fix is to name it in each check rather than to
attach it.
The action always emits `--manifest-path` from its `manifest-path:` input,
defaulting to ./Cargo.toml, so supplying another one inside `arguments:` gave
cargo-deny the flag twice and it refused to run at all:

    error: the argument '--manifest-path <MANIFEST_PATH>' cannot be used multiple times

A harness fault rather than a finding, and one only CI could surface — the flag
is correct on its own and the duplication lives in the action's plumbing.

The other two extensions in this branch worked first time: `cargo audit
--file fuzz/Cargo.lock` and the added `--lockfile=fuzz/Cargo.lock` both ran
green, which is the first vulnerability scan that dependency tree has ever had.
The first cargo-deny run that could see the fuzz workspace rejected
libfuzzer-sys 0.4.13: it is `(MIT OR Apache-2.0) AND NCSA`, and NCSA was in no
allow list. That is a real licensing fact about a dependency this repo has
carried since #428 and nothing could report until now, which is the whole
argument for extending the check.

NCSA is the University of Illinois/NCSA Open Source License — permissive,
OSI-approved, FSF Free/Libre, and compatible with this workspace's AGPL-3.0.
libfuzzer-sys carries it because it wraps LLVM's libFuzzer, which predates
LLVM's relicensing to Apache-2.0-with-LLVM-exception.

Recorded as a per-crate exception rather than a twelfth entry in `allow`. The
grant is sound for this dependency; a global entry would also accept NCSA from
any future crate in the shipped runtime graph, which is a different and
unexamined question. Nothing outside the detached fuzz workspace depends on
libfuzzer-sys.
@forkwright
forkwright merged commit 1cb6fc2 into main Aug 21, 2026
11 checks passed
@forkwright
forkwright deleted the fix/fuzz-crate-lint-debt branch August 21, 2026 10:03
forkwright pushed a commit that referenced this pull request Aug 21, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.6.0](v0.5.0...v0.6.0)
(2026-08-21)


### Features

* **koinon,kryphos:** sign the audit log tip and verify which
installation produced it
([#451](#451))
([9b839bc](9b839bc))


### Bug Fixes

* **fuzz:** the crate was invisible to lint, dependabot, audit, deny,
and osv ([#453](#453))
([1cb6fc2](1cb6fc2))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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