Reconstructing cloud identity intrusions from O365 logs
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 detect
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.
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 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 on a deliberately-unknown managed-identity token-theft step: caught, sequenced correctly, and honestly down-weighted below the floor. A signature engine goes blind on the zero-day; the abductive core degrades gracefully instead of going dark.
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, normalize it, and run the engine scope-gated (it refuses to run on any tenant you haven't explicitly authorized):
# 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, which renders reconstruction.json
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 today). The engine is substrate-independent by design and is meant to port to network, endpoint, and OT later — but that's roadmap, not shipped.
- 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, 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.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.