Skip to content

Correlation: many signals, one risk

Every tool you own answers one question. The failure that hurts you is the one that needs four of those answers at once, and no single console has three of them.

security correlation enginecompound riskalert fatigue reduction

The problem

A workflow file changes in a repository that holds deploy credentials. A certificate is issued by a hierarchy whose keys later turn out to be recoverable. A key that has never been used against this account validates for the first time, and forty minutes later something enumerates the model APIs it can reach.

Four findings, four tools, four severity labels that mean different things. None of them is worth waking anyone for. Together they describe a breach that is already in progress.

This is not a volume problem. Teams that halve alert volume still miss these, because the information needed to judge them lives in products that do not know about each other's existence.

How Hanu approaches it

Collectors normalise everything into one typed shape before anything reasons about it. There are 166 signal types covering cloud posture, identity, code, CI/CD, certificates, containers, Kubernetes, data, AI workloads and runtime. An engine's only job is to emit the right type with the right fields populated — it does not decide whether the finding matters.

Correlation then runs in two tiers over that stream. Tier 1 asks whether one observation is unusual *for you*: has this principal ever been seen, was this resource public yesterday, has this key ever been validated. Tier 2 asks whether observations from different places, inside a bounded window, describe one sequence. Only tier 2 produces a compound risk.

Two tiers, one stream74 registered rules
TierInputOutputRules
1One signal, against history and contextAnnotated signal — unusual, first-use, newly exposed33
2Signals across providers inside a declared windowCompoundRisk with a score and an evidence trail41

Both tiers are declarative rules registered in an index array, each with its ATT&CK technique, its evidence fields and a test. The 609 behavioural detections map to 96 ATT&CK techniques. A registration guard fails the build if a rule is exported but never wired into the tier index — the failure mode that makes detection catalogues quietly wrong over time.

Scoring you can recompute

A compound risk carries a score assembled by arithmetic. There is no model anywhere in the path, which matters less for purity than for the practical reason that a score nobody can recompute is a score nobody can defend.

InputEffect
Severity base from the rule9 for CRITICAL, 7 for HIGH
Each additional contributing signal+0.5, capped at +2
Reaches a publicly exposed asset× 1.3
Reaches a crown-jewel asset× 1.6
Appears in known-exploited intelligence× 1.5
Ceiling10.0
Multipliers compound; the result is capped at 10. Bands: ≥ 9 CRITICAL, ≥ 7 HIGH, ≥ 5 MEDIUM, ≥ 3 LOW.

Read as arithmetic rather than as an incident, a chain that reaches a crown-jewel asset from a public entry point saturates the ceiling quickly. That is the intended behaviour: the top of the queue is reserved for chains where the reach is already established, and everything below it is ordered by how close it got.

CompoundRisk · evidence trail3 signals · 2 providers · one 30-minute window
SignalProviderObservedEvidence
CI_WORKFLOW_TAMPERgithub02:11:04.github/workflows/deploy.yml @ a91f3c2
SECRET_LEAKEDgithub02:19:38AWS_SECRET_ACCESS_KEY · refs/heads/main
LLM_JACKING_ACTIVITYaws02:41:12sts:GetCallerIdentity → bedrock:ListFoundationModels

What you get

COR-01One queue instead of five
Every contributing signal from every provider lands on one risk with one owner. The reviewer reads a sequence rather than a list of unrelated severities that happen to be adjacent in time.
COR-02The score's working, attached
Each risk carries the rule id that produced it and every signal that contributed, each with a pointer into the raw event. Nothing in the trail is generated at read time, so the trail you see is the trail that existed at detection.
COR-03Rules small enough to read
75 correlation rules in two index arrays, each with its ATT&CK mapping and its test. Deliberately reviewable: a detection set nobody can read is a detection set nobody can tune, and tuning is the entire job.
COR-04Response where the work already is
Remediation routes to Jira, ServiceNow, Cortex XSOAR, PagerDuty or Slack, and code-level fixes return to the forge as a check run. The finding and the fix stay in the same thread.

Limits

Correlation cannot join what was never collected. A resource type with no collector is invisible to the graph — Oracle Cloud exists in the codebase as stubs only and is not supported, and any provider you have not connected does not exist as far as a chain is concerned.

Joins depend on stable identifiers. Where a provider exposes a real fingerprint the match is exact; where it exposes only a name, the signal records identifierQuality: "name-pattern" so a reviewer knows the join is a heuristic rather than a fact.

Windows are declared per rule and are not learned. That is deliberate — a threshold that changes on its own is a threshold nobody can regression-test — but it does mean an unusually patient attack that spaces its stages over days will not fuse into one risk. It will still appear as separate findings, which is worse than a compound risk and better than nothing.

There is no tactic-level ATT&CK mapping file in the repository, so we do not publish a "tactics covered" figure. Technique mappings exist per rule; a tactic rollup is not something we can currently reproduce with a command.

Questions

Does anything use a model to decide what is risky?
No. Two LLM call sites exist in the codebase, both inside an optional advisory module that no engine imports. Detection, correlation and scoring are deterministic rules over typed signals, which is what makes a score reproducible and a rule regression-testable.
What happens when two rules fire on the same signals?
They produce two compound risks, each with its own rule id and evidence trail. Deduplication is by incident key rather than by fuzzy similarity, so a risk always traces back to exactly one rule and one set of inputs.
Can we add our own signals and correlation rules?
Yes. Signals are typed on a bus with a declared schema, and both behavioural and correlation rules are declarative entries registered in the tier index arrays with their evidence fields and tests attached. The registration guard fails the build if a rule is exported but not registered, which is intentional rather than annoying.
How does this relate to the SIEM we already run?
It runs beside it. Hanu correlates across cloud, identity, code, certificate and AI surfaces and sends the result to your response process — Jira, ServiceNow, Cortex XSOAR, PagerDuty, Slack, or a Splunk or Datadog pipeline. It is not an event store and does not try to be one.

Run it in your own account

One Docker Compose stack, your datastores, read-only credentials into your clouds. The Community edition is free to run in production.