Skip to content

Public-release setup: metadata, CI, supply chain, fuzzing and governance docs - #4

Merged
yokoszn merged 11 commits into
mainfrom
claude/eager-johnson-cgdqg0
Nov 5, 2026
Merged

yokoszn merged 11 commits into
mainfrom
claude/eager-johnson-cgdqg0

Conversation

@yokoszn

@yokoszn yokoszn commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Prepares transcode for public release. Changes:

  • Build and metadata: Cargo.lock sync (missing dev-dependency image-webp, so --locked failed); license, repository, readme, keywords, categories, rust-version = 1.89 (verified with cargo +1.89.0 build --all-targets); #[forbid(unsafe_code)]; [lints.clippy] with style, complexity and perf allowed for now and correctness and suspicious kept on.
  • Real bug fixes found while enabling the tests: debug-build overflow panics in av1_ec.rs and av1_coef.rs (wrapping ops, release behaviour unchanged). Two previously failing unit tests now pass. Dead-code and warning cleanup, including the binary now using the library modules.
  • Formatting: one commit, cargo fmt only (about 700 hunks before). Use git log -p --ignore-all-space to review the other commits without it.
  • Docs: README rewritten to match the code (the old one described a PDF decoder that is not in this tree). Community files: CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, CONTRIBUTORS, CHANGELOG. docs/OPENSSF.md, docs/SUPPLY-CHAIN.md, docs/RELEASING.md. PR and issue templates.
  • CI and supply chain: Rust checks job (fmt, build, test with ffmpeg installed, clippy -D warnings), MSRV job, CodeQL, Scorecard, dependency review, cargo-audit, cargo-deny, CycloneDX SBOM, Dependabot. Every uses: is pinned to a full SHA verified with git ls-remote.
  • Release: tag-gated, runs tests, checks the tag against Cargo.toml, signs with cosign bundles (.sigstore.json), attaches SLSA provenance via bundle-path.
  • Fuzzing: fuzz/ with seven libFuzzer targets (PNG, JPEG, FLAC, SLAC, MP3, AAC, Opus) and a fuzz workflow.

Output compatibility

Codec output is unchanged. The av1_ec/av1_coef edits keep the release-mode arithmetic identical, and the formatting commit changes no logic. The test suite passes (cargo test --locked: 57 unit tests and all integration tests, one ignored report).

Checks

  • cargo fmt --all -- --check
  • cargo test --locked --no-fail-fast: all pass (ffmpeg installed locally, so the ffmpeg oracle tests ran)
  • cargo clippy --locked --all-targets -- -D warnings
  • cargo build --locked --all-targets on stable and cargo +1.89.0
  • cargo deny check advisories bans licenses sources: ok
  • cargo audit: no advisories against the 4 locked crates
  • All workflow YAML parses. All uses: are SHA-pinned.
  • Fuzzing finds real bugs; the fuzz job will fail on this PR. See below.

Fuzz smoke run (60 s per target, debug+ASan, nightly)

Target Result
fuzz_png clean, 14.0M runs
fuzz_mp3 clean, 86.9k runs
fuzz_opus clean, 4.1M runs
fuzz_jpeg panic: index out of bounds at src/jpeg.rs:633 (APP14 segment read past end of a 97-byte input). Artifact: fuzz/artifacts/fuzz_jpeg/crash-e157e4d4… (not committed)
fuzz_flac hang: a single input never returns (confirmed with -timeout=5). Artifact: timeout-cfba9968… (not committed)
fuzz_slac unbounded allocation: ASan out-of-memory requesting about 17 GB from a tiny input. Artifact: crash-e9befc07… (not committed)
fuzz_aac panic: subtract with overflow at src/aac.rs:722 (cnt += b.bits(8) - 1 when the field is 0)

These are decoder bugs. They are reported here and not patched or suppressed in this PR. The fuzz targets are intentionally kept so the first CI run shows them. A follow-up should fix each one with a bounds check that returns Err, plus a regression test. The JPEG marker walker also appears to loop forever on a zero segment length (read from the code, not yet reproduced).

Notes for reviewers

  • Open human decisions (not resolved here):
    • Patents: H.264, AAC and MP3 coverage needs a legal review before publication. The README says so first. Nothing claims patent safety.
    • Licensing: LICENSE-MIT and license = "MIT". An earlier README said MIT OR Apache-2.0. Dual licensing needs a decision and an LICENSE-APACHE file.
    • CODEOWNERS uses @OWNER-1 @OWNER-2 placeholders. Real maintainers are needed before the code-owner rule.
    • The repository is private. Scorecard publishing, the badges and dependency review on private repos depend on that.
    • No rulesets or branch protection were changed.
  • Not run: caretakerctl ossf (the caretaker workspace depends on private git repos). Templates were rendered with an equivalent script; see docs/OPENSSF.md.
  • Release and provenance commands in docs/SUPPLY-CHAIN.md have not been run against a real tag.
  • The formatting commit is large. Review the logic commits separately.

Generated by Claude Code

Cargo.lock omitted the dev-dependency image-webp (and its transitive
byteorder-lite and quick-error). Without this, cargo build --locked fails
in CI. The resolution is additive; no existing package version changed.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ch6Crfubj3FwdxR7ywn9vT
- license = MIT (matches LICENSE-MIT; see the README licensing note)
- repository, readme, description, keywords, categories
- rust-version = 1.89, verified with cargo +1.89.0 build --all-targets
- #![forbid(unsafe_code)] on the library crate (the crate has no unsafe)
- [lints.clippy]: style, complexity and perf are allowed for now. The crate has
  about 380 findings there, mostly float-literal precision in DSP tables. Correctness
  and suspicious lints stay on, so cargo clippy -- -D warnings gates CI.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ch6Crfubj3FwdxR7ywn9vT
The AV1 range-decoder bool/bit paths (av1_ec.rs) and coefficient context
(av1_coef.rs) rely on 32-bit wrap-around: 'r - 2*v' and '(h - k)' are negative
in the spec sense and wrap, and the wrapped value is multiplied by 0/1 or
clamped with min(). Debug builds panicked with 'attempt to subtract with
overflow', so cargo test failed on the two round-trip unit tests. The
arithmetic is now spelled with wrapping_* ops. Release builds already wrapped,
so their behaviour does not change.

Warnings:
- src/main.rs used to declare its own copy of the codec modules (mod aac; ...),
  so most library items looked unused in the binary. It now uses the library.
- mp3::read_huffman is private (it was more visible than its Br argument type).
- Dead idx store in mp3 Huffman section removed.
- jpeg::ycbcr_to_rgb was unused and is removed.
- opus.rs carries CELT scaffolding for the unfinished Opus decode path; it is
  marked #![allow(dead_code)] with a pointer to ROADMAP.md.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ch6Crfubj3FwdxR7ywn9vT
…ation

- README: rewritten to match the code. The old text described a PDF image
  decoder that does not exist here and paths that are not in the tree. The
  status table is taken from ROADMAP.md and the source. A patents and
  licensing section is first, marked as an open decision.
- CONTRIBUTING, CODE_OF_CONDUCT (Contributor Covenant 2.1), SECURITY (GitHub
  private reporting; no email address), CONTRIBUTORS (human names from git
  history), CHANGELOG (Keep a Changelog).
- docs/OPENSSF.md: Scorecard mapping, Best Practices status, and open human
  items. Placeholder owners, the license question, the patent review, the
  conduct contact, repository visibility and the ruleset are all open.
- docs/SUPPLY-CHAIN.md: pins with verified commit SHAs, verification commands.
- docs/RELEASING.md: maintainer steps.
- PR and issue templates.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ch6Crfubj3FwdxR7ywn9vT
- ci.yml: job 'Rust checks' (fmt, build, test with ffmpeg installed, clippy
  -D warnings) and an MSRV 1.89 build job.
- codeql.yml (Rust), scorecard.yml, dependency-review.yml.
- supply-chain.yml: cargo-audit, cargo-deny, CycloneDX SBOM, evidence record.
- fuzz.yml: matrix over seven libFuzzer targets; crash inputs kept on failure.
- release.yml: tag-gated; runs tests, checks the tag against Cargo.toml,
  builds the CLI, signs with cosign, attests SLSA provenance. No container
  job, since the crate has no Dockerfile.
- Every uses: line is pinned to a full commit SHA. Actions were verified with
  git ls-remote (peeled commits where annotated).
- dependabot.yml for cargo and github-actions.
- CODEOWNERS uses @owner-1 @owner-2 placeholders. Replace them with real
  maintainers before enabling the code-owner rule.
- fuzz/: cargo-fuzz crate with targets for PNG, JPEG, FLAC, SLAC, MP3, AAC
  and Opus. The crate has its own workspace and lock file.
- deny.toml: MIT and Apache-2.0 only, crates.io sources only, no git sources.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ch6Crfubj3FwdxR7ywn9vT
…ndle-path

- cosign sign-blob --bundle writes one .sigstore.json per asset. The previous
  --output-signature/--output-certificate flags are verify-blob options and do
  not belong on sign-blob, and the identity check moves to the verify command.
- The provenance bundle is taken from the attest step's bundle-path output
  and copied as the .intoto.jsonl asset, instead of base64-decoding an output.
- Release assets now list the two .sigstore.json bundles in place of the
  .sig and .cert pairs. SUPPLY-CHAIN.md's verification command is updated to
  --bundle with the identity and issuer checks.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ch6Crfubj3FwdxR7ywn9vT
The crate was never run through rustfmt: cargo fmt --check reported about 700
hunks. This commit is formatting only. Tests and clippy are unchanged. The next
commit's diff can be read without this noise by ignoring whitespace-only changes
(git log -p --ignore-all-space).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ch6Crfubj3FwdxR7ywn9vT

yokoszn commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Dependency review fails on 2cd5f4b for an environment reason: "Dependency review is not supported on this repository. Please ensure that Dependency graph is enabled along with GitHub Advanced Security." This repo is private and GHAS is not enabled. The same check fails on the other private repos in this batch, and nothing in the diff causes it. Needed: enable the dependency graph and GHAS in repo settings, or make the repo public.

Generated by Claude Code


Generated by Claude Code

- docs/STANDARDS.md: for each format the crate decodes or encodes, the
  specification, its body, and the licensing and patent position as understood.
  Each row is marked unverified: the standards hosts did not resolve from the
  session that wrote it, so each row links its primary source for review.
  Open standard is stated as not the same as royalty-free.
- README: a standards and licensing section linking the table, and a note that
  this is a codec implementation, not a certified or licensed product.
- CODEOWNERS: @twn-lloyd @yokoszn replace the placeholders.
- SECURITY.md: security@twn.systems added alongside private reporting.
- CODE_OF_CONDUCT.md: hello@twn.systems as the enforcement contact.
- docs/OPENSSF.md: CODEOWNERS and contact items marked resolved; the
  patent item now points at docs/STANDARDS.md.

No codec, fuzz or manifest change. Licence unchanged (MIT).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ch6Crfubj3FwdxR7ywn9vT

yokoszn commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

CodeQL (Rust) fails on 23713d1 for the same environment reason as Dependency review: the SARIF upload is refused ("Resource not accessible by integration") because GitHub Advanced Security is not enabled on this private repo. The extraction itself is clean: 43 Rust files extracted without error and 0 with errors. Needed: enable GHAS in repo settings, or make the repo public.

Generated by Claude Code


Generated by Claude Code

- jpeg: a marker's length field and the APP14 Adobe segment must lie inside
  the input. Previously the APP14 transform byte was read past the end of a
  97-byte input.
- flac: the metadata loop stops when the reader is past the end of the input.
  Past the end the bit reader returns zeros, so the last-block flag never set
  and the loop never ended.
- codec (SLAC): the per-channel capacity is capped at what the input can hold
  (8 samples per byte). A header frame count of about 4 billion made
  Vec::with_capacity request about 17 GB before any data was read. Capacity
  only; valid output is unchanged.
- aac: a fill element whose escape byte is zero is rejected instead of
  underflowing the count.

tests/fuzz_regressions.rs embeds each crash input and runs the decoder on a
worker thread with a deadline, so a panic, a hang or an abort fails the test.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ch6Crfubj3FwdxR7ywn9vT
…zzing

Each fix rejects malformed input with an error. Valid input is unchanged.

- flac: a unary code read past the end of the input never terminated (the
  bit reader yields zeros). The loop now stops at the end of the input.
- flac: the metadata loop stops when past the end of the input.
- flac: a subframe whose wasted-bits count reaches the sample width is an
  error (it underflowed the width). decode_subframe now returns a Result.
- flac: a frame whose channel count differs from STREAMINFO is an error.
- slac: each channel block checks the input is not exhausted first. A header
  with 49163 channels walked frames and blocks long after the input ended.
- slac: the per-channel capacity is capped at what the input can hold.
- jpeg: every length-bearing marker's segment must lie inside the input before
  its fields are read. DQT, DHT and DRI bound their reads against the segment
  or the input. SOF needs seven bytes, SOS needs its component list to fit.
- jpeg: a scan header with no component count byte is an error.
- jpeg: a scan before any frame header (zero components) is an error.
- aac: a fill element whose escape byte is zero is an error, not an underflow.

tests/fuzz_regressions.rs embeds each crash, hang or allocation input and runs
the decoder on a worker thread with a deadline. A panic, a hang or an abort
fails the test.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ch6Crfubj3FwdxR7ywn9vT
- LICENSE is the Apache License, Version 2.0. LICENSE-MIT is removed.
- Cargo.toml: license = "Apache-2.0". The version stays 0.1.0.
- README, STANDARDS, OPENSSF, SUPPLY-CHAIN and CONTRIBUTING now state
  Apache-2.0. The release workflow packages LICENSE.
- CHANGELOG: Unreleased lists the licence change and the decoder hardening.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ch6Crfubj3FwdxR7ywn9vT
@yokoszn
yokoszn merged commit 7f9fdb2 into main Nov 5, 2026
11 of 15 checks passed
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.

2 participants