Documentation
¶
Overview ¶
Package rules defines Godzilla's rule model and matching primitives.
A taint Rule says: untrusted data produced by any Source that reaches any Sink, without first passing through a Sanitizer, is a vulnerability. Callees are identified by canonical fully-qualified names (see the gIR CallCommon.callee field), e.g. "go:net/http.(*Request).FormValue", and matched against rule patterns as globs where '*' matches any run of characters (including '/' and '.'). This lets one rule span languages, e.g. sinks ["go:*.Query", "py:*.execute"].
Index ¶
- Constants
- Variables
- func ArgHostFixed(a Arg) bool
- func InvalidSinkSpec(entry string) bool
- func ValidConfidence(s string) bool
- type Arg
- type Callee
- type ConstArg
- type EvalHostFixed
- type ExtendRefs
- type GlobSet
- type Guard
- type Rule
- func (r *Rule) AppliesTo(language string) bool
- func (r *Rule) Compile() error
- func (r *Rule) ConstArgRe() *regexp.Regexp
- func (r *Rule) HasValidators() bool
- func (r *Rule) IsDangerousCall() bool
- func (r *Rule) IsPropagator(callee string) bool
- func (r *Rule) IsSanitizer(callee string) bool
- func (r *Rule) IsSecret() bool
- func (r *Rule) IsSink(callee string) bool
- func (r *Rule) IsSource(callee string) bool
- func (r *Rule) IsValidator(callee string) bool
- func (r *Rule) MatchDangerousCallee(callee string) (guard *Guard, ok bool)
- func (r *Rule) MatchSink(callee string) (args []int32, guard *Guard, ok bool)
- func (r *Rule) MatchesRe() *regexp.Regexp
- type RuleSet
- type Severity
- type Sink
Constants ¶
const DynMarker = "<DYN>"
DynMarker is the placeholder Arg.String uses for a run of the argument that is not a compile-time constant (a tainted/dynamic segment): `"cmd:" + x` reconstructs to "cmd:<DYN>", a fully dynamic argument to "<DYN>". This encodes incompleteness into the string, so `arg[0].String startsWith "cmd:"` holds for a partial constant while `arg[0].String == "cmd:"` does not.
Variables ¶
var DenyGuard = &Guard{}
DenyGuard never fires. It stands in for a guard that could not be compiled or is unavailable, so a malformed/unusable `when:` SUPPRESSES its entry instead of degrading to "no guard" (which would fire unconditionally — the very false-positive the guard exists to prevent). Fail closed, never open.
var Severities = []Severity{SeverityCritical, SeverityHigh, SeverityMedium, SeverityLow, SeverityInfo}
Severities lists every recognized severity from worst to best. It is the single authority for severity ordering: Rank derives from it and display surfaces iterate it, so adding one here is enough.
Functions ¶
func ArgHostFixed ¶
ArgHostFixed reports whether one argument's reconstructed value pins a constant scheme://host before its first dynamic run. It reads the skeleton alone -- the text up to DynMarker IS the constant prefix -- so the explicit hostFixed(arg[i]) form needs no engine state. An argument that is entirely dynamic reconstructs to DynMarker, leaving an empty prefix that cannot match: unrecoverable constructions stay "controllable" and keep firing.
func InvalidSinkSpec ¶
InvalidSinkSpec reports whether a sink entry carries a "#" injection-point spec that names no valid argument index — an empty spec ("...Query#") or one whose tokens are not all non-negative integers ("...Query#x", "...Query#-1", "...Query#0,"). Such an entry parses leniently to zero indices, which is indistinguishable from a bare pattern and silently widens the sink to "every argument is an injection point", reintroducing the parameterized-query false positive. The loader rejects it so a typo fails loud at load time.
func ValidConfidence ¶
ValidConfidence reports whether s is a spelling a rule may declare in its `confidence:` field: empty (unset, "use the default") or low|medium|high, case- and space-insensitively. SPELLING only — package rules cannot name analysis.Confidence (analysis imports rules; the reverse would be a cycle), so conversion lives in analysis.ParseConfidence and the loader uses this to reject a typo at load time.
Types ¶
type Arg ¶
type Arg struct {
String string
Complete bool
Type string
// Name is the keyword/named-argument name this argument was passed under
// ("shell" for `subprocess.run(cmd, shell=True)`), or "" for a positional
// argument or a language/frontend that does not record names. Without it a
// guard can only see that SOME boolean argument is true, which cannot
// distinguish the dangerous `shell=True` from an innocuous `check=True`.
// Rules read it through `kwargs`, which indexes arguments by this name.
Name string
// Tainted reports whether this argument carries taint at the call, so a rule
// can ask WHICH argument the untrusted value arrived in: in
// `subprocess.run(["ls", name])` it is an argv element, not the command.
// False where there is no taint state (a dangerous-call guard), so a rule
// keying on it never suppresses there.
Tainted bool
// Elems are an in-place container's elements in order (Type "aggregate"):
// `arg[0].Elems[0]` is argv[0] of `subprocess.run(["sh", "-c", cmd])`.
// Entries is the keyed form (Type "map"), indexed by constant keys; a
// computed key names nothing and is absent.
//
// Both are EMPTY when the structure was not reconstructed, indistinguishably
// from "no elements" — so a rule must demand positive evidence before
// suppressing, and see .TaintInChildren below.
Elems []Arg
Entries map[string]Arg
// TaintInChildren reports that reading Elems/Entries will actually find the
// taint. A value can be Tainted with this FALSE — mutated after it was built
// (`d = {}` then `d[k] = tainted`), taint in a non-constant key, built
// elsewhere (`tainted.split(",")`), or never reconstructed — where walking the
// children finds nothing and would wrongly read as safe.
//
// The polarity is deliberate: the zero value is "not accounted for", so a site
// that forgets this field costs a spurious finding, not a silent miss.
TaintInChildren bool
}
Arg is a call argument as a guard sees it: String is the argument's statically reconstructed value (constant runs verbatim, DynMarker for dynamic runs), Complete is true when the WHOLE argument is a compile-time constant, and Type is its static type ("string"/"int"/"float"/"bool", the container kinds "aggregate"/"map", or "" if unknown).
type Callee ¶
Callee is a dangerous-call pattern with an optional dynamic guard, in the same string-or-{callee, when} shape as Sink.
type ConstArg ¶
type ConstArg struct {
Index int `yaml:"index"` // logical (receiver-excluded) argument index
Matches string `yaml:"matches"` // regexp the constant string argument must match
}
ConstArg is a dangerous-call rule's optional constant-argument condition.
type EvalHostFixed ¶
type EvalHostFixed func() bool
EvalHostFixed is the engine-supplied fact behind the `hostFixed()` guard builtin. The guard layer cannot compute it: deciding it needs the call's injection-point arguments, the current taint state, and the IR def map. Supplying it as a function keeps the *policy* — whether a rule suppresses on it — in the rule's `when:`, instead of the engine branching on a CWE string.
It backs the ZERO-ARG form, which reuses the sink's own #idx pinning and is the one to prefer: the URL is not always argument 0 (requests.request pins #1) and some sinks take a request OBJECT rather than a URL string (net/http Client.Do), so restating the index risks checking the wrong argument.
The explicit hostFixed(arg[i]) form (see ArgHostFixed) stays available for a rule that wants the check spelled out or needs a non-injection-point argument; it reads the skeleton directly and needs no engine state.
type ExtendRefs ¶
type ExtendRefs []string
ExtendRefs is a rule's `extend:` value: one or more fragment references. It accepts either a single scalar (`extend: $_go-common.yaml`) or a YAML sequence (`extend: [$_a.yaml, $_b.yaml]`) so a rule can compose several fragments.
func (*ExtendRefs) UnmarshalYAML ¶
func (e *ExtendRefs) UnmarshalYAML(value *yaml.Node) error
UnmarshalYAML accepts either a scalar or a sequence of scalars.
type GlobSet ¶
type GlobSet struct {
// contains filtered or unexported fields
}
GlobSet is a set of canonical-name globs precompiled to shape-matchers, for a caller that matches the same list against many subjects but does not own a Rule (e.g. the engine's request-object host scan). It is the standalone equivalent of a Rule's own compiled pattern lists.
func NewGlobSet ¶
NewGlobSet precompiles patterns into a GlobSet.
type Guard ¶
type Guard struct {
// contains filtered or unexported fields
}
Guard is a compiled `when:` expression that decides whether a dynamic sink or callee fires, given the call's arguments as `arg[i]` (the i-th logical, receiver-excluded argument). It is standard expr-lang (https://expr-lang.org): a guard works on `arg[i].String` / `.Complete` / `.Type` with expr's native string operators and builtins — `startsWith`, `endsWith`, `contains`, `matches`, `in`, `==`, `hasPrefix`, `hasSuffix`, … A dynamic run is DynMarker, so an argument that cannot be confirmed fails an exact/prefix check and the entry is suppressed. Because DynMarker can be spanned by a wildcard regexp, combine `matches` with `.Complete` when an exact match matters. Compiled once at load.
func CompileGuard ¶
CompileGuard parses, type-checks, and compiles a `when:` expression. It returns an error for a syntax error, an unknown name, a non-boolean result, or an invalid constant regexp in `matches` (expr validates all of these at compile), so a bad guard fails `rules lint` at load rather than silently suppressing findings at scan time. An empty source yields (nil, nil): no guard.
func (*Guard) Eval ¶
Eval reports whether the guard holds for the call's arguments. A nil guard (no `when:`) always fires; DenyGuard never does; a run error (e.g. an out-of-range arg index) is unconfirmed -> false (suppress).
func (*Guard) EvalWith ¶
func (g *Guard) EvalWith(args []Arg, hostFixed EvalHostFixed) bool
EvalWith is Eval with the engine's optional facts supplied. hostFixed may be nil, in which case the guard sees a not-host-fixed answer (fail open).
func (*Guard) NeedsStructure ¶
NeedsStructure reports whether the guard reads container structure, so a caller can skip reconstructing it. A nil guard reads nothing.
type Rule ¶
type Rule struct {
ID string `yaml:"id"`
Languages []string `yaml:"languages"` // empty => applies to all languages
Severity Severity `yaml:"severity"`
// Confidence overrides the confidence a dangerous-call finding is reported
// with; empty means High. Set "medium" for a heuristic call-site rule that
// wants a human/LLM look: confidence is what makes a finding TRIAGEABLE
// (internal/llm.Filter reviews everything at or below its threshold), while
// severity alone decides the CI gate (-fail-on). Ignored by dataflow rules,
// whose confidence comes from the flow itself (intra-procedural High,
// cross-function Medium).
Confidence string `yaml:"confidence"`
CWE string `yaml:"cwe"`
Message string `yaml:"message"`
// Extend names one or more `_`-prefixed fragment files (e.g.
// "$_go-common.yaml") whose pattern-list fields are merged into this rule at
// load time, keeping a language's shared sources/propagators in one place.
// The loader appends the fragment's entries ahead of this rule's own (deduped)
// and then clears Extend; it never reaches the matcher. Accepts a single
// scalar or a YAML sequence. See internal/rules/loader.
Extend ExtendRefs `yaml:"extend"`
Sources []string `yaml:"sources"`
Sanitizers []string `yaml:"sanitizers"`
// RequestObjectSources are source globs whose value is an untrusted HTTP
// request OBJECT (not a scalar), e.g. Go's synthetic "go:@net/http.Request".
// A DEPENDENCY function containing one internally (a framework accessor
// reading *http.Request through a field, with no tainted argument) generates
// request taint out of nowhere, so the engine seeds such a function when user
// code calls it directly (buildReqSourceHosts); otherwise the demand-driven
// dependency scope would never analyze it. These are also ordinary sources
// (list them in Sources too); this only tags the flavor.
RequestObjectSources []string `yaml:"request_object_sources"`
// Sinks are taint-sink patterns; each is a bare glob string or a `{sink, when}`
// mapping adding a dynamic guard (see Sink). A pattern may append a "#i[,j...]"
// suffix limiting the sink to LOGICAL (receiver-excluded) argument indices — so
// for "go:...Query#0" only arg 0 is an injection point and a bound-parameter
// query `db.Query("... = ?", taintedParam)` is correctly NOT flagged.
Sinks []Sink `yaml:"sinks"`
// When is the rule's DEFAULT dynamic guard: a sink (or dangerous-call callee)
// that declares no `when:` of its own inherits this one. A guard like
// `not hostFixed()` is rule POLICY; declaring it once here stops a sink added
// later from silently opting out. An entry's own `when:` always wins, so
// `when: 'true'` on one sink is the per-sink opt-out. Inheritance is applied
// where the guard is resolved (effectiveWhen), so a sink merged in from a
// fragment inherits it too.
When string `yaml:"when"`
Propagators []string `yaml:"propagators"` // callees that pass taint arg->result (e.g. fmt.Sprintf)
// Kind selects the rule's evaluation model:
// ""/"taint" the default source->sink dataflow rule.
// "dangerous-call" a non-dataflow, call-site-syntactic check: any call to a
// Callee glob is a finding, optionally gated on a constant
// string argument — for zero-noise categories like weak
// crypto and insecure randomness that need no taint.
// "secret" a non-dataflow, non-call check: Matches is run over every
// string CONSTANT in the IR and over the lines of textual
// config files, so a credential is caught wherever it is
// written. No callee is involved at all.
Kind string `yaml:"kind"`
// Matches is a kind: secret rule's detector — a Go regexp run against string
// constants and config-file lines. Keep it specific (a fixed prefix or a
// structural marker) rather than entropy-based: a CI gate cannot afford the
// noise. Ignored by other kinds; see MatchesRe.
Matches string `yaml:"matches"`
// Callees are the dangerous-call patterns for a kind: dangerous-call rule —
// each a bare glob string or a `{callee, when}` mapping adding a guard (see Callee).
Callees []Callee `yaml:"callees"`
// ConstArg optionally restricts a dangerous-call match to calls whose constant
// string argument at the LOGICAL index Index matches the Matches regexp — e.g.
// the "MD5" literal in MessageDigest.getInstance("MD5"). Nil means any call to
// a Callee fires regardless of arguments.
ConstArg *ConstArg `yaml:"const_arg"`
// Validators are guard/barrier callees: a boolean-returning check (an
// allowlist test, a regexp match, a path-containment predicate like
// filepath.IsLocal) that, when it dominates the branch leading to a sink,
// clears the checked value's taint on that path. Unlike a Sanitizer — which
// transforms a value and returns a clean result — a Validator returns a bool
// and leaves the value unchanged, neutralizing the finding by controlling
// which path reaches the sink. Matched by canonical-FQN glob, like sinks.
Validators []string `yaml:"validators"`
// contains filtered or unexported fields
}
Rule is a single taint rule loaded from YAML.
func (*Rule) AppliesTo ¶
AppliesTo reports whether the rule is active for the given source language (e.g. "go"). A rule with no declared Languages applies to every language.
func (*Rule) Compile ¶
Compile precompiles the rule's pattern lists into shape-matchers. Call it once (single-threaded) before matching a rule against many call sites — the engine does this for every rule before its parallel analysis. Idempotent.
func (*Rule) ConstArgRe ¶
ConstArgRe returns the rule's compiled const_arg.matches regexp, or nil when the rule declares no const_arg or its regexp is invalid. A rule that declares a const_arg it cannot compile must never fire, so callers treat "ConstArg != nil but ConstArgRe() == nil" as "matches nothing".
func (*Rule) HasValidators ¶
HasValidators reports whether the rule declares any guard/barrier validators, so the engine can skip the (dominator) guard analysis entirely for rules that don't use the feature — keeping the common path free of extra work.
func (*Rule) IsDangerousCall ¶
IsDangerousCall reports whether the rule is a non-dataflow, call-site rule.
func (*Rule) IsPropagator ¶
IsPropagator reports whether callee matches one of the rule's propagator patterns or one of the set-wide defaults (see RuleSet.DefaultPropagators).
func (*Rule) IsSanitizer ¶
IsSanitizer reports whether callee matches any of the rule's sanitizer patterns.
func (*Rule) IsSecret ¶
IsSecret reports whether the rule is a non-dataflow, pattern-over-constants rule (kind: secret).
func (*Rule) IsValidator ¶
IsValidator reports whether callee matches any of the rule's validator (guard) patterns.
func (*Rule) MatchDangerousCallee ¶
MatchDangerousCallee reports whether callee matches one of the rule's dangerous-call globs, returning that entry's optional dynamic guard.
type RuleSet ¶
type RuleSet struct {
Rules []Rule `yaml:"rules"`
// DefaultPropagators are taint-preserving library transforms that apply to
// EVERY rule on top of its own Propagators. The loader fills this from the
// `_default-propagators.yaml` fragment; Compile hands it to each rule, so the
// engine never has to know defaults exist. Not from this document's YAML.
DefaultPropagators []string `yaml:"-"`
// contains filtered or unexported fields
}
RuleSet is a collection of rules, matching the top-level YAML document shape.
func (*RuleSet) Compile ¶
Compile precompiles every rule's patterns (see Rule.Compile). Call it once, single-threaded, before matching — in particular before running independent analysis passes concurrently over the same rule set, so they don't race building per-rule matchers (after this, all matcher access is read-only). Idempotent.
func (*RuleSet) WithRules ¶
WithRules returns a new RuleSet holding the given rules plus every set-wide field of rs. Code deriving a RuleSet from another by filtering or rewriting its rules MUST use this rather than a struct literal: DefaultPropagators is a side channel (`yaml:"-"`, invisible in the rules slice), and dropping it silently strips set-wide propagators from every rule — a mass false-negative, not an error.
type Severity ¶
type Severity string
Severity ranks a finding's importance and drives exit-code gating.
type Sink ¶
Sink is a taint-sink pattern with an optional dynamic guard. In YAML a sink entry is either a bare glob string (static) or a `{sink, when}` mapping (dynamic): the sink fires only when taint reaches it AND `when` proves true against the call's argument values. Pattern keeps the "#idx" suffix (parseSink).