Preface¶
A very common task in security audits goes like this: you identify a vulnerability in a specific file, fix it, but still feel uneasy—could the same pattern appear again in another module? Vulnerabilities rarely exist only in the single line you first examined. The same development habits, a copy-paste operation, or a fix that only addressed one location can leave multiple variants of the same root cause across the codebase.
Doing this manually usually stops at the original file, or you might quickly write a grep command. It can find exact literal matches, but will miss cases where variable names, APIs, or even programming languages have changed. Trail of Bits turned this everyday “variant analysis” work that security engineers do into an Agent Skill: variant-analysis. The current plugin version is 2.0.1 as noted in the metadata, authored by Axel Mierczuk, and licensed under CC BY-SA 4.0. It does not help you find vulnerabilities from scratch, and specifically solves the problem: “you already found one, are there similar instances in the repository?”
What is variant-analysis¶
variant-analysis is a plugin in Trail of Bits’ Skill Marketplace (trailofbits/skills), located in the directory plugins/variant-analysis/. It provides the Agent with a repeatable workflow: first extract the root cause, then write a pattern that only matches the known vulnerable instance, then relax one condition at a time to search the entire repository, and finally triage the results and write a report.
Its official positioning is very narrow, and it is suitable for these scenarios:
- You have confirmed a vulnerability or logical error, and want to find similar instances across the entire codebase
- You want to generalize known vulnerable instances into CodeQL / Semgrep rules to cover the same class of patterns
- After initially identifying an issue, conduct a systematic code audit instead of re-scanning code that “feels suspicious”
- Analyze how the same root cause manifests across different code paths
The official documentation also lists scenarios where it should not be used: when there are no known bugs yet, for general code reviews, when writing repair suggestions, or when you do not yet understand the code. For the first two cases, you should use the audit-context-building or domain audit Skills from the same marketplace respectively; use issue-writer for writing repair suggestions.
The Skill itself follows the universal SKILL.md format, and tools that support Agent Skills such as Claude Code, Codex, and Cursor can load it. The plugin also includes a workflow /variant-analysis:variants, which is a Claude Code plugin command used to scan multiple expansion axes in parallel in large repositories.
Core Method: Five Steps, Not One Super Regex¶
The plugin breaks down a variant search into five steps, each with a corresponding strategy document located in skills/variant-analysis/references/. The Agent will read the corresponding document at each step.
1. First understand the original problematic code. You need to extract why it is wrong, not just what this line of code does. The official recommendation is to first ask four questions: What is the dangerous operation? (eval(), SQL concatenation, authentication checks), What data makes it dangerous? What protection is missing? What context allows it to exist? Then write a root cause statement:
This vulnerability exists because [UNTRUSTED DATA] reaches [DANGEROUS OPERATION] without [REQUIRED PROTECTION].
For example: “User input reaches eval() without being sanitized”. For logical bugs without data flows, rewrite the broken invariant, such as “An unauthenticated caller must return False, but returned True when both IDs were null”. This statement itself becomes the subsequent search pattern. Also list potential directions for variants: semantically similar identifiers, other ways of making the same mistake, null values and edge cases.
2. First write a pattern that only matches the known vulnerable instance. Use ripgrep to perform an exact match on the original code, confirm that it hits exactly the one known vulnerable location. A pattern with zero matches means your understanding of the bug is incorrect, and all subsequent searches will be calibrated on flawed code.
3–4. Generalize only one element at a time. Move from exact matching to pattern families, re-run the search after each change, and review all newly added matches. Stop if false positives exceed about half, roll back, and try a different abstraction path. Do not replace variable names, function names, and parameter positions with wildcards all at once—you will introduce too much noise and will not know which step caused it.
5. Triage. Determine whether a candidate is truly the same class of issue, and assign a severity level. Looking at code snippets alone is not enough: you need to read the surrounding functions, callers, and types of related values, and specifically look for reasons why it might actually be safe: preceding guards, sanitization, parameterized APIs, type constraints. The absence of callers today does not automatically dismiss the finding: unprotected code is still a discovery, just with a lower severity rating.
After the five steps, you should write a report, including the patterns that failed during the process, and provide a rule that can be added to CI to prevent the same class of issues from reoccurring after fixing one instance. The report template is located in resources/variant-report-template.md.
How to Choose Tools¶
The Skill is not tied to a single scanner. The official recommendations are straightforward:
- Use ripgrep for quick reconnaissance: zero configuration, ideal for initial exploration
- Use Semgrep for simple structural patterns: easy syntax to write, no need for a compilable project, works with incomplete code
- Use Semgrep taint or CodeQL when tracking whether values flow from a source to a sink
- Use CodeQL for cross-function, interprocedural analysis
- Continue using Semgrep if the code does not compile, do not jump straight to CodeQL
The recommended order is: ripgrep reconnaissance → Semgrep for iterative pattern refinement → CodeQL for deep data flow analysis. The official documentation lists “only using CodeQL” as an anti-pattern: the faster tools first tell you where CodeQL should focus its efforts.
The repository also includes ready-made templates. CodeQL templates are in resources/codeql/, and Semgrep templates are in resources/semgrep/, covering Python, JavaScript, Java, Go, C++, and more.
Abstraction Ladder: Climbing from the Original Code to Security Properties¶
Patterns have different levels of abstraction. The official uses a SQL injection example as a calibration demo.
Original code:
query = "SELECT * FROM users WHERE id=" + request.args.get('id')
Level 0 is literal matching, used to confirm that your understanding matches the exact instance:
rg 'SELECT \* FROM users WHERE id=" \+ request\.args\.get'
This level should have exactly 1 match, with 0 false positives. It is not for searching, but for calibration.
Level 1 replaces variable names with metavariables, used to find copy-paste variants:
pattern: $QUERY = "SELECT * FROM users WHERE id=" + $INPUT
Level 2 relaxes the structure, for example “any string concatenation used in cursor.execute”, which may return dozens of matches and start introducing false positives.
Level 3 abstracts to the security property itself, using taint analysis: sources are request.args.get / request.form.get, sinks are cursor.execute. This covers the most cases, but also has the highest false positive rate, requiring thorough triaging.
The appropriate level depends on your goal: use Level 0 to verify a fix, use Level 1 to find copy-paste variants, use Level 2 to audit a component, and climb to Level 3 only for full-repository security assessments. In variant searches, you should stop when false positives exceed ~50%; if the rule will be added to CI to block commits, the acceptable false positive rate is stricter, around less than 5%.
The search scope must be the entire repository root directory, do not only search the module where the original bug was found. The official documentation lists “only searching in api/handlers/ and missing utils/auth.py” as the most common cause of failed searches.
Installation and Activation¶
Trail of Bits has packaged this as both a Claude Code plugin and a portable Skill. Installation methods vary by tool:
1. Claude Code (Official Marketplace)
First add the marketplace, then install the plugin:
/plugin marketplace add trailofbits/skills
You can then select it via the menu, or install this plugin directly:
/plugin install trailofbits/skills/plugins/variant-analysis
After installation, you can use the workflow command /variant-analysis:variants. Pass parameters as a JSON object, not as prose. The bug field is required, and it is recommended to include file:line_number; root defaults to the current directory; lang is the primary language used to select tools; out is the report path, defaulting to variant-analysis-report.md.
{"bug": "api/auth.py:42 compares a boolean value to a token string", "root": ".", "lang": "python", "out": "variant-analysis-report.md"}
The plugin’s README notes that when the primary language source files are fewer than ~40 (excluding vendored and test fixtures), the workflow will narrow down to only one scan pass, as parallelization provides no benefit at this scale. For large codebases with many root cause variations, it will spin up parallel sub-agents along expansion axes, looping until no new findings are discovered.
2. Codex
The official README states that Codex can directly load the Claude plugin marketplace:
codex plugin marketplace add trailofbits/skills
codex plugin list
codex plugin add variant-analysis@trailofbits
3. Cursor and other tools supporting SKILL.md
Place the complete Skill directory in the location your tool will scan, do not only copy a single SKILL.md file. The references/ and resources/ folders contain the five-step strategies and rule templates; without them, the Agent can only work from the abstract in the main file. Cursor loads skills from project-level .cursor/skills/, .agents/skills/, user-level ~/.cursor/skills/, ~/.agents/skills/, and for compatibility, also reads .claude/skills/ and .codex/skills/.
The project directory structure should look like this:
.cursor/skills/variant-analysis/
SKILL.md
references/
resources/
The installation command given on officialskills.sh is:
npx skills add https://github.com/trailofbits/skills --skill variant-analysis
After installation, simply ask “are there others like this?” in a conversation to trigger the Skill; you can also call it explicitly with /variant-analysis. The namespaced workflow command /variant-analysis:variants is a Claude Code plugin command, and will usually not appear as a slash command in Cursor.
Typical Usage¶
The official provided an authentication check example, and the workflow can be followed directly.
Suppose you see this line at api/auth.py:42:
if user.isAuthenticated == request.token:
return allow_access()
The root cause is not “this line is poorly written”, but rather: “Comparing a boolean value to a string will always evaluate to false, reversing the intended logic”.
First perform Level 0 calibration:
rg "user.isAuthenticated == request.token"
After confirming that only the original vulnerable location matches, replace the objects with metavariables:
pattern: $USER.isAuthenticated == $INPUT
If the codebase has more than one authentication attribute, expand along the “semantically similar identifiers” direction, adding actual existing names like isActive, isVerified, etc., instead of listing an arbitrary dictionary. Re-run the search after each expansion and review all new matches.
You do not need to write the rule first in the conversation. The Skill’s trigger description notes that phrases like “are there others like this?” or “is this the same bug?” will invoke it. You could say:
I confirmed that the comparison `user.isAuthenticated == request.token` at api/auth.py:42 is incorrect. Please perform a variant search using variant-analysis: first write a root cause statement, then calibrate with an exact pattern, then relax one condition at a time to search the entire repository, and finally write a report per the template.
For large repositories, if you have already installed the plugin in Claude Code, use the workflow command to let it scan along expansion axes in parallel.
The report should at minimum include: root cause statement, tried patterns (level, tool, match count, true positives, false positives), confirmed variants (location, severity, status), and false positives grouped by cause. Finally, refine the pattern that found the most variants into a Semgrep or CodeQL rule that can be added to CI.
Applicable Scenarios and Notes¶
This is suitable for people who already have a known anchor point: security engineers who fixed one vulnerability and want to scan for regressions, developers who just merged a CVE patch and want to check if forked paths missed fixes, or those who want to turn known XSS/injection flaws into repository-wide rules. It does not replace initial audits, nor does it replace the writing of repair solutions.
The official documentation lists specific reasons for failed searches, which are worth checking against:
1. Too narrow scope: Only searching the module where the original bug resides. Variants appearing in other directories are the norm.
2. Too rigid pattern: Only searching the exact original attribute name. When isAuthenticated causes issues, isActive / isAdmin / isVerified are often the same class of checks.
3. Only chasing one vulnerability shape: The root cause is “the condition allows access when it should reject it”, which may also manifest as null value equality bypasses, documentation mismatching implementation, or reversed conditions.
4. Only testing happy paths: Not testing nulls, empty collections, or unauthenticated users. A == comparison evaluating to true when both sides are None is a very common authorization bypass.
5. Generalizing too quickly: Changing multiple elements at once, making noise impossible to attribute.
The official also emphasizes two edge cases during triage. First, documentation and implementation mismatches: a function name or docstring says DENY, but the return value grants access when authorization succeeds—every call site of such a function is a potential finding. Second, do not immediately dismiss a finding as a false positive just because there are no current callers; just assign a lower severity rating.
Related Skills are available in the same marketplace: use codeql for deep interprocedural analysis, semgrep for simple patterns, and sarif-parsing for processing scan results. If you do not yet understand the code, first use audit-context-building.
Summary¶
variant-analysis does one very specific thing: you already have a confirmed bug, and it helps you find all similar instances across the entire codebase based on the root cause, while documenting the search process into a reviewable report and CI-ready rules. The method itself is not new—security engineers have been doing this work for years—but packaging it as a Skill allows developers to have an Agent run through the same standardized workflow after fixing one issue, to catch overlooked copy-pastes and parallel implementations.
Official links:
- Skill: https://github.com/trailofbits/skills/tree/main/plugins/variant-analysis/skills/variant-analysis
- Plugin documentation: https://github.com/trailofbits/skills/tree/main/plugins/variant-analysis
- Skill Marketplace: https://github.com/trailofbits/skills