Skip to content

SIEM events

SIEM events

Cbox\Siem\ValueObjects\SiemEvent is the single, transport-neutral shape your application produces. It is final readonly — immutable by construction — so a built event cannot be mutated on its way to a formatter.

Fields

Field Type Required Notes
id string yes Stable, unique. Becomes ECS event.id, CEF externalId, JSON id.
occurredAt DateTimeImmutable yes Any timezone; always normalized to UTC on the wire.
action string yes Short stable slug: user-login, role-granted.
category EventCategory yes Normalized category (see below).
outcome Outcome yes Success / Failure / Unknown.
severity Severity no (default Info) Drives CEF severity, ECS log.level, GELF level.
actor Party|null no Who did it — a type + id pair.
target Party|null no What it was done to — a type + id pair.
sourceIp string|null no Originating address.
message string|null no Human-readable description.
context array<string, scalar|null> no Already-flattened extra detail.

Cbox\Siem\ValueObjects\Party is a tiny type + id value object used for both the actor and the target. Party::of('service', 'billing') reads a little nicer than the constructor.

The context flattening contract

context is an already-flattened bag: keys are strings, values are scalars (string, int, float, bool) or null. The core does not flatten nested arrays or objects for you, and it will not guess how they should be represented — because each SIEM disagrees. Elastic wants dotted keyword fields; CEF wants key=value; Splunk will take nested JSON. Flatten before you build the event:

// Instead of ['http' => ['method' => 'POST', 'status' => 201]] ...
context: ['http.method' => 'POST', 'http.status' => 201],

Where each formatter puts context: ECS uses labels.*, the JSON formatter a context object, Splunk HEC a context object inside event, GELF _-prefixed additional fields, and CEF cbox_<key> extension keys.

Enums

EventCategory

authentication, authorization, iam, configuration, session, threat, audit. The stored value is emitted verbatim by CEF and JSON. For ECS, ecsCategory() maps each case onto a valid ECS event.category keyword (authorizationiam, auditconfiguration, …). Because ECS has no audit category, the exact domain value is always preserved under ECS cbox.category, so nothing is lost when the mapping broadens.

Outcome

success / failure / unknown — exactly the ECS event.outcome vocabulary, so the mapping is lossless everywhere. Use Unknown only when the outcome is genuinely unknown, never as a placeholder.

Severity

info, low, medium, high, critical, with three deterministic mappings so formatters never have to guess:

Severity cef() (0–10) logLevel() (ECS) syslogLevel() (GELF, 0–7)
Info 1 informational 6
Low 3 notice 5
Medium 5 warning 4
High 7 error 3
Critical 10 critical 2

A severity lives on the event (rather than being inferred by a formatter) because CEF requires a header severity and GELF a numeric level — deriving them from outcome would be guesswork.