chore: supply chain update - 3#121
Conversation
Converge all 13 package.json files onto a single exact-pinned toolchain target — latest stable on npm: @graphprotocol/graph-cli ^0.97.0 / ^0.98.1 -> 0.98.1 @graphprotocol/graph-ts ^0.38.0 / ^0.38.2 -> 0.38.2 matchstick-as 0.5.0 / ^0.6.0 -> 0.6.0 All carets stripped to exact pins for deterministic resolutions. Three subgraphs (staking, predict-omen, predict-polymarket) were already on the 0.98.1 line; the other 9 subgraphs + root are bumped up to converge. Verification (full local CI sequence on Node 22 + yarn 1.22): yarn install --frozen-lockfile : all 13 paths "Already up-to-date" yarn graph codegen + test : all 12 subgraphs pass liquidity 23 tests passed tokenomics-eth 3 tests passed governance 12 tests passed legacy-mech-fees 15 tests passed predict-omen 19 tests passed predict-polymarket 96 tests passed babydegen-optimism 10 tests passed liquidity-l2 15 tests passed staking 18 tests passed tokenomics-l2 8 tests passed service-registry 13 tests passed new-mech-fees 15 tests passed ---------------------------------------- TOTAL 247 Matchstick tests, all green yarn audit delta is modest (latest stable is only 0.98.1; bigger jumps not yet stable on npm). Net change across paths: -13 High advisories on heaviest paths, +3 on three paths (matchstick-as 0.6 transitive), unchanged on the rest. Real wins of this PR are version consistency and exact-pin determinism, not advisory clearance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3: prevent regression — every future PR auto-checked. Closes T5 future-leak detection. Lands remaining hygiene without bundling with PR1 (CI hardening) or PR2 (dep bump). Workflows added: - .github/workflows/supply-chain.yml — audit + install-hooks + lockfile-lint matrix over 13 paths, with all-checks-passed aggregator. Advisory-only at first; promote to required-status in branch protection when team is ready. - .github/workflows/gitleaks.yml — secret scan on every push + PR. Gitleaks 8.30.1 with SHA-256 verified binary download (551f6fc83ea457...). Verified against townhall-kpis pin and upstream checksums.txt. Scripts added: - scripts/audit.mjs — wraps `yarn audit --json` with allowlist suppression. Critical: invoked as `yarn audit:prod` not `yarn audit` (yarn 1.x's built-in shadows same-named scripts). - scripts/audit-install-hooks.mjs — diffs node_modules install-hooks against .supply-chain/install-hooks.allowlist; drift in either direction (new hook OR removed hook) fails the job. Allowlists baselined: - .supply-chain/audit-allowlist.json — 23 high/critical advisories baselined as transitive of @graphprotocol/graph-cli (latest stable 0.98.1 doesn't refresh them). Each entry has reason + added + review (90-day cadence). - .supply-chain/install-hooks.allowlist — empty: graph-cli's transitives are pure JS, no install hooks at root level. Configuration: - .nvmrc → 22.18.0 (resolves prior Node 20/24 drift between test.yaml and deploy-subgraph.yaml workflows). - root package.json: engines.node "22.x", packageManager "yarn@1.22.22", and audit:* script aliases. - Both workflows updated to node-version-file: .nvmrc and Corepack activation with version assertion. Documentation: - SUPPLY-CHAIN-SECURITY.md — threat model (T1-T5), secrets inventory, Dependabot alerts setup (one-time UI toggle — no .github/dependabot.yml since user wants silent alerts only, no PR spam from version-update bot), audit/install-hooks/ lockfile-lint/gitleaks rationale, response playbook, repo-specific watches (graph-cli upstream, SUBGRAPH_STUDIO_KEY rotation). - CLAUDE.md — supply-chain section pointing at the docs + the audit:prod naming-collision warning + the org-wide blast radius note. - .github/CODEOWNERS — expanded to cover .supply-chain/, scripts/, SUPPLY-CHAIN-SECURITY.md, .nvmrc. Pre-merge verification (locally on Node 22 + yarn 1.22): yarn audit:prod : OK (23 allowlisted, no unlisted) yarn audit:install-hooks : OK (0 allowlisted) lockfile-lint x 13 paths : ✔ No issues detected Failure-mode simulations (all gates fail-closed correctly): - Empty allowlist → 23 unlisted advisories blocked ✓ - Stale install-hook entry → drift detected, exit 1 ✓ - codeload.github.com source → invalid host detected, exit 1 ✓ Branch protection settings change is OUT of scope for this PR; the new gates will run advisory until manually promoted to required-status (separate Settings UI change post-merge). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review:
|
|
Thanks for the review @atepem! Knocking out the local-verification ask: ✅
|
| Count | Package | All transitive of @graphprotocol/graph-cli? |
|---|---|---|
| 9 | minimatch | ✓ |
| 5 | axios | ✓ |
| 3 | undici | ✓ |
| 1 | semver | ✓ |
| 1 | picomatch | ✓ |
| 1 | lodash | ✓ |
| 1 | immutable | ✓ |
| 1 | glob | ✓ |
| 1 | cross-spawn | ✓ |
| 23 | total | — |
Every entry has id, ghsa, package, severity, reason (citing the graph-cli 0.98.1 ceiling), added: 2026-05-07, and review: 2026-08-05 (90-day quarterly review).
Re: count clarification
Just to flag — the review mentions "221 entries". The allowlist is actually 23 entries; you may have been looking at line count (221 lines including JSON wrapper / per-entry indented fields). Pasting it here for the record so the PR is unambiguous.
Re: your other recommendations
- Quarterly key rotation — already documented in
SUPPLY-CHAIN-SECURITY.md§3 (SUBGRAPH_STUDIO_KEYquarterly cadence, next rotation tracked off the merge of PR chore: supply chain update - 1 #119). - Promote workflows to required status — intentionally deferred: workflows ship advisory-only here; promotion to required-checks is a manual settings change after one green run on
main(noted in the PR description's "Operating notes").
Heads-up on the downstream — PR #122 (currently against mohan/supply-chain-update-3) introduces yarn resolutions for immutable, cross-spawn, and semver, dropping the allowlist from 23 → 20. So the count moves once the chain merges through.
…blic token addresses A history scan with gitleaks 8.18 / default ruleset reports 54 hits across 374 commits. All 54 are false positives — every redacted secret resolves to a public on-chain Optimism ERC20 contract address (USDC, USDT, DAI, WETH, etc.) hardcoded into babydegen mapping logic for decimals/symbol branching, plus one example token address in subgraphs/liquidity/README.md inside a Dune SQL snippet. This config extends the upstream default ruleset and adds a single allowlist scoped by BOTH path and regex shape (0x + exactly 40 hex chars), so secrets with any other shape — or in any other path — still flag. Verification (gitleaks 8.18.0, full history --log-opts="--all"): - Without config: 54 hits. - With config: 0 hits. - Positive test (synthetic AWS / Slack / sk_live_* fixtures in a temp dir outside the allowlisted paths): all 4 leaks still detected. The allowlist does not silence real secrets. The CI gitleaks workflow (.github/workflows/gitleaks.yml) already runs `gitleaks detect --source=.` and gitleaks auto-loads .gitleaks.toml from the source directory per the documented config precedence — no workflow edit required. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Heads-up @atepem @Tanya-atatakai — pushed an additional commit ( WhyRan the pre-PR-recon gitleaks history scan locally: Result: 374 commits scanned, 54 hits — all false positives. Every hit is the default Without the allowlist, this PR's own What
|
| Test | Expected | Result |
|---|---|---|
| Full history scan, default ruleset | 54 hits | ✅ 54 hits |
| Full history scan, with allowlist | 0 hits | ✅ "no leaks found" |
Positive test: temp dir with synthetic AWS / Slack / sk_live_* fixtures, allowlist active |
leaks detected | ✅ 4 leaks detected (allowlist does NOT silence real secrets) |
The CI workflow already runs gitleaks detect --source=. and gitleaks auto-loads .gitleaks.toml from the source dir per the documented config precedence, so no workflow YAML edit was needed.
rajat2502
left a comment
There was a problem hiding this comment.
Intent
Phase 3 of a 3-PR supply-chain hardening rollout. Adds two new advisory-only CI workflows (supply-chain.yml for audit + install-hook + lockfile-lint, gitleaks.yml for secret scanning), the scripts and allowlists those workflows consume, governance docs, a .nvmrc pin to 22.18.0, and Corepack-asserted yarn@1.22.22 activation in existing workflows. Branch-protection promotion is intentionally deferred to a post-merge admin step. The deploy workflow also gets a --frozen-lockfile tightening. Correct me if that's off-base.
Blocking issues
None blocking.
Security findings
This PR is security work. Reviewed for self-defeating patterns (gates that introduce holes) and for whether the controls do what they claim. No exploitable issues found. Two notes:
gitleaks.yml:33interpolates${{ github.base_ref }}into arun:block. Safe in practice — this is the base ref (set by repo owners), nothead_ref(attacker-controllable on fork PRs) — and the workflow runs inpull_requestcontext withpermissions: contents: read, no secrets. Worth knowing the distinction the next time someone copies this snippet.scripts/audit-install-hooks.mjs:35regex for "trivial" hooks is reasonable defense-in-depth; bypasses I considered ({}brace expansion, leading whitespace, unicode escapes) either don't chain commands or are handled by the priorcmd.trim(). Real attack surface is the allowlist diff, which catches anything new regardless.
Regression risks
| Location | Issue | Fix / verify |
|---|---|---|
.github/workflows/deploy-subgraph.yaml:138 |
yarn install → yarn install --frozen-lockfile will fail any deploy whose subgraph yarn.lock has drifted from package.json. PR2 verified all 13 paths today, but the constraint is now permanent. |
Confirm whatever keeps lockfiles fresh (renovate? manual?) is intact post-merge. Or run yarn install --frozen-lockfile in test.yaml too so drift is caught on PR, not at deploy time. |
.github/workflows/supply-chain.yml:50-66 |
The 13-path matrix duplicates test.yaml's matrix. Adding a new subgraph requires editing both files; lockfile-lint will silently skip the new path until somebody notices. |
Either add a comment in both files cross-referencing each other, or factor the list to a single source (workflow outputs, or a generated JSON read by both). |
.github/workflows/test.yaml:65 and deploy-subgraph.yaml:126 |
Node version moves 20→22 (test) and 24→22 (deploy). PR2 verified Matchstick on Node 22, but the deploy path runs graph deploy, not Matchstick. |
Confirm at least one staging deploy has run on Node 22 with graph-cli 0.98.1. If not, do a dry deploy of one subgraph before promoting to required. |
.gitleaks.toml:9-13 |
Author verified 0 false positives with gitleaks 8.18 locally, but CI installs 8.30.1. Default rulesets evolve between minors. | Run gitleaks 8.30.1 detect --log-opts=\"--all\" once locally with the new config and confirm 0 hits before promoting the gate to required. |
Suggestions
| Location | Issue | Fix / verify |
|---|---|---|
scripts/audit.mjs:91-98 |
parseAdvisories silently continues on JSON parse errors per line. If yarn emits a malformed line for any reason, advisories are dropped without surfacing. |
Track parse failures and emit ::warning:: if any lines failed to parse. Don't fail closed — yarn 1.x mixes log lines into JSON output — but make silent loss visible. |
scripts/audit.mjs:107 |
If yarn audit cannot reach the npm advisory API (transient), it returns 0 advisories. Result: every allowlist entry warns as "stale". Noisy but not failing. |
Optional: detect "0 advisories AND non-empty allowlist" as a soft signal and emit ::warning::audit returned no advisories — possible API failure. |
.supply-chain/audit-allowlist.json |
23 entries each repeat the same reason string verbatim. Diff readability when the next PR3-style rev lands will be poor. |
Optional: consolidate to a per-package block, or reference a single rationale ID. The PR body wording "23 unique advisories" elides that several ids share a ghsa (npm splits by version range) — accurate, but worth flagging in the doc once. |
Open questions
- The PR body says branch-protection promotion is deferred to a post-merge admin step. Worth confirming whose calendar that lands on — without it, the gates run advisory forever, which is fine the first week and bad the third month. The task plan in the body covers it ("after merge + 1 green cycle"), but that's an unowned action item.
gitleaks.ymlpush-to-main runs--log-opts=\"--all\"with a 5-minute timeout. The repo's full history was scannable in commitc617be9; confirm the timeout is comfortable on a cold runner with binary download factored in.
Tanya-atatakai
left a comment
There was a problem hiding this comment.
LGTM — solid design overall. Approving; suggesting two small follow-ups before promoting these gates to required-status.
Strengths
- gitleaks SHA-256-pinned binary; comment explains the threat model exactly right
- Aggregator job (
all-checks-passed) is the right shape for stable branch-protection contract - Corepack version assertion catches silent yarn drift
- Allowlist schema enforcement (
id/reason/added/reviewrequired, date format validated) + bidirectional install-hook drift detection - Path-traversal-resistant scripting in
audit-install-hooks.mjswith explanatory comments - Trivial-hook regex with negative lookahead on shell metacharacters
Suggested follow-ups (non-blocking)
-
Pin
lockfile-lint(supply-chain.yml:232).npx --yes lockfile-lintdownloads latest on every run with no integrity check — same vector this PR is trying to gate. Pin a version (lockfile-lint@4.13.2) or add it as a root devDependency. Worth doing before promoting to required-status. -
Document root-only scope of audit + install-hook gates. Each subgraph has its own
node_modules; the gates only cover root.SUPPLY-CHAIN-SECURITY.md §7says "node-gyp-build and similar … are expected and allowlisted" but the allowlist is empty because graph-cli's transitives at root are pure JS — that distinction should be explicit in the doc so future maintainers don't assume coverage they don't have. Extending the matrix to all 13 paths is cheap (same shape aslockfile-lint).
Minor
audit.mjsusesshell: trueinspawnfor Windows compat — unnecessary on Ubuntu runners; consider dropping to reduce surface area if this script is ever copy-pasted with dynamic args.- 23 allowlist entries share identical
reasonboilerplate; quarterly review is easier with per-advisory specificity or a single grouped entry. .gitleaks.tomlallows any0x[a-fA-F0-9]{40}in babydegen mapper paths — fine since path scope is the real protection, but worth noting the regex shape itself isn't.- Expired allowlist entries print warnings only; nobody watches CI warnings. Consider promoting to failure after a grace period, or a scheduled cron that opens an issue.
gitleaks.ymlscan step doesn't set-euo pipefail. Last command propagates exit code so it works today; cheap insurance against future edits.
Threat model + response playbook in SUPPLY-CHAIN-SECURITY.md are unusually well-scoped for a CI hardening PR.
…date-3 # Conflicts: # .github/CODEOWNERS # .github/workflows/deploy-subgraph.yaml # .github/workflows/test.yaml # CLAUDE.md # subgraphs/babydegen/babydegen-optimism/yarn.lock # subgraphs/predict/predict-omen/yarn.lock # subgraphs/tokenomics-eth/yarn.lock
…ph-studio into mohan/supply-chain-update-3
…n workflow The merge-cascade commit `b810944` bumped `.nvmrc` from 22.18.0 to 24 to align with main's Node 24 preference (Tanya's PR #119 review). The root package.json's engines.node field was missed in that merge — left at "22.x" while .nvmrc said 24. `yarn install` enforces engines.node strictly. On CI: error autonolas-subgraph-studio@: The engine "node" is incompatible with this module. Expected version "22.x". Got "24.14.1" error Found incompatible module. This crashed `yarn install --frozen-lockfile` as the first step in both the `Dependency audit (root tree)` and `Install-hook audit` jobs in supply-chain.yml — surfaced as 2 CI failures on the b810944 push. Locally re-verified on Node 24.4.1: - yarn install --frozen-lockfile: clean - yarn audit:prod: OK (23 allowlisted, no unlisted) - yarn audit:install-hooks: OK (0 allowlisted) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Phase 3 (final) of the supply-chain hardening rollout (see
.plans/supply-chain-update.md, Tier 2 + parts of Tier 4). Stacked on #120, which stacks on #119. Will rebase tomainonce those merge.Adds the regression-prevention layer: every future PR is automatically audited for new vulnerabilities, new install hooks, lockfile drift, and leaked secrets. All new gates land advisory-only — branch protection isn't being changed here, so failing gates won't block merging until you promote them in repo Settings.
Closes T5 (future-leak detection) from the threat model.
Changes
Workflows (added)
.github/workflows/supply-chain.yml— runsyarn audit:prod+ install-hook gate at root, pluslockfile-lintmatrixed over all 13 paths, withall-checks-passedaggregator job..github/workflows/gitleaks.yml— secret scan on every push + PR, with SHA-256 verified gitleaks 8.30.1 binary (551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb, cross-checked against townhall-kpis and upstreamchecksums.txt).Scripts (added)
scripts/audit.mjs— wrapsyarn audit --jsonwith allowlist suppression. Critical: exposed asyarn audit:prod, NOTyarn audit(yarn 1.x's built-in shadows same-named scripts in package.json). Time-bounded suppression: each allowlist entry needsid,reason,added,review(all required); expired entries print a CI warning but don't fail.scripts/audit-install-hooks.mjs— enumerates packages innode_modulesdeclaring non-trivial pre/install/postinstall scripts, diffs against.supply-chain/install-hooks.allowlist. Drift in either direction (new hook OR removed hook) fails the job — the latter catches stale allowlist entries.Allowlists (added, baselined)
.supply-chain/audit-allowlist.json— 23 unique high/critical advisories baselined as transitive of@graphprotocol/graph-cli(latest stable0.98.1doesn't refresh them). 90-day review cadence..supply-chain/install-hooks.allowlist— empty. Notable finding: graph-cli's transitives at root are pure JS, so the install-hook attack surface at root is zero. Useful baseline for catching future regressions.Configuration (changed)
.nvmrc— pins Node22.18.0, resolves prior Node 20/24 drift betweentest.yaml(was 20) anddeploy-subgraph.yaml(was 24).package.json— addsengines.node: "22.x",packageManager: "yarn@1.22.22", andaudit:prod/audit:install-hooksscript aliases.node-version-file: .nvmrcand Corepack activation with version assertion (yarn --versionmust equal1.22.22or job fails). Also tightened deploy install to--frozen-lockfile.Documentation (added/updated)
SUPPLY-CHAIN-SECURITY.md— threat model (T1-T5), secrets inventory, controls reference, response playbook, repo-specific watches.CLAUDE.md— Supply chain & security section pointing at the docs + theaudit:prodnaming-collision warning + org-wide blast radius note..github/CODEOWNERS— expanded for.supply-chain/,scripts/,SUPPLY-CHAIN-SECURITY.md,.nvmrc.Dependabot deliberately not configured
Per scope discussion: this repo wants silent vulnerability alerts in the Security tab, not auto-PR spam from routine version updates.
Therefore
.github/dependabot.ymlis intentionally NOT added. To enable alerts:Documented in
SUPPLY-CHAIN-SECURITY.md§4.Verification (pre-merge)
Local gate runs
yarn audit:prodyarn audit:install-hookslockfile-lint × 13 pathsFailure-mode simulations (all gates fail-closed correctly)
::error::23 HIGH/CRITICAL advisory/advisories not allowlisted, exit 1 ✓::error::install-hook allowlist has entries no longer in the tree (drift), exit 1 ✓codeload.github.comsourcedetected invalid host(s), ✖ Error, exit 1 ✓All sandbox state was reverted after each test; nothing in this PR comes from the failure-mode runs.
Functional regressions check
PR2 already verified all 12 subgraphs pass
yarn install --frozen-lockfile && yarn graph codegen && yarn graph testat Node 22 (247/247 Matchstick tests). PR3 doesn't change subgraph code — only adds CI plumbing, scripts, and docs. The Node version pin via.nvmrcmatches what was tested locally.Operating notes
Branch protection settings change deferred (intentional)
Promoting the new gates to required-status checks is a separate Repo Settings UI change, not a code change. The plan was always to merge PR3 first, watch the gates run green on
mainfor at least one cycle, then add them to branch protection. This PR doesn't touch.github/branch-protection*or settings.To promote later: Repo Settings → Branches →
main→ Branch protection → Require status checks → addAll checks passed(thesupply-chain.ymlaggregator) andGitleaks / scan(cross-workflowneeds:is not supported — both contexts must be listed).Dependabot alerts (one-time admin toggle)
After this PR merges, an admin should enable Dependabot alerts in Settings (one click). The 23 allowlisted Highs will appear there too, and any new advisory will surface via both
audit:prod(CI fail) and the Security tab (silent alert).What's NOT in this PR (as agreed)
supply-chain.yml).RESPONSE.md+ drill (separate workstream).service-registrytemplate/manifest sync bug flagged in PR2 (pre-existing repo issue, not supply-chain).Test plan
yarn audit:prodpasses locally with current allowlist.yarn audit:install-hookspasses locally.lockfile-lintpasses on all 13 paths.All checks passedandGitleaks / scanto required-status checks in branch protection.Rollback
Each commit/file is independently revertible:
yarn audit:prodsimply won't be invokable until restored..nvmrc, restore previousnode-version: "20"and"24"literals in workflows.SUPPLY-CHAIN-SECURITY.md, CLAUDE.md edits, CODEOWNERS expansion) are pure-additive.Stacked on
This PR's base is
mohan/supply-chain-update-2. Once #120 merges tomain, this PR's base auto-rebases. While #120 is open, the diff stays scoped to PR3's contents only.