SAST Rules Reference
SAST runs automatically when greengate scan encounters a supported source file. It uses tree-sitter to parse each file into a real AST before running any checks.
Supported languages:
| Language | Extensions | Features |
|---|---|---|
| JavaScript | .js, .jsx | String-literal scoping, dangerous patterns, code smells, taint tracking |
| TypeScript | .ts, .tsx | String-literal scoping, dangerous patterns, code smells, taint tracking |
| Python | .py | String-literal scoping, dangerous patterns |
| Go | .go | String-literal scoping, dangerous patterns |
Tier-1 intra-procedural taint tracking
GreenGate performs intra-procedural taint tracking for JS/TS to reduce both false positives and false negatives. For every XSS and command-injection sink, the engine resolves where the value came from within the enclosing function body.
How it works
Source seeding — variables assigned from known user-controlled sources are marked tainted:
Source pattern Example req.body.*,req.query.*,req.params.*Express route handlers request.body.*,request.headers.*Fastify / Koa searchParams.get(...)URL search parameters localStorage.getItem(...),sessionStorage.getItem(...)Browser storage document.cookie,location.hash,location.searchBrowser globals event.datapostMessage/ worker eventsprocess.envNode.js environment variables Fixpoint propagation — taint flows through assignments, template literals, and
+concatenation until no new variables are classified.Safe set — variables assigned from static string/number literals or calls to known sanitizers (
DOMPurify.sanitize,JSON.stringify,he.encode,sanitizeHtml, etc.) are marked safe.
Effect on findings
| Sink value | Result |
|---|---|
| Variable in tainted set | Rule ID gains a [tainted] label — e.g. SAST/InnerHTMLAssignment [tainted] — indicating high-confidence confirmed injection |
| Variable in safe set | Finding is suppressed — value is provably sanitised or static |
| Unknown variable | Finding emitted at normal confidence (unchanged behaviour) |
| Static string literal (inline) | Finding is suppressed |
| Call to known sanitizer (inline) | Finding is suppressed |
Suppression rules vs. label-only rules
| Strategy | Rules |
|---|---|
| Suppress if safe, label if tainted | SAST/DangerouslySetInnerHTML, SAST/InnerHTMLAssignment, SAST/OuterHTMLAssignment, SAST/DocumentWrite, SAST/DocumentWriteln, SAST/EvalUsage |
| Label if tainted, never suppress | SAST/ChildProcessExec, SAST/ChildProcessExecSync, SAST/ChildProcessSpawn, SAST/ChildProcessExecFile — a static command name like "bash" is safe by itself but the arguments array may still carry user input |
Example
// Multi-hop taint chain — detected even across intermediate variables
const raw = req.body.comment; // taint source
const html = `<div>${raw}</div>`; // propagated: html is tainted
el.innerHTML = html;
// → SAST/InnerHTMLAssignment [tainted] (high confidence)
// Sanitizer suppresses the finding
const clean = DOMPurify.sanitize(raw); // clean is marked safe
el.innerHTML = clean;
// → suppressed (provably safe)
// Static variable suppresses the finding
const label = "Enter your name:";
el.innerHTML = label;
// → suppressed (provably safe)Scope: Tier-1 tracking is intra-procedural only — taint does not follow calls into other functions. See Limitations for details.
Dangerous pattern rules
These rules fire on specific API calls regardless of whether arguments are string literals:
| Rule ID | What it flags |
|---|---|
SAST/DangerouslySetInnerHTML | dangerouslySetInnerHTML prop with __html value in TSX/JSX |
SAST/InnerHTMLAssignment | element.innerHTML = expr |
SAST/OuterHTMLAssignment | element.outerHTML = expr |
SAST/EvalUsage | eval(expr) |
SAST/FunctionConstructor | new Function(...) |
SAST/SetTimeoutString | setTimeout("code", delay) — string as first argument |
SAST/SetIntervalString | setInterval("code", delay) — string as first argument |
SAST/ChildProcessExec | child_process.exec(cmd) |
SAST/ChildProcessExecSync | child_process.execSync(cmd) |
SAST/ChildProcessSpawn | child_process.spawn(cmd, args) |
SAST/ChildProcessExecFile | child_process.execFile(cmd) |
SAST/DocumentWrite | document.write(expr) |
SAST/DocumentWriteln | document.writeln(expr) |
Python rules
| Rule ID | What it flags | Severity |
|---|---|---|
SAST/PythonEval | eval(expr) — arbitrary code execution | critical |
SAST/PythonExec | exec(code) — arbitrary code execution | critical |
SAST/PythonPickle | pickle.load(f) / pickle.loads(data) — unsafe deserialization | high |
SAST/PythonSubprocessShell | Any call with shell=True keyword argument — command injection | high |
SAST/PythonYamlLoad | yaml.load(data) without a Loader= — use yaml.safe_load instead | high |
Go rules
| Rule ID | What it flags | Severity |
|---|---|---|
SAST/GoUnsafe | import "unsafe" — direct memory manipulation bypasses type safety | high |
SAST/GoExecCommand | exec.Command(cmd, ...) — possible command injection | high |
SAST/GoPanic | panic(...) — unexpected process termination in production code | medium |
Code smell rules
| Rule ID | Trigger | Default threshold |
|---|---|---|
SMELL/LongFunction | Function body exceeds N lines | 50 lines |
SMELL/TooManyParameters | Function has more than N parameters | 5 params |
SMELL/DeepNesting | Control-flow depth exceeds N levels inside a function | 4 levels |
Rule IDs embed the measured value — e.g. SMELL/LongFunction (63 lines, max 50) — for immediate context. Thresholds are configurable in .greengate.toml.
Suppressing findings in Python / Go
Use a same-line comment to suppress a specific finding:
data = pickle.load(f) # greengate: ignorepanic("fatal: unrecoverable state") // greengate: ignoreCustom rules
Define project-specific rules using tree-sitter S-expression queries. Each rule must include a @match capture marking the outermost node to report:
[sast]
custom_rules = [
{ id = "CUSTOM/EvalCall", query = "(call_expression function: (identifier) @_fn (#eq? @_fn \"eval\") arguments: (_) @match)" },
{ id = "CUSTOM/FetchCall", query = "(call_expression function: (identifier) @_fn (#eq? @_fn \"fetch\") @match)" },
]Custom rules are validated at startup — invalid queries are skipped with a warning and never cause greengate to crash.
Disabling rules
[sast]
disabled_rules = [
"SAST/ChildProcessExec",
"SAST/DocumentWrite",
"SMELL/LongFunction",
]Or disable SAST entirely:
[sast]
enabled = false