Benefits of Normalizing Security Logs to OCSF
Every security tool logs a failed login in its own dialect. Windows writes event 4625, Okta writes user.session.start with a FAILURE outcome, and your SIEM gets to reconcile them. When you normalize OCSF security logs into one shape, those become one kind of event you can query, alert on and hand to a model.
The Open Cybersecurity Schema Framework (OCSF) is an open schema for security events. AWS and Splunk started it in 2022, and since 19 November 2024 it has been a Linux Foundation project. The current release on schema.ocsf.io is 1.9.0. Below are a real before and after mapping, four benefits, and the cases where you should skip it.
Map your OCSF security logs: before and after
Here is one failed logon as Windows reports it (event 4625, trimmed to the fields that matter):
{
"EventID": 4625,
"TimeCreated": "2026-10-21T08:14:07Z",
"Computer": "fin-app-01.corp.example.com",
"TargetUserName": "jsmith",
"TargetDomainName": "CORP",
"IpAddress": "203.0.113.10",
"IpPort": "52144",
"LogonType": 3,
"Status": "0xc000006d"
}
And the same kind of failure from Okta, a minute later:
{
"published": "2026-10-21T08:15:31.000Z",
"eventType": "user.session.start",
"outcome": { "result": "FAILURE" },
"actor": { "alternateId": "jsmith@example.com" },
"client": { "ipAddress": "203.0.113.10" }
}
Different field names, different time formats, different nesting. Here is the Windows event as an OCSF Authentication event (class_uid 3002, in the Identity & Access Management category):
{
"class_uid": 3002,
"category_uid": 3,
"activity_id": 1,
"type_uid": 300201,
"severity_id": 1,
"status_id": 2,
"status_code": "0xc000006d",
"time": 1792570447000,
"logon_type_id": 3,
"user": { "name": "jsmith", "domain": "CORP" },
"src_endpoint": { "ip": "203.0.113.10", "port": 52144 },
"dst_endpoint": { "hostname": "fin-app-01.corp.example.com" },
"metadata": {
"version": "1.9.0",
"product": {
"name": "Microsoft-Windows-Security-Auditing",
"vendor_name": "Microsoft"
}
}
}
type_uid is class_uid * 100 + activity_id, so 300201 reads as Authentication: Logon. status_id 2 is Failure, time is UTC epoch milliseconds, and logon_type_id 3 is Network, the same number Windows uses. The Okta event maps to the same class with the same activity_id and status_id. Only values like metadata.product.name and user.name differ.
Keep the original event in raw_data and put fields with no OCSF home in unmapped, so the mapping never destroys evidence.
Four benefits of normalizing security logs to OCSF
One query covers every source. Once both events share a class and field names, the question "who is failing logons from one IP across all our tools" is a single query against your security data lake, not two parsers and a fragile join.
-- Failed logons per source IP across every tool that emits OCSF Authentication events.
-- `time` is epoch milliseconds, as the schema defines it.
-- Quoting of "time" and "user" is for Trino/Athena-style engines; adjust for yours.
SELECT
src_endpoint.ip AS source_ip,
count(*) AS failures,
count(DISTINCT "user".name) AS distinct_users,
count(DISTINCT metadata.product.name) AS tools_seeing_it
FROM authentication_events -- your lake's table for class_uid 3002
WHERE class_uid = 3002
AND status_id = 2
AND "time" > (to_unixtime(current_timestamp) - 3600) * 1000
GROUP BY src_endpoint.ip
HAVING count(*) >= 10
ORDER BY failures DESC;
Watch one catch: OCSF fixes the shape, not the values. jsmith and jsmith@example.com are still two strings, so resolve identities before you trust distinct_users.
Detections get written once. A password spray rule becomes one condition on class_uid = 3002 AND status_id = 2, grouped by src_endpoint.ip, instead of one rule per vendor. When you add a new identity provider, you write one mapping and the existing rules apply without edits.
AI-assisted triage gets cleaner input. A triage agent that reads alerts or writes queries against your lake needs one field vocabulary, not a cheat sheet per vendor. Prompts and tool definitions shrink to a single schema description, and your evals compare like with like. Because you kept raw_data, the agent (and the analyst checking it) can always confirm a claim against the original log.
Your data stays portable. The schema is open source under Apache-2.0 and vendor neutral, so history stored as OCSF can move between a SIEM and a lake without a remapping project. Amazon Security Lake is a concrete example: it stores data as OCSF in Parquet, and for custom sources it expects you to deliver already-converted OCSF, one event class per source. You own the mapping, and the mapping is the portable part.
When normalizing to OCSF is not worth it
Skip it if you run a single-vendor SOC where one platform ingests, stores and detects, and nothing else needs the data. You would pay for a mapping layer to translate into a shape the tool already has.
It is also a poor trade with only a handful of sources, short retention, or no appetite to maintain mappings. Each mapping is real work, and fields with no OCSF home end up in unmapped, where nothing queries them by default.
Pin the schema version
The schema moves: 1.7.0, 1.8.0 and 1.9.0 shipped between November 2025 and August 2026, and a 1.10.0 development build is already listed on the schema site. Write metadata.version on every event and test your queries when you upgrade.
If you want an AI triage agent to be reliable and testable in production, give it security data with one shape first.