From f082630764d3b2cf8c97c939eebab1551531c08e Mon Sep 17 00:00:00 2001 From: Zahin Mohammad Date: Wed, 28 Oct 2026 13:59:08 -0400 Subject: [PATCH 1/3] fix(root): publish validated iyarc-prune pull requests Ticket: WAL-2232 --- .claude/agents/iyarc-prune.md | 89 ++++++++++++++----------------- .github/workflows/iyarc-prune.yml | 81 +++++++++++++++++++++++++--- 2 files changed, 116 insertions(+), 54 deletions(-) diff --git a/.claude/agents/iyarc-prune.md b/.claude/agents/iyarc-prune.md index 61c1057d3b7..d717f5ff8d8 100644 --- a/.claude/agents/iyarc-prune.md +++ b/.claude/agents/iyarc-prune.md @@ -24,12 +24,19 @@ over an unsafe or unverified bump. - 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: +- The release audit uses `osv-scanner` v2.3.8 and writes JSON results: ``` - osv-scanner --config=osv-scanner.toml --severity=HIGH --severity=CRITICAL ./ + osv-scanner --config=osv-scanner.toml --format=json --output-file="$RUNNER_TEMP/osv-results.json" ./ ``` - This is the EXACT command the release pipeline runs, so it is your source of - truth for "fixed". + For a local run, substitute a temporary directory for `$RUNNER_TEMP`. + The scanner has no `--severity` flag. Exit code 1 means findings, not + necessarily a failed release gate. Evaluate the JSON using the shell step in + `.github/actions/osv-severity-gate/action.yml`, with `SCAN_OUTCOME=success` + for exit code 0 or `SCAN_OUTCOME=failure` for exit code 1, `RESULTS_FILE` set + to that output path, and `CVSS_THRESHOLD=7.0`. CVSS (Common Vulnerability + Scoring System) scores of 7.0 or above block release. Exit codes above 1 + are scanner errors and MUST fail validation. Read the current release steps + in `.github/workflows/npmjs-release.yml` before running the audit. - 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 @@ -52,7 +59,7 @@ behind, still exists. Run this check and decide on the parsed state, never on "a PR row exists": - gh pr list --state open --json number,state,headRefName --jq '.[] | select(.headRefName | startswith("osv-scanner-prune/"))' + gh pr list --state open --limit 1000 --json number,state,headRefName --jq '.[] | select(.headRefName | startswith("osv-scanner-prune/"))' - Non-empty output: an open prune PR exists — stop and report — do not open a second. Name the open PR number(s) and head branch(es) in the report. @@ -65,6 +72,7 @@ from merged prune PRs linger until deleted and must not trigger this early exit. ## Read context first Before changing anything, read: + 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). @@ -103,8 +111,9 @@ For each `[[IgnoredVulns]]` entry in `osv-scanner.toml`: After each attempted fix, run the SAME gates the release pipeline runs, in this order: -1. `osv-scanner --config=osv-scanner.toml --severity=HIGH --severity=CRITICAL ./` - It MUST pass with the exclusion removed. Capture the output. + +1. Run the pinned scanner and severity gate described above. The gate MUST + pass with the exclusion removed. Capture the scanner and gate 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 @@ -117,51 +126,33 @@ order: 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 `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 - feedback loop, print the summary table and the "Still blocked" section, and - STOP. Do not commit or open a PR — let the developer review and commit. - -CI commit/PR rules: - -- Commit message: conventional (commitlint extends `@commitlint/config-conventional`; - `deps` and `root` are valid scopes), e.g.: - `chore(deps): bump to , drop 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 (`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 `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 - 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: +## Commit and pull request (only after validated exclusion removals) + +The workflow owns branch creation, signed commits, and pull request creation. +Do not create a branch, commit, push, or open a pull request yourself. + +- **When `IYARC_PRUNE_PR_BODY` is set:** you are running under the publication + workflow, even when the checked-out branch is `master`. Leave the validated + edits uncommitted and write the pull request body to the absolute path in + `IYARC_PRUNE_PR_BODY`. Create this file ONLY after at least one exclusion has + been removed and the entire feedback loop has passed. If an open prune pull + request exists or nothing can be safely removed, do not create this file. + If validation fails, revert the failed changes before finishing. +- **When `IYARC_PRUNE_PR_BODY` is unset:** this is a local run. Make the edits, + run the feedback loop, print the summary table and the "Still blocked" + section, and stop so the developer can review and commit. +- The workflow creates a signed commit with the `Ticket: HSM-429` footer and + a non-draft pull request against `master`, using an `osv-scanner-prune/*` + branch. It applies `automated`, `dependencies`, and `security` labels and + assigns `gokulhost`. +- The pull request body must contain: - A table of each removed exclusion: GHSA id, package, old -> new version, the advisory it resolves. - - The pasted `osv-scanner --config=osv-scanner.toml --severity=HIGH --severity=CRITICAL ./` - and `yarn check-deps` output showing they now pass. + - The pasted scanner, severity-gate, 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 @@ -169,4 +160,6 @@ CI commit/PR rules: prunable this run" in the job summary, including the "Still blocked" breakdown so the result is auditable. - Only ever modify `osv-scanner.toml`, dependency manifests (`package.json`), and - `yarn.lock`. Do not modify product/source code. + `yarn.lock` in the repository. The report at `IYARC_PRUNE_PR_BODY` and temporary + validation output belong outside the repository. Do not modify product/source + code. diff --git a/.github/workflows/iyarc-prune.yml b/.github/workflows/iyarc-prune.yml index 60fbd2d6768..4ca367ae335 100644 --- a/.github/workflows/iyarc-prune.yml +++ b/.github/workflows/iyarc-prune.yml @@ -26,6 +26,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 with: + ref: master fetch-depth: 0 - name: Setup Node @@ -73,12 +74,11 @@ jobs: uses: anthropics/claude-code-action@8d70a0dd265122bd65b8e09d3d959e65373ae2a1 # v1.0.260 with: prompt: ${{ steps.agent.outputs.content }} - use_commit_signing: 'true' - branch_prefix: 'osv-scanner-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' + claude_args: --allowed-tools 'Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash' use_bedrock: 'true' github_token: ${{ secrets.GITHUB_TOKEN }} env: + IYARC_PRUNE_PR_BODY: ${{ runner.temp }}/iyarc-prune-pr.md ANTHROPIC_MODEL: arn:aws:bedrock:us-west-2:168000258654:inference-profile/global.anthropic.claude-sonnet-5-5 # Bedrock: replaying a tool-call turn requires a leading thinking block; disable to avoid 400. MAX_THINKING_TOKENS: '0' @@ -137,7 +137,7 @@ jobs: fi } >> "$GITHUB_STEP_SUMMARY" - - name: Surface Claude error and fail on is_error + - name: Require successful Claude completion if: always() env: EXECUTION_FILE: ${{ steps.claude.outputs.execution_file }} @@ -152,6 +152,75 @@ jobs: echo "=== assistant text turns (no tool output) ===" echo "$EVENTS" | jq -r '.[] | select(.type=="assistant") | .message.content[]? | select(.type=="text") | .text' RES="$(echo "$EVENTS" | jq -c 'map(select(.type=="result"))[-1] // empty')" - if [ "$(echo "$RES" | jq -r '.is_error // false')" = "true" ]; then - echo "::error::Claude returned is_error=true"; exit 1 + if ! echo "$RES" | jq -e '.is_error == false and .subtype == "success"' >/dev/null; then + echo "::error::Claude did not produce a successful result"; exit 1 fi + + - name: Check changes are ready for a pull request + id: changes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + IYARC_PRUNE_PR_BODY: ${{ runner.temp }}/iyarc-prune-pr.md + run: | + set -euo pipefail + CHANGED_FILES="$(git diff --name-only HEAD)" + if [ -z "$CHANGED_FILES" ]; then + echo "No dependency changes to publish." >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + while IFS= read -r path; do + case "$path" in + osv-scanner.toml|package.json|yarn.lock|modules/*/package.json) ;; + *) echo "::error::Unexpected changed file: $path"; exit 1 ;; + esac + done <<< "$CHANGED_FILES" + if git diff --quiet HEAD -- osv-scanner.toml; then + echo "::error::Dependency changes exist without an osv-scanner.toml change"; exit 1 + fi + if [ ! -s "$IYARC_PRUNE_PR_BODY" ]; then + echo "::error::Changes exist but the agent did not produce a validated pull request report"; exit 1 + fi + OPEN_PRS="$(gh pr list --repo "$GITHUB_REPOSITORY" --state open --limit 1000 \ + --json number,headRefName --jq '.[] | select(.headRefName | startswith("osv-scanner-prune/"))')" + if [ -n "$OPEN_PRS" ]; then + echo "An open prune pull request already exists: $OPEN_PRS" >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + for label in automated dependencies security; do + gh label create "$label" --repo "$GITHUB_REPOSITORY" --force + done + echo "ready=true" >> "$GITHUB_OUTPUT" + + - name: Create signed dependency pull request + if: steps.changes.outputs.ready == 'true' + id: pull-request + uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + sign-commits: true + branch: osv-scanner-prune/${{ github.run_id }} + base: master + add-paths: | + osv-scanner.toml + package.json + yarn.lock + modules/*/package.json + commit-message: | + chore(deps): prune resolved vulnerability exclusions + + Ticket: HSM-429 + title: 'chore(deps): prune resolved vulnerability exclusions' + body-path: ${{ runner.temp }}/iyarc-prune-pr.md + labels: automated,dependencies,security + assignees: gokulhost + draft: false + + - name: Report pull request + if: steps.changes.outputs.ready == 'true' + env: + PR_URL: ${{ steps.pull-request.outputs.pull-request-url }} + run: | + if [ -z "$PR_URL" ]; then + echo "::error::Validated dependency changes did not produce a pull request"; exit 1 + fi + echo "Pull request: $PR_URL" >> "$GITHUB_STEP_SUMMARY" From 70c3b4a066703faae3e36566b919e91147811f8e Mon Sep 17 00:00:00 2001 From: Zahin Mohammad Date: Wed, 28 Oct 2026 14:33:20 -0400 Subject: [PATCH 2/3] fix(root): guard prune publication and provision scanner Ticket: WAL-2232 --- .claude/agents/iyarc-prune.md | 18 ++++++++++++------ .github/workflows/iyarc-prune.yml | 30 +++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/.claude/agents/iyarc-prune.md b/.claude/agents/iyarc-prune.md index d717f5ff8d8..e5fd69f0e0c 100644 --- a/.claude/agents/iyarc-prune.md +++ b/.claude/agents/iyarc-prune.md @@ -24,7 +24,8 @@ over an unsafe or unverified bump. - 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 uses `osv-scanner` v2.3.8 and writes JSON results: +- The workflow installs `osv-scanner` v2.3.8, matching the release audit. + It writes JSON results: ``` osv-scanner --config=osv-scanner.toml --format=json --output-file="$RUNNER_TEMP/osv-results.json" ./ ``` @@ -124,7 +125,10 @@ order: 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 `osv-scanner.toml`. Never open a PR with - a red feedback loop. The full test suite still runs in PR CI as a backstop. + a red feedback loop. The workflow publishes with GitHub's repository-scoped + workflow token (`GITHUB_TOKEN`). A maintainer may need to approve workflow + runs before continuous integration (CI) starts on the resulting pull request. + Do not assume those checks ran; they must pass before merging. ## Commit and pull request (only after validated exclusion removals) @@ -151,6 +155,8 @@ Do not create a branch, commit, push, or open a pull request yourself. - The pasted scanner, severity-gate, and `yarn check-deps` output showing they now pass. - Build/test results for the affected module(s). + - A prominent note asking a maintainer to approve pending workflow runs and + verify required checks pass before merging. - A "Still blocked" section listing every exclusion that could NOT be removed and the reason (no upstream fix / incompatible parent pin). @@ -159,7 +165,7 @@ Do not create a branch, commit, push, or open a pull request yourself. - 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 `osv-scanner.toml`, dependency manifests (`package.json`), and - `yarn.lock` in the repository. The report at `IYARC_PRUNE_PR_BODY` and temporary - validation output belong outside the repository. Do not modify product/source - code. +- Only ever modify `osv-scanner.toml`, the root and `modules/*/package.json` + dependency manifests, and `yarn.lock` in the repository. The report at + `IYARC_PRUNE_PR_BODY` and temporary validation output belong outside the + repository. Do not modify product/source code. diff --git a/.github/workflows/iyarc-prune.yml b/.github/workflows/iyarc-prune.yml index 4ca367ae335..c92c13917f8 100644 --- a/.github/workflows/iyarc-prune.yml +++ b/.github/workflows/iyarc-prune.yml @@ -24,6 +24,7 @@ jobs: AWS_REGION: us-west-2 steps: - name: Checkout repository + id: checkout uses: actions/checkout@v6 with: ref: master @@ -37,6 +38,21 @@ jobs: - name: Enable Corepack (yarn 1.22.22) run: corepack enable + - name: Install release audit scanner + env: + OSV_SCANNER_SHA256: bc98e15319ed0d515e3f9235287ba53cdc5535d576d24fd573978ecfe9ab92dc + run: | + set -euo pipefail + SCANNER_DIR="$RUNNER_TEMP/osv-scanner" + mkdir -p "$SCANNER_DIR" + curl --fail --silent --show-error --location --retry 3 \ + https://github.com/google/osv-scanner/releases/download/v2.3.8/osv-scanner_linux_amd64 \ + --output "$SCANNER_DIR/osv-scanner" + printf '%s %s\n' "$OSV_SCANNER_SHA256" "$SCANNER_DIR/osv-scanner" | sha256sum --check --strict + chmod +x "$SCANNER_DIR/osv-scanner" + "$SCANNER_DIR/osv-scanner" --version + echo "$SCANNER_DIR" >> "$GITHUB_PATH" + - name: Configure AWS Credentials (OIDC) uses: aws-actions/configure-aws-credentials@v6 with: @@ -159,11 +175,15 @@ jobs: - name: Check changes are ready for a pull request id: changes env: + BASE_SHA: ${{ steps.checkout.outputs.commit }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} IYARC_PRUNE_PR_BODY: ${{ runner.temp }}/iyarc-prune-pr.md run: | set -euo pipefail - CHANGED_FILES="$(git diff --name-only HEAD)" + if [ "$(git rev-parse HEAD)" != "$BASE_SHA" ]; then + echo "::error::Agent changed HEAD; validated edits must remain uncommitted"; exit 1 + fi + CHANGED_FILES="$(git diff --name-only "$BASE_SHA"; git ls-files --others --exclude-standard)" if [ -z "$CHANGED_FILES" ]; then echo "No dependency changes to publish." >> "$GITHUB_STEP_SUMMARY" exit 0 @@ -174,7 +194,7 @@ jobs: *) echo "::error::Unexpected changed file: $path"; exit 1 ;; esac done <<< "$CHANGED_FILES" - if git diff --quiet HEAD -- osv-scanner.toml; then + if git diff --quiet "$BASE_SHA" -- osv-scanner.toml; then echo "::error::Dependency changes exist without an osv-scanner.toml change"; exit 1 fi if [ ! -s "$IYARC_PRUNE_PR_BODY" ]; then @@ -186,8 +206,11 @@ jobs: echo "An open prune pull request already exists: $OPEN_PRS" >> "$GITHUB_STEP_SUMMARY" exit 0 fi + LABELS="$(gh label list --repo "$GITHUB_REPOSITORY" --limit 1000 --json name)" for label in automated dependencies security; do - gh label create "$label" --repo "$GITHUB_REPOSITORY" --force + if ! jq -e --arg name "$label" 'any(.[]; (.name | ascii_downcase) == $name)' <<< "$LABELS" >/dev/null; then + gh label create "$label" --repo "$GITHUB_REPOSITORY" + fi done echo "ready=true" >> "$GITHUB_OUTPUT" @@ -224,3 +247,4 @@ jobs: echo "::error::Validated dependency changes did not produce a pull request"; exit 1 fi echo "Pull request: $PR_URL" >> "$GITHUB_STEP_SUMMARY" + echo '> **Action required:** Check the pull request for an **Approve workflows to run** banner. A maintainer must approve pending workflow runs and verify required checks pass before merging.' >> "$GITHUB_STEP_SUMMARY" From fcef23b5434d778a211e75c2320938a4a4cf4edb Mon Sep 17 00:00:00 2001 From: Zahin Mohammad Date: Thu, 29 Oct 2026 12:12:13 -0400 Subject: [PATCH 3/3] fix(root): require master for prune workflow dispatches Ticket: WAL-2232 Co-authored-by: Ralph CLI Ralph: 18.12.3-bitgo.6 openrouter/openai/gpt-6.1-sol@default:high openrouter/z-ai/glm-5.3-flash@smol:low+task:medium openrouter/z-ai/glm-5.3@slow:max --- .claude/agents/iyarc-prune.md | 5 +++++ .github/workflows/iyarc-prune.yml | 14 +++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.claude/agents/iyarc-prune.md b/.claude/agents/iyarc-prune.md index e5fd69f0e0c..ba98b5c52df 100644 --- a/.claude/agents/iyarc-prune.md +++ b/.claude/agents/iyarc-prune.md @@ -22,6 +22,11 @@ over an unsafe or unverified bump. ## Environment notes +- For a manual run of [the workflow](../../.github/workflows/iyarc-prune.yml#L26-L38), + select `master` in **Use workflow from**. The AWS login uses the + [workflow's branch identity](https://docs.github.com/en/actions/reference/security/oidc#filtering-for-a-specific-branch), + not the checkout ref. Merge workflow changes before a live run; local agent + invocations are unaffected. - 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 workflow installs `osv-scanner` v2.3.8, matching the release audit. diff --git a/.github/workflows/iyarc-prune.yml b/.github/workflows/iyarc-prune.yml index c92c13917f8..e4084c90e59 100644 --- a/.github/workflows/iyarc-prune.yml +++ b/.github/workflows/iyarc-prune.yml @@ -23,11 +23,19 @@ jobs: env: AWS_REGION: us-west-2 steps: + - name: Require master workflow ref + run: | + set -euo pipefail + if [ "$GITHUB_REF" != 'refs/heads/master' ]; then + echo '::error::Run iyarc-prune from master. The AWS role trusts the workflow ref, not the checkout ref. Merge workflow changes before dispatching them.' + exit 1 + fi + - name: Checkout repository id: checkout uses: actions/checkout@v6 with: - ref: master + ref: ${{ github.sha }} fetch-depth: 0 - name: Setup Node @@ -108,7 +116,7 @@ jobs: # including the Still blocked breakdown so the result is auditable"), so # render the agent's own report there instead of leaving it log-only. - name: Write agent report to job summary - if: always() + if: always() && steps.claude.outcome != 'skipped' env: EXECUTION_FILE: ${{ steps.claude.outputs.execution_file }} run: | @@ -154,7 +162,7 @@ jobs: } >> "$GITHUB_STEP_SUMMARY" - name: Require successful Claude completion - if: always() + if: always() && steps.claude.outcome != 'skipped' env: EXECUTION_FILE: ${{ steps.claude.outputs.execution_file }} run: |