Supply-chain hardening: minimal runtime deps, locked-down CI, install-time guarantees - #634
Open
OBrezhniev wants to merge 14 commits into
Open
Supply-chain hardening: minimal runtime deps, locked-down CI, install-time guarantees#634OBrezhniev wants to merge 14 commits into
OBrezhniev wants to merge 14 commits into
Conversation
…alls - actions/checkout and actions/setup-node pinned to the commit SHAs the v6 tags currently resolve to (tags are mutable; SHAs are not) - permissions: contents: read on both workflows - root npm ci runs --ignore-scripts (the dependency tree is verified to contain zero install scripts; the hardhat/browser_tests subprojects keep scripts for their native toolchains) - npm audit signatures step verifies registry signatures + provenance - tutorial: checkout@v2 -> pinned v6, npm install -> npm ci, and the circom binary download is now release-pinned and sha256-verified Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
- .npmrc: ignore-scripts=true (tree is verified script-free; a future postinstall becomes an explicit decision) and save-exact=true. The pretest hook is folded into the test script since ignore-scripts also disables pre/post hooks. - registry runtime deps exact-pinned (@noble/hashes, bfj, ejs, logplease; circom_runtime already was) - renovate.json with minimumReleaseAge 7 days: registry-takeover malware is typically live for hours, a cooldown makes it unreachable Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
Same line format and colors ([LEVEL] snarkJS: message), same level gating, log() kept as the info alias the ceremony progress messages use. logplease was unmaintained since 2017. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
All 13 CLI call sites were bfj.write(file, value, {space: 1}). The new
src/json_writer.js streams the value with an explicit stack and
backpressured 1 MiB chunks (the exported r1cs/ptau/witness JSONs reach
hundreds of MB), producing output byte-identical to
JSON.stringify(value, null, space) -- verified byte-for-byte against
bfj on wtns/zkey/vkey/ptau exports of real artifacts; r1cs export
differs only in empty-object formatting ("{}" instead of bfj's
"{\n }"), which converges with how proofs were already written.
Removes bfj + check-types + hoopy + tryer from the runtime tree.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
The Solidity verifier templates use exactly four constructs: <% %> scriptlets, <%= %> escaped output, <%- %> raw output, and the -%> newline slurp. src/template_render.js implements that subset with ejs's semantics (with-scoping of the data object, null/undefined as empty, the same XML escape table). Rendered verifiers verified byte-identical to ejs output for all three protocols, and the hardhat verifier-contract suite passes (7/7). Removes a runtime dependency with a recurring CVE history; also shrinks the browser bundles, which inlined all of ejs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
logger.log was a logplease-ism: console-style loggers treat it as info but winston-style loggers read the first argument as a level, so the four ceremony progress calls were the one place a foreign logger could misbehave. Every library call site now sticks to debug/info/warn/error -- the interface every logging library agrees on -- and the CLI logger sheds the now-unused alias. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
The update-delay protection intended by the removed renovate.json, expressed in the tool this repo actually uses. Also lets Dependabot maintain the commit-pinned actions (it bumps the SHA and the version comment together). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
The workflows reference no secrets, so the only credential in a job is the automatic GITHUB_TOKEN -- which actions/checkout persists into .git/config by default, readable by every later step (including anything a compromised dev dependency executes under npm test). No job pushes, so no checkout needs credentials at all: persist-credentials: false everywhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
Findings from the ejs-replacement security review, all fail-closed: - render() rejects data keys starting with __ (with-scoping would let them shadow the renderer's internals: a __out key silently produced an empty document, __esc/__raw a confusing TypeError) - render() throws on an unterminated <% tag instead of emitting the raw tag text (ejs threw here too), and on a non-string template - both verifier exporters assert every interpolated vk leaf is a plain decimal number (protocol/curve tags exempt), so a poisoned vk object from an untrusted vkey JSON cannot smuggle source text into the generated contract; zkey-derived vks always satisfy this Rendered verifiers for all three protocols remain byte-identical to the ejs output. Templates themselves remain trusted code by contract, unchanged from ejs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
The direct bfj import survived the bfj removal unnoticed because the nested worktree resolved it from the parent checkout's node_modules; CI (npm ci from the lockfile) correctly had no bfj to import. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
Root cause of the one hung windows job on this PR: npm ci stalled in a git-dependency clone (npm applies no timeout to those, unlike registry fetches) and silently consumed the whole 30-minute job budget. The lockfile is https-only -- npm's 'ssh://' integrity warnings are its cosmetic hosted-git wording, not the fetch protocol. A capped install step turns a stalled clone into a fast, clearly-attributed failure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
The hung windows job ran with a cold cache (this PR changes package-lock.json, so every OS's first run re-clones the five git deps); the rerun passed precisely because the sibling windows job had saved a warm cache by then. Close the remaining gaps: the tutorial workflow had no cache at all, the contracts job now keys on both lockfiles it installs, and the browser-bundles job keys on the only lockfile it uses instead of the root one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp
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.
Reduces the supply-chain attack surface across the dependency tree, the install pipeline, CI, and the template renderer. Stacked on #628.
Runtime dependency tree: 16 → 10 packages
Every removed package is replaced by a small, tested, zero-dependency module with byte-identical output:
ejs(recurring CVE history)src/template_render.js(~70 lines, exact ejs-subset semantics)bfj+check-types+hoopy+tryersrc/json_writer.js(streaming, backpressured)JSON.stringify(v, null, 1)(10 pinned cases); on a 1.1 GB zkey → 2.59 GB JSON: byte-identical and 3.2× faster (12:53 → 3:58), same peak RSSlogplease(unmaintained since 2017)src/cli_logger.js(~60 lines)debug/info/warn/errorsetWhat remains at runtime: five iden3 repos pinned to git commit SHAs, three iden3-owned registry packages,
@noble/hashes, and theweb-workershim.Install-time guarantees
.npmrc:ignore-scripts=true(the whole tree is verified script-free — a future postinstall becomes an explicit decision, not silent code execution) andsave-exact=truecooldownfor npm and actions (registry-takeover malware typically lives for hours), plus agithub-actionsecosystem entry to maintain the SHA pinsCI lockdown
permissions: contents: readon all workflows;persist-credentials: falseon every checkout (no job pushes, so the GITHUB_TOKEN never lands in.git/configwhere a compromised dev dep running undernpm testcould read it; workflows reference no other secrets)npm ci --ignore-scripts;npm audit signaturesverifies registry signatures + provenance on every runcheckout@v2→ pinned v6,npm install→npm ci, circom binary release-pinned and sha256-verifiedRenderer security review (applied findings)
render()rejects data keys shadowing its internals (a__outkey silently produced an empty document), throws on unterminated<%tags and non-string templates (fail closed, as ejs did)Templates themselves remain trusted code by contract, exactly as with ejs.
Verification
Full suite 284 passed + 1 skipped (16 new tests), lint clean, bundles rebuilt, hardhat verifier contracts green, all byte-compat comparisons above.
Two follow-ups deliberately not included: repo Settings can't be set from files (default workflow token → read-only; require approval for outside-collaborator runs), and the same CI/npmrc treatment for the sibling repos.
🤖 Generated with Claude Code
https://claude.ai/code/session_01833VaUEJmrFZ7bVprrWwpp