Skip to content
Draft
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
106 changes: 55 additions & 51 deletions .claude/agents/iyarc-prune.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,27 @@ 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 release audit gate is:
- The workflow installs `osv-scanner` v2.3.8, matching the release audit.
It 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
Expand All @@ -52,7 +65,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.
Expand All @@ -65,6 +78,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).
Expand Down Expand Up @@ -103,8 +117,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
Expand All @@ -115,58 +130,47 @@ 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.

## 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 <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 (`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 <name> --force`), then apply all three.
- Assign the PR to `gokulhost` so it does not get lost:
`gh pr edit <number> --add-assignee gokulhost`. CODEOWNERS reviewers are
assigned automatically and separately.
- PR body must contain:
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)

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 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).
- 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 `osv-scanner.toml`, dependency manifests (`package.json`), and
`yarn.lock`. 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.
117 changes: 109 additions & 8 deletions .github/workflows/iyarc-prune.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +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: ${{ github.sha }}
fetch-depth: 0

- name: Setup Node
Expand All @@ -36,6 +46,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:
Expand Down Expand Up @@ -73,12 +98,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'
Expand All @@ -92,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: |
Expand Down Expand Up @@ -137,8 +161,8 @@ jobs:
fi
} >> "$GITHUB_STEP_SUMMARY"

- name: Surface Claude error and fail on is_error
if: always()
- name: Require successful Claude completion
if: always() && steps.claude.outcome != 'skipped'
env:
EXECUTION_FILE: ${{ steps.claude.outputs.execution_file }}
run: |
Expand All @@ -152,6 +176,83 @@ 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:
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
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
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 "$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
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
LABELS="$(gh label list --repo "$GITHUB_REPOSITORY" --limit 1000 --json name)"
for label in automated dependencies security; do
if ! jq -e --arg name "$label" 'any(.[]; (.name | ascii_downcase) == $name)' <<< "$LABELS" >/dev/null; then
gh label create "$label" --repo "$GITHUB_REPOSITORY"
fi
done
Comment thread
zahin-mohammad marked this conversation as resolved.
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 }}
Comment thread
zahin-mohammad marked this conversation as resolved.
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"
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"
Loading