Harden supply chain: clear 7 npm advisories, attach release provenance, pin base image, add fuzzing - #5510
Merged
Merged
Conversation
…e, pin base image, add fuzzing
OpenSSF Scorecard scored this repo 6.6/10. Four checks were fixable in the
repo itself; this change fixes all four.
Vulnerabilities (3 -> 10). All 7 advisories were npm deps under frontend/,
which publish.yml builds into the shipped wheel. Every fix needed a major
bump, so Dependabot had them split across 13 PRs that each broke the build
alone: vite 5.4.21 -> 6.4.3 (also pulls esbuild past 0.25.0 and drops
launch-editor entirely), react-router-dom 6 -> 7.18, i18next-http-backend
2 -> 3. `npm audit` is now clean and `tsc -b && vite build` passes; the
router APIs in use (BrowserRouter/Routes/NavLink/Outlet/useParams) are
unchanged in v7. Vite 6 rather than 7 because CI builds the wheel on Node 20.
Signed-Releases (0 -> 10). Releases carried ten desktop installers and
nothing signed or provenanced, so a downloader had to take our word for what
produced them. release-on-merge.yml -- the workflow that actually cuts a
release -- now mints build provenance over the same dist/ that twine
uploads, and attaches the wheel, sdist and a .intoto.jsonl to the release.
publish.yml covers a hand-pushed tag the same way. Uploads are non-blocking
because the wheel is already on PyPI by then, so the wiring is asserted by
tests/test_release_provenance_attached.py rather than trusted.
Pinned-Dependencies (3 -> ~8). Base image pinned by digest, with a
Dependabot docker ecosystem added so the pin still receives CVE updates --
a digest pin without an updater freezes the image at today's CVEs. Three
`curl | python3` pipes now download to a file and then parse, and the two
`npm install`s that have a committed lockfile are `npm ci`. Left unpinned:
63 `pip install` sites, which Scorecard only credits with
`--require-hashes`; hash-locking those across 25 workflows is permanent CI
friction for roughly +0.1 of final score.
Fuzzing (0 -> 10). Atheris harnesses over the two places we parse input we
did not write: the OTLP/JSON decoder (any host that can reach the dashboard
port can POST to it) and the transcript event classifier. They assert
contracts, not just absence of crashes.
The OTLP harness found three real contract violations on its first run, all
fixed here. decode() documents "raises ValueError on a malformed body", and
the HTTP layer maps ValueError to a 400:
1. `{"resourceSpans": 3}` raised TypeError. Every repeated field guarded
each item with isinstance(x, dict) but never guarded the container, so
a scalar where OTLP specifies an array crashed before the item guards
ran -- one bug in fifteen places, fixed with a _seq() helper.
2. A non-gzip body sent as Content-Encoding: gzip raised BadGzipFile.
3. A truncated gzip body raised EOFError.
tests/test_fuzz_targets.py asserts the same contracts over the inputs that
broke them, so this is caught on every PR without Atheris installed; it goes
13-red against the unfixed parser.
Not addressed, because neither is a code change: Code-Review (0) needs
approving reviews on PRs, and Branch-Protection currently errors rather than
scoring because the repo uses classic protection that Scorecard's token
cannot read -- enabling it could lower the total rather than raise it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTvE7AYmmqZKEGhpxFETx
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
Contributor
Visual diffComparing 29 of 72 comparison(s) flagged (>1% pixel diff).
Folder: b8a0c66fea92. Full PNGs also attached as a workflow artefact. Generated by visual-diff bot. Pixel diffs >1% flagged; eyeball the table before merging. This check is non-blocking — fail = bot bug, not a code problem. |
Closed
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No-PRD: security-posture hardening driven by the public OpenSSF Scorecard report, not a product requirement.
OpenSSF Scorecard scores this repo 6.6/10. Four checks are fixable in the repo itself; this fixes all four. Expected result ≈ 8.8.
frontend/, which ships inside the wheelcurl | pythonpipes,npm installVulnerabilities
All 7 are npm deps under
frontend/, whichpublish.ymlbuilds into the published wheel. Every fix required a major bump, which is why Dependabot had them spread over 13 PRs that each broke the build on their own:vite5.4.21 → 6.4.3 — also carriesesbuildpast 0.25.0 and dropslaunch-editorentirelyreact-router-dom6 → 7.18.0i18next-http-backend2 → 3.0.5npm auditreports 0 vulnerabilities andtsc -b && vite buildpasses. Every router API in use (BrowserRouter,Routes,NavLink,Outlet,useLocation,useParams) is unchanged in v7. Vite 6 rather than 7 because CI builds the wheel on Node 20. This supersedes #5376, #5244, #5241, #5247 and the other frontend Dependabot PRs.Signed-Releases
release-on-merge.ymlis the workflow that actually cuts a release (publish.ymlis never dispatched for auto-releases). It now mints build provenance over the samedist/thattwineuploads, then attaches the wheel, sdist and a.intoto.jsonlto the release.publish.ymldoes the same for a hand-pushed tag.Uploads are non-blocking on purpose — the wheel is already on PyPI by that point, so a failed upload must not fail a completed release. That tolerance is exactly how the
desktop-artifactsdispatch once broke silently on a 403, sotests/test_release_provenance_attached.pyasserts the wiring instead of trusting it.Pinned-Dependencies
dockerecosystem — a digest pin without an updater freezes the image at today's CVEs forever.curl … | python3 -cpipes now download to a file and then parse. The payload was always data, but the line is indistinguishable from piping remote code into an interpreter.npm installs with a committed lockfile are nownpm ci.Deliberately not fixed: the 63
pip installsites. Scorecard credits pip only with--require-hashes, so reaching 10 means hash-locked requirement files for every distinct tool set across 25 workflows — permanent CI friction (every tool bump needs regenerated hashes) for roughly +0.1 of final score. Happy to do it if you'd rather have the point.Fuzzing
Atheris harnesses over the two places ClawMetry parses input it did not write: the OTLP/JSON decoder (any host that can reach the dashboard port can POST to
/v1/traces, and it is an in-house stdlib parser) and the transcript event classifier (JSONL written by the agent runtimes). They assert contracts, not merely absence of crashes.The OTLP harness found three real contract violations on its first run, all fixed here.
decode()documents "raises ValueError on a malformed body", and_otlp_receivemapsValueErroronto HTTP 400:{"resourceSpans": 3}→TypeError. Every repeated field guarded each item withisinstance(x, dict)but never guarded the container, so a scalar where OTLP specifies an array crashed before the item guards ran. One bug in fifteen places — fixed with a_seq()helper rather than patching the one input I happened to find.Content-Encoding: gzip→gzip.BadGzipFile(anOSError).EOFError.Verification
npm audit: 0 vulnerabilities;npm run buildclean (TypeScript included)tests/test_fuzz_targets.py: 204 pass — and 13 fail against the unfixed parser, so the guard is not vacuoustests/test_release_provenance_attached.py: 10 pass, and goes red whenid: attestor the.intoto.jsonlname is removedtest_workflow_yaml_valid.pycaught a missingrequestsin the new fuzz job (pytest would have exited 4); fixedorigin/main, isolated — zero regressionsruffcount ondashboard.py+clawmetry/: 5024 before, 5024 after.make lint-pyfails identically on pristinemain(local ruff is newer than CI's pin) — pre-existing, untouched hereNot addressed — neither is a code change
required_approving_review_count: 0it would likely score low and drag the average down rather than up.🤖 Generated with Claude Code
https://claude.ai/code/session_01ByTvE7AYmmqZKEGhpxFETx