Files
trailofbits__skills/plugins/static-analysis
Dan Guido 9b2813356e static-analysis: resolve SARIF severity from the rule, not just result.level (#271)
* static-analysis: resolve SARIF severity from the rule

result.level is optional in SARIF 2.1.0 and CodeQL never emits it: severity
lives on the rule as defaultConfiguration.level and the result inherits it.
sarif-parsing read result.level directly in the helper, the jq reference and
the SKILL, so the documented CI gate counted zero errors on a CodeQL run
however many it found.

resolve_level() now joins the rule by ruleIndex, falls back to ruleId, and
returns result.level when present, "warning" when neither states one, and
"none" for a kind other than "fail" so passing compliance records do not
inherit an error. The jq queries and every SKILL example resolve the same way.

compute_fingerprint() hashed the basename alone, so the same rule at the same
line in two directories collided and deduplicate() dropped the second finding.
It now hashes the whole normalized path.

Two fixtures and a pytest suite pin both: fixtures/codeql-no-level.sarif holds
one error reachable only through its rule, fixtures/levels-on-results.sarif
holds one error on the result, and the suite runs the documented jq gate over
both so the docs cannot drift from the helper.

Fixes #262

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* static-analysis: reject a negative ruleIndex in the jq resolver

SARIF writes ruleIndex: -1 for "no rule", and $rules[-1] in jq is the last
element, so the documented function labelled those results with whatever
severity the final rule in the array happened to carry. The Python resolver
already rejected it through its 0 <= index < len(rules) bound; the jq copies
now require >= 0 too, and a test pins both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* static-analysis: run the documented GitHub Actions gate in the suite

The workflow step inlines its own copy of the resolver, since a workflow has
no shell variable to paste LEVEL_FN into, so comparing the LEVEL_FN blocks
left the one artifact issue #262 named untested. The suite now extracts that
step's jq program and runs it over both fixtures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* static-analysis: coalesce null kind and drop the misleading pysarif level example

resolve_level read `result.get("kind", "fail")`, which only defaults when the key
is absent; an explicit `"kind": null` returned "none" and hid a real error, where
the jq gate's `// "fail"` coalesces it to a fail. Coalesce null to "fail" so the two
resolvers agree, and add a null/fail regression test the buggy form fails.

The pysarif Strategy 2 example computed `result.level or rule_levels.get(...)`, but
pysarif fills a missing result.level with "warning", so the rule-inheritance fallback
was dead code and a CodeQL error printed as "warning". Drop it and point severity
gating at Strategy 1's level() or resolve_level(), which resolve from the rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014UFqJu1ada7gXjD9peo1rX

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 17:38:12 -04:00
..

Static Analysis

A comprehensive static analysis toolkit with CodeQL, Semgrep, and SARIF parsing for security vulnerability detection.

CodeQL and Semgrep skills are based on the Trail of Bits Testing Handbook:

Author: Axel Mierczuk & Paweł Płatek

Skills Included

Skill Purpose
codeql Deep security analysis with taint tracking and data flow
semgrep Fast pattern-based security scanning
sarif-parsing Parse and process results from static analysis tools

When to Use

Use this plugin when you need to:

  • Perform security vulnerability detection on codebases
  • Run CodeQL for interprocedural taint tracking and data flow analysis
  • Use Semgrep for fast pattern-based bug detection
  • Parse SARIF output from security scanners
  • Aggregate and deduplicate findings from multiple tools

What It Does

CodeQL

  • Create databases for Python, JavaScript, Go, Java, C/C++, and more
  • Run security queries with SARIF/CSV output
  • Generate data extension models for project-specific APIs
  • Select and combine query packs (security-extended, Trail of Bits, Community)

Semgrep

  • Quick security scans using built-in rulesets (OWASP, CWE, Trail of Bits)
  • Write custom YAML rules with pattern matching
  • Taint mode for tracking data flow from sources to sinks
  • CI/CD integration with baseline scanning

SARIF Parsing

  • Understand SARIF 2.1.0 structure
  • Resolve a result's severity from the rule it inherits it from, which CodeQL relies on
  • Quick analysis using jq for CLI queries
  • Python scripting with pysarif and sarif-tools
  • Aggregate and deduplicate results from multiple files
  • CI/CD integration patterns

Running a Semgrep scan

Two entry points over one implementation.

/static-analysis:semgrep-scan {"target": "/abs/path", "mode": "run-all"}

runs the scan end to end: detect languages and Pro, select rulesets, scan, merge, report. It does not stop to have the ruleset list approved — invoking it with a target is the opt-in. That is safe because the scan is read-only over the target: no --autofix, every write inside the output directory, and it refuses to run when the output directory is the target.

Ask for the semgrep skill instead when the ruleset selection is the thing that matters. Its five-step path presents the list and waits for approval before anything runs. Both paths read the same references/, so a ruleset added to rulesets.md reaches both.

Workflows Included

Workflow Purpose
workflows/semgrep-scan.js Ships as /static-analysis:semgrep-scan. Four phases: Detect, Select, Scan, Report

Scripts Included

Script Purpose
skills/semgrep/scripts/run-scans.sh Builds every semgrep command from the selected rulesets, clones the third-party rule repos, runs the scans in batches and writes scans.json
skills/semgrep/scripts/merge_sarif.py Merges the per-scan SARIF into one results.sarif

Generating the commands in one place is what makes --metrics=off, the --include scoping rule, and the output-directory --exclude properties of the code rather than instructions a model can drop.

No subagent runs any part of the scan — the workflow's Scan phase calls run-scans.sh. Exit codes come from the semgrep processes and finding counts from the JSON they wrote, so nothing in the result is a self-report that a later step has to go behind and verify.

Tests

Both suites are hermetic, reach no network, and are discovered by CI's existing shell-suite step.

Suite Covers
tests/run_scan_tests.sh run-scans.sh: command generation via --dry-run, plus execution, exit codes and clone failures against stub semgrep and git binaries
tests/run_workflow_tests.sh semgrep-scan.js, via tests/workflow-harness.js, which compiles it with stubbed globals. --self-test mutates the workflow and requires every mutation to turn a scenario red

Installation

/plugin install trailofbits/skills/plugins/static-analysis
  • variant-analysis - Use CodeQL/Semgrep patterns to find bug variants