name: "SentinelOne Unified Alerts → OCSF" description: "Map of a SentinelOne unified (UAM) alert node (data[]) to OCSF. The console-wide alert model, distinct from the cloud-detection alert of get_alerts: one alert per detection across endpoint, cloud, identity and third-party surfaces. The detected process is the actor (actor.*); the file it ran is the target (file.*)." field_mappings: title: "name" description: "description" source: "detectionSource.product" # The last branch yields severity itself, not a number: an alert with no # severity (or one SentinelOne adds later) then falls through to the rule's # default rather than being asserted Informational on data that never said so. severity: "severity = 'CRITICAL' ? 5 : (severity = 'HIGH' ? 4 : (severity = 'MEDIUM' ? 3 : (severity = 'LOW' ? 2 : (severity = 'INFO' ? 1 : severity))))" # source_path is JSONata over ONE alert node (alert rule results_path = data). # Paths absent from a given alert are skipped at ingestion, so extra entries are safe. # A UAM alert carries a few fields with no honest OCSF home — ticketId, # noteExists, asset.policy, asset.connectivityToConsole, kubernetes controllers. # They are fetched and stay in the raw payload for playbooks rather than being # forced into an unrelated field. ocsf: # ── Alert identity ──────────────────────────────────────────────── - { source_path: "id", ocsf_field: "finding_info.uid" } - { source_path: "name", ocsf_field: "finding_info.title" } - { source_path: "description", ocsf_field: "finding_info.desc" } - { source_path: "externalId", ocsf_field: "finding_info.uid_alt" } - { source_path: "classification", ocsf_field: "finding_info.types" } - { source_path: "createdAt", ocsf_field: "finding_info.created_time" } - { source_path: "updatedAt", ocsf_field: "finding_info.modified_time" } - { source_path: "firstSeenAt", ocsf_field: "finding_info.first_seen_time" } - { source_path: "lastSeenAt", ocsf_field: "finding_info.last_seen_time" } - { source_path: "detectedAt", ocsf_field: "metadata.original_time" } - { source_path: "storylineId", ocsf_field: "metadata.correlation_uid" } - { source_path: "attackSurfaces", ocsf_field: "metadata.labels" } - { source_path: "dataSources", ocsf_field: "metadata.log_source" } # ── Triage state ────────────────────────────────────────────────── - { source_path: "severity", ocsf_field: "severity" } - { source_path: "status", ocsf_field: "status" } - { source_path: "analystVerdict", ocsf_field: "status_detail" } - { source_path: "analystVerdict", ocsf_field: "disposition" } - { source_path: "classification", ocsf_field: "activity_name" } - { source_path: "result", ocsf_field: "action" } - { source_path: "confidenceLevel", ocsf_field: "confidence" } # ── Detecting analytic / rule ───────────────────────────────────── - { source_path: "analytics.uid", ocsf_field: "finding_info.analytic.uid" } - { source_path: "analytics.name", ocsf_field: "finding_info.analytic.name" } - { source_path: "analytics.type", ocsf_field: "finding_info.analytic.type" } - { source_path: "analytics.category", ocsf_field: "finding_info.analytic.category" } - { source_path: "analytics.name", ocsf_field: "rule.name" } - { source_path: "analytics.uid", ocsf_field: "rule.uid" } - { source_path: "analytics.typeValue", ocsf_field: "rule.type" } # ── Detecting product ───────────────────────────────────────────── - { source_path: "detectionSource.product", ocsf_field: "metadata.product.name" } - { source_path: "detectionSource.vendor", ocsf_field: "metadata.product.vendor_name" } - { source_path: "detectionSource.engine", ocsf_field: "metadata.product.feature.name" } - { source_path: "asset.agentVersion", ocsf_field: "metadata.product.version" } # ── Asset (current state) ───────────────────────────────────────── - { source_path: "asset.name", ocsf_field: "src_endpoint.hostname" } - { source_path: "asset.name", ocsf_field: "device.hostname" } - { source_path: "asset.osType", ocsf_field: "device.os.type" } - { source_path: "asset.agentVersion", ocsf_field: "device.agent.version" } - { source_path: "asset.agentUuid", ocsf_field: "device.uid" } - { source_path: "asset.id", ocsf_field: "device.uid_alt" } - { source_path: "asset.type", ocsf_field: "device.type" } - { source_path: "asset.assetTypeClassifier", ocsf_field: "src_endpoint.type" } - { source_path: "asset.osType", ocsf_field: "src_endpoint.os.type" } - { source_path: "asset.osVersion", ocsf_field: "src_endpoint.os.version" } - { source_path: "asset.lastLoggedInUser", ocsf_field: "device.owner.name" } # ── Asset (state at detection) ──────────────────────────────────── - { source_path: "detectionTime.asset.ipV4", ocsf_field: "src_endpoint.ip" } - { source_path: "detectionTime.asset.ipV6", ocsf_field: "device.ip" } - { source_path: "detectionTime.asset.domain", ocsf_field: "src_endpoint.domain" } - { source_path: "detectionTime.asset.osName", ocsf_field: "src_endpoint.os.name" } - { source_path: "detectionTime.asset.osRevision", ocsf_field: "device.os.build" } - { source_path: "detectionTime.asset.subscriptionTime", ocsf_field: "device.first_seen_time" } # ── Attacker (the other party) ──────────────────────────────────── - { source_path: "detectionTime.attacker.host", ocsf_field: "dst_endpoint.hostname" } - { source_path: "detectionTime.attacker.ip", ocsf_field: "dst_endpoint.ip" } # ── Targeted user ───────────────────────────────────────────────── - { source_path: "detectionTime.targetUser.name", ocsf_field: "user.name" } - { source_path: "detectionTime.targetUser.emailAddress", ocsf_field: "user.email_addr" } - { source_path: "detectionTime.targetUser.domain", ocsf_field: "user.domain" } # ── Cloud workload ──────────────────────────────────────────────── - { source_path: "detectionTime.cloud.accountId", ocsf_field: "cloud.account.uid" } - { source_path: "detectionTime.cloud.cloudProvider", ocsf_field: "cloud.provider" } - { source_path: "detectionTime.cloud.location", ocsf_field: "cloud.region" } - { source_path: "detectionTime.cloud.instanceId", ocsf_field: "device.instance_uid" } - { source_path: "detectionTime.cloud.network", ocsf_field: "device.vpc_uid" } # ── SentinelOne tenancy (as scoped at detection) ────────────────── - { source_path: "detectionTime.scope.accountName", ocsf_field: "cloud.org.name" } - { source_path: "detectionTime.scope.accountId", ocsf_field: "cloud.org.uid" } - { source_path: "detectionTime.scope.siteName", ocsf_field: "device.zone" } - { source_path: "realTime.scope.site.id", ocsf_field: "metadata.tenant_uid" } # ── Container ───────────────────────────────────────────────────── - { source_path: "detectionTime.kubernetes.containerName", ocsf_field: "container.name" } - { source_path: "detectionTime.kubernetes.containerId", ocsf_field: "container.uid" } - { source_path: "detectionTime.kubernetes.containerImageName", ocsf_field: "container.image.name" } - { source_path: "detectionTime.kubernetes.podName", ocsf_field: "container.pod_uuid" } # ── Detected process — the actor ────────────────────────────────── # actor.process.name alongside the file name: the process-tree block reads # either, but places itself automatically only on a mapper that declares # actor.process.name together with the parent's, and a chain of two named # processes is exactly what this alert carries. - { source_path: "process.file.name", ocsf_field: "actor.process.name" } - { source_path: "process.cmdLine", ocsf_field: "actor.process.cmd_line" } - { source_path: "process.parentName", ocsf_field: "actor.process.parent_process.name" } - { source_path: "process.file.name", ocsf_field: "actor.process.file.name" } - { source_path: "process.file.path", ocsf_field: "actor.process.file.path" } - { source_path: "process.file.sha256", ocsf_field: "actor.process.file.hashes.sha256" } - { source_path: "process.file.sha1", ocsf_field: "actor.process.file.hashes.sha1" } - { source_path: "process.file.md5", ocsf_field: "actor.process.file.hashes.md5" } # ── The file it ran — the target ────────────────────────────────── - { source_path: "process.file.name", ocsf_field: "file.name" } - { source_path: "process.file.path", ocsf_field: "file.path" } - { source_path: "process.file.sha256", ocsf_field: "file.hashes.sha256" } - { source_path: "process.file.sha1", ocsf_field: "file.hashes.sha1" } - { source_path: "process.file.md5", ocsf_field: "file.hashes.md5" } - { source_path: "process.file.certSubject", ocsf_field: "file.signature.value" } # The signer badge on the process node reads company_name; certSubject is the # only signing identity a unified alert carries. - { source_path: "process.file.certSubject", ocsf_field: "actor.process.file.company_name" } # ── Triage ownership ────────────────────────────────────────────── - { source_path: "assignee.fullName", ocsf_field: "assignee.name" } - { source_path: "assignee.email", ocsf_field: "assignee.email_addr" } # ── The alert's own file summary, hash type unstated ────────────── - { source_path: "fileName", ocsf_field: "observables.name" } - { source_path: "fileHash", ocsf_field: "observables.value" } # ── Only present when the fetch ran with_details ────────────────── # Absent paths are skipped at ingestion, so these stay inert on a plain # list fetch. observables comes last on purpose: with the detail read, the # full list replaces the single file summary set just above. - { source_path: "process.file.size", ocsf_field: "actor.process.file.size" } - { source_path: "process.file.size", ocsf_field: "file.size" } # An indicator's observables carry what the alert body leaves null — on a # real STAR alert process.username is null while src.process.user is set, # and the parent process is named but never hashed outside this list. # [0] takes the first indicator to name each one; they agree in practice. - { source_path: '(indicators.observables[name="src.process.user"].value)[0]', ocsf_field: "actor.user.name" } - { source_path: '(indicators.observables[name="src.parent.process.file.path"].value)[0]', ocsf_field: "actor.process.parent_process.file.path" } - { source_path: '(indicators.observables[name="src.parent.process.file.sha256"].value)[0]', ocsf_field: "actor.process.parent_process.file.hashes.sha256" } - { source_path: '(indicators.observables[name="src.parent.process.file.sha1"].value)[0]', ocsf_field: "actor.process.parent_process.file.hashes.sha1" } - { source_path: '(indicators.observables[name="src.parent.process.file.md5"].value)[0]', ocsf_field: "actor.process.parent_process.file.hashes.md5" } # Last, so the alert's own field wins when it is filled in. - { source_path: "process.username", ocsf_field: "actor.user.name" } # [ ] forces a list of one to stay a list; the $exists guard keeps an empty # or absent value undefined, so a plain list fetch skips these entries # instead of writing an empty array over the file summary above. - { source_path: '$exists(indicators) ? [indicators]', ocsf_field: "evidences" } - { source_path: '$count(indicators.attacks) > 0 ? [indicators.attacks.{"tactic": {"name": tactic.name, "uid": tactic.uid}, "technique": {"name": technique.name, "uid": technique.uid}}]', ocsf_field: "finding_info.attacks" } - { source_path: '$count(relatedAlerts) > 0 ? [relatedAlerts.{"uid": id, "title": name}]', ocsf_field: "finding_info.related_events" } # The alert-level list is empty on a STAR alert while every observable sits # under the indicators, so the two are appended rather than one preferred. - { source_path: '$exists(observables) or $exists(indicators.observables) ? [($append($boolean(observables) ? observables : [], $boolean(indicators.observables) ? indicators.observables : [])).{"name": name, "value": value, "type": type}]', ocsf_field: "observables" }