CyberCLI

← All engines · Pipeline

Suppression rule matcher

beta

v1.0.0 · last updated 2026-06-06 · source: cybercli.suppression.matcher.SuppressionMatcher

§1

What it does

Runs every signal against the operator-authored noise rules; suppresses matching signals from the gate; surfaces 'pattern drift' when a rule's traffic shape changes so stale rules don't hide a real attack forever.

§2

Why you care

The SuppressionMatcher is the engine that lets the analyst tell the AI 'I've seen this pattern, it's noise, stop showing it to me' — without breaking the audit chain. Each operator-suppressed pattern becomes a deterministic rule. Future events matching that rule are dropped before the gate sees them, BUT the suppression itself is audited so a forensic replay can re-derive what would have fired if the rule weren't in place.

Other AI security products either lack suppression (every event always pages — alert fatigue is your problem) or hide it from the audit trail (Sentinel suppresses but the suppression itself doesn't carry a row, so 'why didn't we see this attack' becomes unanswerable). CyberCLI suppression is auditable end to end. Every rule has an author, a correlation_key, and a disposition; every match writes a `signal_suppressed` audit row.

Pattern drift is the engine's killer feature. A rule that fires 200 times a day at install time but goes silent for two weeks might have stopped because the attacker stopped — or because the attacker mutated past the rule. The matcher detects the drift, marks the rule as 'review pending,' and surfaces it on the dashboard. The analyst either confirms the rule should stay or deactivates it; either way the choice is recorded.

§3

How it works

Each suppression rule is a SuppressionRule pydantic model carrying a `correlation_key` (the hash the analyst suppressed on), a `disposition` (FP-BENIGN / FP-EXPECTED / DUPLICATE / etc.), a `created_by` actor, a `created_at` timestamp, a `last_match_at` (auto-updated), a `match_count_since_review`, and an active/inactive flag. Rules are persisted in the `suppression_rules` SQLite table and loaded into the matcher on engine start.

On each event arriving at the prefilter stage, the SuppressionMatcher iterates the active rules and checks whether the event's correlation_key matches any. The check is O(rules) per event because rules-per-install rarely exceed the low hundreds; for installs that push past that, a Bloom filter pre-check is in the v1.x backlog.

A match returns the matched rule's disposition. The SocPipeline writes a `signal_suppressed` audit row carrying the rule_id + the original event's id, increments the rule's `match_count_since_review`, updates `last_match_at`, and short-circuits the rest of the pipeline. No gate, no digest, no dispatch — the signal is gone except for its audit footprint.

The drift detector runs separately on a 5-minute cadence. For each active rule it asks: has the rule's `last_match_at` been older than 24h while we've been ingesting events that USED to match? If yes, the rule is marked `drift_suspected` and surfaced on the dashboard's /suppression page. The detector doesn't auto-deactivate — that's the operator's call — but it makes the choice visible.

§4

Configure & observe

CLI

$ cybercli audit tail -n 30

Filter for outcome=signal_suppressed to see every event the matcher caught. Each row carries the rule_id + correlation_key + suppressed event id.

Dashboard surfaces

/suppression

The rule roster. Each rule with author, created date, match count, last match, drift status. Operator can mark a rule inactive or extend it.

/signals (Suppress button)

Where operators author new rules. Pick rows with a shared correlation_key, click Suppress, choose disposition. The rule is born with full audit attribution.

Audit row shape

Outcomes emitted
signal_suppressedsuppression_rule_createdsuppression_rule_deactivatedsuppression_rule_drift_detected
Parameters block

parameters.rule_id + parameters.correlation_key + parameters.disposition on every row. Created/deactivated rows additionally carry parameters.actor. Drift rows carry parameters.last_match_at + parameters.days_silent.

§5

Status

Beta — the matcher is stable in mechanism but the rule-pack catalog is still growing. Today the engine ships only operator-authored rules; the v1.x roadmap adds vendor-shipped baseline rule packs (e.g., 'common nginx crawler patterns') that ship as inactive defaults the operator can activate one at a time.

Shipped 2026-06-07: SuppressionRule.predicate accepts a structured dict with leaf operators ({field, eq|in|contains}) and compound operators (and/or/not). The matcher evaluates the predicate against event_fields after correlation_key + source_connector match; a rule with a predicate suppresses only if the predicate also evaluates True. Storage migrated via idempotent ALTER TABLE ADD COLUMN. The evaluator never raises at runtime — malformed predicates evaluate to False so a bad rule can't crash the matcher.

Shipped 2026-06-07: per-rule `volume_spike_factor_override` + `time_drift_hours_override` on SuppressionRule. The matcher reads the per-rule value before falling back to its global. Rules baselined on intermittent patterns (weekly maintenance, monthly batch) can now widen the time-drift tolerance; rules that legitimately burst can raise the volume-spike factor. SQLite schema migrated via idempotent ALTER TABLE ADD COLUMN.

How this engine talks to the rest

Upstream · feeds this engine
Downstream · this engine feeds