The uncomfortable truth of modern cloud IR is that there is often nothing to "detect" in the old sense. Attackers don't break in anymore; they log in. CrowdStrike's 2026 Global Threat Report put 82% of last year's detections in the malware-free column, with valid-account abuse behind roughly a third of cloud incidents. No dropper, no exploit chain — just a legitimate identity doing legitimate-looking things across Entra, M365, and a dozen SaaS apps.
That changes the hard part of the job. Detection tells you that something happened. It rarely tells you how: which account was really patient zero, which token got replayed, which consent grant quietly opened the door, and in what order it all unfolded. Responders reconstruct that story by hand, stitching a timeline out of fragmented, inconsistent audit logs, under time pressure, while the clock on breakout time keeps shrinking.
And manual reconstruction fails in a specific, predictable way: you anchor. You find the first narrative that fits the evidence, and you stop looking. The real path — usually the quieter one that doesn't announce itself — sits unexamined a branch away.
I built a tool to attack that failure mode head-on. It's called Nimbus Vestige, it's open source, and its defining feature is that it will tell you when it doesn't know.
Reconstruction, not detection
Nimbus Vestige (NV) is not a detector, a SIEM, a scanner, or an auditor. Those tools tell you that something happened and measure it. NV works backward — abductive reconstruction of the mechanism from patterns. Given normalized identity telemetry (sign-ins, consent grants, service-principal and role changes, mailbox access), it reconstructs the most likely path an intrusion took across accounts and services.
The one design decision everything else hangs off: it never hands you a single story. It hands you the ranked space of plausible paths. If your incident could have unfolded three ways, you see all three, ordered by how well the evidence supports each. That is the anchoring antidote — you can't tunnel on the first narrative when the tool is holding the alternatives up next to it. And the paths it didn't rank first but still rates plausible are a bonus deliverable: they're a hardening backlog, the other doors that were standing open.
The honesty problem
This is the part I care most about, because it's where reconstruction tools usually cheat. It is easy to emit a confident number. It is hard to emit a number that means something.
Every step NV reconstructs carries a confidence in [0, 1], built by evidence corroboration:
- a per-technique base rate,
- plus a bonus for each independent corroborating signal (source IP, device, successful outcome, temporal adjacency, broad-consent flags),
- minus a penalty for signals that should be present and aren't (e.g. no source IP to corroborate an origin).
Path scores compose their links by geometric mean, not average — so one weak link honestly drags the whole path down instead of being smoothed over by strong neighbors.
Then there is a trust floor. Below it, a step is not shown as a guess — it is withheld, grayed out, explicitly unsupported. The floor is a slider: drag it up for strict, high-trust reconstruction, down for permissive, high-coverage. That is the honesty-versus-coverage tradeoff made physical, and it lives in the engine's output contract, not bolted onto the UI as an afterthought.
Do the base rates and bonuses actually mean anything? That's a calibration question, and I treat it as one. The weights start as defensible priors, and a harness refits them against labeled ground truth — events where you know which were on the attack path and which were benign — reporting Brier score, ECE, a reliability table, and a trust-floor sweep before and after. A refit is only adopted if it genuinely lowers calibration error rather than just shuffling numbers around. Calibrated confidence means one concrete thing here: NV's 0.7 should correspond to a step that really was on the attack path about 70% of the time.
The honest caveat, stated plainly because the whole point is honesty: the real calibration input is telemetry from an attack-simulation run (BadZure / MAAD-AF) in a live tenant. Until you point it at one, NV ships with a documented-shape proxy corpus, and every artifact fitted from that proxy is stamped as proxy in its provenance so it can never be mistaken for a live-tenant result.
One engine, more than one cloud
Every substrate-independence claim deserves suspicion until a second substrate exists. NV now has one: the same engine that reconstructs an Entra/M365 intrusion reconstructs an AWS one from CloudTrail.
The design that makes this cheap is that the engine core — the graph builder, the confidence model, the abductive layer, the trust floor — never knew anything about Microsoft in the first place. Only the vocabulary is provider-specific: which operation maps to which ATT&CK technique, and whether a step is an initial-access, pivot, or collection move. That vocabulary now lives in provider packs, and adding AWS meant adding a pack (IAM/STS/S3 → ATT&CK) plus a CloudTrail normalizer that emits the same event model. No engine change. The authorization gate generalized the same way: on AWS you scope-gate to an account instead of a tenant domain, and NV still refuses to run on any estate you haven't explicitly authorized.
Two details from the AWS eval are worth calling out because they're the honesty model doing its job on a new substrate, unmodified:
- A deliberately-unknown IAM operation (
PutRolePermissionsBoundary— no pack names it) survives ingestion, gets reconstructed abductively, and is flaggedno known matchat reduced confidence. The abductive layer doesn't care whose cloud it is. - An AWS
ConsoleLoginlands below the trust floor that an Entra sign-in clears. That's not a bug — CloudTrail carries no device-posture signal, so an AWS login genuinely corroborates less, and NV refuses to pretend otherwise. A tool that fabricated a device signal to make the number look better would be exactly the kind of tool this project exists to not be.
The current AWS pack is deliberately narrow — IAM, STS, and S3 object reads, the identity-abuse core. It's a proof of generalization, not parity. Widening it is straightforward now that the seam exists.
Staying current without going blind
Every reconstruction tool meets the same two objections: your patterns will go stale, and you'll be useless on something novel. NV answers them on two independent fronts.
The known layer stays fresh through a validated update pipeline — MITRE ATT&CK (STIX), vetted CTI over TAXII 2.1, and the Sigma community ruleset, pulled on a per-feed cadence. Ingestion is treated as adversarial: every pattern, built-ins included, passes a trusted-source allowlist, a content checksum, schema validation, and an ATT&CK technique-id check before it enters the library, with an audit record for every admission and every rejection. An auto-update pipeline is a poisoning surface in disguise — a bad feed that injects false patterns manufactures false reconstructions — so the same skepticism the engine applies to evidence, it applies to its own patterns. (The feeds are scoped to the M365 beachhead they were built for; they refresh the Entra bindings and leave the AWS pack alone rather than over-mapping across substrates.)
The abductive layer covers what no feed has named yet. When an observed operation matches no known pattern, NV doesn't drop it — it reconstructs from first principles and flags it no known match at reduced confidence. The eval suite proves this twice over now: on a deliberately-unknown managed-identity token-theft step in the Entra corpus, and on the unknown IAM operation in the AWS one. Both caught, sequenced correctly, and honestly down-weighted. A signature engine goes blind on the zero-day; the abductive core degrades gracefully instead of going dark.
The ingest layer is built to serve that guarantee, not undercut it. The normalizers keep every identity-plane operation — including ones NV has never named — so a novel operation reaches the abductive core instead of being silently filtered out upstream. And they never fabricate a signal: if the source record carries no IP, no device, no consent marker, the field is simply absent, and the scorer reads absence as missing evidence rather than inventing corroboration.
Try it on public data
The demo needs neither your tenant nor anyone's cooperation. It runs on the public invictus-ir Office 365 unified audit log corpus, so you can see the whole pipeline end to end:
git clone https://gitlab.com/dobybaxter127/nimbus-vestige
cd nimbus-vestige
# open nv_gui.html — it renders the bundled demo reconstruction
When you point it at real data, that data never leaves your environment — NV is fully self-hosted. You export the M365 unified audit log (CSV with the standard AuditData column, or JSON/JSONL), normalize it, and run the engine scope-gated:
# 1. normalize raw O365/Entra audit logs into the event model
python3 nv_extract_identity_events.py auditrecords.csv nv_identity_events.jsonl
# 2. reconstruct — scope-gated to a tenant you are authorized to investigate
python3 run_nv.py nv_identity_events.jsonl reconstruction.json \
--trust-floor 0.6 --authorize your-tenant.onmicrosoft.com
# 3. view — open nv_gui.html and click "load reconstruction.json"
The GUI is a pure view layer: load the file the engine just produced and the same screen shows your incident, your entities, your confidence bands — and every dot on the embedding canvas re-runs the reconstruction for that entity on click, so pivoting from "the incident" to "what did this account actually do" is one click, not a re-run.
The AWS path is the same three steps against CloudTrail:
python3 nv_extract_cloudtrail_events.py cloudtrail.json aws_events.jsonl
python3 run_nv.py aws_events.jsonl reconstruction.json --authorize aws:123456789012
What it isn't, and where it's early
Being straight about the boundaries, since I'd want the same from anyone else's tool:
- It's not a detector or a SIEM. It comes in after something is flagged, to explain how the pieces fit. It will not alert you.
- It's identity-first. Entra/M365 is the deep substrate; AWS is in as an eval-proven second one, but its pack is IAM/STS/S3 only so far. Wider AWS coverage and a third substrate (GCP, Okta) are the next steps — and network, endpoint, and OT remain roadmap.
- Calibration is currently demonstrated on public / proxy data. Live-tenant weights are what you would actually deploy, and that needs a run in your estate.
It's built by one person and it's early. That is exactly why I'm posting this.
Live demo: https://nimbus-vestige-88b564.gitlab.io/
Repository: https://gitlab.com/dobybaxter127/nimbus-vestige
The core engine, both provider ingests, GUI, and offline eval harness are free and open. There's a one-time commercial license for teams that want the curated live feeds, calibration tooling, and support — but nothing about that gates the core, and nothing asks you to send your telemetry anywhere.