Commit Graph

25 Commits

Author SHA1 Message Date
f3nris dd2b4629a1 sentinelone: count_agents takes agentVersion__gte and __lt too
Folded into the unreleased 1.16.0 entry, which now covers the three
comparators added beside the agentVersion__gt the command already had.
Sent together they bound a range, so a fleet can be counted on a band
of builds rather than only above one. Empty ones are not sent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:45:22 +00:00
f3nris 741db61543 sentinelone 1.16.0: count_agents takes agentVersion__lte
The other end of the agentVersion__gt range the command already had:
agents on the given version or older, so a target build counts as
still to be upgraded rather than as already done. Left empty the
filter is not sent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:43:00 +00:00
f3nris 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
2026-09-09 12:42:28 +00:00
f3nris 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
2026-09-09 12:38:21 +00:00
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
f3nris fe564cec2c feat(sentinelone): account_ids filter on get_unified_alerts (v1.12.0)
get_threats and get_alerts have taken an accountIds filter since 1.4.0, so a
multi-tenant console could be ingested one account at a time. The unified
alerts could not, which left a tenant-per-rule setup unable to move off the
REST commands.

An alert carries its account twice — the scope it sits in now
(realTime.scope.account.id) and the one it was detected in
(detectionTime.scope.accountId). The first is what an operator means by "this
account", so it is filtered on first, degrading to the second and then to a
filter over the fetched alerts if the console rejects the fieldId, the same
ladder the analytic category uses. pagination.accountFilter reports which of
the three ran. Client-side, an alert matches on either of its two accounts.

The query is unchanged for a rule that does not set the new input, verified
against 1.11.0 on the inputs an existing alert rule sends.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Rk9N6vNtp3q7YvLpjcc
2026-09-09 12:03:53 +00:00
f3nris 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
2026-09-09 11:05:54 +00:00
f3nris df27876a8f feat(sentinelone): declare a risk tier on every command (v1.9.0)
An unset risk is treated as destructive by the agent approval gate. Only
test_connection carried one, so the other 83 commands — get-threats,
get-alerts, count-agents, every lookup in the package — sat behind the same
gate as isolate-agent and run-remote-script. A gate that stops everything
stops being read as a signal.

The package was the marketplace's worst on this: 2 of 86 commands annotated,
where all 180 packages declare some and 57 declare them throughout. The next
weakest is panos at 2 of 10.

39 read, 22 safe_write, 24 destructive. The line drawn for destructive is
impact on the estate, not reversibility — isolate-agent is reversible and is
the canonical destructive example:

- what removes detection or protection: creating an exclusion, disabling or
  editing a STAR rule (an edit can silently narrow a rule to match nothing)
- what adds blocking: the hash blocklist, a firewall rule
- what changes containment in either direction: isolate and reconnect both,
  since undoing containment mid-incident is its own decision
- what reboots or shuts a host down: shutdown, uninstall, enable-agent (it
  takes an optional reboot), move-agent (it changes the applied policy)
- every delete, including removals from the blocklist and the exclusions

Authoring detection content in a customer console is a change to that
console, so the whole STAR rule lifecycle is gated: create as well as update,
disable and delete. Draft status is one click from live and does not earn a
lower tier.

Additive threat intel stays safe_write: creating an IOC or enabling an
existing STAR rule widens what the console sees, which is not the direction
that needs a gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 16:26:05 +02:00
f3nris 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>
2026-09-07 16:16:46 +02:00
f3nris 1d475a6cf5 fix(sentinelone): count-agents can be asked about the whole fleet again
Declared as a boolean, isActive rendered as a checkbox in the toolbox,
and a checkbox has no empty state: the count was forced to one half of
the fleet or the other, with no way to ask for both. As text, an empty
field means the filter is not sent.

true/yes/1 and false/no/0 are read in any case, a real JSON boolean from
a playbook still works, and anything else is refused rather than folded
into false — that would answer a different question than the one asked.
2026-08-14 23:48:40 +02:00
f3nris 93309ac74d feat(sentinelone): count-agents can be scoped to an account
A multi-tenant console answered one number for the whole estate.
accountIds narrows the count to the accounts asked for, the way
get_threats and get_alerts already do at fetch time.
2026-08-14 23:40:13 +02:00
f3nris a5095d3f0f feat(sentinelone): count-agents can tell the active fleet from the dormant one
isActive splits a count that until now lumped both together. It is sent
only when the caller sets it: an unset boolean must not reach the console
as isActive=false and quietly count the dormant agents instead of all of
them.
2026-08-14 23:30:47 +02:00
f3nris cebb3af796 feat(sentinelone): count-agents can ask about versions, state and hardware
The count only knew where an agent was, not what it was running or what
it runs on. Four filters carry the console's own vocabulary through:
agentVersion__gt to count the fleet still below a target build,
networkStatuses to keep only the endpoints in a given connection state,
operationalStatesNin to leave states out of the tally, and machineTypes
to count servers apart from laptops.

The list filters are split and re-joined so a hand-typed
"connected, disconnected" does not reach the API with the space inside
the value.
2026-08-14 22:39:29 +02:00
Guillaume BOURGEOIS 7efc873867 feat(sentinelone): scope threat and alert fetches to given accounts (v1.4.0)
A multi-tenant console returns every account's threats and alerts at once.
get_threats and get_alerts now take an optional account_ids input, passed
through as the accountIds query parameter, so an ingestion can be pinned to
the accounts the SOC actually watches.

The input is optional, so existing instances and running ingestions keep
their current behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 15:56:47 +02:00
Guillaume BOURGEOIS 8fd297169f feat(sentinelone): default ingestion (get_threats fetch + OCSF mapper)
Declare get_threats as the default fetch command (results_path data, dedup id,
incremental created_after) with the bundled get_threats OCSF mapper and a
'SentinelOne Threat' default incident type. get_alerts is also marked
ingestion-ready (incremental created_from). Existing filters (mitigation
status, query, created window, limit) let an operator fetch a subset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 14:10:37 +02:00
Guillaume BOURGEOIS b7f88b1a09 feat(sentinelone): exhaustive OCSF mappers using full catalog (actor/target/registry/network/indicators)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 13:36:34 +02:00
Guillaume BOURGEOIS 1157735be1 feat(sentinelone): re-model OCSF mappers to actor/target semantics
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 13:20:35 +02:00
Guillaume BOURGEOIS 24e8c652ac feat(sentinelone): expand OCSF mappers with hashes, ids, parent process, identity
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 12:58:56 +02:00
Guillaume BOURGEOIS 587d7d1340 feat(sentinelone): bundle OCSF mappers for get_threats and get_alerts
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 11:42:51 +02:00
Guillaume BOURGEOIS 5251441962 feat: add test_connection command to all integrations for the instance Test button
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 00:00:06 +02:00
f3nris d7df536eaf feat(sentinelone): add 13 commands (threat analysis, UAM alerts, remote-script status/results, PowerQuery, tag rule, fetch-file, endpoint logs)
Brings the SentinelOne integration to 83 commands. New: threat-analysis,
threat-download-from-cloud, abort-endpoint-scan, endpoint-fetch-logs, fetch-file,
get-remote-script-task-status, get-remote-script-task-results, get-service-users,
list-installed-singularity-marketplace-applications, update-uam-alert-status,
update-uam-alert-verdict, run-powerquery (Singularity Data Lake), create-tag-rule.
Each command ships a stdlib-only script against API v2.1.
2026-06-22 21:50:56 +02:00
f3nris e3e363c2f9 feat(marketplace): prefix official command names with the technology
Rename every command 'name' in the official integrations to a
<technology>-<command> slug (e.g. sentinelone-isolate-agent,
harfanglab-isolate-endpoint) so they group together when searching the
toolbox. Command IDs (and thus script files and action refs) are unchanged.
Bump both integrations to 1.1.0.
2026-06-22 14:41:22 +02:00
f3nris e5237c0df6 docs(sentinelone): reword changelog to reference the SentinelOne API directly 2026-06-22 12:58:51 +02:00
f3nris 9c04290a34 feat(sentinelone): full command coverage (70 commands, SentinelOne API v2.1)
Expand from 9 to 70 commands covering the official + DEV SentinelOne V2
integrations: agents (actions/info/tags/count), threats & alerts (verdict/status/
notes/mitigate/fetch-file), hash blocklist & exclusions, IOCs, STAR rules,
Deep Visibility, remote scripts, endpoint tags, firewall rules and network
discovery. All script-based (urllib, INTEGRATION_SECRETS/INPUTS contract);
inputs prioritized from the DEV integration.
2026-06-22 12:48:43 +02:00
f3nris a3ac1ee30d feat(sentinelone): curated EDR integration (9 analyst commands, API v2.1)
Endpoints derived from the SentinelOne API v2.1.
Script-based (urllib, INTEGRATION_SECRETS/INPUTS contract) to express S1's nested
filter bodies. Config: console url + api_token (ApiToken header).

Commands:
- enrich: get_threats, list_agents, get_agent, get_hash_verdict
- respond: isolate_agent (disconnect), reconnect_agent (connect), mitigate_threat
  (kill/quarantine/remediate/rollback), initiate_scan, write_threat_note
2026-06-22 12:31:45 +02:00