rules

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2026 License: MIT Imports: 11 Imported by: 0

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

View Source
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

View Source
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.

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

func ArgHostFixed(a Arg) bool

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

func InvalidSinkSpec(entry string) bool

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

func ValidConfidence(s string) bool

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

type Callee struct {
	Pattern string
	When    string
}

Callee is a dangerous-call pattern with an optional dynamic guard, in the same string-or-{callee, when} shape as Sink.

func CalleesOf

func CalleesOf(patterns ...string) []Callee

func (*Callee) UnmarshalYAML

func (c *Callee) UnmarshalYAML(value *yaml.Node) error

UnmarshalYAML accepts a scalar (the glob) or a mapping {callee, when}.

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

func NewGlobSet(patterns []string) *GlobSet

NewGlobSet precompiles patterns into a GlobSet.

func (*GlobSet) Match

func (g *GlobSet) Match(s string) bool

Match reports whether s matches any pattern in the set. A nil or empty set matches nothing.

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

func CompileGuard(src string) (*Guard, error)

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

func (g *Guard) Eval(args []Arg) bool

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

func (g *Guard) NeedsStructure() bool

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

func (r *Rule) AppliesTo(language string) bool

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

func (r *Rule) Compile() error

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

func (r *Rule) ConstArgRe() *regexp.Regexp

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

func (r *Rule) HasValidators() bool

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

func (r *Rule) IsDangerousCall() bool

IsDangerousCall reports whether the rule is a non-dataflow, call-site rule.

func (*Rule) IsPropagator

func (r *Rule) IsPropagator(callee string) bool

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

func (r *Rule) IsSanitizer(callee string) bool

IsSanitizer reports whether callee matches any of the rule's sanitizer patterns.

func (*Rule) IsSecret

func (r *Rule) IsSecret() bool

IsSecret reports whether the rule is a non-dataflow, pattern-over-constants rule (kind: secret).

func (*Rule) IsSink

func (r *Rule) IsSink(callee string) bool

IsSink reports whether callee matches any of the rule's sink patterns.

func (*Rule) IsSource

func (r *Rule) IsSource(callee string) bool

IsSource reports whether callee matches any of the rule's source patterns.

func (*Rule) IsValidator

func (r *Rule) IsValidator(callee string) bool

IsValidator reports whether callee matches any of the rule's validator (guard) patterns.

func (*Rule) MatchDangerousCallee

func (r *Rule) MatchDangerousCallee(callee string) (guard *Guard, ok bool)

MatchDangerousCallee reports whether callee matches one of the rule's dangerous-call globs, returning that entry's optional dynamic guard.

func (*Rule) MatchSink

func (r *Rule) MatchSink(callee string) (args []int32, guard *Guard, ok bool)

MatchSink reports whether callee matches one of the rule's sinks and, if so, returns that sink's LOGICAL injection-point argument indices and optional dynamic guard. nil/empty args with ok==true means every argument is an injection point.

func (*Rule) MatchesRe

func (r *Rule) MatchesRe() *regexp.Regexp

MatchesRe returns the rule's compiled `matches` regexp, or nil when it declares none or its regexp was invalid. As with ConstArgRe, a declared-but-uncompilable detector must never fire, so nil means "matches nothing".

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

func (rs *RuleSet) Compile() error

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

func (rs *RuleSet) WithRules(rulesList []Rule) *RuleSet

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.

const (
	SeverityInfo     Severity = "info"
	SeverityLow      Severity = "low"
	SeverityMedium   Severity = "medium"
	SeverityHigh     Severity = "high"
	SeverityCritical Severity = "critical"
)

func (Severity) Rank

func (s Severity) Rank() int

Rank returns a comparable ordering for a severity (higher is worse): info=1 up to critical=5, case-insensitively. Anything unrecognized ranks 0.

type Sink

type Sink struct {
	Pattern string
	When    string
}

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).

func SinksOf

func SinksOf(patterns ...string) []Sink

SinksOf / CalleesOf build static (guard-less) sink/callee lists from bare patterns — a convenience for programmatic and test rule construction.

func (*Sink) UnmarshalYAML

func (s *Sink) UnmarshalYAML(value *yaml.Node) error

UnmarshalYAML accepts a scalar (the glob) or a mapping {sink, when}.

Directories

Path Synopsis
Package loader reads Godzilla taint rules from YAML files (user-supplied or built-in) into rules.RuleSet values.
Package loader reads Godzilla taint rules from YAML files (user-supplied or built-in) into rules.RuleSet values.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL