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)
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_idandspan_idfields to the OCSF event builders incrates/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
unmappedmechanism. The proposed approach adds first-class optional fields to the builder pattern:.trace_context()reads fromtracing::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.comfrom sandboxsb-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_idandspan_idfields, populated by the.trace_context()builder method from the active tracing span. The reviewer pastes thetrace_idinto 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/: Addtrace_context()method to builder pattern, addtrace_id/span_idto the OCSF output formatcrates/openshell-sandbox/that have an active trace span should call.trace_context()Alternatives Considered
Use the
unmappedfield mechanism: The builders already support arbitrary key-value pairs viaunmapped. This would work but makestrace_idan 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
crates/openshell-ocsf/src/builders/unmappedmechanism for arbitrary fields already exists in the builder patternocsf_emit!()which stores events in a thread-local and emits viatracing::info!()tracing::Span::current()at the point of event constructionRelated: #1055 (Enterprise Observability), #2508 (Supervisor OTel span emission), #2507 (Gateway OTel export surface)