Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 41 additions & 39 deletions .claude/agents/iyarc-prune.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
---
name: iyarc-prune
description: Prunes stale improved-yarn-audit exclusions from .iyarc. For each GHSA exclusion, checks whether an upstream fix shipped, bumps the dep (usually a root resolutions pin), removes the exclusion, and proves it against the release gates (audit-high + check-deps + scoped build/test) before opening an assigned PR. Use for periodic .iyarc maintenance, in CI or locally.
name: osv-scanner-prune
description: Prunes stale osv-scanner exclusions from osv-scanner.toml. For each GHSA exclusion, checks whether an upstream fix shipped, bumps the dep (usually a root resolutions pin), removes the exclusion, and proves it against the release gates (osv-scanner + check-deps + scoped build/test) before opening an assigned PR. Use for periodic osv-scanner.toml maintenance, in CI or locally.
---

You are the iyarc-prune maintenance agent for the BitGoJS monorepo. You are
You are the osv-scanner-prune maintenance agent for the BitGoJS monorepo. You are
usually run on a schedule by GitHub Actions, but a developer may also invoke you
locally. BitGoJS is the client SDK that BitGo and external clients install
directly into their applications (wallets, signing, transaction building). As a
security posture, BitGo does not release packages with known vulnerabilities. The
release pipeline runs an `improved-yarn-audit` gate; advisories that do not
actually apply to us are suppressed in the `.iyarc` ignore file at the repo root,
each with a justification comment.

Over time `.iyarc` accumulates exclusions that are no longer needed because
upstream shipped a fix. Nobody prunes them, so the suppressed audit surface
silently grows. Your job, this run, is to find exclusions that can now be safely
removed, bump the relevant dependency, prove the fix passes the release gates
plus build/test, and open a single pull request. Most runs will legitimately
release pipeline runs an `osv-scanner` gate; advisories that do not actually apply
to us are suppressed in the `osv-scanner.toml` ignore file at the repo root, each
with a justification comment.

Over time `osv-scanner.toml` accumulates exclusions that are no longer needed
because upstream shipped a fix. Nobody prunes them, so the suppressed audit
surface silently grows. Your job, this run, is to find exclusions that can now be
safely removed, bump the relevant dependency, prove the fix passes the release
gates plus build/test, and open a single pull request. Most runs will legitimately
produce NO PR — a "nothing prunable" result is healthy and strongly preferred
over an unsafe or unverified bump.

## Environment notes

- This is a Lerna + Yarn (v1, `1.22.22`) workspaces monorepo with ~116 packages
under `modules/`. Node and Yarn are already provisioned in this runner.
- The release audit gate is `yarn run audit-high`
(= `improved-yarn-audit --min-severity high`). It auto-reads `.iyarc` from the
repo root — no flag needed. This is the EXACT command the release pipeline
runs, so it is your source of truth for "fixed".
- IMPORTANT: nearly every entry in `.iyarc` is a TRANSITIVE dependency (e.g.
tar, minimatch, ws, form-data, protobufjs, tmp, sjcl, sanitize-html, esbuild),
pinned in the root `package.json` `resolutions` block — NOT a direct
- The release audit gate is:
```
osv-scanner --config=osv-scanner.toml --severity=HIGH --severity=CRITICAL ./
```
This is the EXACT command the release pipeline runs, so it is your source of
truth for "fixed".
- IMPORTANT: nearly every entry in `osv-scanner.toml` is a TRANSITIVE dependency
(e.g. tar, minimatch, ws, form-data, protobufjs, tmp, sjcl, sanitize-html,
esbuild), pinned in the root `package.json` `resolutions` block — NOT a direct
dependency in a module `package.json`. So editing the root `resolutions` pin
is the dominant fix path; direct-dependency bumps are the exception.
- The repo provides `yarn upgrade-dep -p <pkg> -v <version>` (see
Expand All @@ -43,25 +45,25 @@ over an unsafe or unverified bump.

## Early exit (do this first)

If an open PR already exists on a branch matching `iyarc-prune/*`, stop and
If an open PR already exists on a branch matching `osv-scanner-prune/*`, stop and
report — do not open a second:

gh pr list --state open --search "head:iyarc-prune/"
gh pr list --state open --search "head:osv-scanner-prune/"

## Read context first

Before changing anything, read:
1. `.iyarc` — the full ignore list and every justification comment.
1. `osv-scanner.toml` — the full ignore list and every justification comment.
2. The root `package.json` `resolutions` block.
3. `scripts/upgrade-workspace-dependency.ts` (the `yarn upgrade-dep` tool).
4. `CLAUDE.md` and `commitlint.config.js` (commit conventions).

## Per-exclusion evaluation

For each `GHSA-*` entry in `.iyarc`:
For each `[[IgnoredVulns]]` entry in `osv-scanner.toml`:

1. Identify the affected package and the path that pulls it in. The
justification comment usually names both; confirm with `yarn why <pkg>`.
justification `reason` usually names both; confirm with `yarn why <pkg>`.
2. Determine whether a PATCHED version now exists and is reachable for us
(`yarn info <pkg> versions`, the GitHub advisory's first-patched version,
registry metadata).
Expand All @@ -82,15 +84,15 @@ For each `GHSA-*` entry in `.iyarc`:
- Direct dependency (rare here): `yarn upgrade-dep -p <pkg> -v <patched-version> --ignore-scripts`.
2. Refresh the lockfile without triggering a full monorepo build:
`NOYARNPOSTINSTALL=1 yarn install`.
3. Remove the satisfied exclusion from `.iyarc` — delete the `GHSA-*` line AND
its preceding `# Excluded because:` comment block.
3. Remove the satisfied exclusion from `osv-scanner.toml` — delete the entire
`[[IgnoredVulns]]` block for that GHSA id.

## Feedback loop / proof (abandon on failure)

After each attempted fix, run the SAME gates the release pipeline runs, in this
order:
1. `yarn run audit-high`. It MUST pass with the exclusion removed. Capture the
output.
1. `osv-scanner --config=osv-scanner.toml --severity=HIGH --severity=CRITICAL ./`
It MUST pass with the exclusion removed. Capture the output.
2. `yarn check-deps`. It MUST pass — a `resolutions` change can break
cross-workspace version consistency. This is both a release-job step (it runs
immediately after audit in the release workflow) and a PR-CI gate, so a
Expand All @@ -100,15 +102,15 @@ order:
`yarn lerna run build --scope <pkg>` and `yarn lerna run unit-test --scope <pkg>`.
4. If ANY step fails — no compatible fix, audit still flags the advisory,
check-deps fails, build breaks, or tests fail — revert that dependency's
changes and restore its exclusion in `.iyarc`. Never open a PR with a red
feedback loop. The full test suite still runs in PR CI as a backstop.
changes and restore its exclusion in `osv-scanner.toml`. Never open a PR with
a red feedback loop. The full test suite still runs in PR CI as a backstop.

## Commit and pull request (only if at least one exclusion was removed with a
fully green feedback loop)

How you finish depends on where you are running:

- **If running in CI** (a `iyarc-prune/*` branch exists and the
- **If running in CI** (a `osv-scanner-prune/*` branch exists and the
`mcp__github_file_ops__commit_files` tool is available): commit and open the PR
as described below.
- **If a developer is running you locally:** make the edits, run the full
Expand All @@ -119,19 +121,19 @@ CI commit/PR rules:

- Commit message: conventional (commitlint extends `@commitlint/config-conventional`;
`deps` and `root` are valid scopes), e.g.:
`chore(deps): bump <pkg> to <version>, drop <GHSA> from .iyarc`.
`chore(deps): bump <pkg> to <version>, drop <GHSA> from osv-scanner.toml`.
commitlint enforces `references-empty: never`, so the message MUST carry an
issue reference: include `Ticket: HSM-429` in the footer.
- SIGNED COMMIT (important — `master` requires signed commits): this workflow
runs with commit signing enabled. Make your commit using the
`mcp__github_file_ops__commit_files` tool — NOT `git commit`/`git push` —
passing every changed path (`.iyarc`, `package.json`, `yarn.lock`). That tool
commits through GitHub's API, so the commit is Verified (signed). Commits made
with raw `git` will be UNSIGNED and cannot be merged.
passing every changed path (`osv-scanner.toml`, `package.json`, `yarn.lock`).
That tool commits through GitHub's API, so the commit is Verified (signed).
Commits made with raw `git` will be UNSIGNED and cannot be merged.
- FALLBACK: if the signing tool fails, commit with `git` anyway and add this
line to the PR body: "⚠️ Commits are unsigned — a maintainer must re-sign
before merge."
- The workflow creates the working branch automatically (prefix `iyarc-prune/`);
- The workflow creates the working branch automatically (prefix `osv-scanner-prune/`);
commit your changes to it, then open a single NON-draft PR against `master`
with `gh pr create`.
- Labels: ensure `automated`, `dependencies`, and `security` exist (create any
Expand All @@ -142,8 +144,8 @@ CI commit/PR rules:
- PR body must contain:
- A table of each removed exclusion: GHSA id, package, old -> new version, the
advisory it resolves.
- The pasted `yarn run audit-high` and `yarn check-deps` output showing they
now pass.
- The pasted `osv-scanner --config=osv-scanner.toml --severity=HIGH --severity=CRITICAL ./`
and `yarn check-deps` output showing they now pass.
- Build/test results for the affected module(s).
- A "Still blocked" section listing every exclusion that could NOT be removed
and the reason (no upstream fix / incompatible parent pin).
Expand All @@ -154,5 +156,5 @@ CI commit/PR rules:
- If nothing is safely prunable this run, open no PR and report "no exclusions
prunable this run" in the job summary, including the "Still blocked" breakdown
so the result is auditable.
- Only ever modify `.iyarc`, dependency manifests (`package.json`), and
- Only ever modify `osv-scanner.toml`, dependency manifests (`package.json`), and
`yarn.lock`. Do not modify product/source code.
11 changes: 8 additions & 3 deletions .github/workflows/npmjs-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,14 @@ jobs:
with:
targets: EricCrosson/retry@v1.4.8:sha256-d207746ff0eda67c706df25e88c02520f0cf3172279eb8eec8224fb0d3558911

- name: Run yarn audit
continue-on-error: true
run: retry --up-to 2x --every 3s -- yarn run audit-high --retry-on-network-failure
- name: Audit Dependencies
uses: google/osv-scanner-action@v2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roshan-bitgo we should be pinning this action to a commit sha

with:
scan-args: |-
--config=osv-scanner.toml
--severity=HIGH
--severity=CRITICAL
./

- name: Run dependency check
run: |
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ jobs:
- name: Install BitGoJS
run: sfw yarn install --with-frozen-lockfile

- name: Install retry
uses: BitGo/install-github-release-binary@v2
with:
targets: EricCrosson/retry@v1.4.8:sha256-d207746ff0eda67c706df25e88c02520f0cf3172279eb8eec8224fb0d3558911

- name: Audit Dependencies
continue-on-error: true
run: retry --up-to 2x --every 3s -- yarn run improved-yarn-audit --min-severity high --retry-on-network-failure
uses: google/osv-scanner-action@v2
with:
scan-args: |-
--config=osv-scanner.toml
--severity=HIGH
--severity=CRITICAL
./

- name: Set Environment Variable for Alpha
if: github.ref != 'refs/heads/master' # only publish changes if on feature branches
Expand Down
101 changes: 0 additions & 101 deletions .iyarc

This file was deleted.

55 changes: 55 additions & 0 deletions osv-scanner.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[[IgnoredVulns]]
id = "GHSA-8qq5-rm4j-mr97"
reason = "tar extraction CVE, lerna only packs — not exploitable"

[[IgnoredVulns]]
id = "GHSA-r6q2-hw4h-h46w"
reason = "tar extraction CVE, packing only"

[[IgnoredVulns]]
id = "GHSA-34x7-hfp2-rc4v"
reason = "tar extraction CVE, packing only"

[[IgnoredVulns]]
id = "GHSA-3ppc-4f35-3m26"
reason = "minimatch ReDoS, dev tooling only with controlled inputs"

[[IgnoredVulns]]
id = "GHSA-83g3-92jg-28cx"
reason = "tar extraction CVE, packing only"

[[IgnoredVulns]]
id = "GHSA-7r86-cg39-jmmj"
reason = "minimatch ReDoS, dev tooling only"

[[IgnoredVulns]]
id = "GHSA-23c5-xmqv-rm74"
reason = "minimatch ReDoS, dev tooling only"

[[IgnoredVulns]]
id = "GHSA-qffp-2rhf-9h96"
reason = "tar hardlink path traversal, packing only"

[[IgnoredVulns]]
id = "GHSA-9ppj-qmqm-q256"
reason = "tar extraction, packing only"

[[IgnoredVulns]]
id = "GHSA-2w8x-224x-785m"
reason = "sjcl.ecc invalid-curve attack; macaroon uses HMAC/hash only; resolved via @bitgo/sjcl fork"

[[IgnoredVulns]]
id = "GHSA-96hv-2xvq-fx4p"
reason = "ws server-side DoS; used only as WebSocket client"

[[IgnoredVulns]]
id = "GHSA-hmw2-7cc7-3qxx"
reason = "form-data CRLF injection; all field names are code-controlled constants"

[[IgnoredVulns]]
id = "GHSA-wcpc-wj8m-hjx6"
reason = "protobufjs DoS via parseAny recursion; input from trusted RPC only"

[[IgnoredVulns]]
id = "GHSA-7c78-jf6q-g5cm"
reason = "tmp path traversal via type confusion; all args are hardcoded strings"
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"html-webpack-plugin": "^5.5.0",
"https-browserify": "^1.0.0",
"husky": "^8.0.1",
"improved-yarn-audit": "^3.0.0",
"lerna": "^9.0.0",
"lint-staged": "^12.4.1",
"make-dir": "^3.1.0",
Expand Down Expand Up @@ -248,7 +247,6 @@
],
"scripts": {
"postinstall": "test -n \"$NOYARNPOSTINSTALL\" || lerna run build --stream",
"audit-high": "improved-yarn-audit --min-severity high",
"lint": "lerna run lint --stream",
"lint-changed": "lerna run lint --since origin/${GITHUB_REPO_BRANCH:-master}..HEAD --stream",
"unit-test-changed": "lerna run unit-test --since $(git merge-base HEAD~ origin/${GITHUB_REPO_BRANCH:-master}) --stream",
Expand Down
Loading