From 8e3eec6ef50b760838837bd22902752aa8d82479 Mon Sep 17 00:00:00 2001 From: Rashad Jayshanth-Nizar Date: Fri, 26 Jun 2026 12:28:46 -0400 Subject: [PATCH] feat(root): self-contained scheduled iyarc-prune workflow Ticket: HSM-429 --- .github/prompts/iyarc-prune.md | 141 ++++++++++++++++ .github/workflows/iyarc-prune.yml | 85 ++++++++++ .github/workflows/nightshift-scheduler.yaml | 19 --- .github/workflows/nightshift-task.yaml | 20 --- .nightshift.yaml | 168 -------------------- 5 files changed, 226 insertions(+), 207 deletions(-) create mode 100644 .github/prompts/iyarc-prune.md create mode 100644 .github/workflows/iyarc-prune.yml delete mode 100644 .github/workflows/nightshift-scheduler.yaml delete mode 100644 .github/workflows/nightshift-task.yaml delete mode 100644 .nightshift.yaml diff --git a/.github/prompts/iyarc-prune.md b/.github/prompts/iyarc-prune.md new file mode 100644 index 0000000000..5493b1a01f --- /dev/null +++ b/.github/prompts/iyarc-prune.md @@ -0,0 +1,141 @@ +You are an automated maintenance agent for the BitGoJS monorepo, run on a +schedule by GitHub Actions. 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 +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 + 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 -v ` (see + `scripts/upgrade-workspace-dependency.ts`). It ONLY scans module manifests for + DIRECT deps, so for a transitive dep it will print "No packages found" and do + nothing — that is expected; fall back to a root `resolutions` edit. Also note + `upgrade-dep` runs a plain `yarn install` (full `postinstall` monorepo build) + UNLESS you pass `--ignore-scripts`; always pass `--ignore-scripts` to stay + within the runner time budget. + +## Early exit (do this first) + +If an open PR already exists on a branch matching `iyarc-prune/*`, stop and +report — do not open a second: + + gh pr list --state open --search "head:iyarc-prune/" + +## Read context first + +Before changing anything, read: +1. `.iyarc` — 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`: + +1. Identify the affected package and the path that pulls it in. The + justification comment usually names both; confirm with `yarn why `. +2. Determine whether a PATCHED version now exists and is reachable for us + (`yarn info versions`, the GitHub advisory's first-patched version, + registry metadata). +3. Decide whether to attempt a fix: + - SKIP if the justification is "no upstream fix exists" / patched range is + `<0.0.0` (e.g. `sanitize-html` GHSA-rpr9-rxv7-x643, `sjcl` + GHSA-2w8x-224x-785m) UNLESS a real fix has since shipped. + - SKIP if the only available fix requires a major bump of a pinning parent + (e.g. `tar` / `minimatch` pinned by `lerna` / `yeoman-generator`) AND that + bump is incompatible. Record it under "Still blocked" in the report. + - Otherwise, attempt the bump. + +## Attempt a fix (per removable exclusion) + +1. Bump compatibly: + - Transitive dep controlled by root `resolutions` (the common case): update + the pin in the root `package.json` `resolutions` block. + - Direct dependency (rare here): `yarn upgrade-dep -p -v --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. + +## 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. +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 + failure here means the PR would be rejected anyway. +3. Build and unit-test the affected module(s) only (keep within the runner time + budget — do NOT build/test the whole monorepo): + `yarn lerna run build --scope ` and `yarn lerna run unit-test --scope `. +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. + +## Commit and pull request (only if at least one exclusion was removed with a +fully green feedback loop) + +- Commit message: conventional (commitlint extends `@commitlint/config-conventional`; + `deps` and `root` are valid scopes), e.g.: + `chore(deps): bump to , drop from .iyarc`. + 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. + - 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/`); + 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 + missing one with `gh label create --force`), then apply all three. +- Assign the PR to `gokulhost` so it does not get lost: + `gh pr edit --add-assignee gokulhost`. CODEOWNERS reviewers are + assigned automatically and separately. +- 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. + - 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). + - Only if you hit the signing fallback above: the unsigned-commits note. + +## Output 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 + `yarn.lock`. Do not modify product/source code. diff --git a/.github/workflows/iyarc-prune.yml b/.github/workflows/iyarc-prune.yml new file mode 100644 index 0000000000..dbda2496d1 --- /dev/null +++ b/.github/workflows/iyarc-prune.yml @@ -0,0 +1,85 @@ +name: iyarc-prune +run-name: 'iyarc-prune' + +on: + schedule: + - cron: '0 6 * * 1' # Mondays 06:00 UTC (weekly) + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + issues: write + id-token: write + +concurrency: + group: iyarc-prune + cancel-in-progress: false + +jobs: + prune: + runs-on: ubuntu-latest + timeout-minutes: 60 + env: + AWS_REGION: us-west-2 + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - name: Enable Corepack (yarn 1.22.22) + run: corepack enable + + - name: Configure AWS Credentials (OIDC) + uses: aws-actions/configure-aws-credentials@v6 + with: + role-to-assume: arn:aws:iam::199765120567:role/${{ github.event.repository.name }}-iam-protected + aws-region: us-west-2 + + - name: Assume Bedrock inference role + id: inference-role + run: | + CREDS="$(aws sts assume-role \ + --role-arn arn:aws:iam::168000258654:role/BedrockInferenceRole \ + --role-session-name iyarc-prune-session \ + --query 'Credentials' \ + --output json)" + + AWS_ACCESS_KEY_ID="$(echo "$CREDS" | jq -r '.AccessKeyId')" + AWS_SECRET_ACCESS_KEY="$(echo "$CREDS" | jq -r '.SecretAccessKey')" + AWS_SESSION_TOKEN="$(echo "$CREDS" | jq -r '.SessionToken')" + + echo "::add-mask::$AWS_SECRET_ACCESS_KEY" + { echo "aws-access-key-id=$AWS_ACCESS_KEY_ID"; echo "aws-secret-access-key=$AWS_SECRET_ACCESS_KEY"; echo "aws-session-token=$AWS_SESSION_TOKEN"; } >> "$GITHUB_OUTPUT" + + - name: Read prune prompt + id: read-prompt + run: | + { + echo "prompt<> "$GITHUB_OUTPUT" + + - name: Run Claude Code (Bedrock) + uses: anthropics/claude-code-action@v1 + with: + prompt: ${{ steps.read-prompt.outputs.prompt }} + use_commit_signing: 'true' + branch_prefix: 'iyarc-prune/' + claude_args: --allowed-tools 'Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash,mcp__github_file_ops__commit_files,mcp__github_file_ops__delete_files' + use_bedrock: 'true' + github_token: ${{ secrets.GITHUB_TOKEN }} + env: + ANTHROPIC_MODEL: arn:aws:bedrock:us-west-2:168000258654:inference-profile/us.anthropic.claude-sonnet-4-20250514-v1:0 + AWS_REGION: us-west-2 + AWS_ACCESS_KEY_ID: ${{ steps.inference-role.outputs.aws-access-key-id }} + AWS_SECRET_ACCESS_KEY: ${{ steps.inference-role.outputs.aws-secret-access-key }} + AWS_SESSION_TOKEN: ${{ steps.inference-role.outputs.aws-session-token }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/nightshift-scheduler.yaml b/.github/workflows/nightshift-scheduler.yaml deleted file mode 100644 index 22a10d1201..0000000000 --- a/.github/workflows/nightshift-scheduler.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: nightshift-scheduler -run-name: 'nightshift scheduler' - -on: - schedule: - - cron: '0 6 * * 1-5' # weekdays 06:00 UTC (168h cooldown gates to ~weekly) - workflow_dispatch: - inputs: - dry_run: - description: 'List tasks that would run without executing them' - required: false - default: false - type: boolean - -jobs: - schedule: - uses: BitGo/nightshift-actions/.github/workflows/nightshift-scheduler.yaml@v1 - with: - dry_run: ${{ inputs.dry_run || false }} diff --git a/.github/workflows/nightshift-task.yaml b/.github/workflows/nightshift-task.yaml deleted file mode 100644 index f54d576d76..0000000000 --- a/.github/workflows/nightshift-task.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: nightshift-task -run-name: 'nightshift: ${{ inputs.task_type }}' - -on: - workflow_dispatch: - inputs: - task_type: - description: 'Task type to run' - required: true - type: string - -jobs: - run: - uses: BitGo/nightshift-actions/.github/workflows/nightshift-task.yaml@v1 - with: - task_type: ${{ inputs.task_type }} - provider: bedrock - sign_commits: true - secrets: - VAULT_CF_ACCESS_CLIENT_SECRET: ${{ secrets.VAULT_CF_ACCESS_CLIENT_SECRET }} diff --git a/.nightshift.yaml b/.nightshift.yaml deleted file mode 100644 index 3923640717..0000000000 --- a/.nightshift.yaml +++ /dev/null @@ -1,168 +0,0 @@ -max_tasks_per_run: 1 - -tasks: - enabled: - - iyarc-prune - - priorities: - iyarc-prune: 8 - - cooldowns: - iyarc-prune: '168h' - - custom: - - type: iyarc-prune - name: Audit Exclusion (.iyarc) Pruner - category: pr - cooldown: '168h' - priority: 8 - prompt: | - You are a nightshift automated maintenance agent for the BitGoJS - monorepo. 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, once per cycle, is to find - exclusions that can now be safely removed, bump the relevant dependency, - prove the fix passes the release audit gate plus build/test, and open a - single pull request. Most cycles will legitimately produce NO PR — a - "nothing prunable this cycle" 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 is pinned via `.nvmrc`. - - 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 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 -v ` (see - `scripts/upgrade-workspace-dependency.ts`). It ONLY scans module - manifests for DIRECT deps, so for a transitive dep it will print - "No packages found" and do nothing — that is expected; fall back to a - root `resolutions` edit. Also note `upgrade-dep` runs a plain - `yarn install` (full `postinstall` monorepo build) UNLESS you pass - `--ignore-scripts`; always pass `--ignore-scripts` to stay within the - runner time budget. - - ## Early exit (do this first) - - If an open PR already exists on a branch matching - `nightshift/iyarc-prune-*`, stop and report — do not open a second: - - gh pr list --state open --search "head:nightshift/iyarc-prune" - - ## Read context first - - Before changing anything, read: - 1. `.iyarc` — 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`: - - 1. Identify the affected package and the path that pulls it in. The - justification comment usually names both; confirm with `yarn why `. - 2. Determine whether a PATCHED version now exists and is reachable for us - (`yarn info versions`, the GitHub advisory's first-patched - version, registry metadata). - 3. Decide whether to attempt a fix: - - SKIP if the justification is "no upstream fix exists" / patched - range is `<0.0.0` (e.g. `sanitize-html` GHSA-rpr9-rxv7-x643, `sjcl` - GHSA-2w8x-224x-785m) UNLESS a real fix has since shipped. - - SKIP if the only available fix requires a major bump of a pinning - parent (e.g. `tar` / `minimatch` pinned by `lerna` / - `yeoman-generator`) AND that bump is incompatible. Record it under - "Still blocked" in the report. - - Otherwise, attempt the bump. - - ## Attempt a fix (per removable exclusion) - - 1. Bump compatibly: - - Transitive dep controlled by root `resolutions` (the common case): - update the pin in the root `package.json` `resolutions` block. - - Direct dependency (rare here): `yarn upgrade-dep -p - -v --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. - - ## Feedback loop / proof (abandon on failure) - - After each attempted fix, run the SAME gates the release pipeline runs, - in this order: - 1. Run the release gate: `yarn run audit-high`. It MUST pass with the - exclusion removed. Capture the output. - 2. Run `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 failure here means the PR would be rejected anyway. - 3. Build and unit-test the affected module(s) only (keep within the - runner time budget — do NOT build/test the whole monorepo): - `yarn lerna run build --scope ` and - `yarn lerna run unit-test --scope `. - 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. - - ## Commit and pull request (only if at least one exclusion was removed - with a fully green feedback loop) - - - Branch: `nightshift/iyarc-prune-YYYY-MM-DD` (use today's date). - - Conventional commit (commitlint extends `@commitlint/config-conventional`; - `deps` and `root` are valid scopes), e.g.: - `chore(deps): bump to , drop from .iyarc`. - commitlint enforces `references-empty: never`, so the commit MUST carry - an issue reference: include `Ticket: HSM-410` in the footer. - - Open a single NON-draft PR. Labels: `nightshift`, `automated`, - `dependencies`. - - Assign the PR to `gokulhost` so it does not get lost: - `gh pr edit --add-assignee gokulhost`. CODEOWNERS reviewers are - assigned automatically and separately. - - 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. - - 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). - - ## Output rules - - - If nothing is safely prunable this cycle, open no PR and report - "no exclusions prunable this cycle" in the job summary, including the - "Still blocked" breakdown so reviewers see what was evaluated. - - Only ever modify `.iyarc`, dependency manifests (`package.json`), - and `yarn.lock`. Do not modify product/source code. - -labels: - - nightshift - - automated - - dependencies - -pull_requests: - draft: false - branch_prefix: 'nightshift/' - labels: - - nightshift - - automated - - dependencies