diff --git a/crates/openshell-ocsf/schemas/ocsf/README.md b/crates/openshell-ocsf/schemas/ocsf/README.md index 401ebc3392..3f1a70ddf5 100644 --- a/crates/openshell-ocsf/schemas/ocsf/README.md +++ b/crates/openshell-ocsf/schemas/ocsf/README.md @@ -22,12 +22,12 @@ for offline test validation. - `authentication` [3002] - `base_event` [0] -### Objects (21) +### Objects (22) - `metadata`, `network_endpoint`, `network_proxy`, `process`, `actor` - `device`, `container`, `product`, `firewall_rule`, `finding_info` - `evidences`, `http_request`, `http_response`, `url`, `attack` -- `remediation`, `connection_info`, `ai_model`, `api`, `user`, `service` +- `remediation`, `connection_info`, `ai_model`, `api`, `user`, `service`, `os` ### Profiles (1) @@ -50,7 +50,7 @@ done for object in metadata network_endpoint network_proxy process actor device \ container product firewall_rule finding_info evidences \ http_request http_response url attack remediation connection_info \ - ai_model api user service; do + ai_model api user service os; do curl -s "https://schema.ocsf.io/api/${VERSION}/objects/${object}" \ | python3 -m json.tool > "objects/${object}.json" done diff --git a/crates/openshell-ocsf/schemas/ocsf/v1.8.0/objects/os.json b/crates/openshell-ocsf/schemas/ocsf/v1.8.0/objects/os.json new file mode 100644 index 0000000000..512ec2294f --- /dev/null +++ b/crates/openshell-ocsf/schemas/ocsf/v1.8.0/objects/os.json @@ -0,0 +1,153 @@ +{ + "attributes": { + "name": { + "type": "string_t", + "description": "The operating system name.", + "requirement": "required", + "caption": "Name", + "type_name": "String" + }, + "type": { + "type": "string_t", + "description": "The type of the operating system.", + "requirement": "optional", + "caption": "Type", + "type_name": "String" + }, + "version": { + "type": "string_t", + "description": "The version of the OS running on the device that originated the event. For example: \"Windows 10\", \"OS X 10.7\", or \"iOS 9\".", + "requirement": "optional", + "caption": "Version", + "type_name": "String" + }, + "build": { + "type": "string_t", + "description": "The operating system build number.", + "requirement": "optional", + "caption": "OS Build", + "type_name": "String" + }, + "country": { + "type": "string_t", + "description": "The operating system country code, as defined by the ISO 3166-1 standard (Alpha-2 code).
Note: The two letter country code should be capitalized. For example: US or CA.
en (English), de (German), or fr (French).",
+ "requirement": "optional",
+ "caption": "Language",
+ "type_name": "String"
+ },
+ "type_id": {
+ "type": "integer_t",
+ "enum": {
+ "0": {
+ "description": "The type is unknown.",
+ "caption": "Unknown"
+ },
+ "99": {
+ "description": "The type is not mapped. See the type attribute, which contains a data source specific value.",
+ "caption": "Other"
+ },
+ "101": {
+ "caption": "Windows Mobile"
+ },
+ "100": {
+ "caption": "Windows"
+ },
+ "200": {
+ "caption": "Linux"
+ },
+ "201": {
+ "caption": "Android"
+ },
+ "300": {
+ "caption": "macOS"
+ },
+ "301": {
+ "caption": "iOS"
+ },
+ "302": {
+ "caption": "iPadOS"
+ },
+ "400": {
+ "caption": "Solaris"
+ },
+ "401": {
+ "caption": "AIX"
+ },
+ "402": {
+ "caption": "HP-UX"
+ }
+ },
+ "description": "The type identifier of the operating system.",
+ "requirement": "required",
+ "sibling": "type",
+ "caption": "Type ID",
+ "type_name": "Integer"
+ },
+ "cpe_name": {
+ "type": "string_t",
+ "description": "The Common Platform Enumeration (CPE) name as described by (NIST) For example: cpe:/a:apple:safari:16.2.",
+ "requirement": "optional",
+ "caption": "The product CPE identifier",
+ "type_name": "String"
+ },
+ "cpu_bits": {
+ "type": "integer_t",
+ "description": "The cpu architecture, the number of bits used for addressing in memory. For example: 32 or 64.",
+ "requirement": "optional",
+ "caption": "CPU Bits",
+ "type_name": "Integer"
+ },
+ "edition": {
+ "type": "string_t",
+ "description": "The operating system edition. For example: Professional.",
+ "requirement": "optional",
+ "caption": "OS Edition",
+ "type_name": "String"
+ },
+ "kernel_release": {
+ "type": "string_t",
+ "description": "The kernel release of the operating system. On Unix-based systems, this is determined from the uname -r command output, for example \"5.15.0-122-generic\".",
+ "requirement": "optional",
+ "caption": "Kernel Release",
+ "type_name": "String"
+ },
+ "sp_name": {
+ "type": "string_t",
+ "description": "The name of the latest Service Pack.",
+ "requirement": "optional",
+ "caption": "OS Service Pack",
+ "type_name": "String"
+ },
+ "sp_ver": {
+ "type": "integer_t",
+ "description": "The version number of the latest Service Pack.",
+ "requirement": "optional",
+ "caption": "OS Service Pack Version",
+ "type_name": "Integer"
+ }
+ },
+ "name": "os",
+ "description": "The Operating System (OS) object describes characteristics of an OS, such as Linux or Windows.",
+ "extends": "object",
+ "references": [
+ {
+ "description": "D3FEND\u2122 Ontology d3f:OperatingSystem",
+ "url": "https://d3fend.mitre.org/dao/artifact/d3f:OperatingSystem/"
+ }
+ ],
+ "caption": "Operating System (OS)"
+}
diff --git a/crates/openshell-ocsf/src/enums/mod.rs b/crates/openshell-ocsf/src/enums/mod.rs
index 3ad8978130..84ef4edd0f 100644
--- a/crates/openshell-ocsf/src/enums/mod.rs
+++ b/crates/openshell-ocsf/src/enums/mod.rs
@@ -11,6 +11,7 @@ mod device_type;
mod disposition;
mod http_method;
mod launch;
+mod os_type;
mod security;
mod severity;
mod status;
@@ -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};
diff --git a/crates/openshell-ocsf/src/enums/os_type.rs b/crates/openshell-ocsf/src/enums/os_type.rs
new file mode 100644
index 0000000000..dbf5c3f0c6
--- /dev/null
+++ b/crates/openshell-ocsf/src/enums/os_type.rs
@@ -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,
+}
diff --git a/crates/openshell-ocsf/src/lib.rs b/crates/openshell-ocsf/src/lib.rs
index 746aea9aac..e8eeedfeee 100644
--- a/crates/openshell-ocsf/src/lib.rs
+++ b/crates/openshell-ocsf/src/lib.rs
@@ -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 ---
diff --git a/crates/openshell-ocsf/src/objects/device.rs b/crates/openshell-ocsf/src/objects/device.rs
index 0984c1b7cf..37fbf93fc6 100644
--- a/crates/openshell-ocsf/src/objects/device.rs
+++ b/crates/openshell-ocsf/src/objects/device.rs
@@ -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)]
@@ -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 {
@@ -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")),
}
}
@@ -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")),
}
}
@@ -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)),
}
}
}
@@ -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();
diff --git a/docs/observability/ocsf-json-export.mdx b/docs/observability/ocsf-json-export.mdx
index 5d91ec187d..e6b89d69ca 100644
--- a/docs/observability/ocsf-json-export.mdx
+++ b/docs/observability/ocsf-json-export.mdx
@@ -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.
@@ -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": {