Files
riposte-marketplace/integrations/sentinelone/mappers/get_unified_alerts.yaml
T
f3nris 31e596d770 feat(sentinelone): with_details on the unified alerts fetch (v1.13.0)
The list nodes carry no indicator, no ATT&CK mapping, no observable and no
related alert — everything that says why the console fired, and the analyst
opens the alert in SentinelOne to find it. with_details reads the per-alert
view for every alert the fetch returns and folds it into the alert, so the
incident is created with that context instead of a title and a severity.

Read in aliased batches of ten, so a default-limit fetch costs ten extra
requests rather than a hundred. Merged under the list node — the list is the
authority on the fields both carry, the detail only fills in — so the richer
list asset/process survive the merge and gain process.username and file.size.
Best effort: an alert the console will not detail keeps its list fields and
the shortfall is reported in pagination.details, rather than failing a fetch
whose alerts are already in hand.

Off by default, and verified query-identical to 1.12.0 for a rule that does
not set it.

The mapper gains evidences, finding_info.attacks, finding_info.related_events
and the full observables list. Each is guarded with $exists and wrapped in [ ]
— checked against the jsonata-go build the scheduler uses: the brackets keep a
single indicator from collapsing into a bare object, and the guard leaves the
entry undefined on a plain list fetch so it cannot write an empty array over
the file summary those alerts do carry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Rk9N6vNtp3q7YvLpjcc
2026-09-09 12:32:49 +00:00

130 lines
12 KiB
YAML

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 ──────────────────────────────────
- { 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" }
# ── 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.username", ocsf_field: "actor.user.name" }
- { source_path: "process.file.size", ocsf_field: "actor.process.file.size" }
- { source_path: "process.file.size", ocsf_field: "file.size" }
# [ ] 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: '$exists(indicators.attacks) ? [indicators.attacks.{"tactic": {"name": tactic.name, "uid": tactic.uid}, "technique": {"name": technique.name, "uid": technique.uid}}]', ocsf_field: "finding_info.attacks" }
- { source_path: '$exists(relatedAlerts) ? [relatedAlerts.{"uid": id, "title": name}]', ocsf_field: "finding_info.related_events" }
- { source_path: '$exists(observables) ? [observables.{"name": name, "value": value, "type": type}]', ocsf_field: "observables" }