Skip to content

feat(auto-review): add supply-chain security subagent#75

Draft
ignaciosantise wants to merge 3 commits intomasterfrom
auto-review-supply-chain
Draft

feat(auto-review): add supply-chain security subagent#75
ignaciosantise wants to merge 3 commits intomasterfrom
auto-review-supply-chain

Conversation

@ignaciosantise
Copy link
Copy Markdown

@ignaciosantise ignaciosantise commented Mar 25, 2026

Summary

  • Adds a conditional supply-chain security subagent to the Claude auto-review action
  • Follows the existing subagent pattern (license compliance, data classification, breaking changes)
  • Multi-ecosystem coverage: heuristic triggers on dependency manifests, CI/build configs, build scripts, and suspicious patch patterns across npm, Rust, Gradle, CocoaPods, Python, and Go
  • Agent spec covers per-ecosystem auto-execution vectors: npm install hooks, Rust build.rs, Gradle buildscript/plugins, CocoaPods script_phase/prepare_command, Python setup.py cmdclass/subprocess, Go //go:generate
  • Invisible Unicode / Glassworm-specific checks (PUA range, eval(Buffer.from(...)) decoder pattern)
  • Uses scl- prefixed finding IDs

Review Fixes

  • Fixed: /Function\s*\(/ regex was too broad (matched myFunction(), handleFunction()) — removed in favor of existing new Function( pattern
  • Fixed: Agent spec instructed bash commands (wc -c, cat | tr) but Bash not in allowedTools — rewritten to use Grep/Read
  • Fixed: /Buffer\.from/ triggered on ordinary Node.js code — tightened to eval\s*\(\s*Buffer\.from (Glassworm decoder combo only)

Changes

  • scripts/should-spawn-supply-chain.js — multi-ecosystem heuristic (build scripts, patch patterns for Rust/Gradle/CocoaPods/Python/Go)
  • agents/review-supply-chain.md — agent spec with per-ecosystem auto-execution vectors, lockfile anomaly checks, and false-positive guardrails
  • scripts/__tests__/should-spawn-supply-chain.test.js — 79 tests (up from 51)
  • action.yml — new force_supply_chain_agent input, heuristic step, conditional prompt section

Test plan

  • pnpm test — all 238 tests pass (79 supply-chain)
  • Test with a PR that changes package.json → subagent should spawn
  • Test with a PR that changes build.rs → subagent should spawn
  • Test with a PR that changes build.gradle.kts → subagent should spawn
  • Test with a PR that changes .podspec → subagent should spawn
  • Test with a PR that changes setup.py → subagent should spawn
  • Test with a PR that changes .github/workflows/ → subagent should spawn
  • Test with a PR that only changes .md files → subagent should NOT spawn
  • Test with force_supply_chain_agent: true → subagent should always spawn
  • Test with skip-review label → subagent should NOT spawn

🤖 Generated with Claude Code

Add a conditional subagent that detects Glassworm campaign patterns and
other supply-chain attack techniques during PR review. Triggers when
dependency manifests, lockfiles, CI/build configs, or package manager
configs change, or when patches contain suspicious patterns (eval,
Buffer.from, codePointAt, install hooks).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude bot commented Mar 25, 2026

Claude finished @ignaciosantise's task in 1m 28s —— View job


Found 3 issue(s)

Issue 1: /Function\s*\(/ regex matches any identifier ending in Function

ID: should-spawn-supply-chain-function-regex-broad-a3c1
File: claude/auto-review/scripts/should-spawn-supply-chain.js:72
Severity: HIGH
Category: code_quality

Context:

  • Pattern: /Function\s*\(/ at line 72 is a substring match — it fires on myFunction(, addFunction(x), handleFunction(args), any camelCase or PascalCase function call ending in Function
  • Risk: The intended target is the bare Function() constructor (dynamic code eval), but the pattern catches a vast class of ordinary code
  • Impact: Supply-chain subagent will be spawned on nearly every PR touching JS/TS source files, making the heuristic effectively always-on for JS repos and diluting the signal of the other triggers
  • Trigger: Any patch line containing a JS function call whose name ends in Function

Recommendation: Split into two targeted patterns — one for new Function() (already covered by line 71) and one for standalone Function() constructor calls using a word boundary:

/new\s+Function\s*\(/,
/(?<!\w)Function\s*\(/,   // replaces the overly-broad line 72

Or simply remove line 72 entirely since line 71 already covers new Function(.


Issue 2: Agent spec instructs bash commands not available in --allowedTools

ID: review-supply-chain-bash-unavailable-b7f2
File: claude/auto-review/agents/review-supply-chain.md:29-36
Severity: MEDIUM
Category: code_quality

Context:

  • Pattern: Focus Area feat: adds terraform fmt action #4 ("Byte-Count Cross-Check") tells the agent to run wc -c and cat | tr -cd '[:print:]\n' | wc -c to detect obfuscated payloads with invisible Unicode
  • Risk: action.yml:387 restricts the agent to --allowedTools "Read,Glob,Grep,Task,WebFetch"Bash is not included
  • Impact: The byte-count cross-check capability — arguably the most reliable detector for Glassworm-style invisible Unicode payloads — is entirely non-functional as written. The agent will receive a tool-not-allowed error or silently skip the check
  • Trigger: Every supply-chain agent run

Recommendation: Either add Bash to --allowedTools in action.yml for the supply-chain agent (requires careful scoping since it shares the tool list with the main review), or replace the bash commands in the spec with Grep + Read alternatives that can approximate the check without shell access:

# Instead of wc -c, instruct agent to:
# Use Grep with a pattern matching non-printable chars, or
# Read the file and flag if line character counts seem anomalous

Issue 3: Buffer.from patch trigger fires on ordinary Node.js code

ID: should-spawn-supply-chain-buffer-from-noise-c9e4
File: claude/auto-review/scripts/should-spawn-supply-chain.js:73
Severity: LOW
Category: code_quality

Context:

  • Pattern: /Buffer\.from/ triggers agent spawn on any patch containing Buffer.from(...), which is idiomatic in Node.js for base64 encoding, hex conversion, string→Buffer, etc.
  • Risk: Will cause frequent false-positive spawns on PRs with normal Node.js code (e.g., any crypto utility, HTTP client, encoding helper)
  • Impact: Adds latency and cost to routine PRs; the agent spec correctly notes that Buffer.from is only dangerous combined with eval(), but the heuristic doesn't require that combination
  • Trigger: Any PR patch containing Buffer.from

Recommendation: Tighten to require the Glassworm decoder combination:

/eval\s*\(\s*Buffer\.from/,   // replaces standalone Buffer.from trigger

Subagent results: Breaking changes — no issues found (new input force_supply_chain_agent is additive with default: "false"). Data classification — no issues found.

Extend heuristic and agent spec beyond npm/JS to cover Rust (build.rs,
proc-macro), Gradle (buildscript, apply plugin), CocoaPods (script_phase,
prepare_command), Python (setup.py cmdclass, subprocess), and Go
(//go:generate) auto-execution vectors.

Fix 3 review findings: remove overly-broad Function() regex, tighten
Buffer.from to eval(Buffer.from(...)) combo only, rewrite byte-count
check to use Grep/Read instead of unavailable Bash tool.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ignaciosantise
Copy link
Copy Markdown
Author

@claude review

@claude
Copy link
Copy Markdown

claude bot commented Mar 26, 2026

Claude finished @ignaciosantise's task in 16s —— View job


🔄 Incremental Review (commit 2bef7e6)

Previously Reported Issues

  • Resolved should-spawn-supply-chain-function-regex-broad-a3c1/Function\s*\(/ removed; new\s+Function\s*\( covers the intent. Tests at lines 445–455 confirm myFunction( no longer triggers.
  • Resolved review-supply-chain-bash-unavailable-b7f2 — Focus Area 4 rewritten to use Grep/Read; explicit "Do NOT attempt to use bash commands" note added at line 108 of the spec.
  • Resolved should-spawn-supply-chain-buffer-from-noise-c9e4 — Tightened to /eval\s*\(\s*Buffer\.from/. Test at line 274 confirms standalone Buffer.from no longer spawns.

Found 1 issue(s)

Issue 1: /setup\s*\(/ patch trigger fires on any JS/TS test or utility file

ID: should-spawn-supply-chain-setup-call-broad-d5f1
File: claude/auto-review/scripts/should-spawn-supply-chain.js:112
Severity: MEDIUM
Category: code_quality

Context:

  • Pattern: /setup\s*\(/ at line 112 is a substring match with no file-type filtering — it fires on any patch line containing setup( in any language
  • Risk: In JS/TS codebases setup() is idiomatic in test frameworks (Vitest, Jest, Mocha beforeEach(() => setup())), Vue 3 Composition API (setup() lifecycle), and countless utility/config modules; this is the same false-positive class as the already-fixed /Buffer\.from/
  • Impact: Supply-chain subagent spawns on nearly every JS/TS test PR, diluting the signal and adding unnecessary latency/cost
  • Trigger: Any PR adding a line like setup(config), await setup(), export function setup( in any JS/TS/Ruby/Go file

Recommendation: Simplest fix — remove this pattern entirely since install_requires (line 111) and cmdclass (line 110) already cover the meaningful Python supply-chain vectors from setup.py. Alternatively, scope it narrowly:

// Remove line 112 entirely — install_requires and cmdclass already cover the risk

// OR tighten to the actual Python attack combo:
/setup\s*\(.*cmdclass|cmdclass.*setup\s*\(/,

Breaking changes subagent: no breaking changes found — all action.yml changes are purely additive (new optional input force_supply_chain_agent with default: "false", new steps that don't affect existing consumers).

The /setup\s*\(/ pattern fires on any setup() call in JS/TS (test
frameworks, Vue Composition API, etc.), not just Python setup.py.
install_requires and cmdclass already cover the meaningful Python
supply-chain vectors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant