Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/openshell-ocsf/schemas/ocsf/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

153 changes: 153 additions & 0 deletions crates/openshell-ocsf/schemas/ocsf/v1.8.0/objects/os.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/openshell-ocsf/src/enums/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod device_type;
mod disposition;
mod http_method;
mod launch;
mod os_type;
mod security;
mod severity;
mod status;
Expand All @@ -24,6 +25,7 @@ pub use device_type::DeviceTypeId;
pub use disposition::DispositionId;
pub use http_method::HttpMethod;
pub use launch::LaunchTypeId;
pub use os_type::OsTypeId;
pub use security::{ConfidenceId, RiskLevelId, SecurityLevelId};
pub use severity::SeverityId;
pub use status::{StateId, StatusId};
Expand Down
23 changes: 23 additions & 0 deletions crates/openshell-ocsf/src/enums/os_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! OCSF `os.type_id` enum.

use serde_repr::{Deserialize_repr, Serialize_repr};

/// OCSF OS Type ID.
///
/// Only the values `OpenShell` can produce are modelled; the schema defines a
/// wider set (Android, iOS, Solaris, ...).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
#[repr(u16)]
pub enum OsTypeId {
/// 99 — Other
Other = 99,
/// 100 — Windows
Windows = 100,
/// 200 — Linux
Linux = 200,
/// 300 — macOS
MacOs = 300,
}
4 changes: 2 additions & 2 deletions crates/openshell-ocsf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ pub use events::{
// --- Enum types ---
pub use enums::{
ActionId, ActivityId, AuthProtocolId, AuthTypeId, ConfidenceId, DeviceTypeId, DispositionId,
HttpMethod, LaunchTypeId, OcsfEnum, RiskLevelId, SecurityLevelId, SeverityId, StateId,
StatusId, UserTypeId,
HttpMethod, LaunchTypeId, OcsfEnum, OsTypeId, RiskLevelId, SecurityLevelId, SeverityId,
StateId, StatusId, UserTypeId,
};

// --- Object types ---
Expand Down
74 changes: 64 additions & 10 deletions crates/openshell-ocsf/src/objects/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use serde::{Deserialize, Serialize};

use crate::enums::DeviceTypeId;
use crate::enums::{DeviceTypeId, OsTypeId};

/// OCSF Device object.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -38,9 +38,33 @@ pub struct Device {
pub struct OsInfo {
/// OS name (e.g., "Linux").
pub name: String,

/// OS type id. Required by the OCSF schema.
pub type_id: OsTypeId,

/// Sibling label for `type_id`; the platform name when `type_id` is Other.
#[serde(rename = "type")]
pub type_label: String,
}

impl OsInfo {
/// OS info for a `std::env::consts::OS` value.
#[must_use]
pub fn for_target(os: &str) -> Self {
let type_id = match os {
"linux" => OsTypeId::Linux,
"windows" => OsTypeId::Windows,
"macos" => OsTypeId::MacOs,
_ => OsTypeId::Other,
};
let name = Self::pretty_name(os).to_string();
Self {
type_label: name.clone(),
name,
type_id,
}
}

/// Display name for a `std::env::consts::OS` value.
#[must_use]
pub fn pretty_name(os: &str) -> &str {
Expand All @@ -63,9 +87,7 @@ impl Device {
uid: None,
type_id: DeviceTypeId::Other,
type_label: "Sandbox".to_string(),
os: Some(OsInfo {
name: "Linux".to_string(),
}),
os: Some(OsInfo::for_target("linux")),
}
}

Expand All @@ -78,9 +100,7 @@ impl Device {
uid: None,
type_id: DeviceTypeId::Other,
type_label: "Sandbox".to_string(),
os: Some(OsInfo {
name: "Windows".to_string(),
}),
os: Some(OsInfo::for_target("windows")),
}
}

Expand Down Expand Up @@ -111,9 +131,7 @@ impl Device {
name: Some(name.to_string()),
// Operators assign a unique name per installation; replicas share this UID.
uid: Some(name.to_string()),
os: Some(OsInfo {
name: OsInfo::pretty_name(std::env::consts::OS).to_string(),
}),
os: Some(OsInfo::for_target(std::env::consts::OS)),
}
}
}
Expand Down Expand Up @@ -182,6 +200,42 @@ mod tests {
}
}

#[test]
fn os_type_follows_the_target_os() {
for (target, name, type_id, type_label) in [
("linux", "Linux", 200, "Linux"),
("windows", "Windows", 100, "Windows"),
("macos", "macOS", 300, "macOS"),
("freebsd", "freebsd", 99, "freebsd"),
] {
let json = serde_json::to_value(OsInfo::for_target(target)).unwrap();
assert_eq!(json["name"], name);
assert_eq!(json["type_id"], type_id);
assert_eq!(json["type"], type_label);
}
}

#[test]
fn device_os_matches_vendored_schema() {
use crate::validation::schema::{
load_object_schema, validate_enum_value, validate_known_fields,
validate_required_fields,
};

let schema = load_object_schema("os");
for device in [
Device::linux("sandbox"),
Device::windows("sandbox"),
Device::for_current_os("sandbox"),
Device::gateway("gateway-0", "production"),
] {
let os = serde_json::to_value(device.os).unwrap();
validate_required_fields(&os, &schema);
validate_known_fields(&os, &schema);
validate_enum_value(&os, "type_id", &schema);
}
}

#[test]
fn gateway_device_does_not_inherit_the_sandbox_type() {
let json = serde_json::to_value(Device::gateway("gateway-0", "production")).unwrap();
Expand Down
6 changes: 4 additions & 2 deletions docs/observability/ocsf-json-export.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Windows MXC gateway records use `%PROGRAMDATA%\OpenShell\logs` or `OPENSHELL_OCS

Gateway-produced records use the `OpenShell Gateway` product identity; supervisor records use `OpenShell Sandbox Supervisor`. The producing device and the affected sandbox are separate identities, so a gateway event can still identify an affected sandbox through its container fields.

For gateway-produced records, `device.os.name` identifies the OS running the gateway process: Linux, Windows, or macOS. Linux supervisor records continue to report Linux, regardless of the gateway's OS. A native macOS gateway therefore reports macOS while its Linux sandboxes report Linux; a gateway running inside a Linux container reports Linux, even on a Mac host.
For gateway-produced records, `device.os.name` and `device.os.type_id` identify the OS running the gateway process: Linux (200), Windows (100), or macOS (300). Linux supervisor records continue to report Linux, regardless of the gateway's OS. A native macOS gateway therefore reports macOS while its Linux sandboxes report Linux; a gateway running inside a Linux container reports Linux, even on a Mac host.

`metadata.uid` uniquely identifies an event and stays unchanged when that record is serialized again. Use `container.uid` to associate the event with its sandbox, not `metadata.uid`. Records without a sandbox association omit the container; unknown images are omitted rather than represented by an empty image name.

Expand Down Expand Up @@ -214,7 +214,9 @@ And a denied connection, produced when `curl https://github.com` runs in a sandb
"type_id": 99,
"type": "Sandbox",
"os": {
"name": "Linux"
"name": "Linux",
"type_id": 200,
"type": "Linux"
}
},
"container": {
Expand Down
Loading