Skip to content

fix(ocsf): emit schema-conformant OCSF 1.8 events - #4288

Merged
krishicks merged 1 commit into
NVIDIA:mainfrom
zanetworker:fix/4283-ocsf-18-conformance/zanetworker
Oct 28, 2026
Merged

krishicks merged 1 commit into
NVIDIA:mainfrom
zanetworker:fix/4283-ocsf-18-conformance/zanetworker

Conversation

@zanetworker

@zanetworker zanetworker commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Native OCSF 1.8.0 events failed the official OCSF validator: HTTP Activity emitted an attribute the class doesn't define, Process Activity events had no actor, connection_info lacked direction_id, and SSH Activity events named no endpoint. The vendored-schema tests missed these because they checked only top-level, non-profile attributes.

This makes every builder's native output pass https://schema.ocsf.io/api/1.8.0/v2/validate (apart from top-level container, tracked on #4915) and adds structural checks that keep it that way, including an e2e test that validates the supervisor's real JSONL. The checks cover required and defined attributes, profiles, and at_least_one constraints, but not value types or enum ranges; the official validator stays the reference for those (see schemas/ocsf/README.md).

The schema downgrade is out of scope. #4284 tracks picking the one version OpenShell emits (1.1 or 1.8) and removing the downgrade; until then the existing downgrade is unchanged, so output with ocsf_schema_version set is still not conformant. This PR is needed either way:

Related Issue

Closes #4283

With #4677 (device.os.type_id), this meets #4283's acceptance items apart from top-level container, which this PR leaves unchanged. #4915 tracks container.

Changes

  • HTTP Activity no longer emits is_src_dst_assignment_known, which no OCSF version defines on that class.
  • ProcessActivityBuilder requires an acting process before build() (type-state, like the HTTP and Network builders). Sandbox process events name the openshell-sandbox launcher with its real pid.
  • connection_info carries the required direction_id and direction, defaulting to Unknown.
  • SshActivityBuilder requires a source or destination endpoint before build(). All eight SSH events had none; they now name the supervisor's SSH server as dst_endpoint (svc_name: "ssh" and the listen socket path in name), using a new Endpoint::svc_name field. Each SSH server builds its endpoint from its own socket and passes it to its connection handlers, so a shared supervisor hosting several sandboxes (refactor(supervisor): isolate state per sandbox instance #4701) names the right socket on each sandbox's events.
  • validate_required_fields and validate_known_fields share one recursive walker. Both reject undefined attributes at every level and, when validating an event, attributes from profiles the event doesn't declare. validate_required_fields also checks required attributes and at_least_one constraints, and fails any event not labelled 1.8.0. New test every_builder_emits_schema_conformant_events covers one event per builder, including Authentication and a fully populated Detection Finding with an ATT&CK mapping (tactic and technique are vendored for it).
  • New e2e test ocsf_jsonl_conformance (Podman CI set) enables JSONL, generates denied TCP traffic and a plain HTTP request through the sandbox proxy, waits until Network, HTTP and SSH Activity records are all present, copies the supervisor's JSONL with podman cp, and validates every record. It covers those production call sites rather than builders, and found the SSH endpoint gap. When records can't be copied, the failure names the podman command and its error. The e2e crate takes openshell-ocsf (with test-support) as a dev-dependency so both suites share one validator.
  • With test-support, vendored schemas are compiled into the binary (validation/embedded.rs, drift-checked by embedded_schemas_match_vendored_files), so archived e2e runs on CI machines need no source files. The fallback EventContext carries a sandbox identity, so producer tests exercise the top-level container exception as a supervisor with a sandbox ID emits it. Top-level container is the single named exception in the validator, linked to bug(ocsf): top-level container is undefined in OCSF 1.8.0 event classes #4915.
  • schemas/ocsf/README.md documents the embedded table, regeneration, and validation with the official OCSF server through its v2 endpoint (/api/v2/validate locally, /api/1.8.0/v2/validate hosted), which reports constraint failures such as at_least_one that the older /api/validate misses. The unused v1.7.0 schemas are removed, so only 1.8.0 is vendored and embedded.
  • embedded_schemas_match_vendored_files normalizes line endings, so CRLF checkouts on Windows pass, and skips when the source checkout isn't present, as in a relocated nextest archive.
  • Docs: ocsf-json-export.mdx describes the per-class fields (SSH dst_endpoint, Process actor.process, connection_info.direction_id) and notes that top-level container is the one attribute OCSF 1.8.0 doesn't define.

User-visible: HTTP events drop is_src_dst_assignment_known; SSH events gain dst_endpoint; connection_info gains direction_id; Process Activity events gain actor.process (the openshell-sandbox launcher).

Deferred: top-level container is undefined in every OCSF version but identifies the affected sandbox on gateway events (#4915). The SIGTERM-failure Process Activity event still uses the Process::new("unknown", 0) fallback.

Testing

Rebased on main at fdce6ef26, which includes #4677 (device.os.type_id), the gateway authentication events from #4728, #4761 and #4762, the per-sandbox supervisor state from #4701, the Windows build fix from #4862, and the agent OTLP trace relay from #4276.

  • Real run on Podman (OPENSHELL_E2E_PODMAN_TEST=ocsf_jsonl_conformance mise run e2e:podman, gateway and supervisor images built from this branch): supervisor_jsonl_conforms_to_the_native_schema passes. It runs an L7 policy against a local test server, requires Network, HTTP and SSH Activity records, and validates every supervisor JSONL record against 1.8.0. An earlier version of the test, with raw sockets only, produced no HTTP Activity at all; requiring the classes caught that. Before the SSH fix it failed with "SSH Activity" 1.8.0 "SSH connection accepted on supervisor Unix socket": Missing at_least_one field from [dst_endpoint, src_endpoint].
  • cargo test for openshell-ocsf (with test-support), openshell-server, openshell-supervisor, openshell-supervisor-network, openshell-supervisor-process, openshell-sandbox, openshell-otel: all pass (4783 tests across the seven crates). The embedded-schema drift test also passes against a CRLF copy of embedded.rs.
  • cargo clippy --workspace --all-targets -- -D warnings, cargo fmt --all --check, and no clippy findings in the new e2e file
  • Unit tests added/updated

ssh::exec_input_tests::eof_preserves_queued_input_until_the_child_reads has a 5 s deadline and hit it on this machine on both main and this branch in one run; it passed in the final run.

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable)

@copy-pr-bot

copy-pr-bot Bot commented Oct 7, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@zanetworker
zanetworker marked this pull request as ready for review October 7, 2026 11:40
@zanetworker
zanetworker force-pushed the fix/4283-ocsf-18-conformance/zanetworker branch from ddf7841 to 56390e5 Compare October 7, 2026 13:09
@zanetworker zanetworker changed the title fix(ocsf): emit schema-conformant OCSF 1.8 events fix(ocsf): emit schema-conformant OCSF events and honest downgrades Oct 7, 2026
@zanetworker
zanetworker force-pushed the fix/4283-ocsf-18-conformance/zanetworker branch from 56390e5 to 78621e8 Compare October 7, 2026 13:58
@johntmyers johntmyers added gator:in-review Gator is reviewing or awaiting PR review feedback test:e2e Requires end-to-end coverage test:windows Run native Windows x64 and ARM64 lint/tests on PR mirrors labels Oct 7, 2026
@johntmyers

Copy link
Copy Markdown
Collaborator

/ok to test 78621e8

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown

Label test:e2e applied for 78621e8. Open the existing run and click Re-run all jobs to execute with the label set. The run will execute the standard E2E suite after building the required gateway, sandbox, and supervisor images once. The matching required CI gate status on this PR will flip green automatically once the run finishes.

@johntmyers johntmyers left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

PR Review Status

The independent review found no blocking defects in the OCSF conformance and schema downgrade changes. @zanetworker, I checked your note about top-level container: it is present in the base, remains an acknowledged design question, and the downgrade preserves its data under unmapped.

Current-head testing has started with test:e2e and test:windows. Trivy and Helm are green; Branch Checks, E2E, and both Windows lanes are pending. The E2E Label Help bot requests Re-run all jobs, but the identified run still has an active first attempt.

Action required: rerun Branch E2E Checks after the active attempt finishes. Gator has operator authorization and the narrow sandbox permission to perform that rerun in a subsequent cycle.

Blocking findings: None.

Carried findings: None.

Gator metadata
  • Validation: Focused OCSF correctness fixes for linked issues #4283 and #4284; operator requested review and relevant tests.
  • Docs: Fern documentation updates describe OS identity, mixed-version JSONL, downgrade preservation, gateway configuration, and the kept-native metric.
  • Checks: DCO, OpenShell / Helm Lint, and OpenShell / Trivy Changes pass; Branch Checks and both Windows MSVC PR lint and test lanes are pending on the reviewed head.
  • E2E: test:e2e applied; current-head mirror verified; Branch E2E Checks run 37657018590 attempt 1 is active. Bot-required rerun remains outstanding. Relevant E2E coverage includes the new Podman JSONL conformance test.
  • Head SHA: 78621e8bb1fb9af4c91f9cf8dd5c3ebffc62b963
  • Base SHA: 3fc93e28273fe304db634e6d5e9a52d57200389e
  • Merge base SHA: 834b79a8c2351370e7c6c363423f5be18d440256
  • Patch ID: 33dfd9e90bc20c4d4b8493efc716d9a8d3c32edb
  • Gator payload: 11
  • Review mode: initial
  • Previous reviewed SHA: none
  • Review budget exhausted: no
  • Maintainer decision required: no
  • Next state: gator:blocked
  • Blocked reason: test_dispatch_required

@johntmyers johntmyers added gator:blocked Gator is blocked by process or repository gates and removed gator:in-review Gator is reviewing or awaiting PR review feedback labels Oct 7, 2026
@johntmyers johntmyers added gator:in-review Gator is reviewing or awaiting PR review feedback and removed gator:blocked Gator is blocked by process or repository gates labels Oct 7, 2026
@johntmyers johntmyers added gator:blocked Gator is blocked by process or repository gates gator:in-review Gator is reviewing or awaiting PR review feedback and removed gator:in-review Gator is reviewing or awaiting PR review feedback gator:blocked Gator is blocked by process or repository gates labels Oct 9, 2026
@johntmyers

Copy link
Copy Markdown
Collaborator

gator-agent

Author Follow-Up Nudge

This PR has been in gator:in-review for more than 48 business hours and required CI still needs attention.

@zanetworker, please make the vendored OCSF schemas available to the relocated Podman E2E test and push an update. The rootless Podman run fails in supervisor_jsonl_conforms_to_the_version_each_record_claims because the loader uses the build machine's CARGO_MANIFEST_DIR and cannot find schemas/ocsf/v1.1.0/classes/base_event.json on the remote test machine. Verify that the archived test can load its schemas and validate the JSONL records there.

@NVIDIA/openshell-maintainers, both Windows lanes also need a gate resolution: Clippy reports unnecessary_wraps and needless_pass_by_ref_mut in crates/openshell-cli/src/ssh.rs at lines 369, 376, and 423. That file is unchanged by this PR.

Gator metadata
  • Head SHA: 78621e8bb1fb9af4c91f9cf8dd5c3ebffc62b963
  • Gator payload: 11
  • Next state: gator:in-review
  • Nudge scope: Existing CI follow-up; no new code-review disposition.

@zanetworker

Copy link
Copy Markdown
Contributor Author

Fixed in aae174742: the vendored schemas are now compiled into the test binary (crates/openshell-ocsf/src/validation/embedded.rs, generated with a drift test), so archived binaries need no files from the build machine. Verified by running the ocsf_jsonl_conformance nextest archive from an unrelated directory with --workspace-remap and the repo's schemas/ hidden, against a Podman gateway: 1 passed. The Windows Clippy findings are in crates/openshell-cli/src/ssh.rs, which this PR doesn't change.

@johntmyers could you take a look, and /ok to test aae17474233af3ddd272f43557a01a7cf0c7f677 when you get a chance?

@zanetworker
zanetworker force-pushed the fix/4283-ocsf-18-conformance/zanetworker branch from 78621e8 to aae1747 Compare October 10, 2026 19:52
@johntmyers

Copy link
Copy Markdown
Collaborator

/ok to test aae1747

@zanetworker
zanetworker force-pushed the fix/4283-ocsf-18-conformance/zanetworker branch from a79eaab to 8dbc022 Compare October 21, 2026 19:00
@zanetworker

Copy link
Copy Markdown
Contributor Author

Thanks @houman44, this is a useful before-picture, and it matches what the PR targets: container (#4283, unchanged here) and the SSH Activity endpoint gap.

You're right about the recipe. The v1 endpoint misses at_least_one: for an SSH record without endpoints, /api/1.8.0/validate reports only container, while /api/1.8.0/v2/validate also reports the constraint failure. I've switched the README to the versioned v2 endpoint and to checking error_count (unversioned /api/v2/validate validates against 1.9.0, so the version is pinned).

A rerun on this branch would help, especially for HTTP and Process Activity, which your capture didn't produce. The e2e gets HTTP Activity by running an L7 policy against a local server and sending a request the policy denies; plain TCP to a denied host only produces Network Activity.

@krishicks

Copy link
Copy Markdown
Collaborator

/ok to test 8dbc022

@krishicks

Copy link
Copy Markdown
Collaborator

A few remaining items found by the bot on re-review:

  1. Duplicate tests that use the deleted field (crates/openshell-ocsf/src/validation/schema.rs:279 and :473): rejects_attributes_the_class_does_not_define and known_fields_rejects_attributes_outside_the_schema both inject is_src_dst_assignment_known into a base event and expect a rejection. Now that both validators share one walker, these run the same code. Please keep one, and use a neutral name such as "not_an_ocsf_attribute" so the test doesn't refer to a field we removed.
  2. Docs describe a field we don't emit (docs/observability/ocsf-json-export.mdx:155): "HTTP Activity records omit is_src_dst_assignment_known…" reads like a changelog entry; it tells readers about something that isn't in the output. Please drop this bullet. The SSH dst_endpoint, Process actor and connection_info direction bullets are useful and should stay.
  3. Test of str::replace (schema.rs:411): embedded_table_comparison_ignores_crlf_checkouts only tests with_lf_line_endings, which is a single replace("\r\n", "\n"). Please keep the normalization in the drift test but remove this test.
  4. Drift check can skip without anyone noticing (schema.rs:426): when the source checkout is missing, embedded_schemas_match_vendored_files prints a message and passes. That makes sense for relocated nextest archives. But if the CI job that runs this test only uses archived binaries, the drift check never actually runs. Can you confirm it runs from a full checkout in at least one CI job?
  5. Test comment doesn't match what it shows (schema.rs:342): in object_schemas_are_validated_as_objects_not_events, the comment says "The top-level container exemption applies to events only". The device half passes because device defines container, not because of the exemption. Only the user half (from :346) shows what the comment describes. Please either move the comment to the user half or drop the device half.

@zanetworker
zanetworker force-pushed the fix/4283-ocsf-18-conformance/zanetworker branch from 8dbc022 to 58dd3e0 Compare October 22, 2026 12:40
@zanetworker

zanetworker commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks @krishicks, fixes addressed.

@krishicks

Copy link
Copy Markdown
Collaborator

/ok to test 58dd3e0

@krishicks

krishicks commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

This failed Windows MSVC lint/tests, but not because of any changes here; the build is broken. Fixed here: #4862

@zanetworker

zanetworker commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor Author

@krishicks any missing bits here? if not can we merge, as there are other issues/PR that are depending on this work being in.

@rhuss rhuss left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second review round

All of the comments below are non-blocking. They come from an additional agent-assisted review round with updated review agents, run on the squashed head 1255f6d01. None of them blocks the merge; pick up whatever you find useful here or in a follow-up.

The failure scenarios in the comments were derived from reading the code and were not executed.

The points from my earlier review are settled. The downgrade and JSONL layer changes are no longer part of this PR, and the enum and type checks moved to #4596. The review agents also found krishicks' Oct 19 and Oct 21 items addressed in the current code.

What went well

  • One recursive walker in validation/schema.rs now backs both validators, and its errors carry the full path (event.device.container), which makes producer test failures easy to locate.
  • The type-state markers in builders/ssh.rs and builders/process.rs turn the required actor and the at_least_one endpoint rule into compile errors at every call site.
  • ssh_server_endpoint and the Arc<Endpoint> threaded through the SSH handlers mean a shared supervisor names the right socket on each sandbox's events, and ssh_events_name_their_own_server_socket_and_conform_to_ocsf checks two sockets against the schema.
  • The e2e only validates once Network, HTTP and SSH records are all present, so it can't pass on an empty capture, and supervisor_records tolerates a line caught mid-write.

Findings

Severity File Description
Minor schemas/ocsf/README.md:30 connection_info.json is a 404 stub but is counted, fetched and embedded
Minor builders/ssh.rs:139 Setters hand-copied from the shared macros; macro docs are stale
Minor builders/process.rs:39 compile_fail doctests can pass for an unrelated compile error
Minor ctx.rs:21 test-support now changes the fallback context at runtime
Minor validation/schema.rs:90 validate_known_fields is a subset of validate_required_fields
Minor validation/schema.rs:193 No negative test for array elements in the walker
Minor ocsf-json-export.mdx:156 Example after the conformance claim lacks type_uid
Minor ocsf_jsonl_conformance.rs:226 exec errors are dropped, so traffic failures are hard to triage

One more observation without an inline comment: class_schema_name in the e2e duplicates the uid that each vendored class schema already carries. A load_class_schema_by_uid helper would remove the table, but it's fine as is.

Comment thread crates/openshell-ocsf/schemas/ocsf/README.md
Comment thread crates/openshell-ocsf/src/builders/ssh.rs
Comment thread crates/openshell-ocsf/src/builders/process.rs
Comment thread crates/openshell-ocsf/src/ctx.rs
Comment thread crates/openshell-ocsf/src/validation/schema.rs
Comment thread crates/openshell-ocsf/src/validation/schema.rs
Comment thread docs/observability/ocsf-json-export.mdx
Comment thread e2e/rust/tests/ocsf_jsonl_conformance.rs
@zanetworker

zanetworker commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor Author

@rhuss can we address the minor comments after merging the PR? bots will always have a better solution for all sorts of things :)

@krishicks

Copy link
Copy Markdown
Collaborator

I have some ideas of how to rework this a bit that can incorporate @rhuss's feedback after this lands. @zanetworker, please rebase and fix the merge conflicts and I'll /ok to test it and get it merged.

Native events failed the official OCSF 1.8.0 validator: HTTP Activity
emitted is_src_dst_assignment_known, sandbox Process Activity events had
no actor, connection_info lacked direction_id, and SSH Activity events
named no endpoint. Schema tests missed these because they checked only
top-level, non-profile attributes.

Add a connection direction enum, require an actor on Process Activity
and an endpoint on SSH Activity at compile time, and name the
supervisor's SSH server with svc_name and its socket path.

Validate against OCSF 1.8.0 only. validate_required_fields and
validate_known_fields share one recursive walker that rejects undefined
attributes at every level and, for events, attributes from undeclared
profiles; objects are validated as objects. Records labelled with
another version fail. Vendor tactic and technique so ATT&CK-mapped
findings validate, drop the unused v1.7.0 schemas, and make the
embedded-schema drift test tolerate CRLF checkouts.

Keep it fixed with a per-builder conformance test and a Podman e2e test
that runs an L7 policy against a local server and validates the
supervisor's real Network, HTTP and SSH Activity JSONL. Top-level
container stays unchanged; NVIDIA#4915 tracks it. The schema
downgrade is unchanged.

Closes NVIDIA#4283

Signed-off-by: Adel Zaalouk <azaalouk@redhat.com>
@rhuss

rhuss commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

@zanetworker yes, sure! as mentioned above:

All of the comments below are non-blocking. They come from an additional agent-assisted review round with updated review agents, run on the squashed head 1255f6d. None of them blocks the merge; pick up whatever you find useful here or in a follow-up.

@zanetworker
zanetworker force-pushed the fix/4283-ocsf-18-conformance/zanetworker branch from 1255f6d to 6786fb5 Compare October 28, 2026 19:32
@krishicks

Copy link
Copy Markdown
Collaborator

/ok to test 6786fb5

@krishicks
krishicks enabled auto-merge October 28, 2026 20:02
@krishicks
krishicks added this pull request to the merge queue Oct 28, 2026
Merged via the queue into NVIDIA:main with commit def7da8 Oct 28, 2026
151 of 155 checks passed
purp pushed a commit to purp/OpenShell that referenced this pull request Oct 29, 2026
* fix(ocsf): keep the fallback event context feature-independent

With test-support, the fallback EventContext carried a fake sandbox
identity, so a binary built with that feature unified in, such as by
cargo build --workspace --all-targets, attributed unbound events to a
sandbox that does not exist. Producer tests no longer need it: the
validator accepts top-level container on its own, and the builder
conformance test uses an explicit sandbox context.

Signed-off-by: Kris Hicks <khicks@nvidia.com>

* test(ocsf): pin builder compile_fail doctests to the missing field

The HTTP, Network, Process, and SSH type-state doctests built their
EventContext from a struct literal, so a new EventContext field would
also make them fail to compile and they would keep passing with the
guard removed. Build the context with ctx::ctx() so the missing
required field is the only compile error.

Signed-off-by: Kris Hicks <khicks@nvidia.com>

* refactor(ocsf): share setter macros with type-state builders

The HTTP, Network, Process, and SSH builders carry a type-state
parameter, so they hand-copied the activity, action, disposition,
severity, status, and message setters that the shared macros generate
for other builders. Let the macros take an optional state parameter and
use them on all four, and route both SSH endpoint setters through one
state transition.

Signed-off-by: Kris Hicks <khicks@nvidia.com>

* docs(ocsf): add type_uid to the Network Activity example

OCSF 1.8.0 requires type_uid, so the example that follows the
conformance claim failed validation as written.

Signed-off-by: Kris Hicks <khicks@nvidia.com>

* test(e2e): report the last exec error in the OCSF conformance test

The test discarded the result of the exec that generates traffic, so
when the exec failed, for example without python3 in the image, the
test retried for a minute and then blamed the JSONL copy. Keep the
latest exec error and include it in the missing-classes failure.

Signed-off-by: Kris Hicks <khicks@nvidia.com>

* test(e2e): read the sandbox id label from podman ps JSON

Signed-off-by: Kris Hicks <khicks@nvidia.com>

---------

Signed-off-by: Kris Hicks <khicks@nvidia.com>
@zanetworker
zanetworker deleted the fix/4283-ocsf-18-conformance/zanetworker branch October 29, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gator:blocked Gator is blocked by process or repository gates test:e2e Requires end-to-end coverage test:windows Run native Windows x64 and ARM64 lint/tests on PR mirrors

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(ocsf): native OCSF 1.8 events fail the official schema validator

6 participants