Skip to main content

Why OCSF is awesome

· 7 min read

Log sources have a common problem: the same event can be represented differently by different systems.

Take authentication.

Okta, GCP, a VPN, AWS, EDR, Osquery, and others can all tell you that a user tried to log in. But each source can structure that event differently and use different names for the same action.

For you, that means every new source becomes another parser, another mapping, and another special case in detection logic.

OCSF — the Open Cybersecurity Schema Framework — gives you a common way to describe these events. It defines standard event classes, fields, and meanings for security data, so events from different sources can be represented in the same structure.

In simple terms, OCSF gives us a shared language for security events.

OCSF is useful because it gives security data a common semantic language between producers (Okta, AWS, Palo Alto, etc) and consumers (SIEM, Detections, AI, Dashboards, etc).

Why OCSF is awesome

The problem is bigger than parsing JSON​

Suppose you want to detect this:

A user has several failed authentication attempts and then successfully authenticates from an unusual IP address.

The detection logic is straightforward. The annoying part is getting the data into a form where the detection can actually work.

Without normalization, you end up with something like:

Then the detection has to know about all of them. Add another identity provider and you potentially have to modify the detection.

The identity provider should be responsible for producing its event. The ingestion layer should handle the mapping. The detection should care about authentication, not about which vendor produced the event.

OCSF gives you a common language at that boundary:

info
  • OCSF launched in August 2022, founded by Splunk, AWS, IBM, and 15 other companies.
  • By the 1.0 release in August 2023, it had grown eightfold to over 145 contributing organizations.
  • It's Apache 2.0 licensed and not owned by any single vendor.

What does an OCSF authentication event actually look like?​

Authentication is an OCSF event class.

In OCSF 1.9, the Authentication class has a class_uid of 3002 and belongs to the Identity & Access Management category. It defines fields such as user, src_endpoint, dst_endpoint, auth_protocol, auth_factors, is_mfa, is_remote, service, session, and status_detail.

Its activity_id identifies the activity represented by the event. For the Authentication class, 1 is Logon, 2 is Logoff, while other values represent activities such as Kerberos ticket requests and preauthentication.

A simplified successful login could look like this:

{
"activity_id": 1,
"activity_name": "Logon",
"category_uid": 3,
"category_name": "Identity & Access Management",
"class_uid": 3002,
"class_name": "Authentication",
"status_id": 1,
"status": "Success",
"time": 1757155200000,

"user": {
"email_addr": "alice@example.com"
},

"src_endpoint": {
"ip": "203.0.113.42"
},

"is_mfa": true,

"metadata": {
"version": "1.9.0",
"product": {
"name": "Example IdP",
"vendor_name": "Example"
}
}
}

The event now has standardized semantics for concepts the detection needs:

  • User identity (user)
  • Source and destination (src_endpoint, dst_endpoint)
  • Authentication protocol and factors (auth_protocol, auth_factors)
  • MFA and remote access state (is_mfa, is_remote)
  • Target service and session (service, session)
  • Authentication outcome (status_id, status, status_detail)

OCSF also provides both numeric identifiers and human-readable names. For example:

"activity_id": 1,
"activity_name": "Logon"

The identifier gives consumers a consistent value, while the name makes the event easier to inspect.

Every event class also inherits attributes common across OCSF — class_uid, category_uid, severity_id, metadata — on top of the fields specific to that class.

Now the detection becomes boring​

And that is a good thing.

Now a harder detection:

A user has five failed login attempts and then successfully logs in from an unusual IP address.

Without normalization, the detection has to understand how every source represents the same concepts.

An Okta event might have:

{
"user": "alice@example.com",
"clientIp": "203.0.113.42",
"status": "FAILURE"
}

Another source might use:

{
"userPrincipalName": "alice@example.com",
"source_ip": "203.0.113.42",
"result": {
"code": 1
}
}

The detection now has to contain source-specific logic:

if source == "Okta":
user = event["user"]
ip = event["clientIp"]
failed = event["status"] == "FAILURE"

elif source == "GCP":
user = event["userPrincipalName"]
ip = event["source_ip"]
failed = event["result"]["code"] != 0

Only after doing this can it implement the actual detection:

if failed:
failed_logins[user].append(event)

elif is_unusual_ip(user, ip) and len(failed_logins[user]) >= 5:
alert(user, ip)

The problem is that the detection is now responsible for two different things:

  1. Understanding each vendor's event format.
  2. Detecting suspicious authentication behavior.

The source mapper can convert both events into the same Authentication class. The detection can then work with the normalized fields:

user = event["user"]["email_addr"]
ip = event["src_endpoint"]["ip"]

if event["status_id"] == 2:
failed_logins[user].append(event)

elif event["status_id"] == 1:
if len(failed_logins[user]) >= 5 and is_unusual_ip(user, ip):
alert(user, ip)

Now the detection only describes the behavior we care about:

Five failed logins followed by a successful login from an unusual IP.

It does not need to know whether the original event came from Okta, GCP, a VPN, or another product.

That is the useful boundary OCSF provides. Vendor-specific knowledge stays in the mapping layer, while the detection works with the security semantics of the event. Personally, this is why I like the OCSF model: it makes detections more predictable, maintainable, and straightforward.

Adding another product should not break the detection​

Your company adds another identity provider.

Without a common schema:

With OCSF:

The new work still exists. Someone has to understand the vendor's event format and map it correctly — once.

Without OCSF, that vendor-specific knowledge would show up again in every detection, dashboard, and query that touches this data. With it, it shows up once, in the mapper.

But why OCSF?​

OCSF is not the only option. CIM, UDM, ECS, or your own schema can provide the same basic benefit: normalize once and let consumers work with a common model.

I like OCSF for three reasons.

It is open and vendor-neutral, so your detections are not tied to the SIEM or data platform that consumes them. If another platform supports OCSF, moving the detections becomes much easier.

It is classification-first. OCSF organizes data around security event classes and categories rather than starting with a collection of fields. That gives producers and consumers a shared vocabulary for describing security events.

You don't have to design the model yourself. OCSF is community-driven, so many common security data modeling problems have already been worked through by others.

You still have to build the mappings. You just don't have to invent the schema.

Where you can find OCSF today​

OCSF is not just a schema sitting in a repository.

The trade you're actually making​

OCSF doesn't remove the work of understanding every vendor's format. Someone still has to read Okta's docs, write the mapper, and update it when a field gets renamed. What changes is where that work happens.

Do it once at the edge, and every detection, dashboard, and query downstream gets to assume a stable shape for "a user logged in." Skip that step, and the same vendor-specific knowledge leaks into every rule you write, and every analyst has to relearn it.

That's the whole argument: normalization is a cost you pay once at the ingestion boundary, or repeatedly in the middle of your platform. OCSF just gives you a common target to pay it against.