Repository navigation
Description
Problem Statement
Connect an agent operation to OpenShell enforcement and matching OCSF records. Trace transport alone does not explain policy decisions, credential handling or incomplete delivery.
Proposed Design
sequenceDiagram
box Workload container or Pod
participant H as Agent + Relay / tracing SDK
end
box Trusted OpenShell supervisor
participant P as HTTP/L7 proxy
participant O as OTLP trace receiver
end
participant A as External API
participant C as Collector / trace viewer / log store
H->>P: API request + W3C traceparent
Note over P: Create enforcement span and OpenShell request ID
alt Allowed
P->>A: Approved request + configured credential
A-->>P: API response
else Blocked
Note over P: Local denial<br/>No API request
end
P-->>H: HTTP response + X-OpenShell-Request-ID
Note over H: Optional adapter records a receipt mark
H-->>O: Agent spans via OTLP
O-->>C: Forward attributed agent spans
P-->>C: OpenShell spans + OCSF log export
Note over C: Trace parentage / links connect spans<br/>Request and trace IDs join security records
- Treat W3C context as untrusted correlation, not identity. Parent sampled context; link unsampled context; ignore malformed/duplicate context and incoming
tracestate. - Share an OpenShell-minted request ID across spans, middleware context, OCSF
metadata.correlation_uidand the response header. Prevent header substitution. - Instrument HTTP/L7, middleware, credentials, MCP/JSON-RPC, revisions/approvals and terminal delivery. Keep outcomes separate and secret-free; collector failure must not affect enforcement.
Full OCSF records do not return to the sandbox or become Relay spans. Backend joins and optional response-receipt marks provide correlation. Reverse reporting: #5272.
Deployment boundaries
| Deployment | What changes |
|---|---|
| Native Hermes/Relay inside sandbox | Existing tool context plus HTTP instrumentation; tested. No new mandatory sidecar. |
| Relay CLI gateway inside sandbox | Tool hooks/client instrumentation must carry context on actual requests. Not validated. |
| Relay gateway outside sandbox | Context must reach actual sandbox requests; it does not establish sandbox identity or cover every tool. Not validated. |
| Shared supervisor pool | Needs per-sandbox context isolation; not validated by this prototype. |
Acceptance criteria
- Navigate from an agent operation to enforcement and OCSF records.
- Verify provenance, receipt integrity, metadata bounds and failure handling.
- Document deployment limits without an OpenShell dependency on Relay.
Alternatives Considered
Timestamp joins are ambiguous; caller IDs are not trusted attribution. Full log feedback exposes operator data unnecessarily.
Agent Investigation
Private cumulative prototype: 14 real Hermes/Relay workflows, 807 spans, protected synthetic APIs and authenticated GitHub. Scripted scenarios used real execution/enforcement and private callback/OTLP helpers—not rewritten span parents. This is not isolated-branch, cross-driver or production qualification.
Uses openshell-otel/openshell-ocsf, network/middleware/server instrumentation and existing supervisor-direct OTLP forwarding. These are code attachment points, not new services. Draft #5214 covers only primitives; Rust source compatibility needs review.
Related: #2640, #4557, #2641, #4276, #4544, #4233, #1055.
Checklist
- I've reviewed existing issues and the architecture docs.
- This is a design proposal, not a "please build this" request.
Activity
sftimeless commentedon Nov 4, 2026
Heya...
One aspect of this design that interests me is the fine line between authorization, enforcement, and terminal delivery.
In our Keel/OpenShell experiments, we could independently verify signed authorization decisions and separately observe OpenShell enforcement outcomes, but couldn't establish a complete cryptographic authorization-to-dispatch chain.
The proposed correlation and terminal-delivery records would be useful for reconstructing that sequence, provided the evidence preserves which component actually observed each fact.
Specifically, an external authorization decision, an OpenShell forwarding result, and a downstream service's reported outcome should remain distinguishable, even when they share a request or trace identifier.
I'd be interested in how the proposed receipt model handles partial forwarding or an uncertain downstream outcome without presenting it as confirmed execution.
We have existing sanitized ALLOW/DENY test cases in #4557 that could help validate the correlation portion.
krishicks commentedon Nov 4, 2026
Written by Claude Code, reviewed by @krishicks
I reviewed #5214 together with the
relay-state-*prototype branches and #4789. The direction is right: untrusted W3C context as a correlation hint, and one OpenShell-minted request ID shared across spans, middleware, OCSFmetadata.correlation_uidand the response header. Two parts of the prototype's shape should change before it's split into PRs: how many OCSF records each request produces, and the single large enforcement span.One HTTP Activity record per request
Today an inspected request produces one
HttpActivityrecord (L7_REQUEST allow|deny, emitted at the policy decision). The prototype keeps that record and adds summary records for each middleware phase, for credential handling, and for terminal delivery. For a routine allowed request, that's up to four extra records. OCSF isn't sampled, so this becomes a fixed cost on every request.Proposal:
HttpActivityrecord per inspected request, stamped withcorrelation_uid.http_response.code(plus the upstream status when it differs). For a denial this changes nothing, because the 403 is the response. A guard emits the record if the request ends before headers are sent (upstream failure, cancellation), so every request still gets exactly one record.correlation_uid, only when something goes wrong after the first record: response middleware denies or fails after streaming starts, or delivery to the client fails or is cancelled partway through. Where it's a security finding, emit aDetectionFinding.This replaces the always-emitted summary records and the emission from
Drop.An allowed request is logged when the upstream answers rather than when it's sent. OCSF export already lags, so the extra delay is small, and a complete record per request is worth more than an earlier, partial one.
A root span with a child per stage, not one large span
openshell.http.enforcementdeclares about 55 attributes up front, and most stay empty for any given request. Proposal: a small root span plus child spans for the stages that actually ran.error.stageis no longer needed.This adds spans per request, but spans are sampled and OCSF records aren't, so the net volume that can't be sampled goes down.
Fitting this together with #4789
#4789 already puts the trace in the OCSF event itself (
trace.uid, Trace profile), uses an extractor registered at startup, and adds deny spans for L4 and CONNECT. I suggest landing it first and building on it:openshell.policy.evaluatetakes the place ofsupervisor.egress.denyon inspected L7 paths, and feat(ocsf): correlate audit events with sampled OpenTelemetry traces #4789's deny span stays the decision span for L4 and CONNECT. That avoids two decision spans for one L7 denial.tracingspan, as feat(ocsf): correlate audit events with sampled OpenTelemetry traces #4789's extractor does, rather than requiringwith_span_contextaround each emit. The workspace buildstracing-opentelemetrywithout context activation, soOtelContext::current()is empty unless set by hand.trace.uid. Unsampled and file-only records still join throughcorrelation_uid.OcsfEvent::base_mut. Both PRs currently add it in the same place.Suggested split
traceparent, use the existing per-request middleware UUID as the shared request ID, sendx-openshell-request-idon every inspected response and protect it from being set or removed, add the root span andpolicy.evaluate, and emit the single record when response headers are sent. Documentcorrelation_uidand the header inocsf-json-export.mdx. The header is for joining records, not for authorization.traceparenton middleware gRPC calls, and notable middleware outcomes on the single record.credential.preparespan.firewall_rule.uid/version) andunmapped, so schema validation stays meaningful.mcp.inspectspan.response.deliverspan plus the extra record only for problems after headers are sent. This is the largest and riskiest piece, so it should land last.Two parts don't depend on this sequence:
OTEL_EXPORTER_OTLP_TRACES_*, and handle OTLP partial success without retrying. These aren't about correlation and could be reviewed as their own PR.grpc/policy.rschanges reach beyond observability (locking and reconciliation). It probably needs its own issue.Open questions
unmappedor typed OCSF fields for policy hash, endpoint and revision IDs?yashdotrv commentedon Nov 5, 2026
I’d be happy to take the HTTP correlation core if no one is already working on it. I noticed the L7 policy event is emitted before the middleware request UUID is created. So, think moving the creation of that existing ID earlier and using the same value in the request span, middleware context, OCSF correlation_uid, and a response header that upstream responses or middleware can’t replace.
One question about the proposed single HttpActivity record per request: the current request and response middleware paths also emit records per invocation. Should those existing records be consolidated, or is the goal just to avoid the extra prototype summaries? I’d preserve security findings and cover allow, deny, and pre-header failures in tests.
I saw the suggestion to land #4789 first. Is this slice already owned, and would you prefer I wait until #4789 and #5214 are reconciled? I can keep the first PR to inspected L7 traffic.