| name | static-analysis |
| description | Security reviews, code quality audits, or when the user mentions CodeQL, Semgrep, or finding vulnerability patterns across a codebase. |
Static Analysis
When to activate
Security reviews, code quality audits, or when the user mentions CodeQL, Semgrep, or finding vulnerability patterns across a codebase.
When NOT to use
- Runtime security testing (fuzzing, penetration testing, DAST) — static analysis only covers code paths, not live execution
- Linting for style/formatting — use ESLint, Prettier, Ruff; those are not vulnerability scanners
- Single-file code review without a vulnerability focus — use the
code-review skill instead
Instructions
Semgrep
Running against a repo:
semgrep --config auto .
semgrep --config p/owasp-top-ten .
semgrep --config ./rules/custom.yaml .
semgrep --config auto --error .
Pattern syntax:
$X — metavariable, matches any expression
... — ellipsis, matches zero or more statements or arguments
(int)$X — typed metavariable (Java/C only)
Writing a custom rule:
rules:
- id: sql-string-concat
pattern: |
$QUERY = "..." + $INPUT
$DB.execute($QUERY)
message: "Possible SQL injection: user input concatenated into query"
severity: ERROR
languages: [python, javascript]
metadata:
cwe: "CWE-89"
Key patterns to write for any codebase:
- SQL injection: untrusted input reaching
.execute(), .query(), db.raw()
- Path traversal:
path.join() with user input, fs.readFile() with unchecked paths
- Command injection:
exec(), spawn(), subprocess.run() with string interpolation
- Deserialization:
pickle.loads(), JSON.parse() on network data passed to eval()
- Hardcoded secrets: patterns like
password = "...", api_key = "sk-...", token = "..."
- Reflected XSS: user input into
innerHTML, document.write(), res.send() without sanitization
CodeQL
CLI workflow:
codeql database create mydb --language=javascript --source-root=.
codeql query run queries/sql-injection.ql --database=mydb
codeql database analyze mydb javascript-security-extended.qls --format=sarif-latest --output=results.sarif
Query structure:
import javascript
from DataFlow::PathNode source, DataFlow::PathNode sink, TaintTracking::Configuration cfg
where cfg.hasFlowPath(source, sink)
select sink, source, sink, "Tainted data from $@ reaches SQL query.", source, "user input"
GitHub Actions — Code Scanning:
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript, python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:javascript"
Variant Analysis
When a known vulnerability is found (e.g., one SQL injection), use variant analysis to find all instances of the same pattern:
- Write a Semgrep or CodeQL rule that captures the exact pattern
- Run it across the full codebase, not just the affected file
- Triage each match — true positive, false positive, or needs review
- Document which instances are fixed vs. accepted risk
Fix Verification
After patching a vulnerability:
- Re-run the original rule that detected it — should produce zero findings
- Check adjacent code for the same pattern — the fix may have missed related sinks
- Add the rule to CI so regression is caught automatically
CI Integration Strategy
semgrep --config auto --severity ERROR --error .
semgrep --config auto --severity WARNING --json --output results.json .
Do not fail CI on WARNING for broad rulesets — the false positive rate will block legitimate work. Tune the severity threshold per project.
Example
Security audit of a Node.js Express API:
semgrep --config auto . --json --output results.json
cat > rules/knex-injection.yaml << 'EOF'
rules:
- id: knex-raw-injection
pattern: knex.raw($QUERY + $INPUT)
message: "Unsanitized input in knex.raw()"
severity: ERROR
languages: [javascript, typescript]
EOF
semgrep --config rules/knex-injection.yaml .