Skip to content

feat(ocsf): add trace_id/span_id correlation fields to OCSF event builders #2640

Description

@rhuss

Problem Statement

Open Cybersecurity Schema Framework (OCSF) security events and OpenTelemetry (OTel) traces exist in separate systems with no connection between them. When a security reviewer investigates a suspicious network pattern, they find a DENY event in the OCSF log aggregator (Loki, Splunk) for sandbox sb-abc123. To understand the full request context (which middleware evaluated it, what policy was active, what the agent was doing), they have to manually search for the sandbox ID in Jaeger and hope the timestamps line up. There is no direct link from the security event to its trace.

Both OCSF events and OTel spans are generated in the same code paths within the supervisor. The trace context is available when the OCSF event is constructed, but the OCSF builders do not capture it.

Proposed Design

Add optional trace_id and span_id fields to the OCSF event builders in crates/openshell-ocsf/. When a tracing span is active at the point an OCSF event is constructed, the builder captures the current trace and span IDs. When no trace context is active, the fields are omitted.

Mechanism

The OCSF builders already accept arbitrary fields via the unmapped mechanism. The proposed approach adds first-class optional fields to the builder pattern:

let event = NetworkActivityBuilder::new(crate::ocsf_ctx())
    .activity(ActivityId::Open)
    .action(ActionId::Denied)
    // ... other fields ...
    .trace_context()  // captures current span's trace_id + span_id
    .build();

.trace_context() reads from tracing::Span::current() and populates:

  • trace_id: the W3C trace ID (32 hex chars)
  • span_id: the current span ID (16 hex chars)

These appear in the OCSF JSONL output alongside the existing fields:

{"class_uid": 4001, "activity_id": 1, "trace_id": "0af7651916cd43dd8448eb211c80319c", "span_id": "b7ad6b7169203331", ...}

Persona workflow: security/compliance reviewer correlating a policy violation with its trace

A security reviewer investigating a suspicious network pattern opens the OCSF log aggregator (Loki, Splunk) and filters for DENY events in the last hour. They find a network deny for api.suspicious.com from sandbox sb-abc123. They want to see the full request context: which middleware evaluated it, what policy was active, what the agent was doing. But the OCSF event has no connection to the trace system. The reviewer has to manually search for the sandbox ID in Jaeger and hope the timestamps line up.

How this issue enables the workflow: The OCSF event carries trace_id and span_id fields, populated by the .trace_context() builder method from the active tracing span. The reviewer pastes the trace_id into Jaeger and lands directly on the supervisor's network span that produced the deny. They see the full egress path: Open Policy Agent (OPA) evaluation, L7 enforcement, middleware chain. One click from log to trace.

Scope

  • crates/openshell-ocsf/: Add trace_context() method to builder pattern, add trace_id/span_id to the OCSF output format
  • Existing OCSF event sites in crates/openshell-sandbox/ that have an active trace span should call .trace_context()
  • No breaking changes (the fields are optional additions)

Alternatives Considered

Use the unmapped field mechanism: The builders already support arbitrary key-value pairs via unmapped. This would work but makes trace_id an afterthought rather than a discoverable builder method. First-class fields encourage consistent adoption across all event sites.

Add a tracing layer that automatically injects trace context into all OCSF events: This would avoid per-site .trace_context() calls but adds complexity to the OCSF emission pipeline and makes it harder to reason about which events carry trace context. Explicit is preferred here.

Structured log correlation via tracing fields: The existing JSONL layer could extract trace context from the tracing span and include it automatically. This is worth evaluating as a cleaner approach but depends on the JSONL formatter having access to span context, which may require changes to the tracing layer integration.

Agent Investigation

  • OCSF builders are defined in crates/openshell-ocsf/src/builders/
  • The unmapped mechanism for arbitrary fields already exists in the builder pattern
  • OCSF events are emitted via ocsf_emit!() which stores events in a thread-local and emits via tracing::info!()
  • The shorthand layer and JSONL layer extract events from the thread-local
  • Tracing span context is available via tracing::Span::current() at the point of event construction
  • #2508 sub-issue 5 references OCSF correlation but has not defined the mechanism

Related: #1055 (Enterprise Observability), #2508 (Supervisor OTel span emission), #2507 (Gateway OTel export surface)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions