Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

AST patterns

AST patterns are tree-sitter queries that match dangerous structural shapes in source. No dataflow, no CFG. A match means the construct is present; it’s not proof the construct is exploitable.

Patterns run in every analysis mode. In --mode ast they’re the only active detector.

Rule IDs

<lang>.<category>.<name>

Examples: js.code_exec.eval, py.deser.pickle_loads, c.memory.gets, java.sqli.execute_concat.

Full list: rules.md.

Tiers

TierMeaning
AStructural presence alone is high-signal. gets, eval, pickle.loads, mem::transmute
BPattern includes a tree-sitter heuristic guard. Example: java.sqli.execute_concat only fires when executeQuery receives a binary_expression (string concatenation), not a literal or a parameterized statement

Categories

CategoryExamples
CommandExecsystem, os.system, Runtime.exec, backticks
CodeExeceval, Function, PHP assert("string"), class_eval, instance_eval
Deserializationpickle.loads, yaml.load, Marshal.load, readObject, unserialize
SqlInjectionexecuteQuery/Query/execute with concatenated argument (Tier B)
PathTraversalPHP include $var
Xssdocument.write, outerHTML, insertAdjacentHTML, getWriter().print
Cryptomd5, sha1, Math.random, java.util.Random for security use
Secretshardcoded API keys (Go, JS, TS)
InsecureTransportInsecureSkipVerify, fetch("http://...")
ReflectionClass.forName, Method.invoke, send, constantize
MemorySafetytransmute, unsafe, gets, strcpy, sprintf
Prototype__proto__ assignment, Object.prototype.*
ConfigCORS dynamic origin, rejectUnauthorized: false, insecure session settings
CodeQualityunwrap, panic!, as any

What patterns can’t tell you

  • Dataflow. eval("1+1") (safe) and eval(userInput) (dangerous) both match js.code_exec.eval. The taint detector is the one that distinguishes them.
  • Reachability. A pattern in dead code matches identically.
  • Semantics. strcpy(dst, src) always matches, regardless of buffer sizes.
  • Indirect calls. let e = eval; e(input) doesn’t match eval.
  • Aliased imports. from os import system as s; s(cmd) won’t match system.
  • Macro expansions. Tree-sitter parses the macro call site, not the expansion.

Common false positives

ScenarioWhyMitigation
eval("hardcoded literal")Pattern matches structureRun --mode cfg to drop AST patterns and rely on taint
unsafe block with sound justificationEvery unsafe matches rs.quality.unsafe_blockFilter >=MEDIUM (it’s Medium) or accept the noise
.unwrap() in testsAcceptable in test codeDefault non-prod severity downgrade reduces it
md5 for non-cryptographic checksumsPattern can’t see intentSuppress with --severity ">=MEDIUM" or per-line nyx:ignore
SQL concat with trusted data (Tier B)Heuristic can’t verify the sourceTaint is more precise; or convert to a parameterized query

Confidence levels

Every AST pattern carries an explicit confidence:

ConfidenceUse
HighInherently dangerous construct with no safe usage. gets, pickle.loads, eval with no guard
MediumLikely issue, context may change the call. SQL concatenation (Tier B), unsafe blocks, exec
LowHeuristic. Often appears in safe code. Weak crypto for checksums, unwrap outside tests, Math.random

--min-confidence medium (or output.min_confidence = "medium") drops Low-confidence matches.

Tuning

nyx scan . --severity ">=MEDIUM"        # drop Low-tier patterns
nyx scan . --severity HIGH              # banned APIs and code-exec only
nyx scan . --mode cfg                   # drop AST patterns; keep taint + state + cfg
[scanner]
excluded_directories = ["node_modules", "vendor", "generated"]

Examples

Tier A, structural presence:

char buf[64];
gets(buf);                              // c.memory.gets
import pickle
data = pickle.loads(user_input)         // py.deser.pickle_loads

Tier B, heuristic guard:

// Fires: concatenated argument
stmt.executeQuery("SELECT * FROM users WHERE id=" + userId);  // java.sqli.execute_concat

// Does not fire: parameterized
stmt.executeQuery(preparedSql);
printf(user_input);                     // c.memory.printf_no_fmt: fires (variable as fmt)
printf("%s", user_input);               // does not fire (literal fmt)