18ae4a24fffddf94d3f4a15bf6c8e0dedf33114b
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
18ae4a24ff |
fix(sentinelone): map the unified alert as the console describes it (1.17.0)
Corrected and completed against a real Identity alert. Wrong data, not just missing: - device.type read asset.type, which is UNKNOWN on every alert whose surface is not the endpoint. A domain controller the same alert called a Server was ingested as an unknown device. asset.category is read first now, with a numeric device.type_id beside it. - device.ip took the IPv6 address ahead of the IPv4, so every Windows host carried a link-local fe80:: address nothing can pivot on, and which the indicator extractor mined as an IOC all the same. - The flat file summary went straight into observables, so an alert with no file — identity, cloud — carried one observable whose name and value were both the literal string N/A. It now fills file.name and the file.hashes entry its digest length names, and only when the process block did not already describe the file. - finding_info.types is a list, as OCSF and the layout generator expect. - src_endpoint and dst_endpoint are oriented by the detection rather than by habit. SentinelOne fills detectionTime.attacker only when a party distinct from the asset is involved, so its presence decides which way the pair points: attacker as source and asset as destination when it is filled, asset as source and no destination when it is not. Mapping the attacker to dst_endpoint unconditionally announced a domain controller as the source of the enumeration it had in fact suffered. device.* is the invariant across both branches, and the asset's full OS version moved onto it so the flip costs nothing. - The base event had no clock of its own. time, start_time and end_time come from detectedAt, firstSeenAt and lastSeenAt, so a timeline no longer falls back to the moment Riposte polled. 47 entries added, 97 to 144, covering every one of the 120 fields the query fetches. 23 sit under unmapped.* — the Azure image, instance size and resource tags, the console-side IP, the Kubernetes cluster, namespace, node and controller, the agent's connectivity, pending reboot and policy, the ticket and whether a note exists — surfaced for the page instead of being left in the raw payload. Checked by running the platform's own jsonata-go mapping engine over the real Identity alert, over a STAR alert fetched with_details, over an alert naming only the attacker's address, and over a bare alert: no expression errors on any of them, and the process chain, evidences, attacks and observables come out unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a26e71f29e |
fix(sentinelone): make the process tree draw itself (v1.15.0)
The block reads actor.process.file.name and would have rendered the chain, but mapperHasProcessChain — the test that places it when a layout is generated from a mapper — wants actor.process.name alongside the parent's. This mapper declared only the file name, so the block was never placed and the chain went unseen on every SentinelOne incident. process.file.name now feeds both, and certSubject feeds the node's signer badge (the block reads file.company_name for it, and certSubject is the only signing identity a unified alert carries). Verified by running processTree.ts itself over this mapper's output on the live ClickFix alert: placement true, and a two-node chain explorer.exe -> powershell.exe carrying the host, the user, both paths and both sets of hashes when the fetch ran with_details. Without it the chain still draws, with the parent named but unhashed — which is all the list nodes say. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EU1Rk9N6vNtp3q7YvLpjcc |
||
|
|
da2fcbba6c |
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 |
||
|
|
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 |
||
|
|
4cd1cda7cb |
feat(sentinelone): absorb the SentinelOne SDL package (v1.11.0)
The SDL package covered the same unified (UAM) alerts from a second
integration: one console, two packages, two instances, two tokens. Its
commands now live here, so it can be dropped.
get_unified_alerts gains the SDL filters — classification, attack_surface,
os_type, asset_id, external_id, search_text, unassigned_only,
unmitigated_only and a detectedAt window. status, severity, analyst_verdict
and analytics_category take comma-separated lists (stringEqual on one value,
stringIn on several), and every time input also reads a relative window
('24 hours') on top of ISO8601 and epochs. The query is byte-identical to
1.9.0's for the inputs an existing alert rule sends, apart from enum values
now being upper-cased — a lowercase severity used to filter nothing.
Three capabilities the SDL package had and this one did not:
get_unified_alert_details (indicators, their MITRE mapping, observables,
related alerts), add_uam_alert_note and trigger_uam_alert_action (any console
action by ID). update_uam_alert_assignee completes the status/verdict pair,
on the same alertTriggerActions mutation.
analytics_category filters the fetch to one analytic category, e.g. only the
platform-rule alerts (analyticsCategory=Platform Rule, the console's own
filter). The SDL package showed this endpoint takes dotted fieldIds
(asset.osType, assignee.userId) and spells its sort argument differently, so
both spellings of each are tried before giving up: the category degrades to a
filter over the fetched alerts rather than failing the run, and
pagination.categoryFilter / .sortForm report which shape the console accepted.
update_uam_alert_status and _verdict now report a GraphQL error instead of
printing it as a successful update — a playbook that chained on a refused
update used to carry on as if it had gone through.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Rk9N6vNtp3q7YvLpjcc
|
||
|
|
fd4b1b18f7 |
feat(sentinelone): fetch the unified (UAM) alerts (v1.8.0)
The package could already write to a unified alert — update its status and its analyst verdict — but never read one. The only fetch was get_alerts, which reads /cloud-detection/alerts: a different object, covering only the detections the STAR and custom rules raise. Everything SentinelOne surfaces through the console-wide alert model — cloud, identity, third-party — had no way into an incident. get_unified_alerts reads the unifiedalerts GraphQL endpoint, alongside the two mutations already pointed at it. Notes on the shape it returns: - edges/node is flattened to a plain data[]. The envelope carries no id of its own, so a rule dedup'ing on a top-level id would key every alert on nothing; flattened, the ingest block matches the REST commands in this package (results_path: data, dedup_key: id). - Sorted ascending, so a run truncated by the limit leaves the watermark on the oldest unseen alert instead of skipping everything behind the newest. - created_after is normalised from ISO8601, epoch seconds or epoch milliseconds: the scheduler injects the watermark raw, in whichever of the three formats the rule was configured with. The script builds the GraphQL filter itself, so the incremental input stays a plain timestamp. - A GraphQL error arrives as HTTP 200 with an errors array. Left unread, an unusable query would look like an endless run of empty, successful fetches. The mapper covers 74 fields. Its severity branch ends on the raw value rather than a number, so an alert with no severity — or a level SentinelOne adds later — falls through to the rule's default instead of being asserted Informational on data that never said so. A UAM alert also carries fields with no honest OCSF home (assignee, ticketId, the kubernetes controllers); they are fetched and left in the raw payload rather than forced into an unrelated field. The package default ingestion still points at get_threats: the new command is opt-in from the Collect editor, so no existing instance changes what it collects. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |