Help us improve
Share bugs, ideas, or general feedback.
From agentic-security
Walks through the six-step recipe for adding a new SAST detector — pick module, export scan*(), wire into engine, add fixtures, write test, verify.
npx claudepluginhub clear-capabilities/agentic-security --plugin agentic-securityHow this skill is triggered — by the user, by Claude, or both
Slash command
/agentic-security:add-scan-ruleThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
When the user wants the scanner to detect a new vulnerability pattern, follow this workflow. Loaded on-demand, not into every session — see `scanner/src/sast/CLAUDE.md` for the same content baked into the SAST tree.
Write custom Semgrep SAST rules in YAML to detect application-specific vulnerabilities, enforce coding standards, and integrate into CI/CD pipelines.
Sets up SAST tools like Semgrep, SonarQube, and CodeQL for security scanning, custom rules, and CI/CD integration across Python, JavaScript, Go, Java, and more.
Writes custom Semgrep SAST rules in YAML to detect application-specific vulnerabilities, enforce coding standards, and integrate into CI/CD pipelines.
Share bugs, ideas, or general feedback.
When the user wants the scanner to detect a new vulnerability pattern, follow this workflow. Loaded on-demand, not into every session — see scanner/src/sast/CLAUDE.md for the same content baked into the SAST tree.
| Shape of the rule | Module pattern |
|---|---|
| Language-specific (e.g. Python sink, Kotlin force-unwrap) | Add to or create scanner/src/sast/<language>.js |
| Framework-specific hardening (e.g. Express auth, FastAPI defaults) | scanner/src/sast/<framework>-hardening.js |
| Cross-cutting vuln class (CSRF, prototype pollution, mass assignment) | New top-level scanner/src/sast/<topic>.js |
| Posture annotator (mutates existing findings) | scanner/src/posture/ — see scanner/src/posture/CLAUDE.md |
If the rule is "X but with light cross-file context," still emit from sast/ and let the cross-file annotators in posture/cross-lang-*.js enrich it. Don't put cross-file logic into the SAST module.
Write the detector. Export a scan<Name>(fileContents, opts) function returning Finding[]. Required fields: id, severity, file, line, vuln, cwe, description, remediation. Set family and parser when you know them; posture/finding-defaults.js backfills, but detector-set wins.
Wire into the engine. Open scanner/src/engine.js. Add the import to the existing block (alphabetical) and call it inside runFullScan next to similar rules. Append the results to finalFindings.
Add the fixture pair. Create scanner/test/fixtures/<rule-name>/vulnerable/ and scanner/test/fixtures/<rule-name>/clean/. Each holds one small file demonstrating the vulnerable / fixed shape. The vulnerable file must produce a finding; the clean file must not.
Add the test. Either extend an existing topical test (e.g. test/python-sinks.test.js) or create test/<rule-name>.test.js modelled on the smallest existing test. Wire the file into the matching scoped script in scanner/package.json (test:sast, test:dataflow, etc.).
Verify. Use the scoped script that covers what you touched (npm run test:smoke, npm run test:sast, npm run test:dataflow, …). Always include npm run test:lifecycle — it catches a rule that ships without an engine.js wire-up. The full npm test is the CI gate but is usually overkill for a single new rule.
Rebuild the bundle if anything outside src/ will consume it. npm run build. Then npm run smoke to check the CLI path. If bundle smoke disagrees with unit-test smoke, you have an engine.js wiring miss — unit tests run against src/ directly, but the bundle re-binds at build time.
blankComments() from scanner/src/sast/_comment-strip.js before scanning a file body. Otherwise a vuln pattern inside a // example: block fires.exec('ping ' + req.body.host, …)), the match offset and the readable sink line can diverge. Use the actual sink expression for the finding's snippet, not lines[regex.lastIndex].critical without strong evidence. annotateExploitability will lift high to critical when production context warrants. A flood of false criticals drowns real ones.family, the rule probably covers too much. Split it.scanner/src/sast/bench-shape/ and stays gated by AGENTIC_SECURITY_BENCH_SHAPE=1. Never make a production rule depend on bench shape.After step 5 the lifecycle test should pass with the new file. If it fails with "exported X has no external call site," you forgot step 2 — go back and add the import + call in engine.js.
If npm run smoke reports the new rule fires in clean/ (false positive on the clean fixture), tighten the regex/AST match. If it doesn't fire in vulnerable/, your match shape is wrong — look at the actual fixture content with the actual detector and trace where it diverges.
You're done when:
npm run test:lifecycle passesnpm run build && npm run smoke produces the same finding count via the CLI as the unit test reported