
Security Review
- 1 installs
- 21 repo stars
- Updated August 5, 2026
- joaquimscosta/arkhe-claude-plugins
Runs a security-focused code review that surfaces high-confidence exploitable vulnerabilities with severity and confidence scoring and OWASP 2025 alignment.
About
Identifies high-confidence exploitable vulnerabilities using two-axis severity and confidence scoring, per-finding verification, and false-positive filtering. A developer uses it to security-review code or a PR and optionally post findings to GitHub.
- Two-axis severity plus confidence scoring, OWASP 2025 aligned
- Per-finding verification and optional PR posting
Security Review by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,835 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/joaquimscosta/arkhe-claude-plugins --skill security-reviewAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 21 |
| Last updated | August 5, 2026 |
| Repository | joaquimscosta/arkhe-claude-plugins ↗ |
What it does
Runs a security-focused code review that surfaces high-confidence exploitable vulnerabilities with severity and confidence scoring and OWASP 2025 alignment.
Files
Security Review
Identify HIGH-CONFIDENCE security vulnerabilities with real exploitation potential. Two-axis scoring (severity + confidence), OWASP 2025 aligned, false positive filtered.
Parse Arguments
Output Path Configuration:
- If
$ARGUMENTScontains--post-to-pr: enable GitHub PR posting (Phase 6) - Remaining non-flag arguments: use as output directory (default:
./reviews/security/)
Example usage:
/review:security-review— local report to./reviews/security//review:security-review audits/sec— local report toaudits/sec//review:security-review --post-to-pr— local report + post to GitHub PR/review:security-review audits/sec --post-to-pr— both
Git Analysis
GIT STATUS:
!`git status`FILES MODIFIED:
!`git diff --name-only origin/HEAD...`COMMITS:
!`git log --no-decorate origin/HEAD...`DIFF CONTENT:
!`git diff --merge-base origin/HEAD`Review the complete diff above. Focus ONLY on security implications newly added by this PR.
Objective
- Only flag issues where you have HIGH confidence of actual exploitability
- Skip theoretical issues, style concerns, or low-impact findings
- Prioritize vulnerabilities leading to unauthorized access, data breaches, or system compromise
- Use two-axis scoring: severity (impact) and confidence (accuracy) are independent
Security Categories (OWASP 2025 Aligned)
| Category | Key Checks | OWASP |
|---|---|---|
| Access Control | IDOR, privilege escalation, SSRF, CORS, CSRF, path traversal | A01 |
| Security Misconfiguration | Default credentials, debug endpoints, cloud misconfig, XXE | A02 |
| Supply Chain | Dependency confusion, unpinned actions, vulnerable deps, CI/CD risks | A03 |
| Cryptographic Failures | Hardcoded keys, weak algorithms, insecure randomness, cert validation | A04 |
| Injection | SQLi, command injection, XSS, template injection, NoSQL injection | A05 |
| Auth & Session | Authentication bypass, JWT vulns, session management, missing MFA | A07 |
| Deserialization & Integrity | Unsafe deserialization, prototype pollution, unsigned updates | A08 |
| Error Handling | Fail-open patterns, exception swallowing, verbose error disclosure | A10 |
| API Security | BOLA, mass assignment, shadow APIs, missing rate limiting | API Top 10 |
| LLM/AI Security | Prompt injection, unsafe output handling, excessive agency | LLM Top 10 |
See WORKFLOW.md for detailed subcategories and severity assignment reference.
Analysis Methodology
Phase 1 — Repository Context: Identify existing security frameworks, sanitization patterns, and security model in the codebase.
Phase 1.5 — Automated Security Scan (Optional): Run Trivy (vulnerability/IaC scanning) and Gitleaks (secret detection) if available. Locate the scan script via Glob for **/review/scripts/security-scan.sh, then execute: bash {script_path} --quick --output-dir {output-directory}. If tools are not installed, skip gracefully — this phase is informational only. See WORKFLOW.md for details.
Phase 2 — Comparative Analysis: Compare new code against established secure practices. Flag deviations and new attack surfaces. Cross-reference with automated scan results if available.
Phase 3 — Vulnerability Assessment: Trace data flow from user inputs to sensitive operations. Confirm sink reachability and check for sanitizers in the path.
See WORKFLOW.md for detailed methodology and sub-task orchestration.
Two-Axis Scoring
Severity (Impact)
| Severity | Criteria | Example |
|---|---|---|
| CRITICAL | RCE, auth bypass, mass data exfiltration | Deserialization RCE, SQLi with shell access |
| HIGH | Significant data access or privilege escalation | SQLi read, stored XSS, SSRF to cloud metadata |
| MEDIUM | Limited impact or requires user interaction | Reflected XSS, CSRF, IDOR on non-sensitive data |
| LOW | Defense-in-depth, minimal direct impact | Missing headers, verbose errors |
Confidence (Accuracy)
| Confidence | Description | Action |
|---|---|---|
| HIGH | Data flow confirmed, clear exploit path | Report — include in findings |
| MEDIUM | Pattern match, context needed to confirm | Report only if severity >= HIGH |
| LOW | Theoretical or framework likely handles | Do not report |
Confidence & Signal Quality
Before reporting any finding, assess using both axes:
| Severity \ Confidence | HIGH | MEDIUM | LOW |
|---|---|---|---|
| CRITICAL | Report | Report | Suppress |
| HIGH | Report | Report | Suppress |
| MEDIUM | Report | Suppress | Suppress |
| LOW | Suppress | Suppress | Suppress |
Finding caps: Max 8 meaningful findings (Blocker + Improvement + Question) and max 2 Nits per review. Keep the highest-severity, highest-confidence items.
Self-reflection: After generating all candidate findings, re-evaluate each in context. Remove redundant, low-signal, or theoretical items. Apply false positive filtering from WORKFLOW.md.
Triage Matrix
Categorize every finding:
- [Blocker]: Must fix before merge — CRITICAL/HIGH severity + HIGH confidence. RCE, auth bypass, injection with confirmed data flow (confidence >= HIGH)
- [Improvement]: Strong recommendation — HIGH severity + MEDIUM confidence, or MEDIUM + HIGH. Clear vulnerability pattern, may need context verification (confidence >= MEDIUM)
- [Question]: Seeks clarification — potential vulnerability depending on context, intent unclear (confidence >= MEDIUM)
- [Nit]: Minor hardening suggestion, optional — max 2 per review
- [Praise]: Acknowledge good security practice — max 1 per review
Phase 4 — Per-Finding Haiku Verification
After Phase 3 identifies candidate findings, launch N parallel Haiku agents (one per finding) for independent verification.
Each Haiku agent receives:
- The finding description, severity, confidence, category, and CWE
- The relevant diff section around the flagged code
- The framework/sanitizer context discovered in Phase 1
Each agent verifies: 1. Data flow reachability — Can user input actually reach the vulnerable sink? 2. Sanitizer presence — Are there sanitizers/validators in the path that the main analysis missed? 3. Framework handling — Does the framework's security model prevent this exploit? 4. False positive patterns — Does this match any hard exclusion or precedent from WORKFLOW.md?
Each agent returns a verdict:
- KEEP — Finding confirmed. Data flow verified, no sanitizers found, exploit path is valid.
- DISMISS — False positive. Reason: {specific evidence — framework handling, sanitizer found, etc.}
- DOWNGRADE — Valid but lower severity or confidence. Reason: {what changed — e.g., only reachable by admins, mitigating control exists.}
Apply verdicts: remove DISMISSED findings, adjust DOWNGRADED findings' severity/confidence. Remaining findings proceed to report.
If a Haiku verification agent fails for a finding, default to KEEP (conservative — finding stays).
See WORKFLOW.md for the Haiku verification prompt template and verdict criteria.
Output Format
For each vulnerability found:
### [Triage] Vuln N: {Category Code}: `{file}:{line}`
* **Severity**: {CRITICAL|HIGH|MEDIUM}
* **Confidence**: {HIGH|MEDIUM}
* **Category**: {OWASP A0X:2025 or API/LLM Top 10}
* **CWE**: CWE-XXX
* **Description**: {What the vulnerability is and how it can be exploited}
* **Exploit Scenario**: {Specific attack path with example payload}
* **Recommendation**: {Concrete fix with code example}Phase 5 — Report Generation
1. Create output directory using Bash: mkdir -p {output-directory} 2. Save the report to: {output-directory}/{YYYY-MM-DD}_{HH-MM-SS}_security-review.md
Include this header:
# Security Review Report
**Date**: {ISO 8601 date}
**Branch**: {current branch name}
**Commit**: {short commit hash}
**Reviewer**: Claude Code (security-review)
**Framework**: OWASP Top 10 2025 + API Top 10 + LLM Top 10
## Summary
- **Blocker**: {count} findings
- **Improvement**: {count} findings
- **Question**: {count} findings
- **Total**: {count} actionable findings
- **Automated Scan**: {Passed | X issues found | Skipped — tools not installed}
- **Haiku Verification**: {N} findings verified — {kept} kept, {dismissed} dismissed, {downgraded} downgraded
---3. Display the full report to the user in the chat 4. Confirm the save: Security report saved to: {output-directory}/{filename}
Phase 6 — Optional GitHub PR Posting
Only execute if --post-to-pr flag was passed.
1. Check if an open PR exists for the current branch via gh pr view 2. If no PR exists, inform the user: "No open PR found for this branch. Skipping GitHub posting." 3. If a PR exists, check eligibility via a Haiku agent:
- Is the PR closed? → skip
- Is the PR a draft? → skip
- Has Claude already commented on this PR? → skip
4. If eligible, format findings as a concise PR comment and post via gh pr comment 5. Use the security-specific GitHub comment format from WORKFLOW.md
Phase 7 — Automatic Verification
After saving the report and confirming the save to the user, invoke the false-positive verifier:
1. Use the Skill tool to invoke review:verify-findings with the saved report path as the argument 2. The verifier runs in an isolated forked context and produces a .verified.md report 3. After verification completes, inform the user of both report locations
If the Skill tool is not available (e.g., running inside a subagent), inform the user:
Run verification manually: /review:verify-findings {report-path}False Positive Filtering
Apply the false positive filtering rules from WORKFLOW.md before finalizing. Each finding must pass the signal quality matrix above.
Resources
- WORKFLOW.md - Detailed category taxonomy, analysis methodology, false positive filtering, sub-task orchestration
- EXAMPLES.md - Sample security review reports
- TROUBLESHOOTING.md - Common issues and calibration guidance
Security Review Examples
Invocation
# Default output path
/review:security-review
# Custom output path
/review:security-review audits/security/
# With GitHub PR posting
/review:security-review --post-to-pr
# Custom path + PR posting
/review:security-review audits/security/ --post-to-prOrchestration Flow
What the user sees during a typical security review with Haiku verification:
/review:security-review
Phase 1: Researching repository security context...
- Detected: Express.js + helmet.js, Knex ORM, JWT auth
- Security patterns: parameterized queries in most files
Phase 1.5: Running automated scan...
- Trivy: 2 vulnerabilities found
- Gitleaks: 0 secrets detected
Phase 2: Comparative analysis against established patterns...
Phase 3: Vulnerability assessment with data flow tracing...
- 5 candidate findings identified
Phase 4: Haiku verification (5 parallel agents)...
- Vuln 1 (INJ-SQL): KEEP — data flow confirmed, no parameterization
- Vuln 2 (AC-SSRF): KEEP — user-controlled URL, no host validation
- Vuln 3 (INJ-XSS): DISMISS — React auto-escapes JSX, no unsafe APIs
- Vuln 4 (SC-CICD): KEEP — unpinned GitHub Action with secrets access
- Vuln 5 (ERR-FAILOPEN): DOWNGRADE — only reachable by authenticated admins
Phase 5: Generating report... 4 findings (2 Blockers, 1 Improvement, 1 Question)
Report saved to: ./reviews/security/2026-03-18_14-30-00_security-review.md
Phase 7: Running verification...
Verified report saved to: ./reviews/security/2026-03-18_14-30-00_security-review.verified.mdOrchestration Flow — With PR Posting
/review:security-review --post-to-pr
Phase 1-5: (same as above)
Report saved to: ./reviews/security/2026-03-18_16-00-00_security-review.md
Phase 6: Posting to GitHub PR...
- PR #42 on feat/user-api — open, eligible
- Posted security review comment with 4 findings
Phase 7: Running verification...Orchestration Flow — PR Posting Skipped
/review:security-review --post-to-pr
Phase 1-5: (same as above)
Report saved to: ./reviews/security/2026-03-18_16-00-00_security-review.md
Phase 6: No open PR found for this branch. Skipping GitHub posting.
Phase 7: Running verification...Sample Report — Multiple Vulnerabilities
# Security Review Report
**Date**: 2026-02-28T09:45:00Z
**Branch**: feat/user-api
**Commit**: f7g8h9i
**Reviewer**: Claude Code (security-review)
**Framework**: OWASP Top 10 2025 + API Top 10 + LLM Top 10
## Summary
- **Blocker**: 2 findings
- **Improvement**: 1 finding
- **Question**: 1 finding
- **Total**: 4 actionable findings
- **Haiku Verification**: 5 findings verified — 4 kept, 1 dismissed, 0 downgraded
---
### [Blocker] Vuln 1: INJ-SQL: `src/api/search.ts:28`
* **Severity**: CRITICAL
* **Confidence**: HIGH
* **Category**: OWASP A05:2025 — Injection
* **CWE**: CWE-89
* **Description**: The search query parameter is concatenated directly into a SQL
string via `db.raw()`, bypassing Knex ORM parameterization. User input flows
directly from the request query parameter to the SQL execution sink with no
sanitization.
* **Exploit Scenario**: Attacker sends
`GET /api/search?q=' UNION SELECT username, password FROM users --`
to exfiltrate credentials via UNION-based injection.
* **Recommendation**: Use Knex parameterized binding:
Before:
` ``typescript
const results = db.raw(`SELECT * FROM products WHERE name LIKE '%${query}%'`);
` ``
After:
` ``typescript
const results = db('products').where('name', 'like', `%${query}%`);
` ``
---
### [Blocker] Vuln 2: AC-SSRF: `src/services/webhook.ts:45`
* **Severity**: HIGH
* **Confidence**: HIGH
* **Category**: OWASP A01:2025 — Broken Access Control
* **CWE**: CWE-918
* **Description**: The webhook endpoint accepts a user-supplied URL and fetches it
server-side without validating the target host or protocol. This allows attackers
to probe internal services and access cloud metadata endpoints.
* **Exploit Scenario**: Attacker sends
`POST /api/webhooks {"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}`
to steal AWS IAM credentials.
* **Recommendation**: Validate URLs against an allowlist and block private IP ranges.
Resolve DNS and re-check IP to prevent DNS rebinding. Only allow HTTPS protocol.
---
### [Improvement] Vuln 3: SC-CICD: `.github/workflows/deploy.yml:12`
* **Severity**: HIGH
* **Confidence**: MEDIUM
* **Category**: OWASP A03:2025 — Software Supply Chain Failures
* **CWE**: CWE-829
* **Description**: GitHub Action `actions/setup-node` is referenced with a mutable
tag (`@v4`) instead of a pinned SHA hash. A compromised action maintainer could
inject malicious code into the CI/CD pipeline.
* **Exploit Scenario**: Attacker compromises the action repository and pushes
malicious code to the `v4` tag. Next pipeline run executes attacker-controlled
code with access to repository secrets.
* **Recommendation**: Pin actions to full SHA hashes:
Before:
` ``yaml
- uses: actions/setup-node@v4
` ``
After:
` ``yaml
- uses: actions/setup-node@1a4442cacd436585916f9e20e25f6e9e6e5ffd38 # v4.2.0
` ``
---
### [Question] Vuln 4: ERR-FAILOPEN: `src/middleware/auth.ts:67`
* **Severity**: MEDIUM
* **Confidence**: MEDIUM
* **Category**: OWASP A10:2025 — Mishandling of Exceptional Conditions
* **CWE**: CWE-636
* **Description**: The authentication middleware catches all exceptions and calls
`next()` without the error parameter, effectively granting access when token
validation fails due to unexpected errors (database timeout, library crash).
* **Exploit Scenario**: If the JWT verification service is temporarily unavailable,
all requests pass through authentication unchecked.
* **Recommendation**: Fail closed — deny access on unexpected errors by returning
a 401 response instead of passing through to the next middleware.
---
### [Praise] Good use of parameterized queries in `src/api/users.ts`
The user CRUD endpoints consistently use Knex query builder with parameterized
bindings, preventing SQL injection across all user data operations.
---Sample Report — Clean Review
# Security Review Report
**Date**: 2026-02-28T14:20:00Z
**Branch**: refactor/config-module
**Commit**: j1k2l3m
**Reviewer**: Claude Code (security-review)
**Framework**: OWASP Top 10 2025 + API Top 10 + LLM Top 10
## Summary
- **Blocker**: 0 findings
- **Improvement**: 0 findings
- **Question**: 0 findings
- **Total**: 0 actionable findings
- **Haiku Verification**: 0 findings verified (no candidates)
---
No high-confidence security vulnerabilities were identified in this change set.
The refactoring maintains existing security patterns and does not introduce new
attack surfaces. Configuration handling continues to use validated environment
variables through the existing config module.
---Sample Report — With Automated Scan Results
# Security Review Report
**Date**: 2026-03-01T10:15:00Z
**Branch**: feat/payment-service
**Commit**: a1b2c3d
**Reviewer**: Claude Code (security-review)
**Framework**: OWASP Top 10 2025 + API Top 10 + LLM Top 10
## Summary
- **Blocker**: 1 finding
- **Improvement**: 1 finding
- **Question**: 0 findings
- **Total**: 2 actionable findings
- **Automated Scan**: 3 issues found (2 Trivy vulnerabilities, 1 Gitleaks secret)
- **Haiku Verification**: 2 findings verified — 2 kept, 0 dismissed, 0 downgraded
---
### [Blocker] Vuln 1: CRYPTO-SECRET: `src/config/stripe.ts:14`
* **Severity**: HIGH
* **Confidence**: HIGH (corroborated by Gitleaks automated scan)
* **Category**: OWASP A04:2025 — Cryptographic Failures
* **CWE**: CWE-798
* **Description**: Stripe secret key is hardcoded in the configuration file. Gitleaks
also detected this secret in git history (commit a1b2c3d), confirming the key has
been committed to the repository.
* **Exploit Scenario**: Any developer or attacker with repository access can extract
the production Stripe secret key and process fraudulent charges.
* **Recommendation**: Move the key to an environment variable and rotate the
compromised key immediately. Run `gitleaks detect` to verify no other secrets
remain in git history.
---
### [Improvement] Vuln 2: SC-DEP: `package.json`
* **Severity**: HIGH
* **Confidence**: MEDIUM (identified by Trivy automated scan)
* **Category**: OWASP A03:2025 — Software Supply Chain Failures
* **CWE**: CWE-1395
* **Description**: Trivy detected 2 HIGH severity vulnerabilities in transitive
dependencies: CVE-2026-1234 in `lodash@4.17.20` (prototype pollution) and
CVE-2026-5678 in `express@4.18.1` (path traversal). These are in the dependency
tree of the payment service.
* **Recommendation**: Update affected dependencies: `pnpm update lodash express`.
Review Trivy report at `reviews/security/trivy-payment-service.json` for full details.
---Sample Report — Automated Scan Skipped
# Security Review Report
**Date**: 2026-03-01T14:00:00Z
**Branch**: fix/input-validation
**Commit**: e5f6g7h
**Reviewer**: Claude Code (security-review)
**Framework**: OWASP Top 10 2025 + API Top 10 + LLM Top 10
## Summary
- **Blocker**: 0 findings
- **Improvement**: 0 findings
- **Question**: 0 findings
- **Total**: 0 actionable findings
- **Automated Scan**: Skipped — tools not installed
- **Haiku Verification**: 0 findings verified (no candidates)
---
No high-confidence security vulnerabilities were identified in this change set.
Note: Automated scanning (Trivy, Gitleaks) was skipped because the tools are not
installed. Install with `brew install trivy gitleaks jq` for dependency vulnerability
and secret detection coverage.
---Sample Report — LLM/AI Integration Review
# Security Review Report
**Date**: 2026-02-28T11:30:00Z
**Branch**: feat/ai-assistant
**Commit**: x4y5z6a
**Reviewer**: Claude Code (security-review)
**Framework**: OWASP Top 10 2025 + API Top 10 + LLM Top 10
## Summary
- **Blocker**: 1 finding
- **Improvement**: 1 finding
- **Question**: 0 findings
- **Total**: 2 actionable findings
- **Haiku Verification**: 2 findings verified — 2 kept, 0 dismissed, 0 downgraded
---
### [Blocker] Vuln 1: LLM-OUTPUT: `src/ai/assistant.ts:89`
* **Severity**: HIGH
* **Confidence**: HIGH
* **Category**: OWASP LLM Top 10 — LLM05
* **CWE**: CWE-94
* **Description**: The AI assistant's response is rendered directly into the DOM
without sanitization. LLM output may contain HTML/JavaScript if the model is
manipulated via prompt injection, enabling stored XSS through the AI chat
interface.
* **Exploit Scenario**: User sends a crafted message that causes the LLM to output
malicious HTML/script tags. This executes in the browser of any user viewing
the chat history.
* **Recommendation**: Sanitize LLM output before rendering. Parse markdown first,
then apply an HTML sanitizer (e.g., DOMPurify) before inserting into the DOM.
---
### [Improvement] Vuln 2: LLM-AGENCY: `src/ai/tools.ts:34`
* **Severity**: MEDIUM
* **Confidence**: HIGH
* **Category**: OWASP LLM Top 10 — LLM06
* **CWE**: CWE-269
* **Description**: The AI agent is configured with database write and file delete
tool permissions without requiring human approval for destructive operations.
A prompt injection attack could cause the agent to delete user data.
* **Recommendation**: Add human-in-the-loop confirmation for destructive operations.
Require explicit user approval before executing database writes, file deletions,
or external API calls initiated by the AI agent.
---Sample GitHub PR Comment
When using --post-to-pr, the posted comment looks like:
### Security review
Found 4 security issues:
1. **[CRITICAL]** INJ-SQL — SQL injection via string concatenation in search query (CWE-89)
https://github.com/owner/repo/blob/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0/src/api/search.ts#L27-L30
2. **[HIGH]** AC-SSRF — Server-side request forgery via user-supplied webhook URL (CWE-918)
https://github.com/owner/repo/blob/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0/src/services/webhook.ts#L44-L48
3. **[HIGH]** SC-CICD — Unpinned GitHub Action with secrets access (CWE-829)
https://github.com/owner/repo/blob/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0/.github/workflows/deploy.yml#L11-L13
4. **[MEDIUM]** ERR-FAILOPEN — Auth middleware fails open on unexpected errors (CWE-636)
https://github.com/owner/repo/blob/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0/src/middleware/auth.ts#L66-L70
---
Generated with [Claude Code](https://claude.ai/code)
<sub>If this review was useful, react with :+1:. Otherwise, react with :-1:.</sub>Security Review Troubleshooting
Common issues and calibration guidance for the security-review skill.
---
Git Diff Issues
"No diff output" or empty diff
Cause: No remote tracking branch, or origin/HEAD is not set.
Fix:
# Set origin/HEAD to the default branch
git remote set-head origin --auto
# Or specify the base branch explicitly in your review workflow
git diff --merge-base origin/main"Diff is too large" — review times out or context is truncated
Cause: PR contains too many changed files (e.g., generated code, lock files, large migrations).
Fix:
- Split large PRs into smaller, focused changes
- Exclude generated files from the diff:
git diff --merge-base origin/HEAD -- ':!package-lock.json' ':!*.generated.ts' - Review high-risk files individually rather than the full diff
---
Automated Scan Issues
Scan tools not installed
Symptoms: Automated scan phase reports "Skipped — tools not installed".
Fix: Install the required tools:
brew install trivy gitleaks jqThe security review works without these tools — automated scanning is optional. Manual code analysis runs regardless.
Trivy database update fails
Symptoms: Trivy reports "failed to download vulnerability DB" or times out.
Causes:
- Network connectivity issues
- Trivy cache corrupted
Fixes:
# Clear Trivy cache and retry
trivy clean --all
trivy fs . --download-db-only
# Or skip DB update and use cached data
trivy fs . --skip-db-updateGitleaks false positives on test fixtures
Symptoms: Gitleaks flags secrets in test files, fixtures, or example configurations.
Fixes:
- Create a
.gitleaksignorefile in the project root with SHA hashes of known false positives - Use
--configflag with a custom Gitleaks config that excludes test directories:
# .gitleaks.toml
[allowlist]
paths = ["test/", "tests/", "fixtures/", "**/*.test.*"]Trivy reports vulnerabilities in lock files
Symptoms: Trivy flags vulnerabilities in package-lock.json or pnpm-lock.yaml transitive dependencies.
Context: These are real findings — transitive vulnerabilities can be exploitable. Triage by checking if the vulnerable package is reachable from your application code. Update with pnpm update or npm audit fix.
---
Haiku Verification Issues
All findings dismissed by Haiku verification
Symptoms: Clean report on a branch that introduces security-sensitive functionality.
Fix: This means Haiku agents independently determined all candidate findings were false positives. This is expected when:
- The codebase has strong security frameworks in place
- Changes follow established secure patterns
- Findings were theoretical rather than exploitable
If you believe findings were incorrectly dismissed, check the candidate findings before Haiku verification was applied. Rerun the review — Haiku agents may produce slightly different verdicts.
Haiku verdicts seem inconsistent
Symptoms: Similar findings receive different verdicts (one KEEP, one DISMISS).
Fix: Each finding is verified by an independent Haiku agent. Slight variation is expected. The verdict depends on the specific code context:
- Two similar-looking SQL patterns may differ if one uses parameterized queries
- Framework handling detection depends on which file imports are visible in the context
- If a verdict seems wrong, the post-hoc verify-findings skill provides a second independent check
Haiku agent fails for a finding
Symptoms: A finding has no verification note in the report.
Fix: When a Haiku agent fails, the finding defaults to KEEP (conservative). This is expected behavior — the finding stays in the report for manual review. The verify-findings skill provides additional verification.
---
GitHub PR Posting Issues
"No open PR found" when PR exists
Cause: gh pr view can't find the PR for the current branch.
Fix:
# Verify gh CLI is authenticated
gh auth status
# Check if PR exists for this branch
gh pr list --head $(git branch --show-current)
# If branch isn't pushed, push first
git push -u origin $(git branch --show-current)PR comment fails to post
Cause: Permission denied or PR state changed during review.
Fix:
- Verify
gh auth statusshows correct permissions - Check if the PR was closed/merged during the review
- Ensure you have write access to the repository
PR posting skipped — "already reviewed"
Cause: The eligibility check detected a previous Claude Code security review comment on the PR.
Fix: This prevents duplicate reviews. If you want to re-review:
- Delete the previous review comment on the PR
- Run
/review:security-review --post-to-pragain
---
Finding Quality Issues
Too many findings (noisy report)
Symptoms: Report has 10+ findings, most are LOW severity or speculative.
Causes: 1. Confidence threshold not being applied correctly 2. Framework-aware suppression rules not triggered 3. Hard exclusions not filtering theoretical issues
Fixes:
- Verify the signal quality matrix is applied: only report findings in the "Report" cells
- Check if the codebase uses a framework with built-in protections (React, Django, Spring Security) — these should suppress many XSS and CSRF findings
- Re-read the Hard Exclusions list in WORKFLOW.md — common over-reports include DoS concerns, missing rate limiting, and theoretical race conditions
- Apply the finding cap: max 8 meaningful findings + 2 nits
Too few findings (suspiciously clean)
Symptoms: Clean report on a PR that introduces significant new functionality with user input handling.
Causes: 1. Diff not captured correctly (see Git Diff Issues above) 2. Phase 1 context research didn't identify security-sensitive code paths 3. Analysis focused only on traditional OWASP categories, missing supply chain or API security issues
Fixes:
- Verify the diff output contains all changed files
- Manually check for new endpoints, user input handling, and external service calls
- Review against all 10 security categories in SKILL.md, including A03 (Supply Chain), A10 (Error Handling), API, and LLM categories
False positive in report
Symptoms: A finding describes a theoretical vulnerability that the framework or existing code already handles.
Common false positive patterns:
- XSS flagged in React JSX (React auto-escapes by default)
- SQL injection flagged on ORM parameterized queries
- CSRF flagged when the framework has built-in CSRF middleware
- Command injection flagged on subprocess with argument list (not shell mode)
- Hardcoded secrets that are actually placeholder/example values
Fixes:
- Apply framework-aware suppression rules from WORKFLOW.md
- Check for sanitizers in the data flow path
- Verify entropy for secret detection (placeholders have low entropy)
---
Severity Calibration
When to use CRITICAL vs HIGH
| Use CRITICAL when... | Use HIGH when... |
|---|---|
| RCE is achievable (command injection, deserialization) | Data read access (SQLi read-only, SSRF info leak) |
| Full authentication bypass (admin access) | Stored XSS in privileged context |
| Mass data exfiltration (SQLi with write) | Single-user data breach (IDOR) |
| No authentication required | Authentication required but bypassable |
When to suppress MEDIUM findings
Suppress MEDIUM findings unless they are:
- Obvious and concrete (not theoretical)
- Backed by HIGH confidence (data flow confirmed)
- Actionable without additional investigation
Exploitability modifier confusion
Problem: Unsure whether to increase or decrease severity.
Rule of thumb:
- If an average attacker with public knowledge could exploit it: increase
- If exploitation requires insider access, disabled features, or chained bugs: decrease
- When in doubt, keep the default severity from the quick reference table
---
Category Selection
Finding doesn't fit any category
If a vulnerability doesn't map cleanly to the taxonomy codes: 1. Use the closest OWASP category (A01-A10) 2. Add the CWE identifier for specificity 3. Describe the actual vulnerability clearly — the category is secondary to the finding quality
Overlapping categories
Some vulnerabilities span multiple categories (e.g., an SSRF that also exposes secrets):
- Use the primary category that describes the root cause
- Mention the secondary impact in the description
- Example: SSRF (AC-SSRF) that leads to credential theft — primary is AC-SSRF, mention CRYPTO-SECRET impact in description
---
Output Issues
Report directory creation fails
Cause: Permission denied or invalid path.
Fix: Ensure the output directory path is writable. Default is ./reviews/security/ relative to the project root.
Report file already exists
The skill uses timestamped filenames ({YYYY-MM-DD}_{HH-MM-SS}_security-review.md), so collisions are unlikely. If running multiple reviews in the same second, the second review will overwrite the first — wait a moment between runs.
Security Review Workflow
Detailed category taxonomy, analysis methodology, false positive filtering, and sub-task orchestration.
---
Security Categories — OWASP 2025 Taxonomy
A01: Access Control Vulnerabilities
- AC-IDOR: Insecure Direct Object References — horizontal privilege escalation via ID manipulation
- AC-PRIVESC: Privilege Escalation — vertical escalation from user to admin
- AC-SSRF: Server-Side Request Forgery — internal network access, cloud metadata theft
- Cloud metadata targets: AWS 169.254.169.254, GCP metadata.google.internal, Azure 169.254.169.254
- Common vectors: webhook URLs, PDF generators, preview/proxy endpoints, file imports
- Bypass techniques: IP encoding (decimal, hex, shortened), IPv6 loopback, DNS rebinding
- AC-CORS: CORS Misconfiguration — overly permissive cross-origin policies
- AC-CSRF: Cross-Site Request Forgery — state-changing actions without token verification
- AC-PATH: Path Traversal — accessing files outside intended scope
A02: Security Misconfiguration
- Default or weak credentials in configuration files
- Debug endpoints or admin interfaces exposed in production
- Error messages exposing stack traces or implementation details
- Missing security headers (CSP, HSTS, X-Frame-Options)
- Cloud misconfiguration (public S3 buckets, over-permissioned IAM roles)
- XML External Entity (XXE) processing enabled
- Open redirects (CWE-601)
A03: Software Supply Chain Failures (New in 2025)
- SC-CONFUSION: Dependency confusion — private package names on public registries
- SC-TYPOSQUAT: Typosquatting — lookalike package names
- SC-CICD: CI/CD pipeline risks — unpinned GitHub Actions, secrets in logs, GITHUB_TOKEN with write-all
- SC-DEP: Vulnerable dependencies — known CVEs in transitive dependencies
- SC-SBOM: Missing SBOM or provenance — untracked dependencies, no artifact signing
- SC-LOCKFILE: Missing or inconsistent lock files
A04: Cryptographic Failures
- CRYPTO-SECRET: Hardcoded API keys, passwords, tokens in source code
- CRYPTO-WEAK: Weak algorithms — MD5/SHA-1 for hashing, RC4/DES/ECB mode
- CRYPTO-RNG: Insecure randomness — Math.random() for security-sensitive values
- CRYPTO-CERT: Certificate validation disabled (verify=False, InsecureSkipVerify)
- CRYPTO-KEY: Insufficient key length — RSA < 2048-bit
- CRYPTO-TRANSIT: Missing encryption in transit for sensitive data
A05: Injection
- INJ-SQL: SQL Injection — string concatenation in queries, raw SQL with user input
- INJ-NOSQL: NoSQL Injection — MongoDB $where operator, unvalidated query objects
- INJ-CMD: OS Command Injection — unsanitized user input in subprocess/exec calls
- INJ-XSS: Cross-Site Scripting — stored, reflected, DOM-based variants
- INJ-SSTI: Server-Side Template Injection — Jinja2, Twig, Freemarker, EJS unescaped
- INJ-LDAP: LDAP Injection — unsanitized input in LDAP queries
- INJ-LOG: Log Injection — CRLF injection into log entries (only if PII/secrets exposed)
- INJ-EXPR: Expression Language Injection — Spring EL, OGNL
A07: Authentication & Session Failures
- AUTH-BYPASS: Authentication bypass logic flaws
- AUTH-SESSION: Session management flaws — tokens not invalidated, excessive validity
- AUTH-JWT: JWT vulnerabilities — alg:none, weak secrets, missing expiration
- AUTH-CRED: Credential exposure — passwords in URLs, logs, or GET parameters
- AUTH-MFA: Missing MFA for privileged operations
A08: Software & Data Integrity Failures
- DESER-JAVA: Java ObjectInputStream, XMLDecoder, Jackson enableDefaultTyping — CRITICAL
- DESER-PY: Python unsafe deserialization (yaml.load without SafeLoader) — CRITICAL
- DESER-PHP: PHP unserialize on HTTP parameters — CRITICAL
- DESER-PROTO: JavaScript Prototype Pollution via __proto__ manipulation
- INTEGRITY-UPDATE: Unsigned code updates, missing artifact signing
A10: Mishandling of Exceptional Conditions (New in 2025)
- ERR-FAILOPEN: Fail-open patterns — defaulting to permissive state on error (e.g., auth bypass on timeout)
- ERR-SWALLOW: Bare except/catch blocks hiding security errors
- ERR-VERBOSE: Error messages revealing system internals to users
- ERR-OVERFLOW: Integer overflow/underflow in security-critical calculations
API Security (OWASP API Top 10)
- API-BOLA: Broken Object Level Authorization — accessing other users' resources via ID
- API-MASS: Mass Assignment — binding user JSON to model without field filtering
- API-INVENTORY: Shadow/undocumented API endpoints
- API-RATE: Missing rate limiting on sensitive endpoints
- API-CONSUME: Trusting third-party API responses without validation
LLM/AI Security (OWASP LLM Top 10 v2.0)
- LLM-INJECT: User input concatenated into LLM prompts without sanitization
- LLM-OUTPUT: LLM output used directly in SQL, shell commands, or rendered as unsanitized HTML
- LLM-AGENCY: LLM agent with destructive tool access without human-in-the-loop
- LLM-LEAK: System prompt leakage via extraction attacks
- LLM-RAG: RAG pipeline retrieving and trusting untrusted content
---
Severity Assignment Quick Reference
| Category Code | Default Severity | Rationale |
|---|---|---|
| INJ-SQL (confirmed user input) | CRITICAL | Direct data access, potential RCE |
| INJ-CMD (user input in exec calls) | CRITICAL | Direct RCE |
| DESER-JAVA / DESER-PY / DESER-PHP | CRITICAL | RCE via gadget chains |
| AUTH-BYPASS (admin bypass) | CRITICAL | Full system compromise |
| CRYPTO-SECRET (production keys) | HIGH | Credential compromise |
| AC-SSRF (internal network access) | HIGH | Cloud credential theft |
| INJ-XSS (stored, privileged context) | HIGH | Session hijacking |
| AC-IDOR (sensitive data) | HIGH | Data breach |
| AUTH-JWT (alg:none) | HIGH | Authentication bypass |
| LLM-OUTPUT (SQL/shell execution) | HIGH | Indirect code execution |
| SC-CICD (unpinned actions + secrets) | HIGH | Supply chain compromise |
| INJ-XSS (reflected) | MEDIUM | Requires user interaction |
| AC-CSRF (state-changing) | MEDIUM | Requires user interaction |
| CRYPTO-WEAK (MD5 for hashing) | MEDIUM | Depends on data sensitivity |
| ERR-FAILOPEN | MEDIUM | Auth bypass on error |
| CONFIG-HEADER (missing CSP) | LOW | Defense in depth |
| ERR-VERBOSE (stack traces) | LOW | Information disclosure |
Exploitability Modifiers
Increase severity by one tier if:
- Vulnerability is internet-facing (public endpoint)
- No authentication required to reach the vulnerable code path
- Vulnerability is in payment, authentication, or PII-handling code
- Data flow from user input is confirmed
Decrease severity by one tier if:
- Code is only reachable by authenticated admins
- Feature is behind a disabled feature flag
- Vulnerability exists in development/test code only
- Mitigating control exists (WAF, network segmentation)
---
Analysis Methodology — Detailed Steps
Phase 1: Repository Context Research
Use file search tools to:
- Identify existing security frameworks and libraries in use (e.g., Spring Security, Django CSRF, helmet.js)
- Look for established sanitization and validation patterns
- Examine the project's security model and authentication architecture
- Check for existing security annotations or suppression comments
Phase 1.5: Automated Security Scan (Optional)
Run the automated scan script to complement manual analysis with tool-based vulnerability and secret detection.
Prerequisites: trivy, gitleaks, jq (install via brew install trivy gitleaks jq).
Script Discovery:
# Locate the scan script
Glob for **/review/scripts/security-scan.shExecution:
# Run with --quick (skip git history for faster results) and output to the review directory
bash {script_path} --quick --output-dir {output-directory}Graceful Skip: If the script reports tools are not installed (exit code 0 with skip messages), note "Automated Scan: Skipped — tools not installed" in the report summary and continue to Phase 2. Do not block the review.
Parsing Results: When scans complete, read the JSON reports from {output-directory}/:
trivy-*.json— Dependency vulnerabilities and IaC misconfigurations. Cross-reference with A03 (Supply Chain) and A02 (Security Misconfiguration) findings.gitleaks.json— Detected secrets in git history. Cross-reference with A04 (Cryptographic Failures) findings.
Cross-Referencing with Manual Analysis:
- Trivy findings that overlap with manual supply-chain findings increase confidence to HIGH
- Gitleaks findings that overlap with manual hardcoded secret findings increase confidence to HIGH
- Automated findings that have no manual counterpart should be noted in the report but do not replace manual analysis
- Include automated scan summary (pass/fail/skip counts) in the report Summary section
Phase 2: Comparative Analysis
- Compare new code changes against existing security patterns
- Identify deviations from established secure practices
- Look for inconsistent security implementations across similar code paths
- Flag code that introduces new attack surfaces (new endpoints, new user input handling)
Phase 3: Vulnerability Assessment with Data Flow Tracing
For each potential finding, perform data flow analysis: 1. Identify source — user input entry point (HTTP params, headers, body, file uploads) 2. Trace propagation — assignments, transformations, function calls 3. Confirm sink reachability — the dangerous operation (SQL query, command execution, HTML render) 4. Check for sanitizers — parameterization, escaping, validation in the path 5. Assess context — authenticated? internet-facing? sensitive data involved?
Note: Even if something is only exploitable from the local network, it can still be a HIGH severity issue if it handles sensitive data.
---
Sub-Task Orchestration Pattern
Execute the analysis in 3 steps:
1. Identify vulnerabilities using a sub-task. Use repository exploration tools to understand codebase context, then analyze PR changes for security implications across all OWASP 2025 categories.
2. Filter false positives for each vulnerability identified. Launch these as parallel sub-tasks. Apply all false positive filtering rules below.
3. Apply signal quality matrix: Filter out any findings that don't pass the severity/confidence matrix defined in SKILL.md.
---
False Positive Filtering Rules
Hard Exclusions
Automatically exclude findings matching these patterns:
1. Denial of Service (DoS) vulnerabilities or resource exhaustion attacks 2. Secrets or credentials stored on disk if they are otherwise secured 3. Rate limiting concerns or service overload scenarios 4. Memory consumption or CPU exhaustion issues 5. Lack of input validation on non-security-critical fields without proven security impact 6. Input sanitization concerns for GitHub Action workflows unless clearly triggerable via untrusted input 7. Lack of hardening measures — only flag concrete vulnerabilities, not missing best practices 8. Race conditions or timing attacks that are theoretical rather than practical 9. Vulnerabilities related to outdated third-party libraries (managed separately by SCA tools) 10. Memory safety issues in memory-safe languages (Rust, Go, Java, etc.) 11. Files that are only unit tests or only used as part of running tests 12. Log spoofing concerns — outputting un-sanitized user input to logs is not a vulnerability 13. SSRF vulnerabilities that only control the path (only concern if controlling host or protocol) 14. Including user-controlled content in AI system prompts is not a vulnerability by itself 15. Regex injection — injecting untrusted content into a regex is not a vulnerability 16. Regex DoS concerns 17. Insecure documentation — do not report findings in documentation/markdown files 18. Lack of audit logs is not a vulnerability
Framework-Aware Suppression Rules
Apply these when the corresponding framework is detected in the codebase:
| Framework | Safe Pattern | Unsafe Pattern (flag only this) |
|---|---|---|
| Django templates | Auto-escaped by default | safe filter or mark_safe() |
| React JSX | Renders are safe by default | Unsafe innerHTML prop |
| Angular | Auto-escaped by default | bypassSecurityTrust*() methods |
| Spring/JPA | @Query with named parameters | String concatenation in queries |
| Rails ActiveRecord | .where with hash syntax | .where with string interpolation |
| Express + helmet | Headers set by middleware | Missing helmet() call |
| subprocess (Python) | Argument list form | shell=True with user input |
Sanitizer Recognition
Before flagging injection vulnerabilities, check if any recognized sanitizers exist in the data flow path:
Python: html.escape(), bleach.clean(), parameterized queries, shlex.quote() Java: ESAPI.encoder(), HtmlUtils.htmlEscape(), PreparedStatement, Spring @Param JavaScript: DOMPurify.sanitize(), validator.escape(), parameterized queries Go: html.EscapeString(), parameterized database/sql queries Custom: Look for wrapper functions with names like sanitize, escape, clean, safe
Entropy-Based Secret Detection
When evaluating hardcoded secrets (CRYPTO-SECRET):
- Real secrets have high entropy (> 3.5 bits/char for hex, > 4.5 for base64)
- Exclude known placeholder patterns:
example.com,localhost,test,dummy,placeholder,YOUR_API_KEY,XXXXXXXX,changeme - Exclude template variables:
${VAR},{{var}},<API_KEY> - Environment variables and CLI flags are trusted values — attacks relying on controlling env vars are invalid
Precedents
1. Logging high-value secrets in plaintext IS a vulnerability. Logging URLs is assumed safe. 2. UUIDs can be assumed unguessable and do not need validation. 3. Environment variables and CLI flags are trusted values. 4. Resource management issues (memory/file descriptor leaks) are not valid. 5. Subtle/low-impact web vulnerabilities (tabnabbing, XS-Leaks, open redirects) should not be reported unless extremely high confidence. 6. React and Angular are generally secure against XSS. Do not report XSS unless using explicitly unsafe HTML injection methods. 7. Most GitHub Action workflow vulnerabilities are not exploitable in practice. Ensure a very specific attack path exists. 8. Lack of permission checking in client-side JS/TS is not a vulnerability (backend handles validation). 9. Only include MEDIUM findings if they are obvious and concrete issues. 10. Most notebook (*.ipynb) vulnerabilities are not exploitable in practice. 11. Logging non-PII data is not a vulnerability. Only report if exposing secrets, passwords, or PII. 12. Command injection in shell scripts is generally not exploitable (scripts don't run with untrusted input). Only report with a very specific untrusted-input attack path.
Signal Quality Criteria
For each remaining finding, assess: 1. Is there a concrete, exploitable vulnerability with a clear attack path? 2. Does this represent a real security risk vs theoretical best practice? 3. Are there specific code locations and reproduction steps? 4. Would this finding be actionable for a security team?
If any answer is "no", suppress the finding.
---
Per-Finding Haiku Verification
Haiku Agent Prompt Template
Each Phase 4 verification agent receives this prompt:
You are independently verifying a security finding. Your job is to determine if this finding is real, a false positive, or overstated.
FINDING:
{finding description, severity, confidence, category, CWE}
CODE CONTEXT:
{relevant diff section around the flagged code, ~50 lines}
FRAMEWORK/SANITIZER CONTEXT:
{security frameworks and sanitization patterns discovered in Phase 1}
FALSE POSITIVE RULES:
{hard exclusions and framework-aware suppression rules from this skill}
Verify by checking:
1. Can user input actually reach the vulnerable sink? Trace the data flow.
2. Are there sanitizers, validators, or parameterization in the path?
3. Does the framework's security model prevent this exploit?
4. Does this match any hard exclusion rule or known false positive pattern?
5. What is the actual exploitability — internet-facing? auth required? admin-only?
Return ONE verdict:
- KEEP: Finding confirmed. {brief evidence — e.g., "Data flow traced from req.body.email through to db.raw() with no parameterization"}
- DISMISS: False positive. {specific reason — e.g., "React auto-escapes JSX expressions, no unsafe HTML APIs used"}
- DOWNGRADE: Valid but adjust. {what to change — e.g., "Downgrade severity to MEDIUM: only reachable by authenticated admins behind feature flag"}Verdict Criteria
| Verdict | When to Apply |
|---|---|
| KEEP | Data flow confirmed, no sanitizers in path, exploit scenario is plausible |
| DISMISS | Framework handles it automatically, sanitizer found in path, matches hard exclusion, exploit requires controlling trusted inputs (env vars, CLI flags) |
| DOWNGRADE | Finding is real but exploitability modifiers apply (admin-only, behind feature flag, mitigating control exists, requires chained bugs) |
Integration with Existing Filtering
Haiku verification runs AFTER the main analysis but BEFORE report generation. It complements (not replaces) the existing false positive filtering:
1. Main analysis applies hard exclusions and framework suppression → candidate findings 2. Haiku agents independently verify each candidate → KEEP/DISMISS/DOWNGRADE 3. Report includes only KEEP and DOWNGRADED findings
If ALL findings are dismissed by Haiku verification, generate a clean report.
---
Report Template
# Security Review Report
**Date**: {ISO 8601 date}
**Branch**: {current branch name}
**Commit**: {short commit hash}
**Reviewer**: Claude Code (security-review)
**Framework**: OWASP Top 10 2025 + API Top 10 + LLM Top 10
## Summary
- **Blocker**: {count} findings
- **Improvement**: {count} findings
- **Question**: {count} findings
- **Total**: {count} actionable findings
- **Automated Scan**: {Passed | X issues found | Skipped — tools not installed}
- **Haiku Verification**: {N} findings verified — {kept} kept, {dismissed} dismissed, {downgraded} downgraded
---
### [Blocker] Vuln 1: {Category Code}: `{file}:{line}`
* **Severity**: {CRITICAL|HIGH}
* **Confidence**: HIGH
* **Category**: {OWASP A0X:2025}
* **CWE**: CWE-XXX
* **Description**: {Detailed description of the vulnerability}
* **Exploit Scenario**: {Specific attack path with example payload}
* **Recommendation**: {Concrete fix with code example}
---
## Final Note
Focus on Blocker and Improvement findings. Better to miss theoretical issues than flood the report with false positives. Each finding should be something a security engineer would confidently raise in a PR review.---
GitHub PR Comment Format
Used in Phase 6 when --post-to-pr is enabled. Keep comments brief. Include OWASP codes, CWE refs, and severity.
Comment Template
### Security review
Found {N} security issues:
1. **[{SEVERITY}]** {Category Code} — {brief description} (CWE-{XXX})
{link to file with full SHA and line range}
2. **[{SEVERITY}]** {Category Code} — {brief description} (CWE-{XXX})
{link to file with full SHA and line range}
---
Generated with [Claude Code](https://claude.ai/code)
<sub>If this review was useful, react with :+1:. Otherwise, react with :-1:.</sub>Clean Review Comment
### Security review
No high-confidence security vulnerabilities found. Checked against OWASP Top 10 2025, API Top 10, and LLM Top 10.
---
Generated with [Claude Code](https://claude.ai/code)Code Link Format
Links MUST use full SHA and line range:
https://github.com/{owner}/{repo}/blob/{full-sha}/{path/to/file}#L{start}-L{end}- Use full 40-character SHA (not abbreviated)
- Include at least 1 line of context before and after
- Get the full SHA via:
git rev-parse HEAD