fprule

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Nov 8, 2026 License: AGPL-3.0 Imports: 9 Imported by: 0

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

Families is the closed family set, in documentation order.

Fields is the closed matchable-field set, in documentation order.

Functions

func LoadDir

func LoadDir(dir string) ([]*Rule, []Problem, error)

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 LoadSeed

func LoadSeed() ([]*Rule, []Problem, error)

LoadSeed parses the embedded seed collection.

func Products

func Products(matches []Match) []string

Products reduces Apply's output to the technologies[] form: plain product names, confidence order preserved.

func SeedPlus

func SeedPlus(dir string) (*Registry, []Problem, error)

SeedPlus returns the seed registry extended with operator rules from dir. Operator problems are surfaced in LintProblems; a broken operator rule disables the registry (fail-closed) rather than silently running on the seed subset.

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.

const (
	FamilyWebServer Family = "web-server"
	FamilyCMS       Family = "cms"
	FamilyLanguage  Family = "language"
	FamilyFramework Family = "framework"
	FamilyWebTech   Family = "web-tech"
	FamilyService   Family = "service"
)

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

const (
	FieldHTTPServer       Field = "http.server"
	FieldHTTPTitle        Field = "http.title"
	FieldTLSVersion       Field = "tls.version"
	FieldTLSCertSubjectCN Field = "tls.cert_subject_cn"
	FieldTLSCertIssuerCN  Field = "tls.cert_issuer_cn"
	FieldSSHBanner        Field = "ssh.banner"
)

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

func ApplyTests(r *Rule) []Problem

ApplyTests runs a rule's own regression tests and returns a problem per failure (used by lint and by `gseer rules validate`).

func Lint

func Lint(rules []*Rule) []Problem

Lint statically validates rules (without applying tests — ApplyTests does that) and returns every finding. An empty result means the registry is well-formed.

func (Problem) String

func (p Problem) String() string

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

func NewRegistry(rules []*Rule) *Registry

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 Seed

func Seed() *Registry

Seed returns the compiled embedded seed registry.

func (*Registry) Apply

func (r *Registry) Apply(evidence map[string]any) []Match

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

func (r *Registry) Healthy() bool

Healthy reports whether the registry can Apply.

func (*Registry) LintProblems

func (r *Registry) LintProblems() []Problem

LintProblems returns the compilation problems (empty = healthy).

func (*Registry) Rules

func (r *Registry) Rules() []*Rule

Rules returns the registry's rules in load order.

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.

func ParseRule

func ParseRule(data []byte) (*Rule, error)

ParseRule decodes one rule from YAML bytes.

func (*Rule) Source

func (r *Rule) Source() string

Source is the load origin recorded for lint reports ("embedded seed" or the file path).

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.

Jump to

Keyboard shortcuts

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