Practitioner deep-dive
The tools you can use today.
Everything I'm about to describe is open, it's runnable today, and most of the people who'd get the most out of it were never told what each piece is actually for. That's the part that still surprises me. The schema your events normalize into, the ontology that defines what a process and a file and a credential are, the rule format your detections are written in, the reasoner that can check whether your mappings hold together, the crosswalk tables that say how one vendor's fields line up with another's, all of it ships under an open license and none of it costs anything to clone. So this is the map I wish someone had handed me when I started pulling this stack apart, and I'm holding one rule the whole way through: I'll tell you where each tool is genuinely good and where it's thin, because a tool map that only lists strengths is an advert, and an advert is exactly the thing that gets a practitioner stuck when the tool doesn't do what the marketing implied.
I'll take them roughly bottom to top, the way data moves through them, with the honest read attached to each one. None of this is a finished stack you can buy. It's a set of parts, some sound, some half-built, and knowing which is which is most of the value. Evidence tier B: my own local measurements against the shipped artifacts, plus primary-source verification against the project repos and issue trackers, with the per-tool judgments mine.
OCSF
A good shape, not a meaning-checker.
OCSF is the open, vendor-neutral schema your events normalize into, the thing that says a network-connection event has these attributes with these names so your detection content doesn't have to be rewritten for every appliance you onboard. Judged as a schema, it's good, and I want to be clear about that before I get to the limits, because the limits get more airtime and they shouldn't crowd out the fact that the core works. The version I've been measuring against is v1.x heading toward 1.9.0-dev, around 169 objects and 84 event classes and 850-odd dictionary attributes under Linux Foundation governance with a real RFC process and a three-approver merge, and the internal structure holds together: the category-to-class-to-attribute hierarchy is coherent, the dictionary is a single clean namespace, the patterns for enums and external references are well-formed. If your job is landing a Palo Alto log, a Zeek log, and a Windows event in the same field names, OCSF does that job.
Where it overclaims is the moment anyone calls it a semantic backbone, because it checks shape and not
meaning, and those are different things. A field can fit the OCSF shape exactly and still mean the wrong
thing, or map to a path that isn't there, and OCSF won't object, because shape-conformance is all it was
built to verify. The places this bites are real and they're tracked as open OCSF issues, not things I'm
asserting from the outside. There's no first-class relation that says actor X acted on target Y, so
direction lives in attribute position and per-class convention (issue #1601 documents six classes using
different field pairs for the same before-and-after state). The observable enum mixes scalar observables
like a hostname or a hash with pointers into whole objects like an endpoint or a process, in one flat
list, with no rule for which is canonical (#1376, #1649). And normalization is a one-way projection with
no first-class provenance for the original event, so whatever gets dropped or mis-mapped on the way in
is gone, and unmapped is where it goes to be forgotten (#1487, #1543, #1614). Those four
recurring gaps are OCSF's own, filed by its own community, which is why I'd rather point you at the issue
numbers than wave my hands. OCSF is sound as a schema and it's the right shape to write against. It just
isn't the thing that catches a meaning error, and treating it as if it were is how the meaning errors get
through.
D3FEND
The one real ontology, structurally clean, thin where a reasoner needs it.
D3FEND is MITRE's map of defensive techniques and the digital artifacts they act on, and it's the one genuinely formal ontology in this stack, which is why it's the natural thing to ground OCSF's fields into. It defines what a process, a file, a credential, a user account are as artifacts a defense can touch, and the coverage is broader than most people carry in their heads, since the root has fourteen top-level branches and not just artifacts. Identity is modeled deeply, the user-account and credential trees are detailed, assets and systems go four to six levels down, and attackers are modeled by access vector. As a subsumption hierarchy (the is-a tree) the v1.4.0 ontology is clean: I measured zero subclass cycles over its 4,422 classes and effectively no tangle in the parent structure. The taxonomy is sound.
The honest part is that nearly everything a reasoner would need to do useful checking is thin. Most of the relations carry no domain or range, meaning the machine can't tell what kinds of thing they're allowed to connect, so it can infer nothing from them and can't catch a misuse. And the assertion that actually does the catching, disjointness (the statement that two kinds of thing can't be the same individual, that a process is not a user account even though they're related), is almost entirely absent. D3FEND ships exactly three disjointness pairs in the whole ontology, and none of them are among Process, File, UserAccount, NetworkSession, and NetworkNode, the artifacts your core OCSF objects actually map to. So if you map a user to a process by mistake, nothing in D3FEND off the shelf gives a reasoner any basis to object, and the wrong mapping sails straight through. That's the gap that makes the silent failure possible at the ontology level, and it's not an oversight nobody noticed; D3FEND issue #423 ("assert that some core classes are disjoint") was closed as a deliberate start that explicitly invites the extension down the hierarchy, so the work has a sanctioned home and the maintainers want the PRs.
There's also a deeper defect worth naming because it's the kind of thing that survives a build silently, which D3FEND issue #424 captures. The way D3FEND wires into its upper grounding (CCO) ends up entailing that 922 classes, about a fifth of the ontology including Process and UserAccount, are physical material objects, which is false for a running process, and the reasoner doesn't complain because nothing in the ontology asserts these things are immaterial, so under the open-world assumption there's no contradiction to find. It builds, it's consistent, it's wrong. I'll spare you the upper-ontology mechanics here (the short version is that D3FEND aligned digital content under the class meant for the physical carrier that holds it), but the reason it matters for a tools map is that it's the textbook shape of the whole problem, a mapping to the wrong kind of thing committed in the reference ontology itself, and CCO's own maintainers already agree and are fixing the ambiguity upstream (CCO issue #564). So D3FEND is the strongest open candidate for the grounding layer and I'd build on it, with eyes open about which parts are solid today and which need the community to fill in.
Sigma and pySigma-pipeline-ocsf
The mapping is partial and improving.
Sigma is the open detection-rule format you already know, vendor-neutral rule logic you write once, and
the piece that matters for this stack is pySigma-pipeline-ocsf, the mapping that takes a
Sigma logsource and tags it with OCSF class and type identifiers and remaps the fields. It's at v0.1.x,
a single-maintainer project, and it's sound where a Sigma category lines up one-to-one onto a typed OCSF
activity (process_creation maps cleanly to the OCSF process activity type). It does the obvious cases
well.
It has three holes I'd want you to know about before you trust it on a whole rule set, and they all fail
the quiet way rather than the loud way. The cleanest is class-without-activity: several logsources
(network_connection, firewall, dns, dns_query, registry_event) emit only a class identifier with no
activity, so every rule converted through them collapses to "activity unknown," which is a real loss of
precision the conversion never warns you about. The second is self-mapping, where dozens of fields are
mapped to themselves (Image to Image, Initiated to
Initiated), so the conversion succeeds and emits a working query while the field quietly
rides through as a non-OCSF key, failing open instead of loud, the same silent shape as the D3FEND defect
above. The third is the one I think matters most, and it's the one Matthias Vallentin is substantively
right about: Sigma's correlation and aggregation, a first-class core feature of the format, has no
representation in the OCSF pipeline at all, so the mapping is single-event-shaped and the correlation
layer is simply unmapped. None of these make the pipeline bad. They make it partial, and partial in named
ways you can route around or, better, fix, since the repo takes PRs fast and the typing critique is
already half-addressed by a merged change.
ROBOT and ELK
The reasoner toolchain, and the version trap.
The reasoner is the program that reads your definitions and your mappings and works out what follows, including whether anything contradicts, and it's what actually catches the silent mistake. The two pieces here are ELK, the reasoner that does the working-out, and ROBOT, the command-line tool that runs it and packages ontology operations into a build step you can put in CI. The toolchain I've been running is ROBOT 1.9.x with ELK on Java 17, and the encouraging fact is that it's fast and small: a single wrong mapping shows up as the one class that can't possibly be true in 3.69 seconds over the full D3FEND ontology, in about half a gigabyte of memory, on a laptop. This is the kind of check you could run on every pull request, not a research artifact that needs a cluster.
The honest read here is mostly a warning about a trap. ELK is the reasoner you have to use over raw D3FEND, and you can't swap in the stricter reasoners (Pellet, HermiT) that catch more, because around 600 entities in D3FEND are "punned," used as both a category and a named thing at once, which pushes the ontology into a mode the strict reasoners refuse to run on. ELK tolerates it, which is good, but ELK is also a lighter reasoner that doesn't do everything the strict ones do, so part of why the deductive gate I've described has to add its own disjointness layer is that it can't rely on the stricter checking the punning rules out. And the version trap underneath all of it is Java. ROBOT and ELK want a specific JDK, and a practitioner who hits a Java version mismatch before the demo ever runs has usually already left. The fix is unglamorous and it works: wrap the toolchain in a container so the reader never touches Java directly and the version question never comes up. That's the difference between a check that runs and a check that gets abandoned at the JDK error.
The six-schema crosswalks
The tables, and what they're for.
The last piece is the least theoretical and maybe the most immediately usable: a corpus of crosswalk tables that say, row by row, how one schema's fields line up with OCSF's. The set I built covers six source schemas into OCSF, drawn from Splunk's CIM, Google's UDM, Microsoft's ASIM, Elastic's ECS, OpenTelemetry, and Zeek, about 925 mapping rows in all. Each row is a small claim ("this field over here means the same thing as that field over there"), and a crosswalk is just a pile of those claims, which makes it both the most boring artifact in the stack and the one where the silent errors actually live, because every row is a place a meaning can cross without anything noticing.
What the crosswalks are for is two things. First, they're a reference you can read directly when you're onboarding a source and you want to know how someone else mapped the same fields, which beats reverse-engineering it from a parser at 2am. Second, they're the test surface for the deductive gate: run the gate against the corpus with deliberate type-crossing errors injected the way the real silent error happens, and it caught all of them, 231 of 231 distinct mapping classes, with zero false positives I could attribute to the disjointness layer, and on the way it flagged about eight genuine coarse mappings in the real hand-built tables that a human should look at, an application mapped to a destination host, a principal used as a host, a file path mapped to a process name. The honest boundary, and I keep it attached because this is the empirical-honesty essay, is that the 100% catch is measured on injected corruptions rather than a held-out set of confirmed human mistakes, so the eight organic flags are the closest thing to real catches and they're plausible coarse mappings, not confirmed-wrong ones. That's tier B evidence and I'd rather say so than round it up.
Where to take the gaps
The thin spots are the contribution surface.
The reason I told you where each piece is thin, instead of just where it shines, is that the thin spots are the place to contribute, and every one of them is already an open issue in a project that takes PRs. The ones worth knowing by number:
- D3FEND wants the disjointness extension and has said so (#423), and the upper-grounding defect has a sanctioned fix path with CCO's own people on the thread (#424, CCO #564), so the deepest structural gap already has a home and willing maintainers.
- OCSF's four recurring gaps are filed by OCSF's own community (#1601, #1376, #1487, and their neighbors), so a coarse mapping you find in the wild has somewhere to go rather than dying in your private notes.
- The Sigma pipeline takes fixes fast and the correlation-projection hole is a design issue waiting to be opened, which is the highest-value thing an experienced detection engineer could pick up here.
So if you run this stack against your own mappings and the gate flags something, the move isn't to keep the fix in your own repo; it's to take it upstream, because the whole point of an open stack is that the next person inherits your catch.
A clone-and-run version of this assembled stack is up now at security-data-that-works, with the reasoner toolchain wrapped so you don't fight Java to see it work. The parts are all out there under their own licenses too, and the map above is what I'd have wanted first: an honest read of what each tool is for and exactly where it stops doing it.
If you'd rather walk the connections than read the tables, there's now a read-only MCP server, scg,
over the concept-only version of this map (1,442 nodes and 7,618 deduped edges, the public OCSF/D3FEND/ATT&CK/NIST-800-53/CCI
spine, no telemetry events), in the same sdw-lab-benchmarks repo.
What it carries that a plain crosswalk doesn't is the same honesty discipline made into something a tool enforces: every
edge declares how it's supported, a proxy_quality rank that runs from a measured benchmark result (1.0) down through
SKOS-typed and curated links to the intent-blind offense-to-defense inferences (0.25, and the largest class, roughly
6,000 of the 7,618 edges) and finally to unmapped (0.0, an explicit gap rather than a silent one). A
multi-hop answer is only as trustworthy as its weakest edge, so the paths tool reports the minimum and
raises a flag when the chain leans on one of those cheap cooccurrence guesses. I want to be precise about what this does
and doesn't buy you, because it's the empirical-honesty essay: when I measured it, grounding a model in this graph was
close to inert against a plain schema-validity check, and the graph's structure changed a retrieval answer on only
one of nine incident-reconstruction queries (the identity-collapse case), so this isn't a thing that makes a model more
accurate. It's honest navigation with the provenance attached, the same refusal-to-hide-a-cheap-join that the whole map
is built around, now something you can query instead of having to remember.