fix(sentinelone): map the unified alert as a real one is shaped (v1.14.0)
Checked against a live STAR alert, three of the details mappings were wrong about where the data actually sits. Its alert-level observables list is empty while all twelve observables sit under the indicators, so the mapper saw none. The two lists are appended now rather than reading the alert level alone. The same alert has process.username null while its indicators name the user (src.process.user), and names its parent process without hashing it anywhere else. actor.user.name and the parent process path and hashes are read from the indicator observables, with the alert's own field winning where it is filled in, so nothing regresses on an alert that does populate it. attacks and relatedAlerts are guarded on $count rather than $exists: that alert carries "attacks": null, and null exists in JSONata — the entry would have written an empty array onto every alert without an ATT&CK mapping. Verified by running the whole mapper through the scheduler's own jsonata-go build on that payload, with and without with_details: 95 entries, no empty arrays, and the plain list fetch still falls back to the fileName/fileHash observable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EU1Rk9N6vNtp3q7YvLpjcc
This commit is contained in:
@@ -117,13 +117,25 @@ ocsf:
|
||||
# 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" }
|
||||
# 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: '$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" }
|
||||
- { 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" }
|
||||
|
||||
Reference in New Issue
Block a user