Documentation
¶
Overview ¶
Package fprule implements the v0.1.0 fingerprint rule engine: a versioned, lint-checked rule collection that classifies technology evidence ALREADY OBSERVED into products with confidence.
Honesty contract (v0.1.0, owner directive §2):
- Rules match over evidence fields that a stored observation actually carries (http.server/title, tls.*, ssh.banner). The engine never fabricates an observation, never contacts the network, and never invents a port/service from a technology hit (Discovery ≠ Observation — collect/doc.go).
- Signatures are IDENTIFICATION only. No exploitation, no vulnerability, no credential, no bypass signature is accepted — the linter rejects rule ids/texts that smell like them (see lintExploitationPatterns) and reviews are the second gate.
- Confidence is compositional and evidence-based: rule confidence × per-match weight; the tiers (≥0.9 header-is-product, 0.7–0.89 header-embedded, 0.5–0.69 weak title signal) are documented in docs/RULES.md and pinned by seed tests.
- Every rule ships ≥1 positive and ≥1 negative (false-positive guard) test; `gseer rules validate` runs them and CI fails on a regression.
The embedded seed collection (seed/*.yaml) is the v0.1.0 "fingerprint seed set"; operators contribute additional rules through the format documented in docs/RULES.md and load them via config `fingerprints.rules_dir`.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var Families = []Family{FamilyWebServer, FamilyCMS, FamilyLanguage, FamilyFramework, FamilyWebTech, FamilyService}
Families is the closed family set, in documentation order.
var Fields = []Field{FieldHTTPServer, FieldHTTPTitle, FieldTLSVersion, FieldTLSCertSubjectCN, FieldTLSCertIssuerCN, FieldSSHBanner}
Fields is the closed matchable-field set, in documentation order.
Functions ¶
func LoadDir ¶
LoadDir loads every *.yaml file in dir (one rule per file, named <id>.yaml). Unparseable files become registry-level problems returned alongside the successfully parsed rules — the caller decides whether to proceed.
func Products ¶
Products reduces Apply's output to the technologies[] form: plain product names, confidence order preserved.
Types ¶
type Family ¶
type Family string
Family classifies the product. The set is closed (lint rejects unknown families) so facet counts and docs stay meaningful.
type Field ¶
type Field string
Field is an evidence path a rule may match. Closed set (lint rejects unknown paths — a typo'd path would silently never fire, which is worse than a loud lint failure). The set mirrors the observation evidence schema the storage layer actually stores: the http/tls payloads projected by latest_http/latest_tls and the ssh banner shape used by the fixture corpus (o3).
type Match ¶
type Match struct {
RuleID string `json:"rule_id"`
Family string `json:"family"`
Product string `json:"product"`
Version string `json:"version,omitempty"`
Confidence float64 `json:"confidence"`
}
Match is one technology identification derived from evidence.
type MatchSpec ¶
type MatchSpec struct {
// Field is the evidence path ("http.server").
Field Field `yaml:"field"`
// Pattern is a Go RE2 regexp applied to the field value.
Pattern string `yaml:"pattern"`
// Flags are optional RE2 flags, currently only "i" (case-insensitive).
Flags string `yaml:"flags,omitempty"`
// VersionGroup is the capture group (1-based) holding the product
// version; 0 = the rule carries no version from this spec.
VersionGroup int `yaml:"version_group,omitempty"`
// Weight scales the rule confidence for THIS alternative
// (0 < w ≤ 1). Default 1.0.
Weight float64 `yaml:"weight,omitempty"`
// contains filtered or unexported fields
}
MatchSpec is one alternative signature inside a rule. A rule fires when ANY of its specs matches (OR); the best hit (highest weight, spec order breaking ties) supplies the version and confidence.
type Problem ¶
type Problem struct {
RuleID string `json:"rule_id"`
File string `json:"file,omitempty"`
Field string `json:"field,omitempty"`
Detail string `json:"detail"`
Helpful string `json:"helpful,omitempty"`
}
Problem is one lint finding. RuleID is "" for registry-level findings (duplicate ids, exploitation text).
func ApplyTests ¶
ApplyTests runs a rule's own regression tests and returns a problem per failure (used by lint and by `gseer rules validate`).
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is a compiled rule set: the embedded seed collection, the embedded set plus an operator directory, or an arbitrary set for tests.
func NewRegistry ¶
NewRegistry compiles rules into a registry. Compilation failures are surfaced as lint problems; a registry with any compile problem refuses to Apply (fail-closed).
func (*Registry) Apply ¶
Apply matches the rule set against one evidence payload and returns the deduplicated hits sorted by confidence (desc) then product (asc).
Semantics:
- a rule fires when ANY of its match specs hits (OR);
- within one rule the best hit (highest weight, spec order breaking ties) supplies version and confidence;
- across rules, one product survives: the highest-confidence hit (rule id asc breaking ties) — two rules claiming the same product is a collection bug the linter/tests should catch, but Apply stays deterministic regardless;
- evidence values that are absent or non-string never match;
- a registry with compile problems returns nil (fail-closed).
func (*Registry) LintProblems ¶
LintProblems returns the compilation problems (empty = healthy).
type Rule ¶
type Rule struct {
// ID is the stable kebab-case identifier ("<family-ish>-<product>"
// convention, e.g. "web-server-nginx").
ID string `yaml:"id"`
// Family is one of the closed Family set.
Family Family `yaml:"family"`
// Product is the canonical technology name that lands in
// technologies[] (plain name, no version — search CONTAINS is
// element membership).
Product string `yaml:"product"`
// Version is a fixed version claim (rare; most rules extract from
// evidence via VersionGroup).
Version string `yaml:"version,omitempty"`
// Confidence ∈ [0,1] per docs/RULES.md §4 tiers.
Confidence float64 `yaml:"confidence"`
// Provenance documents the signature's origin: which observable
// the pattern encodes and why it is trustworthy. Required — a
// rule without provenance is a rule nobody can audit.
Provenance string `yaml:"provenance"`
// Matches ≥1 alternatives.
Matches []MatchSpec `yaml:"matches"`
// Tests ≥2 (≥1 positive, ≥1 negative).
Tests []Test `yaml:"tests"`
// contains filtered or unexported fields
}
Rule is one identification signature.
type Test ¶
type Test struct {
Evidence map[string]any `yaml:"evidence"`
// Match is the expected outcome.
Match bool `yaml:"match"`
// Version is the expected extracted version ("" = no assertion on
// version; asserted only when the tag is present in the YAML via
// a pointer, so an absent tag and an explicit empty differ).
Version *string `yaml:"version,omitempty"`
}
Test is one false-positive regression case: the rule is applied to Evidence and must (not) match with the expected version.