name: "Sekoia XDR Alerts → OCSF" description: "Maps a Sekoia Defend (XDR) alert (/v1/sic/alerts, results_path = items) to OCSF Detection Finding fields. Alerts aggregate the matching events; use search_events for per-event detail. Sekoia's `source` and `target` are the alert's main source and target (an IP address or a host), not the producing system." field_mappings: title: "title" # The alert's own sentence, with the rule placeholder Sekoia leaves in it # ('{rule:}') replaced by the rule's name. `details` is the rule's # documentation — the same for every alert of the rule, MITRE prose and # vendor links included — and goes to finding_info.analytic.desc instead. description: "jsonata: description ? $replace(description, '{rule:' & rule.uuid & '}', rule.name) : title" # urgency is a 0-100 score; bucket it onto the 1-5 incident scale. No parentheses (the engine chains # right-associatively) and the lowest band uses '>= 0 ? 1' so the literal 1 sits in a then-branch — # a bare trailing else is read as a field path, not an integer, and would fall back to the default. severity: "urgency.value >= 80 ? 5 : urgency.value >= 60 ? 4 : urgency.value >= 40 ? 3 : urgency.value >= 20 ? 2 : urgency.value >= 0 ? 1 : 1" # The producing system. Sekoia's own `source` field is the alert's source # address or host — it goes to src_endpoint below. source: "jsonata: 'Sekoia XDR'" # results_path = items; source_path is JSONata over ONE alert object. # Paths absent from a given alert return nothing and are skipped. ocsf: # ── Finding ─────────────────────────────────────────────────────── - { source_path: "uuid", ocsf_field: "finding_info.uid" } - { source_path: "title", ocsf_field: "finding_info.title" } - { source_path: "description ? $replace(description, '{rule:' & rule.uuid & '}', rule.name)", ocsf_field: "finding_info.desc" } # OCSF times are epoch milliseconds: created_at/updated_at come in seconds, # first/last seen as ISO 8601. - { source_path: "created_at * 1000", ocsf_field: "finding_info.created_time" } - { source_path: "updated_at * 1000", ocsf_field: "finding_info.modified_time" } - { source_path: "$toMillis(first_seen_at)", ocsf_field: "finding_info.first_seen_time" } - { source_path: "$toMillis(last_seen_at)", ocsf_field: "finding_info.last_seen_time" } - { source_path: "alert_type.category ? [alert_type.category]", ocsf_field: "finding_info.types" } # ── Detection rule (analytic) ───────────────────────────────────── - { source_path: "rule.name", ocsf_field: "finding_info.analytic.name" } - { source_path: "rule.uuid", ocsf_field: "finding_info.analytic.uid" } - { source_path: "rule.type", ocsf_field: "finding_info.analytic.type" } - { source_path: "detection_type", ocsf_field: "finding_info.analytic.category" } # The rule's documentation (Sekoia's `details`); rule objects carry no # description of their own. - { source_path: "details", ocsf_field: "finding_info.analytic.desc" } # ── Incident state ──────────────────────────────────────────────── - { source_path: "status.name", ocsf_field: "status" } - { source_path: "alert_type.value", ocsf_field: "activity_name" } - { source_path: "similar", ocsf_field: "count" } - { source_path: "urgency.value", ocsf_field: "risk_score" } # ── MITRE ATT&CK — every TTP, not only the first ────────────────── # Sekoia names techniques by their STIX id (attack-pattern--…), not their # ATT&CK id (T1583), and gives no tactic: kill_chain_short_id is Sekoia's # own kill chain, not an ATT&CK tactic, so it is no longer written there. # The array is forced with [...] so a one-TTP alert still yields a list. - { source_path: "ttps ? [ttps[type = 'attack-pattern'].{'technique': {'name': name, 'uid': id}}]", ocsf_field: "attacks" } # ── Source and target ───────────────────────────────────────────── # An address goes to .ip, anything else (a host name) to .hostname. The # target is where the activity landed: dst_endpoint, not src_endpoint. - { source_path: "source and ($contains(source, ':') or $contains(source, /^[0-9]{1,3}(\\.[0-9]{1,3}){3}$/)) ? source", ocsf_field: "src_endpoint.ip" } - { source_path: "source and $not($contains(source, ':') or $contains(source, /^[0-9]{1,3}(\\.[0-9]{1,3}){3}$/)) ? source", ocsf_field: "src_endpoint.hostname" } - { source_path: "target and ($contains(target, ':') or $contains(target, /^[0-9]{1,3}(\\.[0-9]{1,3}){3}$/)) ? target", ocsf_field: "dst_endpoint.ip" } - { source_path: "target and $not($contains(target, ':') or $contains(target, /^[0-9]{1,3}(\\.[0-9]{1,3}){3}$/)) ? target", ocsf_field: "dst_endpoint.hostname" } # ── Owning entity / tenant ──────────────────────────────────────── - { source_path: "entity.name", ocsf_field: "cloud.org.name" } - { source_path: "entity.uuid", ocsf_field: "cloud.org.uid" } - { source_path: "short_id", ocsf_field: "metadata.uid" } - { source_path: "community_uuid", ocsf_field: "metadata.tenant_uid" } - { source_path: "'Sekoia XDR'", ocsf_field: "metadata.product.name" } - { source_path: "'Sekoia'", ocsf_field: "metadata.product.vendor_name" }