Skip to content

fix: update security workflow#207

Merged
louisliu2048 merged 12 commits intomainfrom
vui-chee/fix-security-cron
Mar 18, 2026
Merged

fix: update security workflow#207
louisliu2048 merged 12 commits intomainfrom
vui-chee/fix-security-cron

Conversation

@Vui-Chee
Copy link
Contributor

@Vui-Chee Vui-Chee commented Mar 18, 2026

Description

Fixes and hardens the security-dependabot-fix workflow so it reliably detects, deduplicates, and auto-patches Dependabot security alerts without creating duplicate PRs.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Changes

Fix: use gh CLI instead of github-script for alert fetching

The github-script REST client was silently swallowing API errors (e.g. auth/permission failures), leaving has_alerts=false and skipping all downstream steps with no visible error. Switching to the gh CLI means failures exit loudly with a clear message.

Fix: Dependabot alerts API requires a fine-grained PAT

GITHUB_TOKEN returns 404 on the Dependabot alerts endpoint regardless of the security-events: read permission — GitHub maps that scope to code scanning, not Dependabot. A fine-grained PAT (GH_SECURITY_PAT) with "Dependabot alerts: Read" is required. Raw JSON is now fetched to a file first so a gh api error is caught before jq runs (fixes the Cannot index string error that followed the 404).

Fix: avoid GH_TOKEN collision with the runner pre-set value

GitHub Actions runners automatically set GH_TOKEN=GITHUB_TOKEN for the gh CLI before any step runs. Setting GH_TOKEN in the step env block with the PAT was silently overridden by the runner. Fix: store the PAT as a distinctly-named env var (GH_SECURITY_PAT) and apply it inline (GH_TOKEN="$GH_SECURITY_PAT" gh api ...) so the shell-level assignment wins.

Fix: gh pr list used the wrong token for deduplication

The existing-PR check (gh pr list) ran under GH_SECURITY_PAT, which only has "Dependabot alerts: Read" and cannot list pull requests. The command failed silently (2>/dev/null || echo '[]'), making every alert appear unhandled on every run. Fix: use GITHUB_TOKEN (which has pull-requests: write) for the PR list call.

Fix: deduplicate by package name, not alert number

The original filter extracted the alert number from the branch name (split("-") | last) and matched it against each alert's .number. A single package can have multiple CVE alerts, and a single PR can fix all of them under one branch named after the first alert. Subsequent alerts for the same package were never matched and triggered duplicate PRs. Fix: match by package name — skip any alert whose package already has an open fix/security-<package>-* branch.

Fix: use dynamic default branch

Replaced the hardcoded main reference with ${{ github.event.repository.default_branch }} so the workflow works on repos whose default branch is not named main.

Fix: correct allowedTools parameter for claude-code-action

allowed_tools is not a valid input for claude-code-action; the correct form is claude_args: "--allowedTools Bash,Read,Write,Edit,Glob,Grep". The invalid key was silently ignored, leaving Claude with no tool permissions and causing repeated permission denials.

Refactor: pre-select alerts in jq before Claude runs

Filter to critical/high/medium severity, sort by severity rank, check for existing PRs, and write the final top-2 list to /tmp/alerts.json before Claude is invoked. This removes all in-prompt budget logic, keeps the Claude prompt focused on fixing, and prevents prompt injection (advisory text is written to a file, never interpolated into the prompt).

Checklist

  • I have reviewed the relevant code guidelines in the docs/ folder
  • My code follows the coding standards of this project
  • I have performed a self-review of my own code

Vui-Chee added 12 commits March 17, 2026 17:17
The github-script REST client was silently swallowing API errors
(e.g. auth/permission failures), leaving has_alerts=false and
causing all downstream steps to be skipped with no visible reason.

Switch to gh CLI with GH_TOKEN so failures exit loudly with a
clear error message. Also simplifies the code by doing the JSON
filtering and reshaping in a single jq expression.
The Dependabot REST API returns 404 for GITHUB_TOKEN even with
security-events: read in many org configurations. Switch to
cargo audit --json which uses the same RustSec advisory database
that Dependabot uses for Rust, needs no API permissions, and
provides richer data (installed version, exact patched ranges,
CVSS vector).

Also drop the now-unnecessary security-events: read and
id-token: write permissions from the job.
Replace hardcoded 'main' with github.event.repository.default_branch
so the workflow works regardless of the repo's default branch name.

Also add allowed_tools to grant Claude explicit permission to run
Bash, Read, Write, Edit, Glob and Grep — the 12 permission denials
in the previous run indicate the action was blocking tool calls
without an explicit allowlist.
allowed_tools is not a valid input for claude-code-action; the
correct form is claude_args with the --allowedTools flag, matching
how claude-review uses it in claude.yml. The invalid key was silently
ignored, leaving Claude with no tool permissions and causing 20
permission denials.
- Drop cargo audit in favour of the Dependabot alerts API, which
  surfaces the same vulnerabilities with severity already labelled
- Pre-select the top 2 highest-risk alerts (critical > high > medium)
  in jq before Claude runs, removing all in-prompt budget counting
- Restore security-events: read permission needed by the API
- Trim the Claude prompt to the essential fix steps only
- Keep show_full_output: true for debugging and prompt injection
  protection (alerts written to file, not interpolated)
GITHUB_TOKEN cannot access the Dependabot alerts API regardless of
the security-events: read permission — GitHub maps that scope to
code scanning, not Dependabot. The endpoint requires a fine-grained
PAT with the 'Dependabot alerts: Read' repository permission.

- Use GH_SECURITY_PAT secret for the gh api call
- Fetch raw JSON to a file first, then pipe to jq — this way a
  gh API error is caught before jq runs (fixes the 'Cannot index
  string' error that followed the 404)
- Add an early check that prints a clear error if the secret is unset
GitHub Actions runners automatically set GH_TOKEN=GITHUB_TOKEN for
the gh CLI before any step runs. When we set GH_TOKEN via the env
block with an empty or lower-priority secret, gh silently falls back
to the runner's value — causing the fine-grained PAT to never be
used (shows 'never used' in GitHub token settings).

Fix: store the PAT as DEPENDABOT_PAT (different name, no collision)
and apply it inline as GH_TOKEN=$DEPENDABOT_PAT gh api ... so the
shell-level assignment takes precedence over the runner environment.
The Dependabot alerts REST API consistently returns 404 regardless
of token type (GITHUB_TOKEN, fine-grained PAT) due to GitHub API
access restrictions. For Rust repos this is unnecessary — cargo audit
queries the identical RustSec advisory database that Dependabot uses,
with no authentication required.

Selects the top 2 patchable vulnerabilities by severity and writes
them to /tmp/alerts.json for Claude to process.
@Vui-Chee Vui-Chee marked this pull request as ready for review March 18, 2026 09:05
@louisliu2048 louisliu2048 merged commit 062cbad into main Mar 18, 2026
3 checks passed
@Vui-Chee Vui-Chee deleted the vui-chee/fix-security-cron branch March 18, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants