variant-analysis
Hunts for the other instances of a bug already found — the variants of one root cause across a codebase. Use immediately after a vulnerability, logic bug, or bad pattern turns up in a specific file and the question becomes where else it occurs, including the bare conversational f
Install
npx skills add https://github.com/trailofbits/skills/tree/main/plugins/variant-analysis/skills/variant-analysis
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install trailofbits-skills@llmmart
git clone https://github.com/trailofbits/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole trailofbits/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Variant Analysis
Find the other instances of a bug you have already found. One root cause usually has several manifestations, and they are rarely in the module where you found the first one.
When to Use
- A vulnerability has been found and you need to search for similar instances
- Building or refining CodeQL/Semgrep queries for security patterns
- Performing systematic code audits after an initial issue discovery
- Analyzing how a single root cause manifests in different code paths
When NOT to Use
- Initial vulnerability discovery — use audit-context-building or a domain-specific audit
- General code review with no known pattern to search for
- Writing fix recommendations — use issue-writer
- Understanding unfamiliar code — use audit-context-building first
The Five Steps
Read the reference for a step when you reach it.
1. Understand the original issue. Extract the root cause — why the code is wrong, not what it does — and enumerate the directions a variant could hide in: related identifiers, other manifestations of the same mistake, data-type edge cases. → references/root-cause.md
2. Create an exact match. Write a pattern matching ONLY the known instance and confirm it hits. A pattern that matches nothing means you have misunderstood the bug, and every search built on it is calibrated against the wrong code.
3–4. Generalize one element at a time. Climb from the exact match toward the pattern family, running and reading all matches after each single change. Stop when more than half the matches are noise. → references/searching.md — abstraction ladder, tool selection, false-positive filters
5. Triage. Decide which candidates are real, and say so with a severity attached. → references/triage.md
Then write it up, including the patterns that failed and a CI rule to prevent regression. → references/reporting.md
Running it as a Workflow
This plugin ships /variant-analysis:variants, which runs the five steps across parallel
subagents — one per expansion axis, looping until the sweep stops finding anything new.
Each stage reads the reference above that matches its job.
Use the workflow when the codebase is large or the root cause has many manifestations. Work the steps directly when the search is narrow or you want a say in each generalization.
What Makes Hunts Fail
- Narrow scope — searching only the module the original bug was in
- Pattern too specific — searching one attribute and missing the family around it
- One vulnerability class — chasing a single manifestation of the root cause
- Happy-path testing — never trying the null, empty, and boundary cases
- Generalizing too fast — abstracting several elements at once, so noise cannot be attributed to any one of them
The first three are covered in root-cause.md and searching.md, the fourth in triage.md.
Resources
CodeQL (resources/codeql/): python.ql, javascript.ql, java.ql, go.ql, cpp.ql
Semgrep (resources/semgrep/): python.yaml, javascript.yaml, java.yaml, go.yaml, cpp.yaml
Report: resources/variant-report-template.md
Files (skills)
-
agents
-
openai.yaml 247 B
interface: display_name: "Variant Analysis" short_description: "Find other instances of a known vulnerability or bug pattern" icon_small: "assets/trail-of-bits-mark.svg" icon_large: "assets/trail-of-bits-mark.svg" brand_color: "#D83A34"
-
-
assets
-
trail-of-bits-mark.svg 3 KB · in bundle
-
-
references
-
reporting.md 1.5 KB
# Reporting a Variant Hunt Advice for the final stage. The report is written for a security engineer that is reviewing vulnerability variants. ## Structure Fill `../resources/variant-report-template.md` — the path is relative to this file, so from the skill root it is `resources/variant-report-template.md`. Every section earns its place: - **Summary** — original bug, date, codebase, count - **Original vulnerability** — the root cause statement, the origin location, the code - **Search methodology** — the patterns tried, at which level, with match and FP counts - **Findings** — one block per confirmed variant, severity-ordered - **False positive patterns** — grouped by reason, not one row per match - **Recommendations** — immediate fixes, then preventive measures ## The Methodology Table | Version | Pattern | Tool | Matches | TP | FP | |---------|---------|------|---------|----|----| | v1 | exact | ripgrep | 1 | 1 | 0 | | v2 | abstract | semgrep | N | N | N | This table makes the hunt reproducible. It records which abstractions worked, which produced noise, and where the search stopped. Record the patterns that failed alongside those that worked. ## Quote the Real Code Read each confirmed location and quote what is actually there. A report that paraphrases code loses the detail a reviewer needs to confirm the finding, and a wrong quote destroys trust in every other finding in the document. ## Leave a Regression Guard End with a CI-ready rule derived from whichever pattern found the most variants. -
root-cause.md 4.1 KB
# Root Cause and Expansion Axes Strategy for the first stage of a variant hunt: turning one known bug into the set of independent directions worth searching. Everything downstream is calibrated against what you produce here, so a shallow root cause caps the quality of the entire hunt. ## Why Variants Exist Vulnerabilities cluster because developers make consistent mistakes: 1. **Developer habits**: the same person writes similar code and makes similar errors 2. **Copy-paste propagation**: boilerplate spreads a bug across the codebase 3. **API misuse patterns**: complex APIs invite consistent misunderstandings 4. **Framework idioms**: framework patterns create predictable vulnerability shapes 5. **Incomplete fixes**: the original bug was fixed in one place and missed elsewhere Understanding WHY a variant exists predicts WHERE to find it. A copy-paste bug clusters in sibling files; an API misuse bug clusters at every call site of that API, anywhere. ## Extracting the Root Cause Ask these four questions before writing anything: 1. **What operation is dangerous?** (`eval()`, `system()`, raw SQL, an authorization check) 2. **What data makes it dangerous?** (user-controlled input, a null, an attacker-chosen size) 3. **What's missing?** (sanitization, validation, a bounds check, a null guard) 4. **What context enables it?** (authentication state, an error path, a specific caller) Then formulate a statement: > "This vulnerability exists because [UNTRUSTED DATA] reaches [DANGEROUS OPERATION] > without [REQUIRED PROTECTION]." Examples: - "User input reaches `eval()` without sanitization" - "Attacker-controlled size reaches `malloc()` without an overflow check" - "Untrusted path reaches `open()` without canonicalization" For logic bugs that have no data flow, state the violated invariant instead: "this function must return False for unauthenticated callers, and it returns True when both IDs are null." That statement IS the search pattern. Everything below expands it. ## The Expansion Checklist A single root cause manifests in several ways. Enumerate all of them before searching. ### 1. Semantically related identifiers If the bug involves one name, every name that plays the same role is in scope: - `isAuthenticated`: also `isActive`, `isAdmin`, `isVerified`, `isLoggedIn` - `userId`: also `ownerId`, `creatorId`, `authorId` Ground these in the codebase. Grep for the names before claiming them: a list of plausible identifiers that don't exist wastes an entire search axis. ### 2. Other boolean-logic errors The same mistake in a different shape: - Inverted conditions (`if not x` where `if x` was meant) - Wrong default return (`return True` on the fall-through path) - Short-circuit evaluation errors (`or` where `and` was meant) ### 3. Data-type edge cases - Null/None/undefined comparisons, especially where *both* sides can be null - Empty string vs null - Zero vs null - Empty arrays and collections ### 4. Documentation/code mismatches A function whose behavior contradicts its own name or docstring. Search for functions named with `deny`, `restrict`, `block`, `forbid`, `check`, `validate` and confirm the return value means what the name says. ## What Makes a Good Axis Each axis is handed to a separate agent that knows nothing about the others. So an axis must be: - **Independently searchable** — it names concrete identifiers or code constructs to look for, not a theme like "authorization problems" - **Non-overlapping** — two axes that find the same code waste an agent - **Grounded** — its leads exist in this codebase ## Pitfalls at This Stage **Pattern too specific.** Using only the exact attribute from the original bug misses variants built on related constructs. Enumerate the whole family, not the one instance. **Single vulnerability class.** Focusing on one manifestation misses the others. A "returns allow when the condition is false" bug also hides as a null-equality bypass, a docs/code mismatch, and an inverted conditional. List every manifestation before searching. **Ungrounded axes.** Plausible-sounding identifiers that don't appear in the codebase produce an agent that searches hard and finds nothing. -
searching.md 4.5 KB
# Searching: The Abstraction Ladder Strategy for the sweep stage. You have a root cause and one axis to search. The job is to climb from a pattern that matches only the known bug to one that matches its variants, without climbing so far that the results become noise. ## Tool Selection | Scenario | Tool | Why | |----------|------|-----| | Quick surface search | ripgrep | Fast, zero setup | | Simple pattern matching | Semgrep | Easy syntax, no build needed | | Data flow tracking | Semgrep taint / CodeQL | Follows values across functions | | Cross-function analysis | CodeQL | Best interprocedural analysis | | Non-building code | Semgrep | Works on incomplete code | Use ripgrep for recon, Semgrep for iteration, CodeQL for precision. Tool loyalty is an anti-pattern: "I only use CodeQL" costs you the fast passes that tell you where to aim it. ## The Ladder ### Level 0: Exact match Match the literal vulnerable code. ```python # Original vulnerable code query = "SELECT * FROM users WHERE id=" + request.args.get('id') ``` ```bash rg 'SELECT \* FROM users WHERE id=" \+ request\.args\.get' ``` Matches 1, zero false positives. This is not a search, it is the calibration point that proves your understanding of the bug is correct. ### Level 1: Variable abstraction Replace variable names with metavariables. ```yaml pattern: $QUERY = "SELECT * FROM users WHERE id=" + $INPUT ``` Matches 3-5, low FP. Finds copy-paste variants. ### Level 2: Structural abstraction Generalize the surrounding structure. ```yaml patterns: - pattern: $Q = "..." + $INPUT - pattern-inside: | def $FUNC(...): ... cursor.execute($Q) ``` Matches 10-30, medium FP. Finds pattern variants. ### Level 3: Semantic abstraction Abstract to the security property itself. ```yaml mode: taint pattern-sources: - pattern: request.args.get(...) - pattern: request.form.get(...) pattern-sinks: - pattern: cursor.execute(...) ``` Matches 50-100+, high FP. Comprehensive coverage, requires real triage. ### Choosing your level | Goal | Level | |------|-------| | Verify a specific fix | 0 | | Find copy-paste bugs | 1 | | Audit a component | 2 | | Full security assessment | 3 | ## One Change at a Time Never generalize multiple elements at once. ``` BAD: exact code -> fully abstract pattern GOOD: exact code -> abstract var1 -> abstract var2 -> abstract operation ``` Each step: make ONE change, run it, read ALL new matches, decide whether the FP rate is still acceptable, then continue or revert. Jumping straight to Level 3 produces a pile of results with no way to tell which abstraction introduced the noise. ### Decision points **Abstract this variable name?** Yes if different names could carry the same bug. No if the name itself is the semantic constraint you are relying on. **Abstract this literal?** Yes if any value triggers the bug. No if only specific values are dangerous. **Use `...` wildcards?** Yes if argument position doesn't matter. No if only a specific position is a sink. **Add taint tracking?** Yes if you need to prove data actually flows from source to sink. No if the presence of the pattern is already sufficient evidence. ## Search Scope Run every search against the **entire codebase root**, not the directory the original bug lived in. A bug found in `api/handlers/` with a variant in `utils/auth.py` is the normal case, not the exotic one. Narrow scope is the single most common reason a hunt finds nothing. ## False Positive Management ### Acceptable rates by context | Context | Acceptable FP rate | |---------|-------------------| | Automated CI blocking | <5% | | Developer warning | <20% | | Security audit triage | <50% | | Research/exploration | <80% | For a variant hunt, stop generalizing when more than roughly half the matches are noise. That is the signal you climbed one level too far: revert and take a different abstraction rather than pushing further up the same one. ### Common FP sources and filters **Test code** — exclude test trees: ```bash rg "pattern" --glob '!**/test*' --glob '!**/*_test.*' ``` **Already sanitized** — subtract the safe form: ```yaml pattern-not: dangerous_func(sanitize($X)) ``` **Literal values** — not attacker-controlled: ```yaml pattern-not: dangerous_func("...") ``` **Dead code** — add reachability constraints: ```yaml pattern-not-inside: | if False: ... ``` Analyze false positives as you go rather than deferring them. They tell you which abstraction was too aggressive, which is information you lose if you triage in a batch at the end. -
triage.md 3.5 KB
# Triage: Deciding Whether a Candidate Is Real Strategy for the verification stage. You have candidate locations that resemble a known bug. The job is to determine which ones actually carry it, and to say so with a severity attached. ## Argue Against the Candidate The snippet alone is never enough. Read the surrounding function, the callers, and the type of every value involved, then look specifically for the thing that makes it safe: - A guard earlier in the function or in a decorator/middleware - A sanitizer, validator, or parameterized API between source and sink - A type constraint that makes the dangerous value unreachable - A caller set that never supplies attacker-controlled input A candidate survives only if you looked for these and did not find them. Note what is *not* on that list: having no callers. Code that nothing reaches today is still unprotected code, and a variant hunt is exactly the search that finds it before a caller arrives. Report it at lower severity — see Exploitability below — rather than refuting it as dead. ## Exploitability For a surviving candidate, establish: - **Reachable** — is there a path from an external entry point to this code? - **Controllable** — can an attacker influence the value that makes it dangerous? - **Unprotected** — is the protection named in the root cause genuinely absent here? A candidate that is reachable and controllable but has a different protection in place is a false positive worth recording, not a finding. A candidate that is unreachable *today* but unprotected is a real finding at lower severity: say so explicitly, and say what would make it reachable. ## Edge Cases That Hide Real Bugs Test every candidate against these if applicable, because normal-path reasoning misses them. ### Null equality bypasses A common authorization bypass. If both sides of a comparison can be null at the same time, the comparison succeeds for the wrong reason: ```python # anonymous_user.id is None, guest_order.owner_id is None # None == None is True, so the check passes for a user who owns nothing if order.owner_id == current_user.id: return True ``` Ask: what values can each side hold? Can both be null simultaneously? Who can cause that? ### Documentation/code mismatch The function does the opposite of what its name or docstring claims: ```python def check_restricted_permission(user, perm): """Returns True if access should be DENIED.""" if user.has_perm(perm): return True # BUG: returns "deny" for users who DO have permission return False ``` Every caller of a function like this is a potential finding, even where the call site looks correct. Verify the return semantics against the name before trusting any use of it. ### Others worth testing - Unauthenticated and anonymous callers - Empty strings vs null, zero vs null - Empty arrays and collections - Boundary values at the limits of the type ## Severity and Confidence Attach a severity to **every** verdict, including informational ones. Do not suppress findings you judge minor: filtering happens downstream, where the full set is visible, and a finding you decline to mention is a finding nobody sees. Separate the two axes: - **Severity** — the impact if this is real - **Confidence** — how sure you are that it is real ## Recording False Positives When a candidate is ruled out, record *why* it was safe. Grouped by reason, these become the false-positive table in the report, and they are what lets the next stage refine the pattern instead of re-triaging the same matches.
-
-
resources
-
codeql
-
cpp.ql 3.4 KB · in bundle
-
go.ql 2 KB · in bundle
-
java.ql 2.3 KB · in bundle
-
javascript.ql 1.7 KB · in bundle
-
python.ql 2.4 KB · in bundle
-
-
semgrep
-
cpp.yaml 2.7 KB
rules: - id: variant-taint-cpp message: "Potential variant: user input flows to dangerous sink" severity: ERROR languages: [c, cpp] mode: taint pattern-sources: # Command line - pattern: argv[$IDX] # Standard input - pattern: gets(...) - pattern: fgets($BUF, $SIZE, stdin) - pattern: scanf(...) - pattern: fscanf(...) - pattern: getenv(...) # Network - pattern: recv($SOCK, $BUF, ...) - pattern: recvfrom(...) - pattern: read($FD, $BUF, ...) pattern-sinks: # Command injection - pattern: system($SINK) - pattern: popen($SINK, ...) - pattern: execl($SINK, ...) - pattern: execlp($SINK, ...) - pattern: execv($SINK, ...) - pattern: execvp($SINK, ...) # Buffer overflow - pattern: strcpy($DST, $SINK) - pattern: strcat($DST, $SINK) - pattern: sprintf($DST, $FMT, ..., $SINK, ...) - pattern: gets($SINK) # Format string - pattern: printf($SINK) - pattern: fprintf($FILE, $SINK) - pattern: sprintf($BUF, $SINK) - pattern: syslog($PRI, $SINK) # Memory - pattern: malloc($SINK) - pattern: calloc($SINK, ...) - pattern: realloc($PTR, $SINK) - pattern: alloca($SINK) # File operations - pattern: fopen($SINK, ...) - pattern: open($SINK, ...) pattern-sanitizers: - pattern: strncpy($DST, $SRC, $N) - pattern: strncat($DST, $SRC, $N) - pattern: snprintf($BUF, $SIZE, ...) - pattern: strlcpy(...) - pattern: strlcat(...) paths: exclude: - "**/test/**" - "**/*_test.c" - "**/*_test.cpp" - id: unsafe-functions-cpp message: "Use of unsafe function - consider bounded alternative" severity: WARNING languages: [c, cpp] pattern-either: - pattern: gets(...) - pattern: strcpy(...) - pattern: strcat(...) - pattern: sprintf(...) - pattern: vsprintf(...) - id: format-string-cpp message: "Potential format string vulnerability" severity: ERROR languages: [c, cpp] patterns: - pattern-either: - pattern: printf($VAR) - pattern: fprintf($F, $VAR) - pattern: sprintf($B, $VAR) - pattern: snprintf($B, $S, $VAR) - pattern-not: printf("...") - pattern-not: fprintf($F, "...") - pattern-not: sprintf($B, "...") - pattern-not: snprintf($B, $S, "...") - id: integer-overflow-cpp message: "Potential integer overflow before memory allocation" severity: WARNING languages: [c, cpp] patterns: - pattern: | $SIZE = $X * $Y; ... malloc($SIZE) - pattern: malloc($X * $Y) - pattern: calloc($X * $Y, ...) -
go.yaml 1.8 KB
rules: - id: variant-taint-go message: "Potential variant: user input flows to dangerous sink" severity: ERROR languages: [go] mode: taint pattern-sources: # net/http - pattern: $REQ.URL.Query().Get(...) - pattern: $REQ.FormValue(...) - pattern: $REQ.PostFormValue(...) - pattern: $REQ.Header.Get(...) # Gin - pattern: $CTX.Query(...) - pattern: $CTX.Param(...) - pattern: $CTX.PostForm(...) - pattern: $CTX.GetHeader(...) # Echo - pattern: $CTX.QueryParam(...) - pattern: $CTX.FormValue(...) # os.Args - pattern: os.Args[$IDX] - pattern: os.Getenv(...) pattern-sinks: # Command injection - pattern: exec.Command($SINK, ...) - pattern: exec.CommandContext($CTX, $SINK, ...) # SQL injection - pattern: $DB.Query($SINK, ...) - pattern: $DB.QueryRow($SINK, ...) - pattern: $DB.Exec($SINK, ...) # Path traversal - pattern: os.Open($SINK) - pattern: os.OpenFile($SINK, ...) - pattern: os.ReadFile($SINK) - pattern: ioutil.ReadFile($SINK) # Template injection - pattern: template.HTML($SINK) pattern-sanitizers: - pattern: strconv.Atoi($X) - pattern: strconv.ParseInt($X, ...) - pattern: filepath.Clean($X) - pattern: filepath.Base($X) - pattern: html.EscapeString($X) paths: exclude: - "**/*_test.go" - "**/test/**" - "**/vendor/**" - id: variant-pattern-go message: "Suspicious pattern matching known vulnerability" severity: WARNING languages: [go] patterns: - pattern-either: - pattern: exec.Command(...) - pattern: $DB.Query($Q, ...) - pattern-not: exec.Command("...") -
java.yaml 2 KB
rules: - id: variant-taint-java message: "Potential variant: user input flows to dangerous sink" severity: ERROR languages: [java] mode: taint pattern-sources: # Servlet - pattern: (HttpServletRequest $REQ).getParameter(...) - pattern: (HttpServletRequest $REQ).getHeader(...) - pattern: (HttpServletRequest $REQ).getCookies() - pattern: (HttpServletRequest $REQ).getQueryString() - pattern: (HttpServletRequest $REQ).getInputStream() # Spring - pattern: "@RequestParam $TYPE $VAR" - pattern: "@PathVariable $TYPE $VAR" - pattern: "@RequestBody $TYPE $VAR" pattern-sinks: # Command injection - pattern: Runtime.getRuntime().exec($SINK, ...) - pattern: new ProcessBuilder($SINK, ...) # SQL injection - pattern: (Statement $S).executeQuery($SINK) - pattern: (Statement $S).executeUpdate($SINK) - pattern: (Statement $S).execute($SINK) - pattern: (Connection $C).prepareStatement($SINK) # Path traversal - pattern: new File($SINK) - pattern: new FileInputStream($SINK) - pattern: new FileOutputStream($SINK) - pattern: Paths.get($SINK, ...) # XXE - pattern: (DocumentBuilder $DB).parse($SINK) # Deserialization - pattern: (ObjectInputStream $OIS).readObject() pattern-sanitizers: - pattern: Integer.parseInt($X) - pattern: Integer.valueOf($X) - pattern: StringEscapeUtils.escapeHtml4($X) - pattern: ESAPI.encoder().encodeForSQL(...) paths: exclude: - "**/test/**" - "**/*Test.java" - id: variant-pattern-java message: "Suspicious pattern matching known vulnerability" severity: WARNING languages: [java] patterns: - pattern-either: - pattern: Runtime.getRuntime().exec(...) - pattern: new ProcessBuilder(...) - pattern-inside: | $RET $METHOD(..., HttpServletRequest $REQ, ...) { ... } -
javascript.yaml 1.7 KB
rules: - id: variant-taint-js message: "Potential variant: user input flows to dangerous sink" severity: ERROR languages: [javascript, typescript] mode: taint pattern-sources: # Express - pattern: req.query.$PARAM - pattern: req.body.$PARAM - pattern: req.params.$PARAM - pattern: req.cookies.$PARAM # URL/Location - pattern: window.location.$PROP - pattern: document.location.$PROP - pattern: location.search - pattern: location.hash pattern-sinks: # Command injection - pattern: child_process.exec($SINK, ...) - pattern: child_process.execSync($SINK, ...) - pattern: child_process.spawn($SINK, ...) # Code execution - pattern: eval($SINK) - pattern: Function($SINK) - pattern: setTimeout($SINK, ...) - pattern: setInterval($SINK, ...) # SQL - pattern: $DB.query($SINK, ...) - pattern: $DB.raw($SINK) # XSS - pattern: $EL.innerHTML = $SINK - pattern: document.write($SINK) pattern-sanitizers: - pattern: parseInt($X, ...) - pattern: encodeURIComponent($X) - pattern: escape($X) - pattern: $DB.escape($X) paths: exclude: - "**/*.test.js" - "**/*.spec.js" - "**/test/**" - "**/node_modules/**" - id: variant-pattern-js message: "Suspicious pattern matching known vulnerability" severity: WARNING languages: [javascript, typescript] patterns: - pattern-either: - pattern: eval(...) - pattern: Function(...) - pattern: child_process.exec(...) - pattern-not: eval("...") - pattern-not: Function("...") -
python.yaml 2 KB
rules: - id: variant-taint-analysis message: >- Potential variant: user-controlled data flows to dangerous sink. Original bug: [DESCRIBE_ORIGINAL_BUG] severity: ERROR languages: [python] mode: taint pattern-sources: # Flask - pattern: request.args.get(...) - pattern: request.args[...] - pattern: request.form.get(...) - pattern: request.form[...] - pattern: request.json - pattern: request.data # Django (uncomment if needed) # - pattern: request.GET.get(...) # - pattern: request.POST.get(...) # General - pattern: os.environ.get(...) - pattern: input(...) pattern-sinks: # Command injection - pattern: os.system($SINK) - pattern: os.popen($SINK) - pattern: subprocess.call($SINK, ...) - pattern: subprocess.run($SINK, ...) - pattern: subprocess.Popen($SINK, ...) # Code execution - pattern: eval($SINK) - pattern: exec($SINK) # SQL (uncomment if needed) # - pattern: $CURSOR.execute($SINK) # Path traversal (uncomment if needed) # - pattern: open($SINK, ...) pattern-sanitizers: - pattern: shlex.quote(...) - pattern: os.path.basename(...) - pattern: int(...) - pattern: sanitize(...) - pattern: escape(...) - pattern: validate(...) paths: exclude: - "*_test.py" - "test_*.py" - "tests/" - "**/test/**" metadata: category: security confidence: HIGH # Simple pattern matching variant (non-taint) - id: variant-pattern-match message: "Suspicious pattern matching known vulnerability signature" severity: WARNING languages: [python] patterns: - pattern-either: - pattern: dangerous_func($USER_DATA) - pattern: risky_operation(..., $USER_DATA, ...) - pattern-not: dangerous_func("...") paths: exclude: - "tests/" - "*_test.py"
-
-
variant-report-template.md 1.3 KB
# Variant Analysis Report ## Summary | Field | Value | |-------|-------| | **Original Bug** | [BUG_ID / CVE] | | **Analysis Date** | [DATE] | | **Codebase** | [REPO/PROJECT] | | **Variants Found** | [COUNT] | ## Original Vulnerability **Root Cause:** [e.g., "User input reaches SQL query without parameterization"] **Location:** `[path/to/file.py:LINE]` in `function_name()` ```python # Vulnerable code ``` ## Search Methodology | Version | Pattern | Tool | Matches | TP | FP | |---------|---------|------|---------|----|----| | v1 | [exact] | ripgrep | 1 | 1 | 0 | | v2 | [abstract] | semgrep | N | N | N | **Final Pattern:** ```yaml # Pattern used ``` ## Findings ### Variant #1: [BRIEF_TITLE] | Severity | Confidence | Status | |----------|------------|--------| | High | High | Confirmed | **Location:** `[path/to/file.py:LINE]` ```python # Vulnerable code ``` **Analysis:** [Why this is a true/false positive] **Exploitability:** - [ ] Reachable from external input - [ ] User-controlled data - [ ] No sanitization --- <!-- Copy variant template above for additional findings --> ## False Positive Patterns | Pattern | Count | Reason | |---------|-------|--------| | [pattern] | N | [why safe] | ## Recommendations ### Immediate 1. Fix variant in [location] ### Preventive 1. Add Semgrep rule to CI ```yaml # CI-ready rule ```
-
-
SKILL.md 3.8 KB
--- name: variant-analysis description: Hunts for the other instances of a bug already found — the variants of one root cause across a codebase. Use immediately after a vulnerability, logic bug, or bad pattern turns up in a specific file and the question becomes where else it occurs, including the bare conversational form ("are there others like this?", "is this the same bug?"). Also for generalizing one known instance into a CodeQL or Semgrep query for its whole pattern family, and for triaging a set of look-alike candidates against a known root cause. Not for initial discovery with no bug in hand. --- # Variant Analysis Find the other instances of a bug you have already found. One root cause usually has several manifestations, and they are rarely in the module where you found the first one. ## When to Use - A vulnerability has been found and you need to search for similar instances - Building or refining CodeQL/Semgrep queries for security patterns - Performing systematic code audits after an initial issue discovery - Analyzing how a single root cause manifests in different code paths ## When NOT to Use - Initial vulnerability discovery — use audit-context-building or a domain-specific audit - General code review with no known pattern to search for - Writing fix recommendations — use issue-writer - Understanding unfamiliar code — use audit-context-building first ## The Five Steps Read the reference for a step when you reach it. **1. Understand the original issue.** Extract the root cause — why the code is wrong, not what it does — and enumerate the directions a variant could hide in: related identifiers, other manifestations of the same mistake, data-type edge cases. → [references/root-cause.md](references/root-cause.md) **2. Create an exact match.** Write a pattern matching ONLY the known instance and confirm it hits. A pattern that matches nothing means you have misunderstood the bug, and every search built on it is calibrated against the wrong code. **3–4. Generalize one element at a time.** Climb from the exact match toward the pattern family, running and reading all matches after each single change. Stop when more than half the matches are noise. → [references/searching.md](references/searching.md) — abstraction ladder, tool selection, false-positive filters **5. Triage.** Decide which candidates are real, and say so with a severity attached. → [references/triage.md](references/triage.md) **Then write it up**, including the patterns that failed and a CI rule to prevent regression. → [references/reporting.md](references/reporting.md) ## Running it as a Workflow This plugin ships `/variant-analysis:variants`, which runs the five steps across parallel subagents — one per expansion axis, looping until the sweep stops finding anything new. Each stage reads the reference above that matches its job. Use the workflow when the codebase is large or the root cause has many manifestations. Work the steps directly when the search is narrow or you want a say in each generalization. ## What Makes Hunts Fail 1. **Narrow scope** — searching only the module the original bug was in 2. **Pattern too specific** — searching one attribute and missing the family around it 3. **One vulnerability class** — chasing a single manifestation of the root cause 4. **Happy-path testing** — never trying the null, empty, and boundary cases 5. **Generalizing too fast** — abstracting several elements at once, so noise cannot be attributed to any one of them The first three are covered in root-cause.md and searching.md, the fourth in triage.md. ## Resources **CodeQL** (`resources/codeql/`): `python.ql`, `javascript.ql`, `java.ql`, `go.ql`, `cpp.ql` **Semgrep** (`resources/semgrep/`): `python.yaml`, `javascript.yaml`, `java.yaml`, `go.yaml`, `cpp.yaml` **Report**: `resources/variant-report-template.md`
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.