feat(crowdstrike): add alerts, host response, IOC, Spotlight, RTR, and case tools (#6746)

* feat(crowdstrike): add alerts, host response, IOC, Spotlight, RTR, and case tools

CrowdStrike Falcon shipped only three read-only Identity Protection sensor
tools. This adds 20 tools across the response and investigation surface SecOps
teams actually automate against.

Alerts (current Alerts API): query, get details, update status/assignment/
tags/comment/visibility. Hosts: contain, lift containment, hide, unhide. Host
groups: query, get details, add/remove hosts. IOC Management: query, get, create,
update, delete. Spotlight: query vulnerabilities, get vulnerability details. Real
Time Response: init session, execute a read-only command, poll command status,
delete session. Case Management: query cases, get case details.

Every endpoint, request field, and response field is taken from CrowdStrike's
published surface (developer.crowdstrike.com API reference, FalconPy endpoint
definitions, and the swagger-generated gofalcon models). Required API scope is
documented in each tool description.

Deliberately not implemented:
- Detects API: decommissioned 2025-09-30, superseded by Alerts.
- CrowdScore Incidents API and behaviors: decommissioned 2026-03-09 and removed
  from the developer center entirely. Case Management is CrowdStrike's
  replacement, so its two documented read operations are implemented instead.
- Case create/update/merge: the swagger types case `status` and
  `severity_info.level` as bare strings with no enum, so a correct write cannot
  be built without guessing.

CrowdStrike answers 200 with a populated `errors` array for partial failures.
Responses now surface those per-item errors, and an empty result set carrying
errors is reported as a failure rather than silently succeeding.

The route's shared Falcon client, response normalizers, and operation dispatch
move into colocated modules so the handler stays readable at 23 operations.

* fix(crowdstrike): correct the RTR read-tier commands and stop dropping the IOC delete filter

Validation pass over all 23 tools against CrowdStrike's swagger-generated SDKs
(gofalcon falcon/models + falcon/client, FalconPy _endpoint/*.py) turned up four
real defects.

The Execute RTR Command dropdown offered `csrutil` and a bare `reg`. Neither is
a read-tier base command: CrowdStrike's own swagger description for
RTR_ExecuteCommand enumerates cat, cd, clear, env, eventlog, filehash, getsid,
help, history, ipconfig, ls, mount, netstat, ps, and "reg query". `csrutil`
appears nowhere in CrowdStrike's published surface, and `reg` alone is not a
base command — the registry variants are "reg query" (read) and "reg set"/"reg
delete" (Active Responder). Both entries are corrected everywhere they were
repeated: dropdown, tool description, and param description.

Delete Indicators showed a Filter input, declared the param, accepted it in the
contract, and implemented CrowdStrike's documented filter-takes-precedence rule
in the route — but the block never mapped the field into the tool call, so the
filter was silently discarded and a filter-only delete failed validation. The
`filter` case is now mapped alongside the ID list.

A 200 carrying only envelope errors was reported as HTTP 200 with success:false,
which reads as a success to anything inspecting status. Failures now adopt the
per-item error code the envelope supplies, falling back to 502.

Alert updates gain a first-class Remove Tags By Prefix field. The spelling was
previously unresolvable, so it was left to the raw action-parameter escape
hatch; CrowdStrike's swagger settles it as `remove_tags_by_prefix` in both the
PatchEntitiesAlertsV2 and PatchEntitiesAlertsV3 descriptions.

Case Management and Spotlight scopes now name the OAuth scope string
(case-templates:read, spotlight-vulnerabilities:read) alongside the label the
Falcon API client UI shows, so either rendering is findable.

* fix(crowdstrike): stop blank sensor filters reaching Falcon and expose the RTR outputs

The falcon.ts/normalize.ts/operations.ts split routed query_sensors through the
shared buildUrl helper, which skips only undefined. An empty filter or sort
string therefore emitted `?filter=` / `?sort=` where the pre-split route omitted
the param, sending Falcon an empty FQL expression. Reject blank values in the
contract instead, matching the newer operations.

Also surface the ten RTR fields the tools already return but the block never
declared, and broaden the block metadata past the original sensor-only surface.

* fix(crowdstrike): fail query operations on error-only envelopes and guard IOC pagination

Falcon can answer 200 with an errors array and no resources. The detail
operations already treated that as a failure, but the five query branches
returned an empty successful result, so a failed alert query read as a valid
no-match to the calling workflow.

Blank FQL rejection now covers the alert, host-group, indicator, vulnerability,
and case contracts too, not just sensors, and Query Indicators rejects offset
combined with after instead of forwarding a pagination pair CrowdStrike refuses.

* fix(crowdstrike): stop blank inputs reaching Falcon and restore the dropped output docs

The executor merges `tools.config.params` over the raw block inputs, so a key the
mapper omitted kept its raw subBlock value — and an untouched subBlock is stored
as `null`, which the route contract rejects. Query Alerts with an empty Filter,
Update Alerts without every optional field, and Delete Indicators without an
audit comment all 400'd before reaching CrowdStrike. Seed every optional key as
`undefined` so omission is authoritative, which also stops a value left over from
another operation riding along.

Shared output consts in `outputs.ts` were silently dropped from the generated
docs: the generator scans tool source and resolves consts only from `types.ts`,
so `errors`, `affected`, and `pagination` rows vanished from 17 tool pages and
every nested property row with them. Inline the literals.

Against CrowdStrike's own generated SDKs and developer portal:
- add csrutil, ifconfig, users, and the eventlog subcommand forms to the
  read-tier RTR base commands, matching PSFalcon's ValidateSet
- add detection_suppress/detection_unsuppress and cap host actions at the
  documented 100 ids
- cap the IOC search limit at the documented 500, not 2000
- correct the Cases scope to "Cases: Read"; case-templates guards a different
  collection
- type the IOC payload so a blank string cannot clear a stored field on PATCH
- send `MsaRangeSpec` bounds capitalized, as the spec serializes them
- fail the sensor and RTR-session-close paths on a 200 whose envelope carries
  only errors, and surface partial sensor errors
- give Delete Indicators its own filter so a stale alert query cannot widen it
- drop the pre-selected network-isolating host action

* fix(crowdstrike): correct the RTR command tier, IOC update contract, and US-3 region

Independent re-validation against gofalcon's swagger-generated models and
CrowdStrike's developer center turned up several wire-level errors.

- Real Time Response advertised "eventlog backup"/"export"/"list", "reg query",
  ifconfig, and users as base commands. base_command names a command family and
  subcommands belong in command_string; the eventlog write variants are Active
  Responder commands that would fail on scope under this Read-scoped tool, and
  ifconfig/users appear in neither authoritative list. The block now offers the
  16 documented read-tier families and the contract enforces them.
- Indicator updates accepted an entry with no id, which cannot name a record,
  and accepted type/value, which the update model does not expose. Creates
  accepted an entry with no type, value, or applied_globally -- the one property
  CrowdStrike marks required, and the one that decides fleet-wide scope.
- CrowdStrike documents that PATCH overwrites any omitted field with a blank
  value. The contract can only catch blanks, so the update tool now tells the
  caller to read the indicator first and resend its full field set.
- Added the US-3 commercial region, which was missing from every cloud list.
- Aggregate queries silently dropped percents and filters_spec.
- Deleted the response-envelope body unwrap: no endpoint this integration calls
  returns that shape, and getFalconErrorMessage never honored it anyway.
- Softened the Detects and Incidents claims to what the sources actually state.

A tool description longer than the docs generator's 600-character id-search
window silently publishes as an empty string; three descriptions had crossed it.
Shortened them and added a test that fails before the catalog goes blank.

* docs(crowdstrike): name the endpoint and Identity Protection scope on the sensor tools

The three sensor tools were the only ones in the family that named neither their
endpoint nor their OAuth2 scope, and none of them said these are the domain
controllers Falcon Identity Protection monitors rather than Falcon endpoint
sensors -- a distinction an agent choosing between them and the Hosts tools has
no other way to make. Identity Protection Entities: Read is also a separate
product entitlement from Hosts and Alerts.

* refactor(crowdstrike): say which ID caps are CrowdStrike's and which are Sim's

Every bulk-ID limit claimed CrowdStrike as its source, but only the sensor
(5000), host action (100), indicator batch (200), and Spotlight (400) caps are
published. The alert, host group, indicator, and case caps are Sim's own bound
on request size, and the validation message now says so instead of attributing
a limit CrowdStrike does not document.
This commit is contained in:
Waleed
2026-08-15 18:19:20 -07:00
committed by GitHub
parent 76318e42df
commit 4bc89c9256
40 changed files with 8981 additions and 354 deletions
@@ -1,6 +1,6 @@
---
title: CrowdStrike
description: Query CrowdStrike Identity Protection sensors and documented aggregates
description: Investigate and respond to CrowdStrike Falcon alerts, hosts, IOCs, and vulnerabilities
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
@@ -11,29 +11,423 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
/>
{/* MANUAL-CONTENT-START:intro */}
[CrowdStrike](https://www.crowdstrike.com/) is a cybersecurity platform providing endpoint protection, threat intelligence, and identity security through its Falcon suite. This integration connects to the Falcon Identity Protection API to query sensor data.
[CrowdStrike](https://www.crowdstrike.com/) is a cybersecurity platform providing endpoint protection, threat intelligence, and identity security through its Falcon suite. This integration authenticates with a Falcon API client ID and secret against a chosen cloud region and covers the Alerts, Hosts, Host Groups, IOC Management, Spotlight, Real Time Response, Case Management, and Identity Protection APIs.
With this integration, you can:
- **Search sensors**: Query CrowdStrike identity protection sensors by hostname, IP, or related fields using Falcon Query Language filters
- **Fetch sensor details**: Retrieve documented sensor details, including protection status, policy assignments, and protocol configuration, for one or more device IDs
- **Run sensor aggregates**: Execute documented JSON aggregate queries to summarize sensor data into buckets and metrics
- **Triage alerts**: Search Falcon alerts with Falcon Query Language, pull full alert records by composite ID, and update status, assignment, tags, comments, and console visibility
- **Respond on hosts**: Contain or lift containment on a host, and hide or unhide it from the Falcon console
- **Manage host groups**: Search groups, read group details, and add or remove hosts from static groups
- **Manage custom indicators**: Search, read, create, update, and delete indicators of compromise
- **Review vulnerabilities**: Query Spotlight vulnerabilities and read CVE, host, application, and remediation details
- **Run read-only Real Time Response**: Open a session, run a documented read-only command, poll for output, and close the session
- **Read cases**: Search Case Management cases and read case details
- **Query identity sensors**: Search Identity Protection sensors, fetch sensor details, and run aggregate queries
In Sim, the CrowdStrike integration allows your agents to search identity protection sensors, look up detailed sensor records by device ID, and run aggregate queries against sensor data—all authenticated with a Falcon API client ID and secret against a specified cloud region. This lets agents surface device protection status, policy coverage, and protocol configuration (Kerberos, LDAP, NTLM, RDP, SMB) as part of security monitoring and reporting workflows.
Each operation maps to a specific Falcon API scope — for example `Alerts: Read` and `Alerts: Write`, `Hosts: Write` for containment, `Host groups: Read`/`Write`, `IOC Management: Read`/`Write`, `Vulnerabilities: Read`, `Real time response: Read`, and `Cases: Read`. Containment and indicator deletion change live protection behavior, so scope the credential to only the operations your workflows need.
Note that CrowdStrike decommissioned the legacy Detects API (September 30, 2025) and the CrowdScore Incidents API (March 9, 2026). This integration uses the current Alerts API and Case Management API in their place.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate CrowdStrike Identity Protection into workflows to search sensors, fetch documented sensor details by device ID, and run documented sensor aggregate queries.
Integrate CrowdStrike Falcon into workflows to triage alerts, contain hosts, manage host groups and custom indicators of compromise, review Spotlight vulnerabilities, run read-only Real Time Response commands, read Case Management cases, and query Identity Protection sensors.
## Actions
### CrowdStrike Create Indicators
Create custom CrowdStrike Falcon indicators of compromise (POST /iocs/entities/indicators/v1). Each indicator can allow, detect, or block activity across the fleet, so a wrong value can suppress detections or break legitimate software. Requires the "IOC Management: Write" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `indicators` | json | Yes | JSON array of indicators to create. Each entry requires type, value, and applied_globally \(boolean\). type is one of sha256, md5, domain, ipv4, ipv6; action is one of no_action, allow, prevent_no_ui, prevent, detect; severity is one of informational, low, medium, high, critical; platforms entries are windows, mac, or linux. Other documented fields: host_groups \(array\), description, source, tags \(array\), expiration \(ISO 8601\), mobile_action, metadata \(\{ filename \}\). Either applied_globally must be true or host_groups must be supplied. Tenants can extend these value sets, so treat them as the documented defaults rather than a closed list. |
| `comment` | string | No | Audit comment explaining why these indicators were created |
| `retrodetects` | boolean | No | Whether to generate retroactive detections for the new indicators |
| `ignoreWarnings` | boolean | No | Whether to create the indicators even when CrowdStrike returns warnings |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `indicators` | array | Created CrowdStrike indicator records |
| ↳ `id` | string | Indicator identifier |
| ↳ `type` | string | Indicator type |
| ↳ `value` | string | Indicator value |
| ↳ `action` | string | Action taken when the indicator matches |
| ↳ `mobileAction` | string | Action taken on mobile platforms when the indicator matches |
| ↳ `severity` | string | Indicator severity |
| ↳ `description` | string | Indicator description |
| ↳ `source` | string | Indicator source |
| ↳ `appliedGlobally` | boolean | Whether the indicator applies to all hosts |
| ↳ `platforms` | array | Platforms the indicator applies to |
| ↳ `hostGroups` | array | Host group IDs the indicator is scoped to |
| ↳ `tags` | array | Tags applied to the indicator |
| ↳ `expiration` | string | Indicator expiration timestamp |
| ↳ `expired` | boolean | Whether the indicator has expired |
| ↳ `deleted` | boolean | Whether the indicator is deleted |
| ↳ `fromParent` | boolean | Whether the indicator was inherited from a parent CID |
| ↳ `parentCidName` | string | Parent CID name |
| ↳ `createdBy` | string | User who created the indicator |
| ↳ `createdOn` | string | Indicator creation timestamp |
| ↳ `modifiedBy` | string | User who last modified the indicator |
| ↳ `modifiedOn` | string | Indicator modification timestamp |
| ↳ `metadata` | json | File metadata CrowdStrike resolved for the indicator |
| ↳ `avHits` | number | Antivirus hit count |
| ↳ `companyName` | string | Company name |
| ↳ `fileDescription` | string | File description |
| ↳ `fileVersion` | string | File version |
| ↳ `filename` | string | File name |
| ↳ `originalFilename` | string | Original file name |
| ↳ `productName` | string | Product name |
| ↳ `productVersion` | string | Product version |
| ↳ `signed` | boolean | Whether the file is signed |
| `count` | number | Number of indicators created |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Delete Indicators
Permanently delete custom CrowdStrike Falcon indicators of compromise (DELETE /iocs/entities/indicators/v1). Cannot be undone; deleting a blocking indicator removes that protection from every host, and a broad filter can delete far more than intended. Supply an ID list or a filter, never both -- CrowdStrike lets a filter silently override the IDs, so this tool rejects that instead. Requires the "IOC Management: Write" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `indicatorIds` | json | No | JSON array of CrowdStrike IOC IDs to delete. Cannot be combined with a filter. |
| `filter` | string | No | Falcon Query Language filter selecting indicators to delete in bulk. Cannot be combined with an ID list. |
| `comment` | string | No | Audit comment explaining why these indicators were deleted |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deletedIds` | array | IOC IDs CrowdStrike deleted |
| `count` | number | Number of indicators deleted |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Delete RTR Session
Close an open CrowdStrike Falcon Real Time Response session (DELETE /real-time-response/entities/sessions/v1). Requires the "Real time response: Read" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `sessionId` | string | Yes | RTR session ID to close |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `sessionId` | string | RTR session ID that was closed |
| `deleted` | boolean | Whether CrowdStrike accepted the session deletion |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Execute RTR Command
Run a read-only Real Time Response command in an open CrowdStrike Falcon session (POST /real-time-response/entities/command/v1). baseCommand names the family only (cat, cd, clear, csrutil, env, eventlog, filehash, getsid, help, history, ipconfig, ls, mount, netstat, ps, reg); subcommands go in commandString. Host-modifying commands need the Active Responder or Admin endpoints. Requires the "Real time response: Read" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `sessionId` | string | Yes | RTR session ID returned by Init RTR Session |
| `baseCommand` | string | Yes | Read-only RTR base command family, one of: cat, cd, clear, csrutil, env, eventlog, filehash, getsid, help, history, ipconfig, ls, mount, netstat, ps, reg. Subcommands belong in commandString, not here. |
| `commandString` | string | Yes | Full command line to run, such as "ls C:\\Windows" or "reg query HKLM\\Software" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `cloudRequestId` | string | Cloud request ID to poll for command output |
| `sessionId` | string | RTR session the command ran in |
| `queuedCommandOffline` | boolean | Whether the command was queued for an offline host |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Get Alert Details
Get full CrowdStrike Falcon alert records for one or more composite alert IDs (POST /alerts/entities/alerts/v2). Requires the "Alerts: Read" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `compositeIds` | json | Yes | JSON array of CrowdStrike composite alert IDs |
| `includeHidden` | boolean | No | Include previously hidden alerts \(CrowdStrike defaults this to true\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `alerts` | array | CrowdStrike alert records |
| ↳ `compositeId` | string | Composite alert ID |
| ↳ `id` | string | Alert ID |
| ↳ `cid` | string | CrowdStrike customer identifier |
| ↳ `aggregateId` | string | Aggregate identifier |
| ↳ `agentId` | string | Agent \(sensor\) identifier |
| ↳ `deviceId` | string | Device identifier from the alert device |
| ↳ `hostname` | string | Hostname from the alert device |
| ↳ `name` | string | Alert name |
| ↳ `displayName` | string | Alert display name |
| ↳ `description` | string | Alert description |
| ↳ `type` | string | Alert type |
| ↳ `product` | string | Falcon product that raised the alert |
| ↳ `platform` | string | Platform the alert was raised on |
| ↳ `severity` | number | Numeric severity |
| ↳ `severityName` | string | Severity name |
| ↳ `confidence` | number | Confidence score |
| ↳ `status` | string | Alert status |
| ↳ `assignedToName` | string | Assignee display name |
| ↳ `assignedToUid` | string | Assignee user ID |
| ↳ `assignedToUuid` | string | Assignee user UUID |
| ↳ `tactic` | string | MITRE ATT&CK tactic |
| ↳ `tacticId` | string | MITRE ATT&CK tactic ID |
| ↳ `technique` | string | MITRE ATT&CK technique |
| ↳ `techniqueId` | string | MITRE ATT&CK technique ID |
| ↳ `scenario` | string | Alert scenario |
| ↳ `objective` | string | Adversary objective |
| ↳ `resolution` | string | Alert resolution |
| ↳ `showInUi` | boolean | Whether the alert is shown in Falcon |
| ↳ `tags` | array | Tags applied to the alert |
| ↳ `filename` | string | Triggering file name |
| ↳ `filepath` | string | Triggering file path |
| ↳ `cmdline` | string | Triggering command line |
| ↳ `sha256` | string | SHA256 of the triggering file |
| ↳ `sha1` | string | SHA1 of the triggering file |
| ↳ `md5` | string | MD5 of the triggering file |
| ↳ `userName` | string | User name associated with the alert |
| ↳ `userId` | string | User ID associated with the alert |
| ↳ `patternId` | number | Detection pattern ID |
| ↳ `falconHostLink` | string | Deep link into the Falcon console |
| ↳ `controlGraphId` | string | Control graph identifier |
| ↳ `external` | boolean | Whether the alert is external |
| ↳ `emailSent` | boolean | Whether a notification email was sent |
| ↳ `isAggregated` | boolean | Whether the alert is aggregated |
| ↳ `isFalconPlatformIoa` | boolean | Whether the alert is a Falcon platform IOA |
| ↳ `dataDomains` | array | Data domains the alert belongs to |
| ↳ `iocValues` | array | Indicator values associated with the alert |
| ↳ `linkedCaseIds` | array | Case IDs linked to the alert |
| ↳ `linkedBehavioralDetections` | array | Behavioral detection IDs linked to the alert |
| ↳ `timestamp` | string | Alert timestamp |
| ↳ `createdTimestamp` | string | Alert creation timestamp |
| ↳ `updatedTimestamp` | string | Alert update timestamp |
| ↳ `crawledTimestamp` | string | Alert crawl timestamp |
| ↳ `contextTimestamp` | string | Alert context timestamp |
| `count` | number | Number of alerts returned |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Get Case Details
Get CrowdStrike Falcon Case Management case records for one or more case IDs (POST /cases/entities/cases/v2). Requires the "Cases: Read" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `caseIds` | json | Yes | JSON array of CrowdStrike case IDs |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `cases` | array | CrowdStrike Case Management case records |
| ↳ `id` | string | Case identifier |
| ↳ `cid` | string | CrowdStrike customer identifier |
| ↳ `name` | string | Case name |
| ↳ `description` | string | Case description |
| ↳ `descriptionFormat` | string | Format of the case description |
| ↳ `status` | string | Case status |
| ↳ `severity` | number | Numeric case severity |
| ↳ `severityLevel` | string | Case severity level name |
| ↳ `referenceId` | string | Human-readable case reference ID |
| ↳ `version` | number | Case version for optimistic concurrency |
| ↳ `tags` | array | Tags applied to the case |
| ↳ `assignedTo` | json | Falcon user the case is assigned to |
| ↳ `uuid` | string | Falcon user UUID |
| ↳ `email` | string | Falcon user email |
| ↳ `fullName` | string | Falcon user full name |
| ↳ `createdBy` | json | Falcon user who created the case |
| ↳ `uuid` | string | Falcon user UUID |
| ↳ `email` | string | Falcon user email |
| ↳ `fullName` | string | Falcon user full name |
| ↳ `lastUpdatedBy` | json | Falcon user who last updated the case |
| ↳ `uuid` | string | Falcon user UUID |
| ↳ `email` | string | Falcon user email |
| ↳ `fullName` | string | Falcon user full name |
| ↳ `createdTimestamp` | string | Case creation timestamp |
| ↳ `updatedTimestamp` | string | Case update timestamp |
| ↳ `startTimestamp` | string | Case start timestamp |
| ↳ `endTimestamp` | string | Case end timestamp |
| ↳ `templateId` | string | Case template identifier |
| ↳ `templateName` | string | Case template name |
| ↳ `slaId` | string | SLA identifier applied to the case |
| ↳ `slaName` | string | SLA name applied to the case |
| ↳ `isReadOnly` | boolean | Whether the case is read only |
| `count` | number | Number of cases returned |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Get Host Group Details
Get CrowdStrike Falcon host group records for one or more group IDs (GET /devices/entities/host-groups/v1). Requires the "Host groups: Read" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `hostGroupIds` | json | Yes | JSON array of CrowdStrike host group IDs |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `hostGroups` | array | CrowdStrike host group records |
| ↳ `id` | string | Host group identifier |
| ↳ `name` | string | Host group name |
| ↳ `description` | string | Host group description |
| ↳ `groupType` | string | Group type \(static, dynamic, staticByID\) |
| ↳ `assignmentRule` | string | FQL assignment rule for dynamic groups |
| ↳ `createdBy` | string | User who created the group |
| ↳ `createdTimestamp` | string | Group creation timestamp |
| ↳ `modifiedBy` | string | User who last modified the group |
| ↳ `modifiedTimestamp` | string | Group modification timestamp |
| `count` | number | Number of host groups returned |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Get Indicator Details
Get custom CrowdStrike Falcon indicator of compromise (IOC) records for one or more IOC IDs (GET /iocs/entities/indicators/v1). Requires the "IOC Management: Read" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `indicatorIds` | json | Yes | JSON array of CrowdStrike IOC IDs |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `indicators` | array | CrowdStrike indicator of compromise records |
| ↳ `id` | string | Indicator identifier |
| ↳ `type` | string | Indicator type |
| ↳ `value` | string | Indicator value |
| ↳ `action` | string | Action taken when the indicator matches |
| ↳ `mobileAction` | string | Action taken on mobile platforms when the indicator matches |
| ↳ `severity` | string | Indicator severity |
| ↳ `description` | string | Indicator description |
| ↳ `source` | string | Indicator source |
| ↳ `appliedGlobally` | boolean | Whether the indicator applies to all hosts |
| ↳ `platforms` | array | Platforms the indicator applies to |
| ↳ `hostGroups` | array | Host group IDs the indicator is scoped to |
| ↳ `tags` | array | Tags applied to the indicator |
| ↳ `expiration` | string | Indicator expiration timestamp |
| ↳ `expired` | boolean | Whether the indicator has expired |
| ↳ `deleted` | boolean | Whether the indicator is deleted |
| ↳ `fromParent` | boolean | Whether the indicator was inherited from a parent CID |
| ↳ `parentCidName` | string | Parent CID name |
| ↳ `createdBy` | string | User who created the indicator |
| ↳ `createdOn` | string | Indicator creation timestamp |
| ↳ `modifiedBy` | string | User who last modified the indicator |
| ↳ `modifiedOn` | string | Indicator modification timestamp |
| ↳ `metadata` | json | File metadata CrowdStrike resolved for the indicator |
| ↳ `avHits` | number | Antivirus hit count |
| ↳ `companyName` | string | Company name |
| ↳ `fileDescription` | string | File description |
| ↳ `fileVersion` | string | File version |
| ↳ `filename` | string | File name |
| ↳ `originalFilename` | string | Original file name |
| ↳ `productName` | string | Product name |
| ↳ `productVersion` | string | Product version |
| ↳ `signed` | boolean | Whether the file is signed |
| `count` | number | Number of indicators returned |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Get RTR Command Status
Get the status and output of a Real Time Response command by cloud request ID (GET /real-time-response/entities/command/v1). Long output is chunked across sequences, so increment the sequence ID to read the next chunk. Requires the "Real time response: Read" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `cloudRequestId` | string | Yes | Cloud request ID returned by Execute RTR Command |
| `sequenceId` | number | No | Output chunk to retrieve, starting at 0 |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `complete` | boolean | Whether the command has finished running |
| `stdout` | string | Standard output from the command |
| `stderr` | string | Standard error from the command |
| `baseCommand` | string | Base command that was run |
| `sessionId` | string | RTR session the command ran in |
| `taskId` | string | Task identifier for the command |
| `sequenceId` | number | Output chunk sequence this response covers |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Get Sensor Aggregates
Get documented CrowdStrike Identity Protection sensor aggregates from a JSON aggregate query body
Aggregate CrowdStrike Identity Protection sensors from a JSON aggregate query body (POST /identity-protection/aggregates/devices/GET/v1). These are the domain controllers Falcon Identity Protection monitors, not Falcon endpoint sensors. Requires the "Identity Protection Entities: Read" API scope.
#### Input
@@ -56,7 +450,7 @@ Get documented CrowdStrike Identity Protection sensor aggregates from a JSON agg
| ↳ `label` | json | Bucket label object |
| ↳ `stringFrom` | string | String lower bound |
| ↳ `stringTo` | string | String upper bound |
| ↳ `subAggregates` | json | Nested aggregate results for this bucket |
| ↳ `subAggregates` | array | Nested aggregate results for this bucket |
| ↳ `to` | number | Bucket upper bound |
| ↳ `value` | number | Bucket metric value |
| ↳ `valueAsString` | string | String representation of the bucket value |
@@ -64,10 +458,14 @@ Get documented CrowdStrike Identity Protection sensor aggregates from a JSON agg
| ↳ `name` | string | Aggregate result name |
| ↳ `sumOtherDocCount` | number | Document count not included in the returned buckets |
| `count` | number | Number of aggregate result groups returned |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Get Sensor Details
Get documented CrowdStrike Identity Protection sensor details for one or more device IDs
Get CrowdStrike Identity Protection sensor details for one or more device IDs (POST /identity-protection/entities/devices/GET/v1). These are the domain controllers Falcon Identity Protection monitors, not Falcon endpoint sensors. Requires the "Identity Protection Entities: Read" API scope.
#### Input
@@ -103,14 +501,295 @@ Get documented CrowdStrike Identity Protection sensor details for one or more de
| ↳ `statusCauses` | array | Documented causes behind the current status |
| ↳ `tiEnabled` | string | Threat intelligence enablement status |
| `count` | number | Number of sensors returned |
| `pagination` | json | Pagination metadata when returned by the underlying API |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Get Vulnerability Details
Get CrowdStrike Falcon Spotlight vulnerability records for one or more vulnerability IDs, including CVE, affected host, application, and remediation details (GET /spotlight/entities/vulnerabilities/v2). Requires the spotlight-vulnerabilities:read API scope, shown as "Vulnerabilities: Read" in the Falcon API client UI.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `vulnerabilityIds` | json | Yes | JSON array of Spotlight vulnerability IDs \(maximum 400 per request\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `vulnerabilities` | array | CrowdStrike Spotlight vulnerability records |
| ↳ `id` | string | Vulnerability identifier |
| ↳ `aid` | string | Agent identifier of the affected host |
| ↳ `cid` | string | CrowdStrike customer identifier |
| ↳ `status` | string | Vulnerability status \(open, closed, reopen\) |
| ↳ `confidence` | string | Detection confidence |
| ↳ `vulnerabilityId` | string | Underlying vulnerability ID |
| ↳ `createdTimestamp` | string | Creation timestamp |
| ↳ `updatedTimestamp` | string | Last update timestamp |
| ↳ `closedTimestamp` | string | Closure timestamp |
| ↳ `cve` | json | CVE details for the vulnerability |
| ↳ `id` | string | CVE identifier |
| ↳ `baseScore` | number | CVSS base score |
| ↳ `severity` | string | CVE severity |
| ↳ `exprtRating` | string | CrowdStrike ExPRT rating |
| ↳ `exploitStatus` | number | Exploit status code |
| ↳ `exploitabilityScore` | number | CVSS exploitability score |
| ↳ `impactScore` | number | CVSS impact score |
| ↳ `remediationLevel` | string | CVSS remediation level |
| ↳ `description` | string | CVE description |
| ↳ `publishedDate` | string | CVE publication date |
| ↳ `vector` | string | CVSS vector string |
| ↳ `types` | array | CVE types |
| ↳ `isCisaKev` | boolean | Whether the CVE is in the CISA Known Exploited Vulnerabilities catalog |
| ↳ `cisaDueDate` | string | CISA remediation due date |
| ↳ `app` | json | Affected application |
| ↳ `productNameNormalized` | string | Normalized product name |
| ↳ `productNameVersion` | string | Product name and version |
| ↳ `vendorNormalized` | string | Normalized vendor name |
| ↳ `hostInfo` | json | Affected host details |
| ↳ `hostname` | string | Host name |
| ↳ `localIp` | string | Local IP address |
| ↳ `machineDomain` | string | Machine domain |
| ↳ `osVersion` | string | Operating system version |
| ↳ `platform` | string | Platform name |
| ↳ `productTypeDesc` | string | Product type description |
| ↳ `assetCriticality` | string | Asset criticality |
| ↳ `internetExposure` | string | Internet exposure |
| ↳ `tags` | array | Host tags |
| ↳ `groups` | array | Host group names the host belongs to |
| ↳ `remediationIds` | array | Remediation IDs for the vulnerability |
| ↳ `remediations` | array | Remediation entities for the vulnerability |
| ↳ `id` | string | Remediation identifier |
| ↳ `title` | string | Remediation title |
| ↳ `action` | string | Remediation action |
| ↳ `type` | string | Remediation type |
| ↳ `link` | string | Remediation link |
| ↳ `reference` | string | Remediation reference |
| ↳ `vendorUrl` | string | Vendor advisory URL |
| ↳ `suppressionInfo` | json | Suppression state for the vulnerability |
| ↳ `isSuppressed` | boolean | Whether the finding is suppressed |
| ↳ `reason` | string | Suppression reason |
| `count` | number | Number of vulnerabilities returned |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Init RTR Session
Open a CrowdStrike Falcon Real Time Response session against a host so read-only commands can be run on it (POST /real-time-response/entities/sessions/v1). This connects a live remote shell to the endpoint. Requires the "Real time response: Read" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `deviceId` | string | Yes | CrowdStrike host agent ID \(AID\) to open the session against |
| `queueOffline` | boolean | No | Queue the session so it runs when an offline host comes back online |
| `origin` | string | No | Optional session origin string recorded by CrowdStrike |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `sessionId` | string | RTR session ID to use for subsequent commands |
| `deviceId` | string | Host agent ID for the session |
| `platform` | string | Platform of the connected host |
| `pwd` | string | Working directory the session started in |
| `offlineQueued` | boolean | Whether the session was queued for an offline host |
| `existingAidSessions` | number | Number of sessions already open against this host |
| `createdAt` | string | Session creation timestamp |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Perform Host Action
Act on CrowdStrike Falcon hosts (POST /devices/entities/devices-actions/v2). Actions: contain, lift_containment, hide_host, unhide_host, detection_suppress, detection_unsuppress. contain network-isolates the host so it can only reach the Falcon cloud; hide_host removes the host record from the console. Both are immediately disruptive. Up to 100 host IDs per call. Requires the "Hosts: Write" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `actionName` | string | Yes | Action to take: contain, lift_containment, hide_host, unhide_host, detection_suppress, or detection_unsuppress. "contain" network-isolates the host; "hide_host" removes it from the Falcon console. |
| `deviceIds` | json | Yes | JSON array of up to 100 CrowdStrike host agent IDs \(AIDs\) to act on |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `affected` | array | Entities affected by the action |
| ↳ `id` | string | Affected entity identifier |
| ↳ `path` | string | API path of the affected entity |
| `count` | number | Number of hosts the action was applied to |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Perform Host Group Action
Add hosts to or remove hosts from a CrowdStrike Falcon static host group (POST /devices/entities/host-group-actions/v1). Group membership drives policy assignment, so changing it changes which policies apply to those hosts. Requires the "Host groups: Write" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `actionName` | string | Yes | Action to take: add-hosts or remove-hosts |
| `hostGroupId` | string | Yes | CrowdStrike host group ID to modify \(static groups only\) |
| `deviceIds` | json | Yes | JSON array of CrowdStrike host agent IDs \(AIDs\) to add to or remove from the group |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `hostGroups` | array | Host group records returned after the action |
| ↳ `id` | string | Host group identifier |
| ↳ `name` | string | Host group name |
| ↳ `description` | string | Host group description |
| ↳ `groupType` | string | Group type \(static, dynamic, staticByID\) |
| ↳ `assignmentRule` | string | FQL assignment rule for dynamic groups |
| ↳ `createdBy` | string | User who created the group |
| ↳ `createdTimestamp` | string | Group creation timestamp |
| ↳ `modifiedBy` | string | User who last modified the group |
| ↳ `modifiedTimestamp` | string | Group modification timestamp |
| `count` | number | Number of host group records returned |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Query Alerts
Search CrowdStrike Falcon alerts with a Falcon Query Language filter and return their composite IDs. Uses the current Alerts API (GET /alerts/queries/alerts/v2), which supersedes the deprecated Detects API. Requires the "Alerts: Read" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `filter` | string | No | Falcon Query Language filter over alert fields |
| `q` | string | No | Free-text search across all alert metadata |
| `limit` | number | No | Maximum number of alert IDs to return \(max 10000\) |
| `offset` | number | No | Pagination offset for the alert query |
| `sort` | string | No | Sort expression such as "created_timestamp\|desc" |
| `includeHidden` | boolean | No | Include previously hidden alerts \(CrowdStrike defaults this to true\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `alertIds` | array | Composite alert IDs matching the query, ready for Get Alert Details |
| `count` | number | Number of alert IDs returned |
| `pagination` | json | Pagination metadata \(limit, offset, total\) |
| ↳ `limit` | number | Page size used for the query |
| ↳ `offset` | number | Offset returned by CrowdStrike |
| ↳ `total` | number | Total records available |
### CrowdStrike Query Cases
Search CrowdStrike Falcon Case Management cases with a Falcon Query Language filter and return their IDs (GET /cases/queries/cases/v1). Case Management supersedes the CrowdScore Incidents API, which CrowdStrike has removed from its published API spec. Requires the "Cases: Read" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `filter` | string | No | Falcon Query Language filter. Exact-match fields include cid and id; wildcard fields include assigned_to_name and assigned_to_uuid; range fields include created_timestamp and updated_timestamp. |
| `q` | string | No | Free-text search across all case metadata |
| `limit` | number | No | Maximum number of case IDs to return \(max 10000, default 100\) |
| `offset` | number | No | Pagination offset for the case query |
| `sort` | string | No | Sort expression such as "created_timestamp\|desc" or "status\|asc" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `caseIds` | array | Case IDs matching the query |
| `count` | number | Number of case IDs returned |
| `pagination` | json | Pagination metadata \(limit, offset, total\) |
| ↳ `limit` | number | Page size used for the query |
| ↳ `offset` | number | Offset returned by CrowdStrike |
| ↳ `total` | number | Total records available |
### CrowdStrike Query Host Groups
Search CrowdStrike Falcon host groups with a Falcon Query Language filter and return their IDs (GET /devices/queries/host-groups/v1). Requires the "Host groups: Read" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `filter` | string | No | Falcon Query Language filter over host group fields |
| `limit` | number | No | Maximum number of host group IDs to return \(1-5000\) |
| `offset` | number | No | Pagination offset for the host group query |
| `sort` | string | No | Sort expression such as "name.asc" or "modified_timestamp.desc" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `hostGroupIds` | array | Host group IDs matching the query |
| `count` | number | Number of host group IDs returned |
| `pagination` | json | Pagination metadata \(limit, offset, total\) |
| ↳ `limit` | number | Page size used for the query |
| ↳ `offset` | number | Offset returned by CrowdStrike |
| ↳ `total` | number | Total records available |
### CrowdStrike Query Indicators
Search custom CrowdStrike Falcon indicators of compromise (IOCs) with a Falcon Query Language filter and return their IDs (GET /iocs/queries/indicators/v1). Requires the "IOC Management: Read" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `filter` | string | No | Falcon Query Language filter over IOC fields |
| `limit` | number | No | Maximum number of IOC IDs to return \(1-500, default 100\) |
| `offset` | number | No | Pagination offset. Mutually exclusive with the after cursor; use after beyond 10,000 IOCs. |
| `after` | string | No | Pagination cursor from a previous response. Mutually exclusive with offset. |
| `sort` | string | No | Sort expression. Supported fields include action, applied_globally, created_by, created_on, expiration, expired, modified_by, modified_on, severity_number, source, type, and value. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `indicatorIds` | array | IOC IDs matching the query |
| `count` | number | Number of IOC IDs returned |
| `pagination` | json | Pagination metadata \(limit, offset, total, after\) |
| ↳ `limit` | number | Page size used for the query |
| ↳ `offset` | number | Offset returned by CrowdStrike |
| ↳ `total` | number | Total records available |
| ↳ `after` | string | Cursor for the next page |
### CrowdStrike Query Sensors
Search CrowdStrike identity protection sensors by hostname, IP, or related fields
Search CrowdStrike Identity Protection sensors -- the domain controllers Falcon Identity Protection monitors, not Falcon endpoint sensors -- and return their device IDs (GET /identity-protection/queries/devices/v1). Sort uses the dot form, for example status.desc. Requires the "Identity Protection Entities: Read" API scope, a separate entitlement from Hosts and Alerts.
#### Input
@@ -153,5 +832,130 @@ Search CrowdStrike identity protection sensors by hostname, IP, or related field
| ↳ `limit` | number | Page size used for the query |
| ↳ `offset` | number | Offset returned by CrowdStrike |
| ↳ `total` | number | Total records available |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Query Vulnerabilities
Search CrowdStrike Falcon Spotlight vulnerabilities with a required Falcon Query Language filter and return their IDs (GET /spotlight/queries/vulnerabilities/v1). Requires the spotlight-vulnerabilities:read API scope, shown as "Vulnerabilities: Read" in the Falcon API client UI.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `filter` | string | Yes | Falcon Query Language filter \(required by Spotlight\). Filterable fields include status, aid, cid, last_seen_within, cve.id, cve.severity, cve.exprt_rating, cve.is_cisa_kev, cve.base_score, host_info.platform_name, host_info.groups, host_info.tags, host_info.internet_exposure, and suppression_info.is_suppressed. |
| `limit` | number | No | Maximum number of vulnerability IDs to return \(1-400, default 100\) |
| `after` | string | No | Pagination cursor from a previous response. Spotlight does not support offset. |
| `sort` | string | No | Sort expression such as "updated_timestamp\|desc" or "closed_timestamp\|asc" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `vulnerabilityIds` | array | Spotlight vulnerability IDs matching the query |
| `count` | number | Number of vulnerability IDs returned |
| `pagination` | json | Cursor pagination metadata \(limit, total, after\) |
| ↳ `limit` | number | Page size used for the query |
| ↳ `total` | number | Total records available |
| ↳ `after` | string | Cursor for the next page |
### CrowdStrike Update Alerts
Update CrowdStrike Falcon alerts by composite ID: change status, assign or unassign an analyst, add or remove tags, append a comment, or toggle visibility (PATCH /alerts/entities/alerts/v3). This modifies live alerts in the Falcon console. Requires the "Alerts: Write" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `compositeIds` | json | Yes | JSON array of CrowdStrike composite alert IDs to update |
| `updateStatus` | string | No | New alert status: new, in_progress, reopened, or closed |
| `assignToUuid` | string | No | Assign the alert to this Falcon user UUID |
| `assignToUserId` | string | No | Assign the alert to this Falcon user ID, such as user@example.com |
| `assignToName` | string | No | Assign the alert to this Falcon username, such as John Doe |
| `unassign` | boolean | No | Clear the assigned user UUID, user ID, and username from the alert |
| `appendComment` | string | No | Comment to append to the alert in the Falcon console |
| `addTag` | string | No | Tag to add to the alert |
| `removeTag` | string | No | Tag to remove from the alert |
| `removeTagsByPrefix` | string | No | Remove every tag on the alert that starts with this prefix |
| `showInUi` | boolean | No | Whether the alert is displayed in the Falcon console |
| `actionParameters` | json | No | Raw JSON array of additional CrowdStrike action parameters, each shaped \{ "name": string, "value": string \} |
| `includeHidden` | boolean | No | Include previously hidden alerts \(CrowdStrike defaults this to true\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `updatedIds` | array | Composite alert IDs the update was submitted for |
| `count` | number | Number of alerts the update was submitted for |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
### CrowdStrike Update Indicators
Update custom CrowdStrike Falcon indicators of compromise by ID (PATCH /iocs/entities/indicators/v1). DESTRUCTIVE: CrowdStrike blanks out any field you omit, so read each indicator with crowdstrike_get_indicator_details first and resend its full field set with your edits applied. Changing action or scope changes prevention behavior fleet-wide. type and value are immutable. Requires the "IOC Management: Write" API scope.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | CrowdStrike Falcon API client ID |
| `clientSecret` | string | Yes | CrowdStrike Falcon API client secret |
| `cloud` | string | Yes | CrowdStrike Falcon cloud region |
| `indicators` | json | Yes | JSON array of indicators to update. Each entry requires id, and must also repeat every field it wants to keep: CrowdStrike blanks out any updatable field the entry omits. Updatable fields: action, severity, description, source, tags \(array\), platforms \(array\), applied_globally \(boolean\), host_groups \(array\), expiration \(ISO 8601\), mobile_action, metadata \(\{ filename \}\). type and value cannot be changed. |
| `comment` | string | No | Audit comment explaining why these indicators were updated |
| `retrodetects` | boolean | No | Whether to generate retroactive detections for the updated indicators |
| `ignoreWarnings` | boolean | No | Whether to apply the updates even when CrowdStrike returns warnings |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `indicators` | array | Updated CrowdStrike indicator records |
| ↳ `id` | string | Indicator identifier |
| ↳ `type` | string | Indicator type |
| ↳ `value` | string | Indicator value |
| ↳ `action` | string | Action taken when the indicator matches |
| ↳ `mobileAction` | string | Action taken on mobile platforms when the indicator matches |
| ↳ `severity` | string | Indicator severity |
| ↳ `description` | string | Indicator description |
| ↳ `source` | string | Indicator source |
| ↳ `appliedGlobally` | boolean | Whether the indicator applies to all hosts |
| ↳ `platforms` | array | Platforms the indicator applies to |
| ↳ `hostGroups` | array | Host group IDs the indicator is scoped to |
| ↳ `tags` | array | Tags applied to the indicator |
| ↳ `expiration` | string | Indicator expiration timestamp |
| ↳ `expired` | boolean | Whether the indicator has expired |
| ↳ `deleted` | boolean | Whether the indicator is deleted |
| ↳ `fromParent` | boolean | Whether the indicator was inherited from a parent CID |
| ↳ `parentCidName` | string | Parent CID name |
| ↳ `createdBy` | string | User who created the indicator |
| ↳ `createdOn` | string | Indicator creation timestamp |
| ↳ `modifiedBy` | string | User who last modified the indicator |
| ↳ `modifiedOn` | string | Indicator modification timestamp |
| ↳ `metadata` | json | File metadata CrowdStrike resolved for the indicator |
| ↳ `avHits` | number | Antivirus hit count |
| ↳ `companyName` | string | Company name |
| ↳ `fileDescription` | string | File description |
| ↳ `fileVersion` | string | File version |
| ↳ `filename` | string | File name |
| ↳ `originalFilename` | string | Original file name |
| ↳ `productName` | string | Product name |
| ↳ `productVersion` | string | Product version |
| ↳ `signed` | boolean | Whether the file is signed |
| `count` | number | Number of indicators updated |
| `errors` | array | Errors CrowdStrike returned alongside a partially successful response |
| ↳ `code` | number | CrowdStrike error code |
| ↳ `id` | string | Identifier the error applies to |
| ↳ `message` | string | Error message |
@@ -0,0 +1,243 @@
import { isRecordLike } from '@sim/utils/object'
import type { CrowdStrikeBaseParams, CrowdStrikeCloud } from '@/tools/crowdstrike/types'
export type JsonRecord = Record<string, unknown>
const CLOUD_BASE_URLS: Record<CrowdStrikeCloud, string> = {
'eu-1': 'https://api.eu-1.crowdstrike.com',
'us-1': 'https://api.crowdstrike.com',
'us-2': 'https://api.us-2.crowdstrike.com',
'us-3': 'https://api.us-3.crowdstrike.com',
'us-gov-1': 'https://api.laggar.gcw.crowdstrike.com',
'us-gov-2': 'https://api.us-gov-2.crowdstrike.mil',
}
export function getCloudBaseUrl(cloud: CrowdStrikeCloud): string {
return CLOUD_BASE_URLS[cloud]
}
export function getString(value: unknown): string | null {
return typeof value === 'string' ? value : null
}
export function getNumber(value: unknown): number | null {
return typeof value === 'number' ? value : null
}
export function getBoolean(value: unknown): boolean | null {
return typeof value === 'boolean' ? value : null
}
export function getStringArray(value: unknown): string[] {
if (!Array.isArray(value)) {
return []
}
return value.filter((entry): entry is string => typeof entry === 'string')
}
export function getRecordArray(value: unknown): JsonRecord[] {
if (!Array.isArray(value)) {
return []
}
return value.filter(isRecordLike)
}
export function getRecord(value: unknown): JsonRecord | null {
return isRecordLike(value) ? value : null
}
/**
* Every Falcon endpoint this integration calls answers with a flat
* `{ meta, resources, errors }` envelope, so the envelope readers below and
* `getFalconErrorMessage` both read the payload root directly.
*/
export function getResourcesArray(data: unknown): unknown[] {
if (!isRecordLike(data) || !Array.isArray(data.resources)) {
return []
}
return data.resources
}
export function getRecordResources(data: unknown): JsonRecord[] {
return getResourcesArray(data).filter(isRecordLike)
}
export function getStringResources(data: unknown): string[] {
return getStringArray(getResourcesArray(data))
}
export function getFirstRecordResource(data: unknown): JsonRecord | null {
return getRecordResources(data)[0] ?? null
}
export function getPagination(data: unknown) {
if (!isRecordLike(data) || !isRecordLike(data.meta) || !isRecordLike(data.meta.pagination)) {
return null
}
const { pagination } = data.meta
return {
limit: getNumber(pagination.limit),
offset: getNumber(pagination.offset),
total: getNumber(pagination.total),
}
}
/** Offset pagination plus the `after` cursor the IOC Management API returns. */
export function getCursorPagination(data: unknown) {
if (!isRecordLike(data) || !isRecordLike(data.meta) || !isRecordLike(data.meta.pagination)) {
return null
}
const { pagination } = data.meta
return {
after: getString(pagination.after),
limit: getNumber(pagination.limit),
offset: getNumber(pagination.offset),
total: getNumber(pagination.total),
}
}
/** Spotlight paginates by cursor only — it returns no offset. */
export function getSpotlightPagination(data: unknown) {
if (!isRecordLike(data) || !isRecordLike(data.meta) || !isRecordLike(data.meta.pagination)) {
return null
}
const { pagination } = data.meta
return {
after: getString(pagination.after),
limit: getNumber(pagination.limit),
total: getNumber(pagination.total),
}
}
/**
* CrowdStrike returns `{ meta, resources, errors }` on every endpoint, and a 200
* can still carry a populated `errors` array for the IDs that failed.
*/
export function getEnvelopeErrors(data: unknown) {
if (!isRecordLike(data)) {
return []
}
return getRecordArray(data.errors).map((entry) => ({
code: getNumber(entry.code),
id: getString(entry.id),
message: getString(entry.message),
}))
}
export function getFalconErrorMessage(data: unknown, fallback: string): string {
if (!isRecordLike(data)) {
return fallback
}
const errors = Array.isArray(data.errors) ? data.errors : []
const firstError = errors[0]
if (isRecordLike(firstError)) {
const firstMessage = getString(firstError.message) ?? getString(firstError.code)
if (firstMessage) {
return firstMessage
}
}
return (
getString(data.message) ??
getString(data.error_description) ??
getString(data.error) ??
fallback
)
}
export async function getAccessToken(params: CrowdStrikeBaseParams): Promise<string> {
const baseUrl = getCloudBaseUrl(params.cloud)
const response = await fetch(`${baseUrl}/oauth2/token`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: params.clientId,
client_secret: params.clientSecret,
grant_type: 'client_credentials',
}).toString(),
cache: 'no-store',
})
const data: unknown = await response.json().catch(() => null)
if (!response.ok) {
throw new Error(getFalconErrorMessage(data, 'Failed to authenticate with CrowdStrike'))
}
if (!isRecordLike(data) || typeof data.access_token !== 'string') {
throw new Error('CrowdStrike authentication did not return an access token')
}
return data.access_token
}
interface CrowdStrikeRequestOptions {
method: 'GET' | 'POST' | 'PATCH' | 'DELETE'
path: string
query?: Record<string, string | number | boolean | undefined>
repeatedQuery?: Record<string, string[] | undefined>
body?: unknown
}
export interface CrowdStrikeCallResult {
ok: boolean
status: number
data: unknown
}
export function buildUrl(baseUrl: string, options: CrowdStrikeRequestOptions): string {
const url = new URL(options.path, baseUrl)
for (const [key, value] of Object.entries(options.query ?? {})) {
if (value !== undefined) {
url.searchParams.set(key, String(value))
}
}
for (const [key, values] of Object.entries(options.repeatedQuery ?? {})) {
for (const value of values ?? []) {
url.searchParams.append(key, value)
}
}
return url.toString()
}
export async function callCrowdStrike(
baseUrl: string,
accessToken: string,
options: CrowdStrikeRequestOptions
): Promise<CrowdStrikeCallResult> {
const headers: Record<string, string> = {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
}
if (options.body !== undefined) {
headers['Content-Type'] = 'application/json'
}
const response = await fetch(buildUrl(baseUrl, options), {
method: options.method,
headers,
body: options.body === undefined ? undefined : JSON.stringify(options.body),
cache: 'no-store',
})
const data: unknown = await response.json().catch(() => null)
return { ok: response.ok, status: response.status, data }
}
@@ -0,0 +1,266 @@
import {
getBoolean,
getNumber,
getRecord,
getRecordArray,
getString,
getStringArray,
type JsonRecord,
} from '@/app/api/tools/crowdstrike/query/falcon'
import type {
CrowdStrikeAffectedEntity,
CrowdStrikeAlert,
CrowdStrikeCase,
CrowdStrikeFalconUser,
CrowdStrikeHostGroup,
CrowdStrikeIndicator,
CrowdStrikeVulnerability,
} from '@/tools/crowdstrike/types'
export function normalizeAlert(resource: JsonRecord): CrowdStrikeAlert {
const device = getRecord(resource.device)
return {
compositeId: getString(resource.composite_id),
id: getString(resource.id),
cid: getString(resource.cid),
aggregateId: getString(resource.aggregate_id),
agentId: getString(resource.agent_id),
deviceId: device ? getString(device.device_id) : null,
hostname: device ? getString(device.hostname) : null,
name: getString(resource.name),
displayName: getString(resource.display_name),
description: getString(resource.description),
type: getString(resource.type),
product: getString(resource.product),
platform: getString(resource.platform),
severity: getNumber(resource.severity),
severityName: getString(resource.severity_name),
confidence: getNumber(resource.confidence),
status: getString(resource.status),
assignedToName: getString(resource.assigned_to_name),
assignedToUid: getString(resource.assigned_to_uid),
assignedToUuid: getString(resource.assigned_to_uuid),
tactic: getString(resource.tactic),
tacticId: getString(resource.tactic_id),
technique: getString(resource.technique),
techniqueId: getString(resource.technique_id),
scenario: getString(resource.scenario),
objective: getString(resource.objective),
resolution: getString(resource.resolution),
showInUi: getBoolean(resource.show_in_ui),
tags: getStringArray(resource.tags),
filename: getString(resource.filename),
filepath: getString(resource.filepath),
cmdline: getString(resource.cmdline),
sha256: getString(resource.sha256),
sha1: getString(resource.sha1),
md5: getString(resource.md5),
userName: getString(resource.user_name),
userId: getString(resource.user_id),
patternId: getNumber(resource.pattern_id),
falconHostLink: getString(resource.falcon_host_link),
controlGraphId: getString(resource.control_graph_id),
external: getBoolean(resource.external),
emailSent: getBoolean(resource.email_sent),
isAggregated: getBoolean(resource.is_aggregated),
isFalconPlatformIoa: getBoolean(resource.is_falcon_platform_ioa),
dataDomains: getStringArray(resource.data_domains),
iocValues: getStringArray(resource.ioc_values),
linkedCaseIds: getStringArray(resource.linked_case_ids),
linkedBehavioralDetections: getStringArray(resource.linked_behavioral_detections),
timestamp: getString(resource.timestamp),
createdTimestamp: getString(resource.created_timestamp),
updatedTimestamp: getString(resource.updated_timestamp),
crawledTimestamp: getString(resource.crawled_timestamp),
contextTimestamp: getString(resource.context_timestamp),
}
}
export function normalizeAffectedEntity(resource: JsonRecord): CrowdStrikeAffectedEntity {
return {
id: getString(resource.id),
path: getString(resource.path),
}
}
export function normalizeHostGroup(resource: JsonRecord): CrowdStrikeHostGroup {
return {
id: getString(resource.id),
name: getString(resource.name),
description: getString(resource.description),
groupType: getString(resource.group_type),
assignmentRule: getString(resource.assignment_rule),
createdBy: getString(resource.created_by),
createdTimestamp: getString(resource.created_timestamp),
modifiedBy: getString(resource.modified_by),
modifiedTimestamp: getString(resource.modified_timestamp),
}
}
export function normalizeIndicator(resource: JsonRecord): CrowdStrikeIndicator {
const metadata = getRecord(resource.metadata)
return {
id: getString(resource.id),
type: getString(resource.type),
value: getString(resource.value),
action: getString(resource.action),
mobileAction: getString(resource.mobile_action),
severity: getString(resource.severity),
description: getString(resource.description),
source: getString(resource.source),
appliedGlobally: getBoolean(resource.applied_globally),
platforms: getStringArray(resource.platforms),
hostGroups: getStringArray(resource.host_groups),
tags: getStringArray(resource.tags),
expiration: getString(resource.expiration),
expired: getBoolean(resource.expired),
deleted: getBoolean(resource.deleted),
fromParent: getBoolean(resource.from_parent),
parentCidName: getString(resource.parent_cid_name),
createdBy: getString(resource.created_by),
createdOn: getString(resource.created_on),
modifiedBy: getString(resource.modified_by),
modifiedOn: getString(resource.modified_on),
metadata: metadata
? {
avHits: getNumber(metadata.av_hits),
companyName: getString(metadata.company_name),
fileDescription: getString(metadata.file_description),
fileVersion: getString(metadata.file_version),
filename: getString(metadata.filename),
originalFilename: getString(metadata.original_filename),
productName: getString(metadata.product_name),
productVersion: getString(metadata.product_version),
signed: getBoolean(metadata.signed),
}
: null,
}
}
export function normalizeVulnerability(resource: JsonRecord): CrowdStrikeVulnerability {
const cve = getRecord(resource.cve)
const cisaInfo = cve ? getRecord(cve.cisa_info) : null
const app = getRecord(resource.app)
const hostInfo = getRecord(resource.host_info)
const remediation = getRecord(resource.remediation)
const suppressionInfo = getRecord(resource.suppression_info)
return {
id: getString(resource.id),
aid: getString(resource.aid),
cid: getString(resource.cid),
status: getString(resource.status),
confidence: getString(resource.confidence),
vulnerabilityId: getString(resource.vulnerability_id),
createdTimestamp: getString(resource.created_timestamp),
updatedTimestamp: getString(resource.updated_timestamp),
closedTimestamp: getString(resource.closed_timestamp),
cve: cve
? {
id: getString(cve.id),
baseScore: getNumber(cve.base_score),
severity: getString(cve.severity),
exprtRating: getString(cve.exprt_rating),
exploitStatus: getNumber(cve.exploit_status),
exploitabilityScore: getNumber(cve.exploitability_score),
impactScore: getNumber(cve.impact_score),
remediationLevel: getString(cve.remediation_level),
description: getString(cve.description),
publishedDate: getString(cve.published_date),
vector: getString(cve.vector),
types: getStringArray(cve.types),
isCisaKev: cisaInfo ? getBoolean(cisaInfo.is_cisa_kev) : null,
cisaDueDate: cisaInfo ? getString(cisaInfo.due_date) : null,
}
: null,
app: app
? {
productNameNormalized: getString(app.product_name_normalized),
productNameVersion: getString(app.product_name_version),
vendorNormalized: getString(app.vendor_normalized),
}
: null,
hostInfo: hostInfo
? {
hostname: getString(hostInfo.hostname),
localIp: getString(hostInfo.local_ip),
machineDomain: getString(hostInfo.machine_domain),
osVersion: getString(hostInfo.os_version),
platform: getString(hostInfo.platform),
productTypeDesc: getString(hostInfo.product_type_desc),
assetCriticality: getString(hostInfo.asset_criticality),
internetExposure: getString(hostInfo.internet_exposure),
tags: getStringArray(hostInfo.tags),
groups: getRecordArray(hostInfo.groups)
.map((group) => getString(group.name))
.filter((name): name is string => name !== null),
}
: null,
remediationIds: remediation ? getStringArray(remediation.ids) : [],
remediations: remediation
? getRecordArray(remediation.entities).map((entity) => ({
id: getString(entity.id),
title: getString(entity.title),
action: getString(entity.action),
type: getString(entity.type),
link: getString(entity.link),
reference: getString(entity.reference),
vendorUrl: getString(entity.vendor_url),
}))
: [],
suppressionInfo: suppressionInfo
? {
isSuppressed: getBoolean(suppressionInfo.is_suppressed),
reason: getString(suppressionInfo.reason),
}
: null,
}
}
function normalizeFalconUser(value: unknown): CrowdStrikeFalconUser | null {
const user = getRecord(value)
if (!user) {
return null
}
return {
uuid: getString(user.uuid),
email: getString(user.email),
fullName: getString(user.full_name),
}
}
export function normalizeCase(resource: JsonRecord): CrowdStrikeCase {
const severityInfo = getRecord(resource.severity_info)
const template = getRecord(resource.template)
const sla = getRecord(resource.sla)
const readOnly = getRecord(resource.read_only)
return {
id: getString(resource.id),
cid: getString(resource.cid),
name: getString(resource.name),
description: getString(resource.description),
descriptionFormat: getString(resource.description_format),
status: getString(resource.status),
severity: getNumber(resource.severity),
severityLevel: severityInfo ? getString(severityInfo.level) : null,
referenceId: getString(resource.reference_id),
version: getNumber(resource.version),
tags: getStringArray(resource.tags),
assignedTo: normalizeFalconUser(resource.assigned_to),
createdBy: normalizeFalconUser(resource.created_by),
lastUpdatedBy: normalizeFalconUser(resource.last_updated_by),
createdTimestamp: getString(resource.created_timestamp),
updatedTimestamp: getString(resource.updated_timestamp),
startTimestamp: getString(resource.start_timestamp),
endTimestamp: getString(resource.end_timestamp),
templateId: template ? getString(template.id) : null,
templateName: template ? getString(template.name) : null,
slaId: sla ? getString(sla.id) : null,
slaName: sla ? getString(sla.name) : null,
isReadOnly: readOnly ? getBoolean(readOnly.is_read_only) : null,
}
}
@@ -0,0 +1,936 @@
/**
* @vitest-environment node
*/
import { createMockRequest, hybridAuthMockFns } from '@sim/testing'
import { beforeEach, describe, expect, it, vi } from 'vitest'
const { fetchMock } = vi.hoisted(() => ({
fetchMock: vi.fn(),
}))
import { POST } from '@/app/api/tools/crowdstrike/query/route'
function jsonResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { 'Content-Type': 'application/json' },
})
}
const credentials = {
clientId: 'client-id',
clientSecret: 'client-secret',
cloud: 'us-1' as const,
}
function requestFor(body: Record<string, unknown>) {
return createMockRequest('POST', { ...credentials, ...body })
}
describe('CrowdStrike extended operations', () => {
beforeEach(() => {
vi.clearAllMocks()
fetchMock.mockReset()
vi.stubGlobal('fetch', fetchMock)
hybridAuthMockFns.mockCheckInternalAuth.mockResolvedValue({
success: true,
userId: 'user-123',
authType: 'internal_jwt',
})
fetchMock.mockResolvedValueOnce(jsonResponse({ access_token: 'token-123' }))
})
it('queries alerts and returns composite ids with pagination', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
meta: { pagination: { limit: 2, offset: 0, total: 7 } },
resources: ['cid:aid:alert-1', 'cid:aid:alert-2'],
})
)
const response = await POST(
requestFor({ operation: 'crowdstrike_query_alerts', filter: 'status:"new"', limit: 2 })
)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.output).toEqual({
alertIds: ['cid:aid:alert-1', 'cid:aid:alert-2'],
count: 2,
pagination: { limit: 2, offset: 0, total: 7 },
})
const queryUrl = new URL(fetchMock.mock.calls[1][0])
expect(queryUrl.pathname).toBe('/alerts/queries/alerts/v2')
expect(queryUrl.searchParams.get('filter')).toBe('status:"new"')
expect(queryUrl.searchParams.get('limit')).toBe('2')
})
it('normalizes alert details from documented fields', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
resources: [
{
composite_id: 'cid:aid:alert-1',
id: 'alert-1',
severity: 70,
severity_name: 'High',
status: 'new',
tags: ['triage'],
device: { device_id: 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1', hostname: 'web-01' },
},
],
})
)
const response = await POST(
requestFor({
operation: 'crowdstrike_get_alert_details',
compositeIds: ['cid:aid:alert-1'],
})
)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.output.count).toBe(1)
expect(data.output.alerts[0]).toMatchObject({
compositeId: 'cid:aid:alert-1',
id: 'alert-1',
severity: 70,
severityName: 'High',
status: 'new',
tags: ['triage'],
deviceId: 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1',
hostname: 'web-01',
})
const [, detailsCall] = fetchMock.mock.calls
expect(JSON.parse(detailsCall[1].body)).toEqual({ composite_ids: ['cid:aid:alert-1'] })
})
it('builds documented action parameters when updating alerts', async () => {
fetchMock.mockResolvedValueOnce(jsonResponse({ meta: {}, errors: [] }))
const response = await POST(
requestFor({
operation: 'crowdstrike_update_alerts',
compositeIds: ['cid:aid:alert-1'],
updateStatus: 'closed',
appendComment: 'Resolved by automation',
showInUi: false,
})
)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.output.updatedIds).toEqual(['cid:aid:alert-1'])
const [, updateCall] = fetchMock.mock.calls
expect(updateCall[1].method).toBe('PATCH')
expect(JSON.parse(updateCall[1].body)).toEqual({
action_parameters: [
{ name: 'update_status', value: 'closed' },
{ name: 'append_comment', value: 'Resolved by automation' },
{ name: 'show_in_ui', value: 'false' },
],
composite_ids: ['cid:aid:alert-1'],
})
})
it('rejects an alert update that carries no action', async () => {
const response = await POST(
requestFor({
operation: 'crowdstrike_update_alerts',
compositeIds: ['cid:aid:alert-1'],
})
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('contains hosts through the documented action endpoint', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse(
{
resources: [
{ id: 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1', path: '/devices/entities/devices/v1' },
],
},
202
)
)
const response = await POST(
requestFor({
operation: 'crowdstrike_perform_host_action',
actionName: 'contain',
deviceIds: ['a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1'],
})
)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.output.affected).toEqual([
{ id: 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1', path: '/devices/entities/devices/v1' },
])
const actionUrl = new URL(fetchMock.mock.calls[1][0])
expect(actionUrl.pathname).toBe('/devices/entities/devices-actions/v2')
expect(actionUrl.searchParams.get('action_name')).toBe('contain')
expect(JSON.parse(fetchMock.mock.calls[1][1].body)).toEqual({
ids: ['a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1'],
})
})
it('rejects an unsupported host action', async () => {
const response = await POST(
requestFor({
operation: 'crowdstrike_perform_host_action',
actionName: 'delete_host',
deviceIds: ['a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1'],
})
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('adds hosts to a group with a device_id FQL filter', async () => {
fetchMock.mockResolvedValueOnce(jsonResponse({ resources: [{ id: 'group-1', name: 'SOC' }] }))
const response = await POST(
requestFor({
operation: 'crowdstrike_perform_host_group_action',
actionName: 'add-hosts',
hostGroupId: 'group-1',
deviceIds: ['a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1', 'b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2'],
})
)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.output.hostGroups[0]).toMatchObject({ id: 'group-1', name: 'SOC' })
const groupUrl = new URL(fetchMock.mock.calls[1][0])
expect(groupUrl.pathname).toBe('/devices/entities/host-group-actions/v1')
expect(groupUrl.searchParams.get('action_name')).toBe('add-hosts')
expect(JSON.parse(fetchMock.mock.calls[1][1].body)).toEqual({
action_parameters: [
{
name: 'filter',
value:
"(device_id:['a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1','b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2'])",
},
],
ids: ['group-1'],
})
})
it('treats a 200 with only envelope errors as a failure', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
resources: [],
errors: [{ code: 404, id: 'ioc-1', message: 'Indicator not found' }],
})
)
const response = await POST(
requestFor({ operation: 'crowdstrike_get_indicator_details', indicatorIds: ['ioc-1'] })
)
const data = await response.json()
expect(response.status).toBe(404)
expect(data.success).toBe(false)
expect(data.error).toBe('Indicator not found')
})
it('falls back to 502 when a 200 carries errors without a usable code', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({ resources: [], errors: [{ message: 'Upstream unavailable' }] })
)
const response = await POST(
requestFor({ operation: 'crowdstrike_get_indicator_details', indicatorIds: ['ioc-1'] })
)
const data = await response.json()
expect(response.status).toBe(502)
expect(data.success).toBe(false)
expect(data.error).toBe('Upstream unavailable')
})
it('fails an alert update when the meta-only envelope reports any error', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({ meta: {}, errors: [{ code: 403, message: 'Alert is read only' }] })
)
const response = await POST(
requestFor({
operation: 'crowdstrike_update_alerts',
compositeIds: ['cid:aid:alert-1'],
updateStatus: 'closed',
})
)
const data = await response.json()
expect(response.status).toBe(403)
expect(data.success).toBe(false)
expect(data.error).toBe('Alert is read only')
})
it('sends remove_tags_by_prefix using the documented action parameter name', async () => {
fetchMock.mockResolvedValueOnce(jsonResponse({ meta: {}, errors: [] }))
const response = await POST(
requestFor({
operation: 'crowdstrike_update_alerts',
compositeIds: ['cid:aid:alert-1'],
removeTagsByPrefix: 'auto-',
})
)
expect(response.status).toBe(200)
const [, updateCall] = fetchMock.mock.calls
expect(JSON.parse(updateCall[1].body).action_parameters).toEqual([
{ name: 'remove_tags_by_prefix', value: 'auto-' },
])
})
it('surfaces envelope errors alongside partial indicator results', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
resources: [{ id: 'ioc-1', type: 'sha256', value: 'abc', action: 'prevent' }],
errors: [{ code: 404, id: 'ioc-2', message: 'Indicator not found' }],
})
)
const response = await POST(
requestFor({
operation: 'crowdstrike_get_indicator_details',
indicatorIds: ['ioc-1', 'ioc-2'],
})
)
const data = await response.json()
expect(data.success).toBe(true)
expect(data.output.count).toBe(1)
expect(data.output.errors).toEqual([{ code: 404, id: 'ioc-2', message: 'Indicator not found' }])
})
it('deletes indicators by filter without an ids list', async () => {
fetchMock.mockResolvedValueOnce(jsonResponse({ resources: ['ioc-1'] }))
const response = await POST(
requestFor({
operation: 'crowdstrike_delete_indicators',
filter: "source:'automation'",
comment: 'cleanup',
})
)
const data = await response.json()
expect(data.output.deletedIds).toEqual(['ioc-1'])
const deleteUrl = new URL(fetchMock.mock.calls[1][0])
expect(fetchMock.mock.calls[1][1].method).toBe('DELETE')
expect(deleteUrl.searchParams.get('filter')).toBe("source:'automation'")
expect(deleteUrl.searchParams.getAll('ids')).toEqual([])
expect(deleteUrl.searchParams.get('comment')).toBe('cleanup')
})
it('rejects a delete that supplies both ids and a filter', async () => {
const response = await POST(
requestFor({
operation: 'crowdstrike_delete_indicators',
filter: "source:'automation'",
indicatorIds: ['ioc-9'],
})
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('rejects a host agent ID that is not a 32-character AID', async () => {
const response = await POST(
requestFor({
operation: 'crowdstrike_perform_host_action',
actionName: 'contain',
deviceIds: ["not-an-aid') or (device_id:['*'"],
})
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('rejects an alert update that both assigns and unassigns', async () => {
const response = await POST(
requestFor({
operation: 'crowdstrike_update_alerts',
compositeIds: ['cid:aid:alert-1'],
assignToUuid: 'user-uuid',
unassign: true,
})
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('rejects an indicator update whose entry carries no id', async () => {
const response = await POST(
requestFor({
operation: 'crowdstrike_update_indicators',
indicators: [{ action: 'prevent' }],
})
)
expect(response.status).toBe(400)
expect(await response.json()).toMatchObject({ error: expect.stringContaining('id') })
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('rejects an indicator update that tries to change the immutable type or value', async () => {
const response = await POST(
requestFor({
operation: 'crowdstrike_update_indicators',
indicators: [{ id: 'ioc-1', value: 'evil.example' }],
})
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('rejects an indicator create that omits the required applied_globally scope', async () => {
const response = await POST(
requestFor({
operation: 'crowdstrike_create_indicators',
indicators: [{ type: 'sha256', value: 'a'.repeat(64), action: 'prevent' }],
})
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('rejects a write-tier RTR base command on the read-scoped endpoint', async () => {
const response = await POST(
requestFor({
operation: 'crowdstrike_execute_rtr_command',
sessionId: 'session-1',
baseCommand: 'eventlog backup',
commandString: 'eventlog backup Security',
})
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('routes an aggregate request to the US-3 cloud', async () => {
fetchMock.mockResolvedValueOnce(jsonResponse({ resources: [] }))
const response = await POST(
createMockRequest('POST', {
clientId: 'client-id',
clientSecret: 'client-secret',
cloud: 'us-3',
operation: 'crowdstrike_query_sensors',
})
)
expect(response.status).toBe(200)
expect(String(fetchMock.mock.calls[0][0])).toBe('https://api.us-3.crowdstrike.com/oauth2/token')
expect(new URL(fetchMock.mock.calls[1][0]).host).toBe('api.us-3.crowdstrike.com')
})
it('rejects an aggregate query that names neither a field nor a type', async () => {
const response = await POST(
requestFor({
operation: 'crowdstrike_get_sensor_aggregates',
aggregateQuery: { size: 10 },
})
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('forwards the percents and filters_spec aggregate fields instead of stripping them', async () => {
fetchMock.mockResolvedValueOnce(jsonResponse({ resources: [] }))
const aggregateQuery = {
field: 'status',
name: 'by-status',
percents: [50, 95],
filters_spec: { filters: { stale: "status:'inactive'" }, other_bucket: true },
}
await POST(requestFor({ operation: 'crowdstrike_get_sensor_aggregates', aggregateQuery }))
expect(JSON.parse(fetchMock.mock.calls[1][1].body)).toEqual(aggregateQuery)
})
it('rejects a delete with neither ids nor a filter', async () => {
const response = await POST(requestFor({ operation: 'crowdstrike_delete_indicators' }))
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('requires a filter for Spotlight vulnerability queries', async () => {
const response = await POST(requestFor({ operation: 'crowdstrike_query_vulnerabilities' }))
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('returns Spotlight cursor pagination', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
meta: { pagination: { after: 'cursor-1', limit: 1, total: 12 } },
resources: ['vuln-1'],
})
)
const response = await POST(
requestFor({ operation: 'crowdstrike_query_vulnerabilities', filter: 'status:"open"' })
)
const data = await response.json()
expect(data.output).toEqual({
vulnerabilityIds: ['vuln-1'],
count: 1,
pagination: { after: 'cursor-1', limit: 1, total: 12 },
})
})
it('normalizes nested vulnerability details', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
resources: [
{
id: 'vuln-1',
aid: 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1',
status: 'open',
cve: {
id: 'CVE-2026-0001',
base_score: 9.8,
severity: 'CRITICAL',
cisa_info: { is_cisa_kev: true, due_date: '2026-09-01' },
},
host_info: { hostname: 'web-01', groups: [{ id: 'g1', name: 'SOC' }], tags: ['prod'] },
remediation: { ids: ['rem-1'], entities: [{ id: 'rem-1', title: 'Patch now' }] },
},
],
})
)
const response = await POST(
requestFor({
operation: 'crowdstrike_get_vulnerability_details',
vulnerabilityIds: ['vuln-1'],
})
)
const data = await response.json()
const vulnerability = data.output.vulnerabilities[0]
expect(vulnerability.cve).toMatchObject({
id: 'CVE-2026-0001',
baseScore: 9.8,
severity: 'CRITICAL',
isCisaKev: true,
cisaDueDate: '2026-09-01',
})
expect(vulnerability.hostInfo).toMatchObject({ hostname: 'web-01', groups: ['SOC'] })
expect(vulnerability.remediationIds).toEqual(['rem-1'])
expect(vulnerability.remediations[0]).toMatchObject({ id: 'rem-1', title: 'Patch now' })
})
it('opens and closes a Real Time Response session', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse(
{
resources: [
{
session_id: 'session-1',
device_id: 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1',
pwd: 'C:\\',
offline_queued: false,
existing_aid_sessions: 0,
created_at: '2026-08-15T00:00:00Z',
},
],
},
201
)
)
const initResponse = await POST(
requestFor({
operation: 'crowdstrike_init_rtr_session',
deviceId: 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1',
})
)
const initData = await initResponse.json()
expect(initData.output).toMatchObject({
sessionId: 'session-1',
deviceId: 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1',
pwd: 'C:\\',
})
expect(JSON.parse(fetchMock.mock.calls[1][1].body)).toEqual({
device_id: 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1',
})
fetchMock.mockResolvedValueOnce(jsonResponse({ access_token: 'token-123' }))
fetchMock.mockResolvedValueOnce(jsonResponse({ meta: {} }))
const deleteResponse = await POST(
requestFor({ operation: 'crowdstrike_delete_rtr_session', sessionId: 'session-1' })
)
const deleteData = await deleteResponse.json()
expect(deleteData.output).toMatchObject({ sessionId: 'session-1', deleted: true })
const deleteUrl = new URL(fetchMock.mock.calls[3][0])
expect(deleteUrl.pathname).toBe('/real-time-response/entities/sessions/v1')
expect(deleteUrl.searchParams.get('session_id')).toBe('session-1')
})
it('defaults the RTR command status sequence to zero', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
resources: [
{
session_id: 'session-1',
complete: true,
stdout: 'Directory listing',
stderr: '',
base_command: 'ls',
sequence_id: 0,
},
],
})
)
const response = await POST(
requestFor({ operation: 'crowdstrike_get_rtr_command_status', cloudRequestId: 'req-1' })
)
const data = await response.json()
expect(data.output).toMatchObject({ complete: true, stdout: 'Directory listing' })
const statusUrl = new URL(fetchMock.mock.calls[1][0])
expect(statusUrl.searchParams.get('cloud_request_id')).toBe('req-1')
expect(statusUrl.searchParams.get('sequence_id')).toBe('0')
})
it('normalizes Case Management case details', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
resources: [
{
id: 'case-1',
name: 'Suspicious login',
status: 'In Progress',
severity: 3,
severity_info: { level: 'High' },
reference_id: 'CASE-42',
assigned_to: { uuid: 'u-1', email: 'a@example.com', full_name: 'Analyst One' },
template: { id: 't-1', name: 'Triage' },
read_only: { is_read_only: false },
tags: ['phishing'],
},
],
})
)
const response = await POST(
requestFor({ operation: 'crowdstrike_get_case_details', caseIds: ['case-1'] })
)
const data = await response.json()
expect(data.output.cases[0]).toMatchObject({
id: 'case-1',
name: 'Suspicious login',
status: 'In Progress',
severity: 3,
severityLevel: 'High',
referenceId: 'CASE-42',
assignedTo: { uuid: 'u-1', email: 'a@example.com', fullName: 'Analyst One' },
templateName: 'Triage',
isReadOnly: false,
tags: ['phishing'],
})
expect(JSON.parse(fetchMock.mock.calls[1][1].body)).toEqual({ ids: ['case-1'] })
})
it('propagates a CrowdStrike error status', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({ errors: [{ code: 403, message: 'access denied' }] }, 403)
)
const response = await POST(
requestFor({ operation: 'crowdstrike_query_host_groups', filter: 'name:"SOC"' })
)
const data = await response.json()
expect(response.status).toBe(403)
expect(data).toEqual({ success: false, error: 'access denied' })
})
it('fails an alert query whose 200 envelope carries only errors', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
resources: [],
errors: [{ code: 403, id: null, message: 'insufficient scope' }],
})
)
const response = await POST(
requestFor({ operation: 'crowdstrike_query_alerts', filter: 'status:"new"' })
)
const data = await response.json()
expect(response.status).toBe(403)
expect(data).toEqual({ success: false, error: 'insufficient scope' })
})
it('fails a case query whose 200 envelope carries only errors', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
resources: [],
errors: [{ code: 500, id: null, message: 'case service unavailable' }],
})
)
const response = await POST(requestFor({ operation: 'crowdstrike_query_cases' }))
const data = await response.json()
expect(response.status).toBe(500)
expect(data).toEqual({ success: false, error: 'case service unavailable' })
})
it('still reports a genuinely empty alert query as a success', async () => {
fetchMock.mockResolvedValueOnce(jsonResponse({ resources: [], errors: [] }))
const response = await POST(
requestFor({ operation: 'crowdstrike_query_alerts', filter: 'status:"new"' })
)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.output.alertIds).toEqual([])
expect(data.output.count).toBe(0)
})
it('rejects an indicator query that combines offset and after pagination', async () => {
const response = await POST(
requestFor({ operation: 'crowdstrike_query_indicators', offset: 0, after: 'cursor-1' })
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('rejects a blank alert filter so Falcon never sees an empty FQL expression', async () => {
const response = await POST(
requestFor({ operation: 'crowdstrike_query_alerts', filter: ' ' })
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('rejects a blank sensor filter instead of sending an empty FQL expression', async () => {
const response = await POST(
requestFor({ operation: 'crowdstrike_query_sensors', filter: ' ' })
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('rejects a blank sensor sort instead of sending an empty sort expression', async () => {
const response = await POST(requestFor({ operation: 'crowdstrike_query_sensors', sort: '' }))
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('forwards trimmed sensor filter and sort values', async () => {
fetchMock.mockResolvedValueOnce(jsonResponse({ resources: [] }))
const response = await POST(
requestFor({
operation: 'crowdstrike_query_sensors',
filter: ' hostname:"dc-01" ',
sort: ' hostname.asc ',
})
)
expect(response.status).toBe(200)
const queryUrl = new URL(fetchMock.mock.calls[1][0])
expect(queryUrl.pathname).toBe('/identity-protection/queries/devices/v1')
expect(queryUrl.searchParams.get('filter')).toBe('hostname:"dc-01"')
expect(queryUrl.searchParams.get('sort')).toBe('hostname.asc')
})
it('fails an RTR session close whose 200 envelope reports an error', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({ meta: {}, errors: [{ code: 404, message: 'session not found' }] })
)
const response = await POST(
requestFor({ operation: 'crowdstrike_delete_rtr_session', sessionId: 'session-1' })
)
const data = await response.json()
expect(response.status).toBe(404)
expect(data.success).toBe(false)
expect(data.error).toBe('session not found')
})
it('fails a sensor query whose 200 envelope carries only errors', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
resources: [],
errors: [{ code: 403, message: 'access denied for Identity Protection' }],
})
)
const response = await POST(requestFor({ operation: 'crowdstrike_query_sensors' }))
const data = await response.json()
expect(response.status).toBe(403)
expect(data.success).toBe(false)
expect(data.error).toBe('access denied for Identity Protection')
})
it('fails a sensor detail lookup whose 200 envelope carries only errors', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({ resources: [], errors: [{ code: 403, message: 'access denied' }] })
)
const response = await POST(
requestFor({
operation: 'crowdstrike_get_sensor_details',
ids: ['a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1'],
})
)
const data = await response.json()
expect(response.status).toBe(403)
expect(data.success).toBe(false)
})
it('fails a sensor aggregate whose 200 envelope carries only errors', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({ resources: [], errors: [{ code: 403, message: 'access denied' }] })
)
const response = await POST(
requestFor({
operation: 'crowdstrike_get_sensor_aggregates',
aggregateQuery: { field: 'status', name: 'by_status', type: 'terms' },
})
)
const data = await response.json()
expect(response.status).toBe(403)
expect(data.success).toBe(false)
})
it('surfaces partial sensor errors alongside the sensors that resolved', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
resources: [{ device_id: 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1', hostname: 'dc-01' }],
errors: [
{ code: 404, id: 'b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2', message: 'sensor not found' },
],
})
)
const response = await POST(
requestFor({
operation: 'crowdstrike_get_sensor_details',
ids: ['a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1', 'b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2'],
})
)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.output.count).toBe(1)
expect(data.output.errors).toEqual([
{ code: 404, id: 'b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2', message: 'sensor not found' },
])
})
it('preserves a scalar aggregate bucket label', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({
resources: [{ name: 'by_status', buckets: [{ label: 'contained', count: 3 }] }],
})
)
const response = await POST(
requestFor({
operation: 'crowdstrike_get_sensor_aggregates',
aggregateQuery: { field: 'status', name: 'by_status', type: 'terms' },
})
)
const data = await response.json()
expect(data.output.aggregates[0].buckets[0].label).toBe('contained')
})
it('rejects an indicator payload whose blank field would clear stored data', async () => {
const response = await POST(
requestFor({
operation: 'crowdstrike_update_indicators',
indicators: [{ id: 'ioc-1', description: '' }],
})
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('rejects a host action targeting more than the documented 100 hosts', async () => {
const response = await POST(
requestFor({
operation: 'crowdstrike_perform_host_action',
actionName: 'contain',
deviceIds: Array.from({ length: 101 }, (_, index) => `aid-${index}`),
})
)
expect(response.status).toBe(400)
expect(fetchMock).toHaveBeenCalledTimes(0)
})
it('accepts the documented detection suppression host actions', async () => {
fetchMock.mockResolvedValueOnce(
jsonResponse({ resources: [{ id: 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1' }] })
)
const response = await POST(
requestFor({
operation: 'crowdstrike_perform_host_action',
actionName: 'detection_suppress',
deviceIds: ['a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1'],
})
)
expect(response.status).toBe(200)
expect(new URL(fetchMock.mock.calls[1][0]).searchParams.get('action_name')).toBe(
'detection_suppress'
)
})
})
@@ -0,0 +1,626 @@
import type { CrowdstrikeQueryBody } from '@/lib/api/contracts/tools/crowdstrike'
import {
type CrowdStrikeCallResult,
callCrowdStrike,
getBoolean,
getCursorPagination,
getEnvelopeErrors,
getFalconErrorMessage,
getFirstRecordResource,
getNumber,
getPagination,
getRecordResources,
getSpotlightPagination,
getString,
getStringResources,
} from '@/app/api/tools/crowdstrike/query/falcon'
import {
normalizeAffectedEntity,
normalizeAlert,
normalizeCase,
normalizeHostGroup,
normalizeIndicator,
normalizeVulnerability,
} from '@/app/api/tools/crowdstrike/query/normalize'
import type { CrowdStrikeActionParameter } from '@/tools/crowdstrike/types'
type ExtendedOperation = Exclude<
CrowdstrikeQueryBody['operation'],
| 'crowdstrike_query_sensors'
| 'crowdstrike_get_sensor_details'
| 'crowdstrike_get_sensor_aggregates'
>
type ExtendedBody = Extract<CrowdstrikeQueryBody, { operation: ExtendedOperation }>
export interface OperationFailure {
ok: false
status: number
error: string
}
export interface OperationSuccess {
ok: true
output: Record<string, unknown>
}
export type OperationResult = OperationSuccess | OperationFailure
/**
* CrowdStrike can answer 200 while the envelope carries only errors. Reporting
* that as HTTP 200 would read as a success, so fall back to the per-item error
* code the envelope supplies, and to 502 when it supplies none.
*/
export function failureStatus(result: CrowdStrikeCallResult): number {
if (!result.ok) {
return result.status
}
const envelopeCode = getEnvelopeErrors(result.data)[0]?.code
if (envelopeCode != null && envelopeCode >= 400 && envelopeCode <= 599) {
return envelopeCode
}
return 502
}
function fail(result: CrowdStrikeCallResult, fallback: string): OperationFailure {
return {
ok: false,
status: failureStatus(result),
error: getFalconErrorMessage(result.data, fallback),
}
}
/**
* CrowdStrike answers 200 with a populated `errors` array when only some IDs
* fail. Treat that as an outright failure only when nothing came back at all.
*/
export function failedWithoutResources(
result: CrowdStrikeCallResult,
resourceCount: number
): boolean {
return resourceCount === 0 && getEnvelopeErrors(result.data).length > 0
}
function buildAlertActionParameters(
body: Extract<ExtendedBody, { operation: 'crowdstrike_update_alerts' }>
) {
const parameters: CrowdStrikeActionParameter[] = []
const push = (name: string, value: string | undefined) => {
if (value !== undefined) {
parameters.push({ name, value })
}
}
push('update_status', body.updateStatus)
push('assign_to_uuid', body.assignToUuid)
push('assign_to_user_id', body.assignToUserId)
push('assign_to_name', body.assignToName)
push('append_comment', body.appendComment)
push('add_tag', body.addTag)
push('remove_tag', body.removeTag)
push('remove_tags_by_prefix', body.removeTagsByPrefix)
if (body.unassign === true) {
parameters.push({ name: 'unassign', value: '' })
}
if (body.showInUi !== undefined) {
parameters.push({ name: 'show_in_ui', value: String(body.showInUi) })
}
for (const parameter of body.actionParameters ?? []) {
parameters.push({ name: parameter.name, value: parameter.value })
}
return parameters
}
/**
* CrowdStrike's host-group action endpoint selects the hosts to add or remove
* with an FQL `device_id` filter rather than an ID list.
*/
function buildDeviceIdFilter(deviceIds: string[]): string {
const values = deviceIds.map((id) => `'${id.replaceAll("'", "\\'")}'`).join(',')
return `(device_id:[${values}])`
}
export async function executeCrowdStrikeOperation(
body: ExtendedBody,
baseUrl: string,
accessToken: string
): Promise<OperationResult> {
switch (body.operation) {
case 'crowdstrike_query_alerts': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'GET',
path: '/alerts/queries/alerts/v2',
query: {
filter: body.filter,
include_hidden: body.includeHidden,
limit: body.limit,
offset: body.offset,
q: body.q,
sort: body.sort,
},
})
if (!result.ok) return fail(result, 'Failed to query CrowdStrike alerts')
const alertIds = getStringResources(result.data)
if (failedWithoutResources(result, alertIds.length)) {
return fail(result, 'Failed to query CrowdStrike alerts')
}
return {
ok: true,
output: { alertIds, count: alertIds.length, pagination: getPagination(result.data) },
}
}
case 'crowdstrike_get_alert_details': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'POST',
path: '/alerts/entities/alerts/v2',
query: { include_hidden: body.includeHidden },
body: { composite_ids: body.compositeIds },
})
if (!result.ok) return fail(result, 'Failed to fetch CrowdStrike alert details')
const alerts = getRecordResources(result.data).map(normalizeAlert)
if (failedWithoutResources(result, alerts.length)) {
return fail(result, 'Failed to fetch CrowdStrike alert details')
}
return {
ok: true,
output: { alerts, count: alerts.length, errors: getEnvelopeErrors(result.data) },
}
}
case 'crowdstrike_update_alerts': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'PATCH',
path: '/alerts/entities/alerts/v3',
query: { include_hidden: body.includeHidden },
body: {
action_parameters: buildAlertActionParameters(body),
composite_ids: body.compositeIds,
},
})
if (!result.ok) return fail(result, 'Failed to update CrowdStrike alerts')
const errors = getEnvelopeErrors(result.data)
if (errors.length > 0) {
return fail(result, 'Failed to update CrowdStrike alerts')
}
return {
ok: true,
output: { updatedIds: body.compositeIds, count: body.compositeIds.length, errors },
}
}
case 'crowdstrike_perform_host_action': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'POST',
path: '/devices/entities/devices-actions/v2',
query: { action_name: body.actionName },
body: { ids: body.deviceIds },
})
if (!result.ok) return fail(result, 'Failed to perform CrowdStrike host action')
const affected = getRecordResources(result.data).map(normalizeAffectedEntity)
if (failedWithoutResources(result, affected.length)) {
return fail(result, 'Failed to perform CrowdStrike host action')
}
return {
ok: true,
output: { affected, count: affected.length, errors: getEnvelopeErrors(result.data) },
}
}
case 'crowdstrike_query_host_groups': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'GET',
path: '/devices/queries/host-groups/v1',
query: {
filter: body.filter,
limit: body.limit,
offset: body.offset,
sort: body.sort,
},
})
if (!result.ok) return fail(result, 'Failed to query CrowdStrike host groups')
const hostGroupIds = getStringResources(result.data)
if (failedWithoutResources(result, hostGroupIds.length)) {
return fail(result, 'Failed to query CrowdStrike host groups')
}
return {
ok: true,
output: {
hostGroupIds,
count: hostGroupIds.length,
pagination: getPagination(result.data),
},
}
}
case 'crowdstrike_get_host_group_details': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'GET',
path: '/devices/entities/host-groups/v1',
repeatedQuery: { ids: body.hostGroupIds },
})
if (!result.ok) return fail(result, 'Failed to fetch CrowdStrike host group details')
const hostGroups = getRecordResources(result.data).map(normalizeHostGroup)
if (failedWithoutResources(result, hostGroups.length)) {
return fail(result, 'Failed to fetch CrowdStrike host group details')
}
return {
ok: true,
output: {
hostGroups,
count: hostGroups.length,
errors: getEnvelopeErrors(result.data),
},
}
}
case 'crowdstrike_perform_host_group_action': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'POST',
path: '/devices/entities/host-group-actions/v1',
query: { action_name: body.actionName },
body: {
action_parameters: [{ name: 'filter', value: buildDeviceIdFilter(body.deviceIds) }],
ids: [body.hostGroupId],
},
})
if (!result.ok) return fail(result, 'Failed to perform CrowdStrike host group action')
const hostGroups = getRecordResources(result.data).map(normalizeHostGroup)
if (failedWithoutResources(result, hostGroups.length)) {
return fail(result, 'Failed to perform CrowdStrike host group action')
}
return {
ok: true,
output: {
hostGroups,
count: hostGroups.length,
errors: getEnvelopeErrors(result.data),
},
}
}
case 'crowdstrike_query_indicators': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'GET',
path: '/iocs/queries/indicators/v1',
query: {
after: body.after,
filter: body.filter,
limit: body.limit,
offset: body.offset,
sort: body.sort,
},
})
if (!result.ok) return fail(result, 'Failed to query CrowdStrike indicators')
const indicatorIds = getStringResources(result.data)
if (failedWithoutResources(result, indicatorIds.length)) {
return fail(result, 'Failed to query CrowdStrike indicators')
}
return {
ok: true,
output: {
indicatorIds,
count: indicatorIds.length,
pagination: getCursorPagination(result.data),
},
}
}
case 'crowdstrike_get_indicator_details': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'GET',
path: '/iocs/entities/indicators/v1',
repeatedQuery: { ids: body.indicatorIds },
})
if (!result.ok) return fail(result, 'Failed to fetch CrowdStrike indicator details')
const indicators = getRecordResources(result.data).map(normalizeIndicator)
if (failedWithoutResources(result, indicators.length)) {
return fail(result, 'Failed to fetch CrowdStrike indicator details')
}
return {
ok: true,
output: {
indicators,
count: indicators.length,
errors: getEnvelopeErrors(result.data),
},
}
}
case 'crowdstrike_create_indicators':
case 'crowdstrike_update_indicators': {
const isCreate = body.operation === 'crowdstrike_create_indicators'
const result = await callCrowdStrike(baseUrl, accessToken, {
method: isCreate ? 'POST' : 'PATCH',
path: '/iocs/entities/indicators/v1',
query: {
ignore_warnings: body.ignoreWarnings,
retrodetects: body.retrodetects,
},
body: {
comment: body.comment,
indicators: body.indicators,
},
})
if (!result.ok) {
return fail(
result,
isCreate
? 'Failed to create CrowdStrike indicators'
: 'Failed to update CrowdStrike indicators'
)
}
const indicators = getRecordResources(result.data).map(normalizeIndicator)
if (failedWithoutResources(result, indicators.length)) {
return fail(
result,
isCreate
? 'Failed to create CrowdStrike indicators'
: 'Failed to update CrowdStrike indicators'
)
}
return {
ok: true,
output: {
indicators,
count: indicators.length,
errors: getEnvelopeErrors(result.data),
},
}
}
case 'crowdstrike_delete_indicators': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'DELETE',
path: '/iocs/entities/indicators/v1',
query: { comment: body.comment, filter: body.filter },
repeatedQuery: { ids: body.filter ? undefined : body.indicatorIds },
})
if (!result.ok) return fail(result, 'Failed to delete CrowdStrike indicators')
const deletedIds = getStringResources(result.data)
if (failedWithoutResources(result, deletedIds.length)) {
return fail(result, 'Failed to delete CrowdStrike indicators')
}
return {
ok: true,
output: {
deletedIds,
count: deletedIds.length,
errors: getEnvelopeErrors(result.data),
},
}
}
case 'crowdstrike_query_vulnerabilities': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'GET',
path: '/spotlight/queries/vulnerabilities/v1',
query: {
after: body.after,
filter: body.filter,
limit: body.limit,
sort: body.sort,
},
})
if (!result.ok) return fail(result, 'Failed to query CrowdStrike vulnerabilities')
const vulnerabilityIds = getStringResources(result.data)
if (failedWithoutResources(result, vulnerabilityIds.length)) {
return fail(result, 'Failed to query CrowdStrike vulnerabilities')
}
return {
ok: true,
output: {
vulnerabilityIds,
count: vulnerabilityIds.length,
pagination: getSpotlightPagination(result.data),
},
}
}
case 'crowdstrike_get_vulnerability_details': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'GET',
path: '/spotlight/entities/vulnerabilities/v2',
repeatedQuery: { ids: body.vulnerabilityIds },
})
if (!result.ok) return fail(result, 'Failed to fetch CrowdStrike vulnerability details')
const vulnerabilities = getRecordResources(result.data).map(normalizeVulnerability)
if (failedWithoutResources(result, vulnerabilities.length)) {
return fail(result, 'Failed to fetch CrowdStrike vulnerability details')
}
return {
ok: true,
output: {
vulnerabilities,
count: vulnerabilities.length,
errors: getEnvelopeErrors(result.data),
},
}
}
case 'crowdstrike_init_rtr_session': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'POST',
path: '/real-time-response/entities/sessions/v1',
body: {
device_id: body.deviceId,
origin: body.origin,
queue_offline: body.queueOffline,
},
})
if (!result.ok) return fail(result, 'Failed to initialize CrowdStrike RTR session')
const session = getFirstRecordResource(result.data)
if (!session) return fail(result, 'CrowdStrike did not return an RTR session')
return {
ok: true,
output: {
sessionId: getString(session.session_id),
deviceId: getString(session.device_id),
platform: getString(session.platform),
pwd: getString(session.pwd),
offlineQueued: getBoolean(session.offline_queued),
existingAidSessions: getNumber(session.existing_aid_sessions),
createdAt: getString(session.created_at),
errors: getEnvelopeErrors(result.data),
},
}
}
case 'crowdstrike_execute_rtr_command': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'POST',
path: '/real-time-response/entities/command/v1',
body: {
base_command: body.baseCommand,
command_string: body.commandString,
session_id: body.sessionId,
},
})
if (!result.ok) return fail(result, 'Failed to execute CrowdStrike RTR command')
const command = getFirstRecordResource(result.data)
if (!command) return fail(result, 'CrowdStrike did not return an RTR command result')
return {
ok: true,
output: {
cloudRequestId: getString(command.cloud_request_id),
sessionId: getString(command.session_id),
queuedCommandOffline: getBoolean(command.queued_command_offline),
errors: getEnvelopeErrors(result.data),
},
}
}
case 'crowdstrike_get_rtr_command_status': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'GET',
path: '/real-time-response/entities/command/v1',
query: {
cloud_request_id: body.cloudRequestId,
sequence_id: body.sequenceId ?? 0,
},
})
if (!result.ok) return fail(result, 'Failed to fetch CrowdStrike RTR command status')
const status = getFirstRecordResource(result.data)
if (!status) return fail(result, 'CrowdStrike did not return an RTR command status')
return {
ok: true,
output: {
complete: getBoolean(status.complete),
stdout: getString(status.stdout),
stderr: getString(status.stderr),
baseCommand: getString(status.base_command),
sessionId: getString(status.session_id),
taskId: getString(status.task_id),
sequenceId: getNumber(status.sequence_id),
errors: getEnvelopeErrors(result.data),
},
}
}
case 'crowdstrike_delete_rtr_session': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'DELETE',
path: '/real-time-response/entities/sessions/v1',
query: { session_id: body.sessionId },
})
if (!result.ok) return fail(result, 'Failed to delete CrowdStrike RTR session')
const deleteErrors = getEnvelopeErrors(result.data)
if (deleteErrors.length > 0) {
return fail(result, 'Failed to delete CrowdStrike RTR session')
}
return {
ok: true,
output: {
sessionId: body.sessionId,
deleted: true,
errors: deleteErrors,
},
}
}
case 'crowdstrike_query_cases': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'GET',
path: '/cases/queries/cases/v1',
query: {
filter: body.filter,
limit: body.limit,
offset: body.offset,
q: body.q,
sort: body.sort,
},
})
if (!result.ok) return fail(result, 'Failed to query CrowdStrike cases')
const caseIds = getStringResources(result.data)
if (failedWithoutResources(result, caseIds.length)) {
return fail(result, 'Failed to query CrowdStrike cases')
}
return {
ok: true,
output: { caseIds, count: caseIds.length, pagination: getPagination(result.data) },
}
}
case 'crowdstrike_get_case_details': {
const result = await callCrowdStrike(baseUrl, accessToken, {
method: 'POST',
path: '/cases/entities/cases/v2',
body: { ids: body.caseIds },
})
if (!result.ok) return fail(result, 'Failed to fetch CrowdStrike case details')
const cases = getRecordResources(result.data).map(normalizeCase)
if (failedWithoutResources(result, cases.length)) {
return fail(result, 'Failed to fetch CrowdStrike case details')
}
return {
ok: true,
output: { cases, count: cases.length, errors: getEnvelopeErrors(result.data) },
}
}
}
}
@@ -113,6 +113,7 @@ describe('CrowdStrike query route', () => {
})
expect(data.output).toEqual({
count: 1,
errors: [],
pagination: {
limit: 1,
offset: 0,
@@ -153,6 +154,7 @@ describe('CrowdStrike query route', () => {
})
expect(data.output).toEqual({
count: 1,
errors: [],
pagination: null,
sensors: [normalizedSensor],
})
@@ -266,6 +268,7 @@ describe('CrowdStrike query route', () => {
},
],
count: 1,
errors: [],
})
})
})
+143 -233
View File
@@ -1,187 +1,38 @@
import { createLogger } from '@sim/logger'
import { toError } from '@sim/utils/errors'
import { generateId } from '@sim/utils/id'
import { isRecordLike } from '@sim/utils/object'
import { type NextRequest, NextResponse } from 'next/server'
import { crowdstrikeQueryContract } from '@/lib/api/contracts/tools/crowdstrike'
import { getValidationErrorMessage, parseRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import {
type CrowdStrikeCallResult,
callCrowdStrike,
getAccessToken,
getCloudBaseUrl,
getEnvelopeErrors,
getFalconErrorMessage,
getNumber,
getPagination,
getRecordArray,
getRecordResources,
getString,
getStringArray,
getStringResources,
type JsonRecord,
} from '@/app/api/tools/crowdstrike/query/falcon'
import {
executeCrowdStrikeOperation,
failedWithoutResources,
failureStatus,
} from '@/app/api/tools/crowdstrike/query/operations'
import type {
CrowdStrikeAggregateQuery,
CrowdStrikeBaseParams,
CrowdStrikeCloud,
CrowdStrikeQuerySensorsParams,
CrowdStrikeSensorAggregateBucket,
CrowdStrikeSensorAggregateResult,
} from '@/tools/crowdstrike/types'
const logger = createLogger('CrowdStrikeIdentityProtectionAPI')
type JsonRecord = Record<string, unknown>
function getCloudBaseUrl(cloud: CrowdStrikeCloud): string {
const cloudMap: Record<CrowdStrikeCloud, string> = {
'eu-1': 'https://api.eu-1.crowdstrike.com',
'us-1': 'https://api.crowdstrike.com',
'us-2': 'https://api.us-2.crowdstrike.com',
'us-gov-1': 'https://api.laggar.gcw.crowdstrike.com',
'us-gov-2': 'https://api.us-gov-2.crowdstrike.mil',
}
return cloudMap[cloud]
}
function getString(value: unknown): string | null {
return typeof value === 'string' ? value : null
}
function getNumber(value: unknown): number | null {
return typeof value === 'number' ? value : null
}
function getStringArray(value: unknown): string[] {
if (!Array.isArray(value)) {
return []
}
return value.filter((entry): entry is string => typeof entry === 'string')
}
function getRecordArray(value: unknown): JsonRecord[] {
if (!Array.isArray(value)) {
return []
}
return value.filter(isRecordLike)
}
function getResourcesArray(data: unknown): unknown[] {
const root = getResponseRoot(data)
if (!isRecordLike(root) || !Array.isArray(root.resources)) {
return []
}
return root.resources
}
function getRecordResources(data: unknown): JsonRecord[] {
return getResourcesArray(data).filter(isRecordLike)
}
function getStringResources(data: unknown): string[] {
return getStringArray(getResourcesArray(data))
}
function getResponseRoot(data: unknown): unknown {
if (!isRecordLike(data)) {
return null
}
if (isRecordLike(data.body)) {
return data.body
}
return data
}
function getPagination(data: unknown) {
const root = getResponseRoot(data)
if (!isRecordLike(root) || !isRecordLike(root.meta) || !isRecordLike(root.meta.pagination)) {
return null
}
return {
limit: getNumber(root.meta.pagination.limit),
offset: getNumber(root.meta.pagination.offset),
total: getNumber(root.meta.pagination.total),
}
}
function getErrorMessage(data: unknown, fallback: string): string {
if (!isRecordLike(data)) {
return fallback
}
const errors = Array.isArray(data.errors) ? data.errors : []
const firstError = errors[0]
if (isRecordLike(firstError)) {
const firstMessage = getString(firstError.message) ?? getString(firstError.code)
if (firstMessage) {
return firstMessage
}
}
return (
getString(data.message) ??
getString(data.error_description) ??
getString(data.error) ??
fallback
)
}
function buildQueryUrl(baseUrl: string, params: CrowdStrikeQuerySensorsParams): string {
const url = new URL(baseUrl)
url.pathname = '/identity-protection/queries/devices/v1'
if (params.filter) {
url.searchParams.set('filter', params.filter)
}
if (params.limit != null) {
url.searchParams.set('limit', params.limit.toString())
}
if (params.offset != null) {
url.searchParams.set('offset', params.offset.toString())
}
if (params.sort) {
url.searchParams.set('sort', params.sort)
}
return url.toString()
}
function buildSensorDetailsUrl(baseUrl: string): string {
const url = new URL(baseUrl)
url.pathname = '/identity-protection/entities/devices/GET/v1'
return url.toString()
}
function buildSensorAggregatesUrl(baseUrl: string): string {
const url = new URL(baseUrl)
url.pathname = '/identity-protection/aggregates/devices/GET/v1'
return url.toString()
}
async function getAccessToken(params: CrowdStrikeBaseParams): Promise<string> {
const baseUrl = getCloudBaseUrl(params.cloud)
const response = await fetch(`${baseUrl}/oauth2/token`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: params.clientId,
client_secret: params.clientSecret,
grant_type: 'client_credentials',
}).toString(),
cache: 'no-store',
})
const data: unknown = await response.json().catch(() => null)
if (!response.ok) {
throw new Error(getErrorMessage(data, 'Failed to authenticate with CrowdStrike'))
}
if (!isRecordLike(data) || typeof data.access_token !== 'string') {
throw new Error('CrowdStrike authentication did not return an access token')
}
return data.access_token
}
const logger = createLogger('CrowdStrikeAPI')
function normalizeSensor(resource: JsonRecord) {
return {
@@ -212,11 +63,32 @@ function normalizeSensorsOutput(data: unknown, paginationData?: unknown) {
return {
count: sensors.length,
errors: getEnvelopeErrors(data),
pagination: paginationData == null ? null : getPagination(paginationData),
sensors,
}
}
/**
* CrowdStrike answers 200 while the envelope carries only errors. Mirrors the
* shared operation executor so the Identity Protection branches cannot report a
* resource-less error envelope as a success.
*/
function envelopeFailureResponse(
result: CrowdStrikeCallResult,
resourceCount: number,
fallback: string
) {
if (!failedWithoutResources(result, resourceCount)) {
return null
}
return NextResponse.json(
{ success: false, error: getFalconErrorMessage(result.data, fallback) },
{ status: failureStatus(result) }
)
}
function normalizeAggregationResult(resource: JsonRecord): CrowdStrikeSensorAggregateResult {
return {
buckets: getRecordArray(resource.buckets).map(normalizeAggregationBucket),
@@ -231,7 +103,7 @@ function normalizeAggregationBucket(resource: JsonRecord): CrowdStrikeSensorAggr
count: getNumber(resource.count),
from: getNumber(resource.from),
keyAsString: getString(resource.key_as_string),
label: isRecordLike(resource.label) ? resource.label : null,
label: resource.label ?? null,
stringFrom: getString(resource.string_from),
stringTo: getString(resource.string_to),
subAggregates: getRecordArray(resource.sub_aggregates).map(normalizeAggregationResult),
@@ -247,29 +119,25 @@ function normalizeAggregatesOutput(data: unknown) {
return {
aggregates,
count: aggregates.length,
errors: getEnvelopeErrors(data),
}
}
async function postCrowdStrikeJson(
url: string,
accessToken: string,
body: JsonRecord | CrowdStrikeAggregateQuery
) {
return fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
cache: 'no-store',
})
function sensorQuery(params: CrowdStrikeQuerySensorsParams) {
return {
filter: params.filter,
limit: params.limit,
offset: params.offset,
sort: params.sort,
}
}
/**
* Special route: this proxies workflow tool calls to CrowdStrike Falcon with the
* caller's own API credentials, so it authenticates through `checkInternalAuth`
* rather than an application use case and uses raw `withRouteHandler`.
*/
export const POST = withRouteHandler(async (request: NextRequest) => {
const requestId = generateId().slice(0, 8)
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
if (!authResult.success) {
return NextResponse.json(
@@ -300,111 +168,153 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
const baseUrl = getCloudBaseUrl(params.cloud)
const accessToken = await getAccessToken(params)
logger.info(`[${requestId}] CrowdStrike request`, {
logger.info('CrowdStrike request', {
cloud: params.cloud,
operation: params.operation,
})
if (params.operation === 'crowdstrike_query_sensors') {
const queryResponse = await fetch(buildQueryUrl(baseUrl, params), {
const queryResponse = await callCrowdStrike(baseUrl, accessToken, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
cache: 'no-store',
path: '/identity-protection/queries/devices/v1',
query: sensorQuery(params),
})
const queryData: unknown = await queryResponse.json().catch(() => null)
if (!queryResponse.ok) {
return NextResponse.json(
{
success: false,
error: getErrorMessage(queryData, 'CrowdStrike request failed'),
error: getFalconErrorMessage(queryResponse.data, 'CrowdStrike request failed'),
},
{ status: queryResponse.status }
)
}
const ids = getStringResources(queryData)
const ids = getStringResources(queryResponse.data)
const queryFailure = envelopeFailureResponse(
queryResponse,
ids.length,
'Failed to query CrowdStrike sensors'
)
if (queryFailure) return queryFailure
if (ids.length === 0) {
return NextResponse.json({
success: true,
output: normalizeSensorsOutput({ resources: [] }, queryData),
output: normalizeSensorsOutput({ resources: [] }, queryResponse.data),
})
}
const detailResponse = await postCrowdStrikeJson(
buildSensorDetailsUrl(baseUrl),
accessToken,
{ ids }
)
const detailResponse = await callCrowdStrike(baseUrl, accessToken, {
method: 'POST',
path: '/identity-protection/entities/devices/GET/v1',
body: { ids },
})
const detailData: unknown = await detailResponse.json().catch(() => null)
if (!detailResponse.ok) {
return NextResponse.json(
{
success: false,
error: getErrorMessage(detailData, 'Failed to fetch CrowdStrike sensor details'),
error: getFalconErrorMessage(
detailResponse.data,
'Failed to fetch CrowdStrike sensor details'
),
},
{ status: detailResponse.status }
)
}
const detailFailure = envelopeFailureResponse(
detailResponse,
getRecordResources(detailResponse.data).length,
'Failed to fetch CrowdStrike sensor details'
)
if (detailFailure) return detailFailure
return NextResponse.json({
success: true,
output: normalizeSensorsOutput(detailData, queryData),
output: normalizeSensorsOutput(detailResponse.data, queryResponse.data),
})
}
if (params.operation === 'crowdstrike_get_sensor_details') {
const detailResponse = await postCrowdStrikeJson(
buildSensorDetailsUrl(baseUrl),
accessToken,
{ ids: params.ids }
)
const detailResponse = await callCrowdStrike(baseUrl, accessToken, {
method: 'POST',
path: '/identity-protection/entities/devices/GET/v1',
body: { ids: params.ids },
})
const detailData: unknown = await detailResponse.json().catch(() => null)
if (!detailResponse.ok) {
return NextResponse.json(
{
success: false,
error: getErrorMessage(detailData, 'Failed to fetch CrowdStrike sensor details'),
error: getFalconErrorMessage(
detailResponse.data,
'Failed to fetch CrowdStrike sensor details'
),
},
{ status: detailResponse.status }
)
}
const detailsFailure = envelopeFailureResponse(
detailResponse,
getRecordResources(detailResponse.data).length,
'Failed to fetch CrowdStrike sensor details'
)
if (detailsFailure) return detailsFailure
return NextResponse.json({
success: true,
output: normalizeSensorsOutput(detailData),
output: normalizeSensorsOutput(detailResponse.data),
})
}
const aggregateResponse = await postCrowdStrikeJson(
buildSensorAggregatesUrl(baseUrl),
accessToken,
params.aggregateQuery
)
if (params.operation === 'crowdstrike_get_sensor_aggregates') {
const aggregateResponse = await callCrowdStrike(baseUrl, accessToken, {
method: 'POST',
path: '/identity-protection/aggregates/devices/GET/v1',
body: params.aggregateQuery,
})
const aggregateData: unknown = await aggregateResponse.json().catch(() => null)
if (!aggregateResponse.ok) {
if (!aggregateResponse.ok) {
return NextResponse.json(
{
success: false,
error: getFalconErrorMessage(
aggregateResponse.data,
'Failed to fetch CrowdStrike sensor aggregates'
),
},
{ status: aggregateResponse.status }
)
}
const aggregateFailure = envelopeFailureResponse(
aggregateResponse,
getRecordResources(aggregateResponse.data).length,
'Failed to fetch CrowdStrike sensor aggregates'
)
if (aggregateFailure) return aggregateFailure
return NextResponse.json({
success: true,
output: normalizeAggregatesOutput(aggregateResponse.data),
})
}
const result = await executeCrowdStrikeOperation(params, baseUrl, accessToken)
if (!result.ok) {
return NextResponse.json(
{
success: false,
error: getErrorMessage(aggregateData, 'Failed to fetch CrowdStrike sensor aggregates'),
},
{ status: aggregateResponse.status }
{ success: false, error: result.error },
{ status: result.status || 502 }
)
}
return NextResponse.json({
success: true,
output: normalizeAggregatesOutput(aggregateData),
})
return NextResponse.json({ success: true, output: result.output })
} catch (error) {
const message = toError(error).message
logger.error(`[${requestId}] CrowdStrike request failed`, { error: message })
logger.error('CrowdStrike request failed', { error: message })
return NextResponse.json({ success: false, error: message }, { status: 500 })
}
})
+226
View File
@@ -0,0 +1,226 @@
/**
* @vitest-environment node
*/
import fs from 'node:fs'
import path from 'node:path'
import { describe, expect, it } from 'vitest'
import { CrowdStrikeBlock } from '@/blocks/blocks/crowdstrike'
/**
* `buildToolDescriptionMap` in `scripts/generate-docs.ts` searches only the 600
* characters that follow a tool's `id:` for its `name:` and `description:`. A
* description whose closing quote falls outside that window does not fail the
* build it silently publishes as an empty string in `integrations.json` and in
* the generated MDX.
*/
const DOCS_GENERATOR_ID_WINDOW = 600
/** Leave headroom so a small wording edit cannot silently cross the window. */
const DESCRIPTION_SPAN_BUDGET = DOCS_GENERATOR_ID_WINDOW - 40
const mapParams = CrowdStrikeBlock.tools.config?.params
if (!mapParams) {
throw new Error('CrowdStrike block must define tools.config.params')
}
const credentials = {
clientId: 'client-id',
clientSecret: 'client-secret',
cloud: 'us-1',
}
/**
* The executor merges the mapped params over the raw block inputs
* (`{ ...inputs, ...transformedParams }`), so a key the mapper omits keeps its raw
* subBlock value. Every assertion here runs against the merged result, because a
* mapper-only assertion passes even when the raw value survives onto the wire.
*/
function merge(inputs: Record<string, unknown>) {
return { ...inputs, ...mapParams(inputs) }
}
describe('CrowdStrike block params', () => {
it('drops untouched optional subBlocks instead of forwarding their stored null', () => {
const merged = merge({
...credentials,
operation: 'crowdstrike_query_alerts',
filter: null,
q: null,
limit: null,
offset: null,
sort: null,
includeHidden: null,
})
expect(merged.filter).toBeUndefined()
expect(merged.q).toBeUndefined()
expect(merged.limit).toBeUndefined()
expect(merged.offset).toBeUndefined()
expect(merged.sort).toBeUndefined()
expect(merged.includeHidden).toBeUndefined()
})
it('drops a blank alert update field rather than sending an empty action value', () => {
const merged = merge({
...credentials,
operation: 'crowdstrike_update_alerts',
compositeIds: '["cid:aid:alert"]',
updateStatus: 'closed',
assignToUuid: null,
appendComment: '',
addTag: null,
})
expect(merged.updateStatus).toBe('closed')
expect(merged.assignToUuid).toBeUndefined()
expect(merged.appendComment).toBeUndefined()
expect(merged.addTag).toBeUndefined()
})
it('clears an advanced value left over from another operation', () => {
const merged = merge({
...credentials,
operation: 'crowdstrike_init_rtr_session',
deviceId: 'aid-1',
includeHidden: 'true',
q: 'stale free-text search',
after: 'stale-cursor',
updateStatus: 'closed',
})
expect(merged.deviceId).toBe('aid-1')
expect(merged.includeHidden).toBeUndefined()
expect(merged.q).toBeUndefined()
expect(merged.after).toBeUndefined()
expect(merged.updateStatus).toBeUndefined()
})
it('sends the free-text search only to the operations that accept it', () => {
expect(
merge({ ...credentials, operation: 'crowdstrike_query_alerts', q: 'ransomware' }).q
).toBe('ransomware')
expect(
merge({ ...credentials, operation: 'crowdstrike_query_host_groups', q: 'ransomware' }).q
).toBeUndefined()
})
it('sends the after cursor only to the cursor-paginated collections', () => {
expect(
merge({ ...credentials, operation: 'crowdstrike_query_indicators', after: 'cursor-1' }).after
).toBe('cursor-1')
expect(
merge({ ...credentials, operation: 'crowdstrike_query_alerts', after: 'cursor-1' }).after
).toBeUndefined()
})
it('never sends an offset to Spotlight, which paginates by cursor only', () => {
const merged = merge({
...credentials,
operation: 'crowdstrike_query_vulnerabilities',
filter: "status:'open'",
offset: '100',
})
expect(merged.filter).toBe("status:'open'")
expect(merged.offset).toBeUndefined()
})
it('keeps the alert filter out of a destructive indicator delete', () => {
const merged = merge({
...credentials,
operation: 'crowdstrike_delete_indicators',
indicatorIds: '["ioc-1"]',
filter: "status:'new'",
deleteFilter: null,
})
expect(merged.indicatorIds).toEqual(['ioc-1'])
expect(merged.filter).toBeUndefined()
})
it('forwards the dedicated delete filter', () => {
const merged = merge({
...credentials,
operation: 'crowdstrike_delete_indicators',
deleteFilter: "type:'sha256'",
})
expect(merged.filter).toBe("type:'sha256'")
})
it('rejects an IOC limit above the documented maximum of 500', () => {
expect(() =>
merge({ ...credentials, operation: 'crowdstrike_query_indicators', limit: '2000' })
).toThrow(/500/)
})
it('offers only the documented read-tier RTR base command families', () => {
const baseCommand = CrowdStrikeBlock.subBlocks.find((subBlock) => subBlock.id === 'baseCommand')
const ids = (baseCommand?.options as { id: string }[] | undefined)?.map((option) => option.id)
expect(ids).toEqual([
'cat',
'cd',
'clear',
'csrutil',
'env',
'eventlog',
'filehash',
'getsid',
'help',
'history',
'ipconfig',
'ls',
'mount',
'netstat',
'ps',
'reg',
])
})
it('offers no write-tier RTR base command under the read-scoped tool', () => {
const baseCommand = CrowdStrikeBlock.subBlocks.find((subBlock) => subBlock.id === 'baseCommand')
const ids = (baseCommand?.options as { id: string }[] | undefined)?.map((option) => option.id)
for (const writeTier of ['eventlog backup', 'eventlog export', 'put', 'get', 'runscript']) {
expect(ids).not.toContain(writeTier)
}
})
it('exposes every CrowdStrike commercial and GovCloud region', () => {
const cloud = CrowdStrikeBlock.subBlocks.find((subBlock) => subBlock.id === 'cloud')
const ids = (cloud?.options as { id: string }[] | undefined)?.map((option) => option.id)
expect(ids).toEqual(['us-1', 'us-2', 'us-3', 'eu-1', 'us-gov-1', 'us-gov-2'])
})
it('does not preselect the network-isolating host action', () => {
const hostAction = CrowdStrikeBlock.subBlocks.find(
(subBlock) => subBlock.id === 'hostActionName'
)
const ids = (hostAction?.options as { id: string }[] | undefined)?.map((option) => option.id)
expect(hostAction?.value).toBeUndefined()
expect(ids).toContain('detection_suppress')
expect(ids).toContain('detection_unsuppress')
})
it('keeps every tool description inside the docs generator id-search window', () => {
const toolsDir = path.join(__dirname, '../../tools/crowdstrike')
const offenders: string[] = []
for (const file of fs.readdirSync(toolsDir)) {
if (file === 'index.ts' || file === 'types.ts') continue
const source = fs.readFileSync(path.join(toolsDir, file), 'utf-8')
const idIndex = source.search(/\bid\s*:\s*'crowdstrike_/)
const descriptionEnd = source.indexOf("',", source.indexOf('description:'))
if (idIndex < 0 || descriptionEnd < 0) continue
const span = descriptionEnd + 2 - idIndex
if (span > DESCRIPTION_SPAN_BUDGET) {
offenders.push(`${file} (${span} chars)`)
}
}
expect(offenders).toEqual([])
})
})
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+87 -7
View File
@@ -4561,32 +4561,112 @@
"type": "crowdstrike",
"slug": "crowdstrike",
"name": "CrowdStrike",
"description": "Query CrowdStrike Identity Protection sensors and documented aggregates",
"longDescription": "Integrate CrowdStrike Identity Protection into workflows to search sensors, fetch documented sensor details by device ID, and run documented sensor aggregate queries.",
"description": "Investigate and respond to CrowdStrike Falcon alerts, hosts, IOCs, and vulnerabilities",
"longDescription": "Integrate CrowdStrike Falcon into workflows to triage alerts, contain hosts, manage host groups and custom indicators of compromise, review Spotlight vulnerabilities, run read-only Real Time Response commands, read Case Management cases, and query Identity Protection sensors.",
"bgColor": "#E01F3D",
"iconName": "CrowdStrikeIcon",
"docsUrl": "https://docs.sim.ai/integrations/crowdstrike",
"operations": [
{
"name": "Query Alerts",
"description": "Search CrowdStrike Falcon alerts with a Falcon Query Language filter and return their composite IDs. Uses the current Alerts API (GET /alerts/queries/alerts/v2), which supersedes the deprecated Detects API. Requires the \"Alerts: Read\" API scope."
},
{
"name": "Get Alert Details",
"description": "Get full CrowdStrike Falcon alert records for one or more composite alert IDs (POST /alerts/entities/alerts/v2). Requires the \"Alerts: Read\" API scope."
},
{
"name": "Update Alerts",
"description": "Update CrowdStrike Falcon alerts by composite ID: change status, assign or unassign an analyst, add or remove tags, append a comment, or toggle visibility (PATCH /alerts/entities/alerts/v3). This modifies live alerts in the Falcon console. Requires the \"Alerts: Write\" API scope."
},
{
"name": "Perform Host Action",
"description": "Act on CrowdStrike Falcon hosts (POST /devices/entities/devices-actions/v2). Actions: contain, lift_containment, hide_host, unhide_host, detection_suppress, detection_unsuppress. contain network-isolates the host so it can only reach the Falcon cloud; hide_host removes the host record from the console. Both are immediately disruptive. Up to 100 host IDs per call. Requires the \"Hosts: Write\" API scope."
},
{
"name": "Query Host Groups",
"description": "Search CrowdStrike Falcon host groups with a Falcon Query Language filter and return their IDs (GET /devices/queries/host-groups/v1). Requires the \"Host groups: Read\" API scope."
},
{
"name": "Get Host Group Details",
"description": "Get CrowdStrike Falcon host group records for one or more group IDs (GET /devices/entities/host-groups/v1). Requires the \"Host groups: Read\" API scope."
},
{
"name": "Perform Host Group Action",
"description": "Add hosts to or remove hosts from a CrowdStrike Falcon static host group (POST /devices/entities/host-group-actions/v1). Group membership drives policy assignment, so changing it changes which policies apply to those hosts. Requires the \"Host groups: Write\" API scope."
},
{
"name": "Query Indicators",
"description": "Search custom CrowdStrike Falcon indicators of compromise (IOCs) with a Falcon Query Language filter and return their IDs (GET /iocs/queries/indicators/v1). Requires the \"IOC Management: Read\" API scope."
},
{
"name": "Get Indicator Details",
"description": "Get custom CrowdStrike Falcon indicator of compromise (IOC) records for one or more IOC IDs (GET /iocs/entities/indicators/v1). Requires the \"IOC Management: Read\" API scope."
},
{
"name": "Create Indicators",
"description": "Create custom CrowdStrike Falcon indicators of compromise (POST /iocs/entities/indicators/v1). Each indicator can allow, detect, or block activity across the fleet, so a wrong value can suppress detections or break legitimate software. Requires the \"IOC Management: Write\" API scope."
},
{
"name": "Update Indicators",
"description": "Update custom CrowdStrike Falcon indicators of compromise by ID (PATCH /iocs/entities/indicators/v1). DESTRUCTIVE: CrowdStrike blanks out any field you omit, so read each indicator with crowdstrike_get_indicator_details first and resend its full field set with your edits applied. Changing action or scope changes prevention behavior fleet-wide. type and value are immutable. Requires the \"IOC Management: Write\" API scope."
},
{
"name": "Delete Indicators",
"description": "Permanently delete custom CrowdStrike Falcon indicators of compromise (DELETE /iocs/entities/indicators/v1). Cannot be undone; deleting a blocking indicator removes that protection from every host, and a broad filter can delete far more than intended. Supply an ID list or a filter, never both -- CrowdStrike lets a filter silently override the IDs, so this tool rejects that instead. Requires the \"IOC Management: Write\" API scope."
},
{
"name": "Query Vulnerabilities",
"description": "Search CrowdStrike Falcon Spotlight vulnerabilities with a required Falcon Query Language filter and return their IDs (GET /spotlight/queries/vulnerabilities/v1). Requires the spotlight-vulnerabilities:read API scope, shown as \"Vulnerabilities: Read\" in the Falcon API client UI."
},
{
"name": "Get Vulnerability Details",
"description": "Get CrowdStrike Falcon Spotlight vulnerability records for one or more vulnerability IDs, including CVE, affected host, application, and remediation details (GET /spotlight/entities/vulnerabilities/v2). Requires the spotlight-vulnerabilities:read API scope, shown as \"Vulnerabilities: Read\" in the Falcon API client UI."
},
{
"name": "Init RTR Session",
"description": "Open a CrowdStrike Falcon Real Time Response session against a host so read-only commands can be run on it (POST /real-time-response/entities/sessions/v1). This connects a live remote shell to the endpoint. Requires the \"Real time response: Read\" API scope."
},
{
"name": "Execute RTR Command",
"description": "Run a read-only Real Time Response command in an open CrowdStrike Falcon session (POST /real-time-response/entities/command/v1). baseCommand names the family only (cat, cd, clear, csrutil, env, eventlog, filehash, getsid, help, history, ipconfig, ls, mount, netstat, ps, reg); subcommands go in commandString. Host-modifying commands need the Active Responder or Admin endpoints. Requires the \"Real time response: Read\" API scope."
},
{
"name": "Get RTR Command Status",
"description": "Get the status and output of a Real Time Response command by cloud request ID (GET /real-time-response/entities/command/v1). Long output is chunked across sequences, so increment the sequence ID to read the next chunk. Requires the \"Real time response: Read\" API scope."
},
{
"name": "Delete RTR Session",
"description": "Close an open CrowdStrike Falcon Real Time Response session (DELETE /real-time-response/entities/sessions/v1). Requires the \"Real time response: Read\" API scope."
},
{
"name": "Query Cases",
"description": "Search CrowdStrike Falcon Case Management cases with a Falcon Query Language filter and return their IDs (GET /cases/queries/cases/v1). Case Management supersedes the CrowdScore Incidents API, which CrowdStrike has removed from its published API spec. Requires the \"Cases: Read\" API scope."
},
{
"name": "Get Case Details",
"description": "Get CrowdStrike Falcon Case Management case records for one or more case IDs (POST /cases/entities/cases/v2). Requires the \"Cases: Read\" API scope."
},
{
"name": "Query Sensors",
"description": "Search CrowdStrike identity protection sensors by hostname, IP, or related fields"
"description": "Search CrowdStrike Identity Protection sensors -- the domain controllers Falcon Identity Protection monitors, not Falcon endpoint sensors -- and return their device IDs (GET /identity-protection/queries/devices/v1). Sort uses the dot form, for example status.desc. Requires the \"Identity Protection Entities: Read\" API scope, a separate entitlement from Hosts and Alerts."
},
{
"name": "Get Sensor Details",
"description": "Get documented CrowdStrike Identity Protection sensor details for one or more device IDs"
"description": "Get CrowdStrike Identity Protection sensor details for one or more device IDs (POST /identity-protection/entities/devices/GET/v1). These are the domain controllers Falcon Identity Protection monitors, not Falcon endpoint sensors. Requires the \"Identity Protection Entities: Read\" API scope."
},
{
"name": "Get Sensor Aggregates",
"description": "Get documented CrowdStrike Identity Protection sensor aggregates from a JSON aggregate query body"
"description": "Aggregate CrowdStrike Identity Protection sensors from a JSON aggregate query body (POST /identity-protection/aggregates/devices/GET/v1). These are the domain controllers Falcon Identity Protection monitors, not Falcon endpoint sensors. Requires the \"Identity Protection Entities: Read\" API scope."
}
],
"operationCount": 3,
"operationCount": 23,
"triggers": [],
"triggerCount": 0,
"authType": "api-key",
"category": "tools",
"integrationType": "security",
"tags": ["identity", "monitoring"]
"tags": ["identity", "monitoring", "incident-management", "automation"]
},
{
"type": "cursor_v2",
@@ -0,0 +1,226 @@
import type {
CrowdStrikeCreateIndicatorsParams,
CrowdStrikeCreateIndicatorsResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeCreateIndicatorsTool: ToolConfig<
CrowdStrikeCreateIndicatorsParams,
CrowdStrikeCreateIndicatorsResponse
> = {
id: 'crowdstrike_create_indicators',
name: 'CrowdStrike Create Indicators',
description:
'Create custom CrowdStrike Falcon indicators of compromise (POST /iocs/entities/indicators/v1). Each indicator can allow, detect, or block activity across the fleet, so a wrong value can suppress detections or break legitimate software. Requires the "IOC Management: Write" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
indicators: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'JSON array of indicators to create. Each entry requires type, value, and applied_globally (boolean). type is one of sha256, md5, domain, ipv4, ipv6; action is one of no_action, allow, prevent_no_ui, prevent, detect; severity is one of informational, low, medium, high, critical; platforms entries are windows, mac, or linux. Other documented fields: host_groups (array), description, source, tags (array), expiration (ISO 8601), mobile_action, metadata ({ filename }). Either applied_globally must be true or host_groups must be supplied. Tenants can extend these value sets, so treat them as the documented defaults rather than a closed list.',
},
comment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Audit comment explaining why these indicators were created',
},
retrodetects: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to generate retroactive detections for the new indicators',
},
ignoreWarnings: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to create the indicators even when CrowdStrike returns warnings',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
comment: params.comment,
ignoreWarnings: params.ignoreWarnings,
indicators: params.indicators,
operation: 'crowdstrike_create_indicators',
retrodetects: params.retrodetects,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to create CrowdStrike indicators')
}
return {
success: true,
output: data.output,
}
},
outputs: {
indicators: {
type: 'array',
description: 'Created CrowdStrike indicator records',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Indicator identifier', optional: true },
type: { type: 'string', description: 'Indicator type', optional: true },
value: { type: 'string', description: 'Indicator value', optional: true },
action: {
type: 'string',
description: 'Action taken when the indicator matches',
optional: true,
},
mobileAction: {
type: 'string',
description: 'Action taken on mobile platforms when the indicator matches',
optional: true,
},
severity: { type: 'string', description: 'Indicator severity', optional: true },
description: { type: 'string', description: 'Indicator description', optional: true },
source: { type: 'string', description: 'Indicator source', optional: true },
appliedGlobally: {
type: 'boolean',
description: 'Whether the indicator applies to all hosts',
optional: true,
},
platforms: {
type: 'array',
description: 'Platforms the indicator applies to',
optional: true,
items: { type: 'string' },
},
hostGroups: {
type: 'array',
description: 'Host group IDs the indicator is scoped to',
optional: true,
items: { type: 'string' },
},
tags: {
type: 'array',
description: 'Tags applied to the indicator',
optional: true,
items: { type: 'string' },
},
expiration: {
type: 'string',
description: 'Indicator expiration timestamp',
optional: true,
},
expired: {
type: 'boolean',
description: 'Whether the indicator has expired',
optional: true,
},
deleted: {
type: 'boolean',
description: 'Whether the indicator is deleted',
optional: true,
},
fromParent: {
type: 'boolean',
description: 'Whether the indicator was inherited from a parent CID',
optional: true,
},
parentCidName: { type: 'string', description: 'Parent CID name', optional: true },
createdBy: {
type: 'string',
description: 'User who created the indicator',
optional: true,
},
createdOn: {
type: 'string',
description: 'Indicator creation timestamp',
optional: true,
},
modifiedBy: {
type: 'string',
description: 'User who last modified the indicator',
optional: true,
},
modifiedOn: {
type: 'string',
description: 'Indicator modification timestamp',
optional: true,
},
metadata: {
type: 'json',
description: 'File metadata CrowdStrike resolved for the indicator',
optional: true,
properties: {
avHits: { type: 'number', description: 'Antivirus hit count', optional: true },
companyName: { type: 'string', description: 'Company name', optional: true },
fileDescription: { type: 'string', description: 'File description', optional: true },
fileVersion: { type: 'string', description: 'File version', optional: true },
filename: { type: 'string', description: 'File name', optional: true },
originalFilename: {
type: 'string',
description: 'Original file name',
optional: true,
},
productName: { type: 'string', description: 'Product name', optional: true },
productVersion: { type: 'string', description: 'Product version', optional: true },
signed: {
type: 'boolean',
description: 'Whether the file is signed',
optional: true,
},
},
},
},
},
},
count: {
type: 'number',
description: 'Number of indicators created',
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -0,0 +1,111 @@
import type {
CrowdStrikeDeleteIndicatorsParams,
CrowdStrikeDeleteIndicatorsResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeDeleteIndicatorsTool: ToolConfig<
CrowdStrikeDeleteIndicatorsParams,
CrowdStrikeDeleteIndicatorsResponse
> = {
id: 'crowdstrike_delete_indicators',
name: 'CrowdStrike Delete Indicators',
description:
'Permanently delete custom CrowdStrike Falcon indicators of compromise (DELETE /iocs/entities/indicators/v1). Cannot be undone; deleting a blocking indicator removes that protection from every host, and a broad filter can delete far more than intended. Supply an ID list or a filter, never both -- CrowdStrike lets a filter silently override the IDs, so this tool rejects that instead. Requires the "IOC Management: Write" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
indicatorIds: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'JSON array of CrowdStrike IOC IDs to delete. Cannot be combined with a filter.',
},
filter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Falcon Query Language filter selecting indicators to delete in bulk. Cannot be combined with an ID list.',
},
comment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Audit comment explaining why these indicators were deleted',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
comment: params.comment,
filter: params.filter,
indicatorIds: params.indicatorIds,
operation: 'crowdstrike_delete_indicators',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to delete CrowdStrike indicators')
}
return {
success: true,
output: data.output,
}
},
outputs: {
deletedIds: {
type: 'array',
description: 'IOC IDs CrowdStrike deleted',
items: { type: 'string' },
},
count: {
type: 'number',
description: 'Number of indicators deleted',
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -0,0 +1,89 @@
import type {
CrowdStrikeDeleteRtrSessionParams,
CrowdStrikeDeleteRtrSessionResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeDeleteRtrSessionTool: ToolConfig<
CrowdStrikeDeleteRtrSessionParams,
CrowdStrikeDeleteRtrSessionResponse
> = {
id: 'crowdstrike_delete_rtr_session',
name: 'CrowdStrike Delete RTR Session',
description:
'Close an open CrowdStrike Falcon Real Time Response session (DELETE /real-time-response/entities/sessions/v1). Requires the "Real time response: Read" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
sessionId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'RTR session ID to close',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
operation: 'crowdstrike_delete_rtr_session',
sessionId: params.sessionId,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to delete CrowdStrike RTR session')
}
return {
success: true,
output: data.output,
}
},
outputs: {
sessionId: { type: 'string', description: 'RTR session ID that was closed' },
deleted: { type: 'boolean', description: 'Whether CrowdStrike accepted the session deletion' },
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -0,0 +1,114 @@
import type {
CrowdStrikeExecuteRtrCommandParams,
CrowdStrikeExecuteRtrCommandResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeExecuteRtrCommandTool: ToolConfig<
CrowdStrikeExecuteRtrCommandParams,
CrowdStrikeExecuteRtrCommandResponse
> = {
id: 'crowdstrike_execute_rtr_command',
name: 'CrowdStrike Execute RTR Command',
description:
'Run a read-only Real Time Response command in an open CrowdStrike Falcon session (POST /real-time-response/entities/command/v1). baseCommand names the family only (cat, cd, clear, csrutil, env, eventlog, filehash, getsid, help, history, ipconfig, ls, mount, netstat, ps, reg); subcommands go in commandString. Host-modifying commands need the Active Responder or Admin endpoints. Requires the "Real time response: Read" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
sessionId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'RTR session ID returned by Init RTR Session',
},
baseCommand: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Read-only RTR base command family, one of: cat, cd, clear, csrutil, env, eventlog, filehash, getsid, help, history, ipconfig, ls, mount, netstat, ps, reg. Subcommands belong in commandString, not here.',
},
commandString: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Full command line to run, such as "ls C:\\Windows" or "reg query HKLM\\Software"',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
baseCommand: params.baseCommand,
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
commandString: params.commandString,
operation: 'crowdstrike_execute_rtr_command',
sessionId: params.sessionId,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to execute CrowdStrike RTR command')
}
return {
success: true,
output: data.output,
}
},
outputs: {
cloudRequestId: {
type: 'string',
description: 'Cloud request ID to poll for command output',
optional: true,
},
sessionId: { type: 'string', description: 'RTR session the command ran in', optional: true },
queuedCommandOffline: {
type: 'boolean',
description: 'Whether the command was queued for an offline host',
optional: true,
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -0,0 +1,253 @@
import type {
CrowdStrikeGetAlertDetailsParams,
CrowdStrikeGetAlertDetailsResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeGetAlertDetailsTool: ToolConfig<
CrowdStrikeGetAlertDetailsParams,
CrowdStrikeGetAlertDetailsResponse
> = {
id: 'crowdstrike_get_alert_details',
name: 'CrowdStrike Get Alert Details',
description:
'Get full CrowdStrike Falcon alert records for one or more composite alert IDs (POST /alerts/entities/alerts/v2). Requires the "Alerts: Read" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
compositeIds: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'JSON array of CrowdStrike composite alert IDs',
},
includeHidden: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include previously hidden alerts (CrowdStrike defaults this to true)',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
compositeIds: params.compositeIds,
includeHidden: params.includeHidden,
operation: 'crowdstrike_get_alert_details',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to fetch CrowdStrike alert details')
}
return {
success: true,
output: data.output,
}
},
outputs: {
alerts: {
type: 'array',
description: 'CrowdStrike alert records',
items: {
type: 'object',
properties: {
compositeId: { type: 'string', description: 'Composite alert ID', optional: true },
id: { type: 'string', description: 'Alert ID', optional: true },
cid: { type: 'string', description: 'CrowdStrike customer identifier', optional: true },
aggregateId: { type: 'string', description: 'Aggregate identifier', optional: true },
agentId: { type: 'string', description: 'Agent (sensor) identifier', optional: true },
deviceId: {
type: 'string',
description: 'Device identifier from the alert device',
optional: true,
},
hostname: {
type: 'string',
description: 'Hostname from the alert device',
optional: true,
},
name: { type: 'string', description: 'Alert name', optional: true },
displayName: { type: 'string', description: 'Alert display name', optional: true },
description: { type: 'string', description: 'Alert description', optional: true },
type: { type: 'string', description: 'Alert type', optional: true },
product: {
type: 'string',
description: 'Falcon product that raised the alert',
optional: true,
},
platform: {
type: 'string',
description: 'Platform the alert was raised on',
optional: true,
},
severity: { type: 'number', description: 'Numeric severity', optional: true },
severityName: { type: 'string', description: 'Severity name', optional: true },
confidence: { type: 'number', description: 'Confidence score', optional: true },
status: { type: 'string', description: 'Alert status', optional: true },
assignedToName: { type: 'string', description: 'Assignee display name', optional: true },
assignedToUid: { type: 'string', description: 'Assignee user ID', optional: true },
assignedToUuid: { type: 'string', description: 'Assignee user UUID', optional: true },
tactic: { type: 'string', description: 'MITRE ATT&CK tactic', optional: true },
tacticId: { type: 'string', description: 'MITRE ATT&CK tactic ID', optional: true },
technique: { type: 'string', description: 'MITRE ATT&CK technique', optional: true },
techniqueId: { type: 'string', description: 'MITRE ATT&CK technique ID', optional: true },
scenario: { type: 'string', description: 'Alert scenario', optional: true },
objective: { type: 'string', description: 'Adversary objective', optional: true },
resolution: { type: 'string', description: 'Alert resolution', optional: true },
showInUi: {
type: 'boolean',
description: 'Whether the alert is shown in Falcon',
optional: true,
},
tags: {
type: 'array',
description: 'Tags applied to the alert',
optional: true,
items: { type: 'string' },
},
filename: { type: 'string', description: 'Triggering file name', optional: true },
filepath: { type: 'string', description: 'Triggering file path', optional: true },
cmdline: { type: 'string', description: 'Triggering command line', optional: true },
sha256: { type: 'string', description: 'SHA256 of the triggering file', optional: true },
sha1: { type: 'string', description: 'SHA1 of the triggering file', optional: true },
md5: { type: 'string', description: 'MD5 of the triggering file', optional: true },
userName: {
type: 'string',
description: 'User name associated with the alert',
optional: true,
},
userId: {
type: 'string',
description: 'User ID associated with the alert',
optional: true,
},
patternId: { type: 'number', description: 'Detection pattern ID', optional: true },
falconHostLink: {
type: 'string',
description: 'Deep link into the Falcon console',
optional: true,
},
controlGraphId: {
type: 'string',
description: 'Control graph identifier',
optional: true,
},
external: {
type: 'boolean',
description: 'Whether the alert is external',
optional: true,
},
emailSent: {
type: 'boolean',
description: 'Whether a notification email was sent',
optional: true,
},
isAggregated: {
type: 'boolean',
description: 'Whether the alert is aggregated',
optional: true,
},
isFalconPlatformIoa: {
type: 'boolean',
description: 'Whether the alert is a Falcon platform IOA',
optional: true,
},
dataDomains: {
type: 'array',
description: 'Data domains the alert belongs to',
optional: true,
items: { type: 'string' },
},
iocValues: {
type: 'array',
description: 'Indicator values associated with the alert',
optional: true,
items: { type: 'string' },
},
linkedCaseIds: {
type: 'array',
description: 'Case IDs linked to the alert',
optional: true,
items: { type: 'string' },
},
linkedBehavioralDetections: {
type: 'array',
description: 'Behavioral detection IDs linked to the alert',
optional: true,
items: { type: 'string' },
},
timestamp: { type: 'string', description: 'Alert timestamp', optional: true },
createdTimestamp: {
type: 'string',
description: 'Alert creation timestamp',
optional: true,
},
updatedTimestamp: {
type: 'string',
description: 'Alert update timestamp',
optional: true,
},
crawledTimestamp: {
type: 'string',
description: 'Alert crawl timestamp',
optional: true,
},
contextTimestamp: {
type: 'string',
description: 'Alert context timestamp',
optional: true,
},
},
},
},
count: {
type: 'number',
description: 'Number of alerts returned',
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -0,0 +1,187 @@
import type {
CrowdStrikeGetCaseDetailsParams,
CrowdStrikeGetCaseDetailsResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeGetCaseDetailsTool: ToolConfig<
CrowdStrikeGetCaseDetailsParams,
CrowdStrikeGetCaseDetailsResponse
> = {
id: 'crowdstrike_get_case_details',
name: 'CrowdStrike Get Case Details',
description:
'Get CrowdStrike Falcon Case Management case records for one or more case IDs (POST /cases/entities/cases/v2). Requires the "Cases: Read" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
caseIds: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'JSON array of CrowdStrike case IDs',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
caseIds: params.caseIds,
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
operation: 'crowdstrike_get_case_details',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to fetch CrowdStrike case details')
}
return {
success: true,
output: data.output,
}
},
outputs: {
cases: {
type: 'array',
description: 'CrowdStrike Case Management case records',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Case identifier', optional: true },
cid: { type: 'string', description: 'CrowdStrike customer identifier', optional: true },
name: { type: 'string', description: 'Case name', optional: true },
description: { type: 'string', description: 'Case description', optional: true },
descriptionFormat: {
type: 'string',
description: 'Format of the case description',
optional: true,
},
status: { type: 'string', description: 'Case status', optional: true },
severity: { type: 'number', description: 'Numeric case severity', optional: true },
severityLevel: {
type: 'string',
description: 'Case severity level name',
optional: true,
},
referenceId: {
type: 'string',
description: 'Human-readable case reference ID',
optional: true,
},
version: {
type: 'number',
description: 'Case version for optimistic concurrency',
optional: true,
},
tags: {
type: 'array',
description: 'Tags applied to the case',
optional: true,
items: { type: 'string' },
},
assignedTo: {
type: 'json',
description: 'Falcon user the case is assigned to',
optional: true,
properties: {
uuid: { type: 'string', description: 'Falcon user UUID', optional: true },
email: { type: 'string', description: 'Falcon user email', optional: true },
fullName: { type: 'string', description: 'Falcon user full name', optional: true },
},
},
createdBy: {
type: 'json',
description: 'Falcon user who created the case',
optional: true,
properties: {
uuid: { type: 'string', description: 'Falcon user UUID', optional: true },
email: { type: 'string', description: 'Falcon user email', optional: true },
fullName: { type: 'string', description: 'Falcon user full name', optional: true },
},
},
lastUpdatedBy: {
type: 'json',
description: 'Falcon user who last updated the case',
optional: true,
properties: {
uuid: { type: 'string', description: 'Falcon user UUID', optional: true },
email: { type: 'string', description: 'Falcon user email', optional: true },
fullName: { type: 'string', description: 'Falcon user full name', optional: true },
},
},
createdTimestamp: {
type: 'string',
description: 'Case creation timestamp',
optional: true,
},
updatedTimestamp: {
type: 'string',
description: 'Case update timestamp',
optional: true,
},
startTimestamp: { type: 'string', description: 'Case start timestamp', optional: true },
endTimestamp: { type: 'string', description: 'Case end timestamp', optional: true },
templateId: { type: 'string', description: 'Case template identifier', optional: true },
templateName: { type: 'string', description: 'Case template name', optional: true },
slaId: {
type: 'string',
description: 'SLA identifier applied to the case',
optional: true,
},
slaName: { type: 'string', description: 'SLA name applied to the case', optional: true },
isReadOnly: {
type: 'boolean',
description: 'Whether the case is read only',
optional: true,
},
},
},
},
count: {
type: 'number',
description: 'Number of cases returned',
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -0,0 +1,129 @@
import type {
CrowdStrikeGetHostGroupDetailsParams,
CrowdStrikeGetHostGroupDetailsResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeGetHostGroupDetailsTool: ToolConfig<
CrowdStrikeGetHostGroupDetailsParams,
CrowdStrikeGetHostGroupDetailsResponse
> = {
id: 'crowdstrike_get_host_group_details',
name: 'CrowdStrike Get Host Group Details',
description:
'Get CrowdStrike Falcon host group records for one or more group IDs (GET /devices/entities/host-groups/v1). Requires the "Host groups: Read" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
hostGroupIds: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'JSON array of CrowdStrike host group IDs',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
hostGroupIds: params.hostGroupIds,
operation: 'crowdstrike_get_host_group_details',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to fetch CrowdStrike host group details')
}
return {
success: true,
output: data.output,
}
},
outputs: {
hostGroups: {
type: 'array',
description: 'CrowdStrike host group records',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Host group identifier', optional: true },
name: { type: 'string', description: 'Host group name', optional: true },
description: { type: 'string', description: 'Host group description', optional: true },
groupType: {
type: 'string',
description: 'Group type (static, dynamic, staticByID)',
optional: true,
},
assignmentRule: {
type: 'string',
description: 'FQL assignment rule for dynamic groups',
optional: true,
},
createdBy: { type: 'string', description: 'User who created the group', optional: true },
createdTimestamp: {
type: 'string',
description: 'Group creation timestamp',
optional: true,
},
modifiedBy: {
type: 'string',
description: 'User who last modified the group',
optional: true,
},
modifiedTimestamp: {
type: 'string',
description: 'Group modification timestamp',
optional: true,
},
},
},
},
count: {
type: 'number',
description: 'Number of host groups returned',
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -0,0 +1,204 @@
import type {
CrowdStrikeGetIndicatorDetailsParams,
CrowdStrikeGetIndicatorDetailsResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeGetIndicatorDetailsTool: ToolConfig<
CrowdStrikeGetIndicatorDetailsParams,
CrowdStrikeGetIndicatorDetailsResponse
> = {
id: 'crowdstrike_get_indicator_details',
name: 'CrowdStrike Get Indicator Details',
description:
'Get custom CrowdStrike Falcon indicator of compromise (IOC) records for one or more IOC IDs (GET /iocs/entities/indicators/v1). Requires the "IOC Management: Read" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
indicatorIds: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'JSON array of CrowdStrike IOC IDs',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
indicatorIds: params.indicatorIds,
operation: 'crowdstrike_get_indicator_details',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to fetch CrowdStrike indicator details')
}
return {
success: true,
output: data.output,
}
},
outputs: {
indicators: {
type: 'array',
description: 'CrowdStrike indicator of compromise records',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Indicator identifier', optional: true },
type: { type: 'string', description: 'Indicator type', optional: true },
value: { type: 'string', description: 'Indicator value', optional: true },
action: {
type: 'string',
description: 'Action taken when the indicator matches',
optional: true,
},
mobileAction: {
type: 'string',
description: 'Action taken on mobile platforms when the indicator matches',
optional: true,
},
severity: { type: 'string', description: 'Indicator severity', optional: true },
description: { type: 'string', description: 'Indicator description', optional: true },
source: { type: 'string', description: 'Indicator source', optional: true },
appliedGlobally: {
type: 'boolean',
description: 'Whether the indicator applies to all hosts',
optional: true,
},
platforms: {
type: 'array',
description: 'Platforms the indicator applies to',
optional: true,
items: { type: 'string' },
},
hostGroups: {
type: 'array',
description: 'Host group IDs the indicator is scoped to',
optional: true,
items: { type: 'string' },
},
tags: {
type: 'array',
description: 'Tags applied to the indicator',
optional: true,
items: { type: 'string' },
},
expiration: {
type: 'string',
description: 'Indicator expiration timestamp',
optional: true,
},
expired: {
type: 'boolean',
description: 'Whether the indicator has expired',
optional: true,
},
deleted: {
type: 'boolean',
description: 'Whether the indicator is deleted',
optional: true,
},
fromParent: {
type: 'boolean',
description: 'Whether the indicator was inherited from a parent CID',
optional: true,
},
parentCidName: { type: 'string', description: 'Parent CID name', optional: true },
createdBy: {
type: 'string',
description: 'User who created the indicator',
optional: true,
},
createdOn: {
type: 'string',
description: 'Indicator creation timestamp',
optional: true,
},
modifiedBy: {
type: 'string',
description: 'User who last modified the indicator',
optional: true,
},
modifiedOn: {
type: 'string',
description: 'Indicator modification timestamp',
optional: true,
},
metadata: {
type: 'json',
description: 'File metadata CrowdStrike resolved for the indicator',
optional: true,
properties: {
avHits: { type: 'number', description: 'Antivirus hit count', optional: true },
companyName: { type: 'string', description: 'Company name', optional: true },
fileDescription: { type: 'string', description: 'File description', optional: true },
fileVersion: { type: 'string', description: 'File version', optional: true },
filename: { type: 'string', description: 'File name', optional: true },
originalFilename: {
type: 'string',
description: 'Original file name',
optional: true,
},
productName: { type: 'string', description: 'Product name', optional: true },
productVersion: { type: 'string', description: 'Product version', optional: true },
signed: {
type: 'boolean',
description: 'Whether the file is signed',
optional: true,
},
},
},
},
},
},
count: {
type: 'number',
description: 'Number of indicators returned',
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -0,0 +1,109 @@
import type {
CrowdStrikeGetRtrCommandStatusParams,
CrowdStrikeGetRtrCommandStatusResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeGetRtrCommandStatusTool: ToolConfig<
CrowdStrikeGetRtrCommandStatusParams,
CrowdStrikeGetRtrCommandStatusResponse
> = {
id: 'crowdstrike_get_rtr_command_status',
name: 'CrowdStrike Get RTR Command Status',
description:
'Get the status and output of a Real Time Response command by cloud request ID (GET /real-time-response/entities/command/v1). Long output is chunked across sequences, so increment the sequence ID to read the next chunk. Requires the "Real time response: Read" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
cloudRequestId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Cloud request ID returned by Execute RTR Command',
},
sequenceId: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Output chunk to retrieve, starting at 0',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
cloudRequestId: params.cloudRequestId,
operation: 'crowdstrike_get_rtr_command_status',
sequenceId: params.sequenceId,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to fetch CrowdStrike RTR command status')
}
return {
success: true,
output: data.output,
}
},
outputs: {
complete: {
type: 'boolean',
description: 'Whether the command has finished running',
optional: true,
},
stdout: { type: 'string', description: 'Standard output from the command', optional: true },
stderr: { type: 'string', description: 'Standard error from the command', optional: true },
baseCommand: { type: 'string', description: 'Base command that was run', optional: true },
sessionId: { type: 'string', description: 'RTR session the command ran in', optional: true },
taskId: { type: 'string', description: 'Task identifier for the command', optional: true },
sequenceId: {
type: 'number',
description: 'Output chunk sequence this response covers',
optional: true,
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -11,7 +11,7 @@ export const crowdstrikeGetSensorAggregatesTool: ToolConfig<
id: 'crowdstrike_get_sensor_aggregates',
name: 'CrowdStrike Get Sensor Aggregates',
description:
'Get documented CrowdStrike Identity Protection sensor aggregates from a JSON aggregate query body',
'Aggregate CrowdStrike Identity Protection sensors from a JSON aggregate query body (POST /identity-protection/aggregates/devices/GET/v1). These are the domain controllers Falcon Identity Protection monitors, not Falcon endpoint sensors. Requires the "Identity Protection Entities: Read" API scope.',
version: '1.0.0',
params: {
@@ -113,9 +113,10 @@ export const crowdstrikeGetSensorAggregatesTool: ToolConfig<
optional: true,
},
subAggregates: {
type: 'json',
type: 'array',
description: 'Nested aggregate results for this bucket',
optional: true,
items: { type: 'object' },
},
to: {
type: 'number',
@@ -157,5 +158,18 @@ export const crowdstrikeGetSensorAggregatesTool: ToolConfig<
type: 'number',
description: 'Number of aggregate result groups returned',
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -11,7 +11,7 @@ export const crowdstrikeGetSensorDetailsTool: ToolConfig<
id: 'crowdstrike_get_sensor_details',
name: 'CrowdStrike Get Sensor Details',
description:
'Get documented CrowdStrike Identity Protection sensor details for one or more device IDs',
'Get CrowdStrike Identity Protection sensor details for one or more device IDs (POST /identity-protection/entities/devices/GET/v1). These are the domain controllers Falcon Identity Protection monitors, not Falcon endpoint sensors. Requires the "Identity Protection Entities: Read" API scope.',
version: '1.0.0',
params: {
@@ -179,14 +179,17 @@ export const crowdstrikeGetSensorDetailsTool: ToolConfig<
type: 'number',
description: 'Number of sensors returned',
},
pagination: {
type: 'json',
description: 'Pagination metadata when returned by the underlying API',
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
properties: {
limit: { type: 'number', description: 'Page size used for the query', optional: true },
offset: { type: 'number', description: 'Offset returned by CrowdStrike', optional: true },
total: { type: 'number', description: 'Total records available', optional: true },
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
@@ -0,0 +1,277 @@
import type {
CrowdStrikeGetVulnerabilityDetailsParams,
CrowdStrikeGetVulnerabilityDetailsResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeGetVulnerabilityDetailsTool: ToolConfig<
CrowdStrikeGetVulnerabilityDetailsParams,
CrowdStrikeGetVulnerabilityDetailsResponse
> = {
id: 'crowdstrike_get_vulnerability_details',
name: 'CrowdStrike Get Vulnerability Details',
description:
'Get CrowdStrike Falcon Spotlight vulnerability records for one or more vulnerability IDs, including CVE, affected host, application, and remediation details (GET /spotlight/entities/vulnerabilities/v2). Requires the spotlight-vulnerabilities:read API scope, shown as "Vulnerabilities: Read" in the Falcon API client UI.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
vulnerabilityIds: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'JSON array of Spotlight vulnerability IDs (maximum 400 per request)',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
operation: 'crowdstrike_get_vulnerability_details',
vulnerabilityIds: params.vulnerabilityIds,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to fetch CrowdStrike vulnerability details')
}
return {
success: true,
output: data.output,
}
},
outputs: {
vulnerabilities: {
type: 'array',
description: 'CrowdStrike Spotlight vulnerability records',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Vulnerability identifier', optional: true },
aid: {
type: 'string',
description: 'Agent identifier of the affected host',
optional: true,
},
cid: { type: 'string', description: 'CrowdStrike customer identifier', optional: true },
status: {
type: 'string',
description: 'Vulnerability status (open, closed, reopen)',
optional: true,
},
confidence: { type: 'string', description: 'Detection confidence', optional: true },
vulnerabilityId: {
type: 'string',
description: 'Underlying vulnerability ID',
optional: true,
},
createdTimestamp: { type: 'string', description: 'Creation timestamp', optional: true },
updatedTimestamp: {
type: 'string',
description: 'Last update timestamp',
optional: true,
},
closedTimestamp: { type: 'string', description: 'Closure timestamp', optional: true },
cve: {
type: 'json',
description: 'CVE details for the vulnerability',
optional: true,
properties: {
id: { type: 'string', description: 'CVE identifier', optional: true },
baseScore: { type: 'number', description: 'CVSS base score', optional: true },
severity: { type: 'string', description: 'CVE severity', optional: true },
exprtRating: {
type: 'string',
description: 'CrowdStrike ExPRT rating',
optional: true,
},
exploitStatus: { type: 'number', description: 'Exploit status code', optional: true },
exploitabilityScore: {
type: 'number',
description: 'CVSS exploitability score',
optional: true,
},
impactScore: { type: 'number', description: 'CVSS impact score', optional: true },
remediationLevel: {
type: 'string',
description: 'CVSS remediation level',
optional: true,
},
description: { type: 'string', description: 'CVE description', optional: true },
publishedDate: {
type: 'string',
description: 'CVE publication date',
optional: true,
},
vector: { type: 'string', description: 'CVSS vector string', optional: true },
types: {
type: 'array',
description: 'CVE types',
optional: true,
items: { type: 'string' },
},
isCisaKev: {
type: 'boolean',
description:
'Whether the CVE is in the CISA Known Exploited Vulnerabilities catalog',
optional: true,
},
cisaDueDate: {
type: 'string',
description: 'CISA remediation due date',
optional: true,
},
},
},
app: {
type: 'json',
description: 'Affected application',
optional: true,
properties: {
productNameNormalized: {
type: 'string',
description: 'Normalized product name',
optional: true,
},
productNameVersion: {
type: 'string',
description: 'Product name and version',
optional: true,
},
vendorNormalized: {
type: 'string',
description: 'Normalized vendor name',
optional: true,
},
},
},
hostInfo: {
type: 'json',
description: 'Affected host details',
optional: true,
properties: {
hostname: { type: 'string', description: 'Host name', optional: true },
localIp: { type: 'string', description: 'Local IP address', optional: true },
machineDomain: { type: 'string', description: 'Machine domain', optional: true },
osVersion: {
type: 'string',
description: 'Operating system version',
optional: true,
},
platform: { type: 'string', description: 'Platform name', optional: true },
productTypeDesc: {
type: 'string',
description: 'Product type description',
optional: true,
},
assetCriticality: {
type: 'string',
description: 'Asset criticality',
optional: true,
},
internetExposure: {
type: 'string',
description: 'Internet exposure',
optional: true,
},
tags: {
type: 'array',
description: 'Host tags',
optional: true,
items: { type: 'string' },
},
groups: {
type: 'array',
description: 'Host group names the host belongs to',
optional: true,
items: { type: 'string' },
},
},
},
remediationIds: {
type: 'array',
description: 'Remediation IDs for the vulnerability',
optional: true,
items: { type: 'string' },
},
remediations: {
type: 'array',
description: 'Remediation entities for the vulnerability',
optional: true,
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Remediation identifier', optional: true },
title: { type: 'string', description: 'Remediation title', optional: true },
action: { type: 'string', description: 'Remediation action', optional: true },
type: { type: 'string', description: 'Remediation type', optional: true },
link: { type: 'string', description: 'Remediation link', optional: true },
reference: { type: 'string', description: 'Remediation reference', optional: true },
vendorUrl: { type: 'string', description: 'Vendor advisory URL', optional: true },
},
},
},
suppressionInfo: {
type: 'json',
description: 'Suppression state for the vulnerability',
optional: true,
properties: {
isSuppressed: {
type: 'boolean',
description: 'Whether the finding is suppressed',
optional: true,
},
reason: { type: 'string', description: 'Suppression reason', optional: true },
},
},
},
},
},
count: {
type: 'number',
description: 'Number of vulnerabilities returned',
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
+20
View File
@@ -1,4 +1,24 @@
export { crowdstrikeCreateIndicatorsTool } from './create_indicators'
export { crowdstrikeDeleteIndicatorsTool } from './delete_indicators'
export { crowdstrikeDeleteRtrSessionTool } from './delete_rtr_session'
export { crowdstrikeExecuteRtrCommandTool } from './execute_rtr_command'
export { crowdstrikeGetAlertDetailsTool } from './get_alert_details'
export { crowdstrikeGetCaseDetailsTool } from './get_case_details'
export { crowdstrikeGetHostGroupDetailsTool } from './get_host_group_details'
export { crowdstrikeGetIndicatorDetailsTool } from './get_indicator_details'
export { crowdstrikeGetRtrCommandStatusTool } from './get_rtr_command_status'
export { crowdstrikeGetSensorAggregatesTool } from './get_sensor_aggregates'
export { crowdstrikeGetSensorDetailsTool } from './get_sensor_details'
export { crowdstrikeGetVulnerabilityDetailsTool } from './get_vulnerability_details'
export { crowdstrikeInitRtrSessionTool } from './init_rtr_session'
export { crowdstrikePerformHostActionTool } from './perform_host_action'
export { crowdstrikePerformHostGroupActionTool } from './perform_host_group_action'
export { crowdstrikeQueryAlertsTool } from './query_alerts'
export { crowdstrikeQueryCasesTool } from './query_cases'
export { crowdstrikeQueryHostGroupsTool } from './query_host_groups'
export { crowdstrikeQueryIndicatorsTool } from './query_indicators'
export { crowdstrikeQuerySensorsTool } from './query_sensors'
export { crowdstrikeQueryVulnerabilitiesTool } from './query_vulnerabilities'
export * from './types'
export { crowdstrikeUpdateAlertsTool } from './update_alerts'
export { crowdstrikeUpdateIndicatorsTool } from './update_indicators'
@@ -0,0 +1,124 @@
import type {
CrowdStrikeInitRtrSessionParams,
CrowdStrikeInitRtrSessionResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeInitRtrSessionTool: ToolConfig<
CrowdStrikeInitRtrSessionParams,
CrowdStrikeInitRtrSessionResponse
> = {
id: 'crowdstrike_init_rtr_session',
name: 'CrowdStrike Init RTR Session',
description:
'Open a CrowdStrike Falcon Real Time Response session against a host so read-only commands can be run on it (POST /real-time-response/entities/sessions/v1). This connects a live remote shell to the endpoint. Requires the "Real time response: Read" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
deviceId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'CrowdStrike host agent ID (AID) to open the session against',
},
queueOffline: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Queue the session so it runs when an offline host comes back online',
},
origin: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional session origin string recorded by CrowdStrike',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
deviceId: params.deviceId,
operation: 'crowdstrike_init_rtr_session',
origin: params.origin,
queueOffline: params.queueOffline,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to initialize CrowdStrike RTR session')
}
return {
success: true,
output: data.output,
}
},
outputs: {
sessionId: {
type: 'string',
description: 'RTR session ID to use for subsequent commands',
optional: true,
},
deviceId: { type: 'string', description: 'Host agent ID for the session', optional: true },
platform: { type: 'string', description: 'Platform of the connected host', optional: true },
pwd: {
type: 'string',
description: 'Working directory the session started in',
optional: true,
},
offlineQueued: {
type: 'boolean',
description: 'Whether the session was queued for an offline host',
optional: true,
},
existingAidSessions: {
type: 'number',
description: 'Number of sessions already open against this host',
optional: true,
},
createdAt: { type: 'string', description: 'Session creation timestamp', optional: true },
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -0,0 +1,110 @@
import type {
CrowdStrikePerformHostActionParams,
CrowdStrikePerformHostActionResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikePerformHostActionTool: ToolConfig<
CrowdStrikePerformHostActionParams,
CrowdStrikePerformHostActionResponse
> = {
id: 'crowdstrike_perform_host_action',
name: 'CrowdStrike Perform Host Action',
description:
'Act on CrowdStrike Falcon hosts (POST /devices/entities/devices-actions/v2). Actions: contain, lift_containment, hide_host, unhide_host, detection_suppress, detection_unsuppress. contain network-isolates the host so it can only reach the Falcon cloud; hide_host removes the host record from the console. Both are immediately disruptive. Up to 100 host IDs per call. Requires the "Hosts: Write" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
actionName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Action to take: contain, lift_containment, hide_host, unhide_host, detection_suppress, or detection_unsuppress. "contain" network-isolates the host; "hide_host" removes it from the Falcon console.',
},
deviceIds: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'JSON array of up to 100 CrowdStrike host agent IDs (AIDs) to act on',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
actionName: params.actionName,
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
deviceIds: params.deviceIds,
operation: 'crowdstrike_perform_host_action',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to perform CrowdStrike host action')
}
return {
success: true,
output: data.output,
}
},
outputs: {
affected: {
type: 'array',
description: 'Entities affected by the action',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Affected entity identifier', optional: true },
path: { type: 'string', description: 'API path of the affected entity', optional: true },
},
},
},
count: {
type: 'number',
description: 'Number of hosts the action was applied to',
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -0,0 +1,144 @@
import type {
CrowdStrikePerformHostGroupActionParams,
CrowdStrikePerformHostGroupActionResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikePerformHostGroupActionTool: ToolConfig<
CrowdStrikePerformHostGroupActionParams,
CrowdStrikePerformHostGroupActionResponse
> = {
id: 'crowdstrike_perform_host_group_action',
name: 'CrowdStrike Perform Host Group Action',
description:
'Add hosts to or remove hosts from a CrowdStrike Falcon static host group (POST /devices/entities/host-group-actions/v1). Group membership drives policy assignment, so changing it changes which policies apply to those hosts. Requires the "Host groups: Write" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
actionName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Action to take: add-hosts or remove-hosts',
},
hostGroupId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'CrowdStrike host group ID to modify (static groups only)',
},
deviceIds: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'JSON array of CrowdStrike host agent IDs (AIDs) to add to or remove from the group',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
actionName: params.actionName,
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
deviceIds: params.deviceIds,
hostGroupId: params.hostGroupId,
operation: 'crowdstrike_perform_host_group_action',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to perform CrowdStrike host group action')
}
return {
success: true,
output: data.output,
}
},
outputs: {
hostGroups: {
type: 'array',
description: 'Host group records returned after the action',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Host group identifier', optional: true },
name: { type: 'string', description: 'Host group name', optional: true },
description: { type: 'string', description: 'Host group description', optional: true },
groupType: {
type: 'string',
description: 'Group type (static, dynamic, staticByID)',
optional: true,
},
assignmentRule: {
type: 'string',
description: 'FQL assignment rule for dynamic groups',
optional: true,
},
createdBy: { type: 'string', description: 'User who created the group', optional: true },
createdTimestamp: {
type: 'string',
description: 'Group creation timestamp',
optional: true,
},
modifiedBy: {
type: 'string',
description: 'User who last modified the group',
optional: true,
},
modifiedTimestamp: {
type: 'string',
description: 'Group modification timestamp',
optional: true,
},
},
},
},
count: {
type: 'number',
description: 'Number of host group records returned',
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
+128
View File
@@ -0,0 +1,128 @@
import type {
CrowdStrikeQueryAlertsParams,
CrowdStrikeQueryAlertsResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeQueryAlertsTool: ToolConfig<
CrowdStrikeQueryAlertsParams,
CrowdStrikeQueryAlertsResponse
> = {
id: 'crowdstrike_query_alerts',
name: 'CrowdStrike Query Alerts',
description:
'Search CrowdStrike Falcon alerts with a Falcon Query Language filter and return their composite IDs. Uses the current Alerts API (GET /alerts/queries/alerts/v2), which supersedes the deprecated Detects API. Requires the "Alerts: Read" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
filter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Falcon Query Language filter over alert fields',
},
q: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Free-text search across all alert metadata',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of alert IDs to return (max 10000)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Pagination offset for the alert query',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort expression such as "created_timestamp|desc"',
},
includeHidden: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include previously hidden alerts (CrowdStrike defaults this to true)',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
filter: params.filter,
includeHidden: params.includeHidden,
limit: params.limit,
offset: params.offset,
operation: 'crowdstrike_query_alerts',
q: params.q,
sort: params.sort,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to query CrowdStrike alerts')
}
return {
success: true,
output: data.output,
}
},
outputs: {
alertIds: {
type: 'array',
description: 'Composite alert IDs matching the query, ready for Get Alert Details',
items: { type: 'string' },
},
count: {
type: 'number',
description: 'Number of alert IDs returned',
},
pagination: {
type: 'json',
description: 'Pagination metadata (limit, offset, total)',
optional: true,
properties: {
limit: { type: 'number', description: 'Page size used for the query', optional: true },
offset: { type: 'number', description: 'Offset returned by CrowdStrike', optional: true },
total: { type: 'number', description: 'Total records available', optional: true },
},
},
},
}
+122
View File
@@ -0,0 +1,122 @@
import type {
CrowdStrikeQueryCasesParams,
CrowdStrikeQueryCasesResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeQueryCasesTool: ToolConfig<
CrowdStrikeQueryCasesParams,
CrowdStrikeQueryCasesResponse
> = {
id: 'crowdstrike_query_cases',
name: 'CrowdStrike Query Cases',
description:
'Search CrowdStrike Falcon Case Management cases with a Falcon Query Language filter and return their IDs (GET /cases/queries/cases/v1). Case Management supersedes the CrowdScore Incidents API, which CrowdStrike has removed from its published API spec. Requires the "Cases: Read" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
filter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Falcon Query Language filter. Exact-match fields include cid and id; wildcard fields include assigned_to_name and assigned_to_uuid; range fields include created_timestamp and updated_timestamp.',
},
q: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Free-text search across all case metadata',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of case IDs to return (max 10000, default 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Pagination offset for the case query',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort expression such as "created_timestamp|desc" or "status|asc"',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
filter: params.filter,
limit: params.limit,
offset: params.offset,
operation: 'crowdstrike_query_cases',
q: params.q,
sort: params.sort,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to query CrowdStrike cases')
}
return {
success: true,
output: data.output,
}
},
outputs: {
caseIds: {
type: 'array',
description: 'Case IDs matching the query',
items: { type: 'string' },
},
count: {
type: 'number',
description: 'Number of case IDs returned',
},
pagination: {
type: 'json',
description: 'Pagination metadata (limit, offset, total)',
optional: true,
properties: {
limit: { type: 'number', description: 'Page size used for the query', optional: true },
offset: { type: 'number', description: 'Offset returned by CrowdStrike', optional: true },
total: { type: 'number', description: 'Total records available', optional: true },
},
},
},
}
@@ -0,0 +1,114 @@
import type {
CrowdStrikeQueryHostGroupsParams,
CrowdStrikeQueryHostGroupsResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeQueryHostGroupsTool: ToolConfig<
CrowdStrikeQueryHostGroupsParams,
CrowdStrikeQueryHostGroupsResponse
> = {
id: 'crowdstrike_query_host_groups',
name: 'CrowdStrike Query Host Groups',
description:
'Search CrowdStrike Falcon host groups with a Falcon Query Language filter and return their IDs (GET /devices/queries/host-groups/v1). Requires the "Host groups: Read" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
filter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Falcon Query Language filter over host group fields',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of host group IDs to return (1-5000)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Pagination offset for the host group query',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort expression such as "name.asc" or "modified_timestamp.desc"',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
filter: params.filter,
limit: params.limit,
offset: params.offset,
operation: 'crowdstrike_query_host_groups',
sort: params.sort,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to query CrowdStrike host groups')
}
return {
success: true,
output: data.output,
}
},
outputs: {
hostGroupIds: {
type: 'array',
description: 'Host group IDs matching the query',
items: { type: 'string' },
},
count: {
type: 'number',
description: 'Number of host group IDs returned',
},
pagination: {
type: 'json',
description: 'Pagination metadata (limit, offset, total)',
optional: true,
properties: {
limit: { type: 'number', description: 'Page size used for the query', optional: true },
offset: { type: 'number', description: 'Offset returned by CrowdStrike', optional: true },
total: { type: 'number', description: 'Total records available', optional: true },
},
},
},
}
@@ -0,0 +1,124 @@
import type {
CrowdStrikeQueryIndicatorsParams,
CrowdStrikeQueryIndicatorsResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeQueryIndicatorsTool: ToolConfig<
CrowdStrikeQueryIndicatorsParams,
CrowdStrikeQueryIndicatorsResponse
> = {
id: 'crowdstrike_query_indicators',
name: 'CrowdStrike Query Indicators',
description:
'Search custom CrowdStrike Falcon indicators of compromise (IOCs) with a Falcon Query Language filter and return their IDs (GET /iocs/queries/indicators/v1). Requires the "IOC Management: Read" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
filter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Falcon Query Language filter over IOC fields',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of IOC IDs to return (1-500, default 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description:
'Pagination offset. Mutually exclusive with the after cursor; use after beyond 10,000 IOCs.',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor from a previous response. Mutually exclusive with offset.',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Sort expression. Supported fields include action, applied_globally, created_by, created_on, expiration, expired, modified_by, modified_on, severity_number, source, type, and value.',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
after: params.after,
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
filter: params.filter,
limit: params.limit,
offset: params.offset,
operation: 'crowdstrike_query_indicators',
sort: params.sort,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to query CrowdStrike indicators')
}
return {
success: true,
output: data.output,
}
},
outputs: {
indicatorIds: {
type: 'array',
description: 'IOC IDs matching the query',
items: { type: 'string' },
},
count: {
type: 'number',
description: 'Number of IOC IDs returned',
},
pagination: {
type: 'json',
description: 'Pagination metadata (limit, offset, total, after)',
optional: true,
properties: {
limit: { type: 'number', description: 'Page size used for the query', optional: true },
offset: { type: 'number', description: 'Offset returned by CrowdStrike', optional: true },
total: { type: 'number', description: 'Total records available', optional: true },
after: { type: 'string', description: 'Cursor for the next page', optional: true },
},
},
},
}
+15 -1
View File
@@ -10,7 +10,8 @@ export const crowdstrikeQuerySensorsTool: ToolConfig<
> = {
id: 'crowdstrike_query_sensors',
name: 'CrowdStrike Query Sensors',
description: 'Search CrowdStrike identity protection sensors by hostname, IP, or related fields',
description:
'Search CrowdStrike Identity Protection sensors -- the domain controllers Falcon Identity Protection monitors, not Falcon endpoint sensors -- and return their device IDs (GET /identity-protection/queries/devices/v1). Sort uses the dot form, for example status.desc. Requires the "Identity Protection Entities: Read" API scope, a separate entitlement from Hosts and Alerts.',
version: '1.0.0',
params: {
@@ -209,5 +210,18 @@ export const crowdstrikeQuerySensorsTool: ToolConfig<
total: { type: 'number', description: 'Total records available', optional: true },
},
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -0,0 +1,115 @@
import type {
CrowdStrikeQueryVulnerabilitiesParams,
CrowdStrikeQueryVulnerabilitiesResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeQueryVulnerabilitiesTool: ToolConfig<
CrowdStrikeQueryVulnerabilitiesParams,
CrowdStrikeQueryVulnerabilitiesResponse
> = {
id: 'crowdstrike_query_vulnerabilities',
name: 'CrowdStrike Query Vulnerabilities',
description:
'Search CrowdStrike Falcon Spotlight vulnerabilities with a required Falcon Query Language filter and return their IDs (GET /spotlight/queries/vulnerabilities/v1). Requires the spotlight-vulnerabilities:read API scope, shown as "Vulnerabilities: Read" in the Falcon API client UI.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
filter: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Falcon Query Language filter (required by Spotlight). Filterable fields include status, aid, cid, last_seen_within, cve.id, cve.severity, cve.exprt_rating, cve.is_cisa_kev, cve.base_score, host_info.platform_name, host_info.groups, host_info.tags, host_info.internet_exposure, and suppression_info.is_suppressed.',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of vulnerability IDs to return (1-400, default 100)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor from a previous response. Spotlight does not support offset.',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort expression such as "updated_timestamp|desc" or "closed_timestamp|asc"',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
after: params.after,
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
filter: params.filter,
limit: params.limit,
operation: 'crowdstrike_query_vulnerabilities',
sort: params.sort,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to query CrowdStrike vulnerabilities')
}
return {
success: true,
output: data.output,
}
},
outputs: {
vulnerabilityIds: {
type: 'array',
description: 'Spotlight vulnerability IDs matching the query',
items: { type: 'string' },
},
count: {
type: 'number',
description: 'Number of vulnerability IDs returned',
},
pagination: {
type: 'json',
description: 'Cursor pagination metadata (limit, total, after)',
optional: true,
properties: {
limit: { type: 'number', description: 'Page size used for the query', optional: true },
total: { type: 'number', description: 'Total records available', optional: true },
after: { type: 'string', description: 'Cursor for the next page', optional: true },
},
},
},
}
+546 -4
View File
@@ -1,6 +1,6 @@
import type { ToolResponse } from '@/tools/types'
export type CrowdStrikeCloud = 'us-1' | 'us-2' | 'eu-1' | 'us-gov-1' | 'us-gov-2'
export type CrowdStrikeCloud = 'us-1' | 'us-2' | 'us-3' | 'eu-1' | 'us-gov-1' | 'us-gov-2'
export interface CrowdStrikeBaseParams {
clientId: string
@@ -29,9 +29,17 @@ interface CrowdStrikeAggregateExtendedBoundsSpec {
min: string
}
/** CrowdStrike's `MsaRangeSpec` serializes its bounds capitalized, unlike every sibling spec. */
interface CrowdStrikeAggregateRangeSpec {
from: number
to: number
From: number
To: number
}
/** CrowdStrike's `MsaAPIFiltersSpec`: an FQL-per-bucket map plus the catch-all bucket controls. */
export interface CrowdStrikeAggregateFiltersSpec {
filters: Record<string, string>
other_bucket?: boolean
other_bucket_key?: string
}
export interface CrowdStrikeAggregateQuery {
@@ -40,6 +48,7 @@ export interface CrowdStrikeAggregateQuery {
extended_bounds?: CrowdStrikeAggregateExtendedBoundsSpec
field?: string
filter?: string
filters_spec?: CrowdStrikeAggregateFiltersSpec
from?: number
include?: string
interval?: string
@@ -47,6 +56,7 @@ export interface CrowdStrikeAggregateQuery {
min_doc_count?: number
missing?: string
name?: string
percents?: number[]
q?: string
ranges?: CrowdStrikeAggregateRangeSpec[]
size?: number
@@ -108,7 +118,7 @@ export interface CrowdStrikeSensorAggregateBucket {
count: number | null
from: number | null
keyAsString: string | null
label: Record<string, unknown> | null
label: unknown
stringFrom: string | null
stringTo: string | null
subAggregates: CrowdStrikeSensorAggregateResult[]
@@ -131,7 +141,539 @@ export interface CrowdStrikeGetSensorAggregatesResponse extends ToolResponse {
}
}
export interface CrowdStrikeApiError {
code: number | null
id: string | null
message: string | null
}
interface CrowdStrikeCursorPagination extends CrowdStrikePagination {
after: string | null
}
interface CrowdStrikeSpotlightPagination {
after: string | null
limit: number | null
total: number | null
}
export interface CrowdStrikeQueryAlertsParams extends CrowdStrikeBaseParams {
filter?: string
q?: string
limit?: number
offset?: number
sort?: string
includeHidden?: boolean
}
export interface CrowdStrikeGetAlertDetailsParams extends CrowdStrikeBaseParams {
compositeIds: string[]
includeHidden?: boolean
}
export interface CrowdStrikeUpdateAlertsParams extends CrowdStrikeBaseParams {
compositeIds: string[]
updateStatus?: string
assignToUuid?: string
assignToUserId?: string
assignToName?: string
unassign?: boolean
appendComment?: string
addTag?: string
removeTag?: string
removeTagsByPrefix?: string
showInUi?: boolean
actionParameters?: CrowdStrikeActionParameter[]
includeHidden?: boolean
}
export interface CrowdStrikeActionParameter {
name: string
value: string
}
export interface CrowdStrikeAlert {
compositeId: string | null
id: string | null
cid: string | null
aggregateId: string | null
agentId: string | null
deviceId: string | null
hostname: string | null
name: string | null
displayName: string | null
description: string | null
type: string | null
product: string | null
platform: string | null
severity: number | null
severityName: string | null
confidence: number | null
status: string | null
assignedToName: string | null
assignedToUid: string | null
assignedToUuid: string | null
tactic: string | null
tacticId: string | null
technique: string | null
techniqueId: string | null
scenario: string | null
objective: string | null
resolution: string | null
showInUi: boolean | null
tags: string[]
filename: string | null
filepath: string | null
cmdline: string | null
sha256: string | null
sha1: string | null
md5: string | null
userName: string | null
userId: string | null
patternId: number | null
falconHostLink: string | null
controlGraphId: string | null
external: boolean | null
emailSent: boolean | null
isAggregated: boolean | null
isFalconPlatformIoa: boolean | null
dataDomains: string[]
iocValues: string[]
linkedCaseIds: string[]
linkedBehavioralDetections: string[]
timestamp: string | null
createdTimestamp: string | null
updatedTimestamp: string | null
crawledTimestamp: string | null
contextTimestamp: string | null
}
export interface CrowdStrikeQueryAlertsResponse extends ToolResponse {
output: {
alertIds: string[]
count: number
pagination: CrowdStrikePagination | null
}
}
export interface CrowdStrikeGetAlertDetailsResponse extends ToolResponse {
output: {
alerts: CrowdStrikeAlert[]
count: number
errors: CrowdStrikeApiError[]
}
}
export interface CrowdStrikeUpdateAlertsResponse extends ToolResponse {
output: {
updatedIds: string[]
count: number
errors: CrowdStrikeApiError[]
}
}
export interface CrowdStrikePerformHostActionParams extends CrowdStrikeBaseParams {
actionName: string
deviceIds: string[]
}
export interface CrowdStrikeAffectedEntity {
id: string | null
path: string | null
}
export interface CrowdStrikePerformHostActionResponse extends ToolResponse {
output: {
affected: CrowdStrikeAffectedEntity[]
count: number
errors: CrowdStrikeApiError[]
}
}
export interface CrowdStrikeQueryHostGroupsParams extends CrowdStrikeBaseParams {
filter?: string
limit?: number
offset?: number
sort?: string
}
export interface CrowdStrikeGetHostGroupDetailsParams extends CrowdStrikeBaseParams {
hostGroupIds: string[]
}
export interface CrowdStrikePerformHostGroupActionParams extends CrowdStrikeBaseParams {
actionName: string
hostGroupId: string
deviceIds: string[]
}
export interface CrowdStrikeHostGroup {
id: string | null
name: string | null
description: string | null
groupType: string | null
assignmentRule: string | null
createdBy: string | null
createdTimestamp: string | null
modifiedBy: string | null
modifiedTimestamp: string | null
}
export interface CrowdStrikeQueryHostGroupsResponse extends ToolResponse {
output: {
hostGroupIds: string[]
count: number
pagination: CrowdStrikePagination | null
}
}
export interface CrowdStrikeGetHostGroupDetailsResponse extends ToolResponse {
output: {
hostGroups: CrowdStrikeHostGroup[]
count: number
errors: CrowdStrikeApiError[]
}
}
export interface CrowdStrikePerformHostGroupActionResponse extends ToolResponse {
output: {
hostGroups: CrowdStrikeHostGroup[]
count: number
errors: CrowdStrikeApiError[]
}
}
export interface CrowdStrikeQueryIndicatorsParams extends CrowdStrikeBaseParams {
filter?: string
limit?: number
offset?: number
after?: string
sort?: string
}
export interface CrowdStrikeGetIndicatorDetailsParams extends CrowdStrikeBaseParams {
indicatorIds: string[]
}
export interface CrowdStrikeCreateIndicatorsParams extends CrowdStrikeBaseParams {
indicators: Record<string, unknown>[]
comment?: string
retrodetects?: boolean
ignoreWarnings?: boolean
}
export interface CrowdStrikeUpdateIndicatorsParams extends CrowdStrikeBaseParams {
indicators: Record<string, unknown>[]
comment?: string
retrodetects?: boolean
ignoreWarnings?: boolean
}
export interface CrowdStrikeDeleteIndicatorsParams extends CrowdStrikeBaseParams {
indicatorIds?: string[]
filter?: string
comment?: string
}
export interface CrowdStrikeIndicatorMetadata {
avHits: number | null
companyName: string | null
fileDescription: string | null
fileVersion: string | null
filename: string | null
originalFilename: string | null
productName: string | null
productVersion: string | null
signed: boolean | null
}
export interface CrowdStrikeIndicator {
id: string | null
type: string | null
value: string | null
action: string | null
mobileAction: string | null
severity: string | null
description: string | null
source: string | null
appliedGlobally: boolean | null
platforms: string[]
hostGroups: string[]
tags: string[]
expiration: string | null
expired: boolean | null
deleted: boolean | null
fromParent: boolean | null
parentCidName: string | null
createdBy: string | null
createdOn: string | null
modifiedBy: string | null
modifiedOn: string | null
metadata: CrowdStrikeIndicatorMetadata | null
}
export interface CrowdStrikeQueryIndicatorsResponse extends ToolResponse {
output: {
indicatorIds: string[]
count: number
pagination: CrowdStrikeCursorPagination | null
}
}
export interface CrowdStrikeIndicatorListResponse extends ToolResponse {
output: {
indicators: CrowdStrikeIndicator[]
count: number
errors: CrowdStrikeApiError[]
}
}
export type CrowdStrikeGetIndicatorDetailsResponse = CrowdStrikeIndicatorListResponse
export type CrowdStrikeCreateIndicatorsResponse = CrowdStrikeIndicatorListResponse
export type CrowdStrikeUpdateIndicatorsResponse = CrowdStrikeIndicatorListResponse
export interface CrowdStrikeDeleteIndicatorsResponse extends ToolResponse {
output: {
deletedIds: string[]
count: number
errors: CrowdStrikeApiError[]
}
}
export interface CrowdStrikeQueryVulnerabilitiesParams extends CrowdStrikeBaseParams {
filter: string
limit?: number
after?: string
sort?: string
}
export interface CrowdStrikeGetVulnerabilityDetailsParams extends CrowdStrikeBaseParams {
vulnerabilityIds: string[]
}
export interface CrowdStrikeVulnerabilityCve {
id: string | null
baseScore: number | null
severity: string | null
exprtRating: string | null
exploitStatus: number | null
exploitabilityScore: number | null
impactScore: number | null
remediationLevel: string | null
description: string | null
publishedDate: string | null
vector: string | null
types: string[]
isCisaKev: boolean | null
cisaDueDate: string | null
}
export interface CrowdStrikeVulnerabilityApp {
productNameNormalized: string | null
productNameVersion: string | null
vendorNormalized: string | null
}
export interface CrowdStrikeVulnerabilityHostInfo {
hostname: string | null
localIp: string | null
machineDomain: string | null
osVersion: string | null
platform: string | null
productTypeDesc: string | null
assetCriticality: string | null
internetExposure: string | null
tags: string[]
groups: string[]
}
export interface CrowdStrikeVulnerabilityRemediation {
id: string | null
title: string | null
action: string | null
type: string | null
link: string | null
reference: string | null
vendorUrl: string | null
}
export interface CrowdStrikeVulnerability {
id: string | null
aid: string | null
cid: string | null
status: string | null
confidence: string | null
vulnerabilityId: string | null
createdTimestamp: string | null
updatedTimestamp: string | null
closedTimestamp: string | null
cve: CrowdStrikeVulnerabilityCve | null
app: CrowdStrikeVulnerabilityApp | null
hostInfo: CrowdStrikeVulnerabilityHostInfo | null
remediationIds: string[]
remediations: CrowdStrikeVulnerabilityRemediation[]
suppressionInfo: { isSuppressed: boolean | null; reason: string | null } | null
}
export interface CrowdStrikeQueryVulnerabilitiesResponse extends ToolResponse {
output: {
vulnerabilityIds: string[]
count: number
pagination: CrowdStrikeSpotlightPagination | null
}
}
export interface CrowdStrikeGetVulnerabilityDetailsResponse extends ToolResponse {
output: {
vulnerabilities: CrowdStrikeVulnerability[]
count: number
errors: CrowdStrikeApiError[]
}
}
export interface CrowdStrikeInitRtrSessionParams extends CrowdStrikeBaseParams {
deviceId: string
queueOffline?: boolean
origin?: string
}
export interface CrowdStrikeExecuteRtrCommandParams extends CrowdStrikeBaseParams {
sessionId: string
baseCommand: string
commandString: string
}
export interface CrowdStrikeGetRtrCommandStatusParams extends CrowdStrikeBaseParams {
cloudRequestId: string
sequenceId?: number
}
export interface CrowdStrikeDeleteRtrSessionParams extends CrowdStrikeBaseParams {
sessionId: string
}
export interface CrowdStrikeInitRtrSessionResponse extends ToolResponse {
output: {
sessionId: string | null
deviceId: string | null
platform: string | null
pwd: string | null
offlineQueued: boolean | null
existingAidSessions: number | null
createdAt: string | null
errors: CrowdStrikeApiError[]
}
}
export interface CrowdStrikeExecuteRtrCommandResponse extends ToolResponse {
output: {
cloudRequestId: string | null
sessionId: string | null
queuedCommandOffline: boolean | null
errors: CrowdStrikeApiError[]
}
}
export interface CrowdStrikeGetRtrCommandStatusResponse extends ToolResponse {
output: {
complete: boolean | null
stdout: string | null
stderr: string | null
baseCommand: string | null
sessionId: string | null
taskId: string | null
sequenceId: number | null
errors: CrowdStrikeApiError[]
}
}
export interface CrowdStrikeDeleteRtrSessionResponse extends ToolResponse {
output: {
sessionId: string
deleted: boolean
errors: CrowdStrikeApiError[]
}
}
export interface CrowdStrikeQueryCasesParams extends CrowdStrikeBaseParams {
filter?: string
q?: string
limit?: number
offset?: number
sort?: string
}
export interface CrowdStrikeGetCaseDetailsParams extends CrowdStrikeBaseParams {
caseIds: string[]
}
export interface CrowdStrikeFalconUser {
uuid: string | null
email: string | null
fullName: string | null
}
export interface CrowdStrikeCase {
id: string | null
cid: string | null
name: string | null
description: string | null
descriptionFormat: string | null
status: string | null
severity: number | null
severityLevel: string | null
referenceId: string | null
version: number | null
tags: string[]
assignedTo: CrowdStrikeFalconUser | null
createdBy: CrowdStrikeFalconUser | null
lastUpdatedBy: CrowdStrikeFalconUser | null
createdTimestamp: string | null
updatedTimestamp: string | null
startTimestamp: string | null
endTimestamp: string | null
templateId: string | null
templateName: string | null
slaId: string | null
slaName: string | null
isReadOnly: boolean | null
}
export interface CrowdStrikeQueryCasesResponse extends ToolResponse {
output: {
caseIds: string[]
count: number
pagination: CrowdStrikePagination | null
}
}
export interface CrowdStrikeGetCaseDetailsResponse extends ToolResponse {
output: {
cases: CrowdStrikeCase[]
count: number
errors: CrowdStrikeApiError[]
}
}
export type CrowdStrikeResponse =
| CrowdStrikeQuerySensorsResponse
| CrowdStrikeGetSensorDetailsResponse
| CrowdStrikeGetSensorAggregatesResponse
| CrowdStrikeQueryAlertsResponse
| CrowdStrikeGetAlertDetailsResponse
| CrowdStrikeUpdateAlertsResponse
| CrowdStrikePerformHostActionResponse
| CrowdStrikeQueryHostGroupsResponse
| CrowdStrikeGetHostGroupDetailsResponse
| CrowdStrikePerformHostGroupActionResponse
| CrowdStrikeQueryIndicatorsResponse
| CrowdStrikeIndicatorListResponse
| CrowdStrikeDeleteIndicatorsResponse
| CrowdStrikeQueryVulnerabilitiesResponse
| CrowdStrikeGetVulnerabilityDetailsResponse
| CrowdStrikeInitRtrSessionResponse
| CrowdStrikeExecuteRtrCommandResponse
| CrowdStrikeGetRtrCommandStatusResponse
| CrowdStrikeDeleteRtrSessionResponse
| CrowdStrikeQueryCasesResponse
| CrowdStrikeGetCaseDetailsResponse
+181
View File
@@ -0,0 +1,181 @@
import type {
CrowdStrikeUpdateAlertsParams,
CrowdStrikeUpdateAlertsResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeUpdateAlertsTool: ToolConfig<
CrowdStrikeUpdateAlertsParams,
CrowdStrikeUpdateAlertsResponse
> = {
id: 'crowdstrike_update_alerts',
name: 'CrowdStrike Update Alerts',
description:
'Update CrowdStrike Falcon alerts by composite ID: change status, assign or unassign an analyst, add or remove tags, append a comment, or toggle visibility (PATCH /alerts/entities/alerts/v3). This modifies live alerts in the Falcon console. Requires the "Alerts: Write" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
compositeIds: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'JSON array of CrowdStrike composite alert IDs to update',
},
updateStatus: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New alert status: new, in_progress, reopened, or closed',
},
assignToUuid: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Assign the alert to this Falcon user UUID',
},
assignToUserId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Assign the alert to this Falcon user ID, such as user@example.com',
},
assignToName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Assign the alert to this Falcon username, such as John Doe',
},
unassign: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Clear the assigned user UUID, user ID, and username from the alert',
},
appendComment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comment to append to the alert in the Falcon console',
},
addTag: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Tag to add to the alert',
},
removeTag: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Tag to remove from the alert',
},
removeTagsByPrefix: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Remove every tag on the alert that starts with this prefix',
},
showInUi: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the alert is displayed in the Falcon console',
},
actionParameters: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description:
'Raw JSON array of additional CrowdStrike action parameters, each shaped { "name": string, "value": string }',
},
includeHidden: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include previously hidden alerts (CrowdStrike defaults this to true)',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
actionParameters: params.actionParameters,
addTag: params.addTag,
appendComment: params.appendComment,
assignToName: params.assignToName,
assignToUserId: params.assignToUserId,
assignToUuid: params.assignToUuid,
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
compositeIds: params.compositeIds,
includeHidden: params.includeHidden,
operation: 'crowdstrike_update_alerts',
removeTag: params.removeTag,
removeTagsByPrefix: params.removeTagsByPrefix,
showInUi: params.showInUi,
unassign: params.unassign,
updateStatus: params.updateStatus,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to update CrowdStrike alerts')
}
return {
success: true,
output: data.output,
}
},
outputs: {
updatedIds: {
type: 'array',
description: 'Composite alert IDs the update was submitted for',
items: { type: 'string' },
},
count: {
type: 'number',
description: 'Number of alerts the update was submitted for',
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
@@ -0,0 +1,226 @@
import type {
CrowdStrikeUpdateIndicatorsParams,
CrowdStrikeUpdateIndicatorsResponse,
} from '@/tools/crowdstrike/types'
import type { ToolConfig } from '@/tools/types'
export const crowdstrikeUpdateIndicatorsTool: ToolConfig<
CrowdStrikeUpdateIndicatorsParams,
CrowdStrikeUpdateIndicatorsResponse
> = {
id: 'crowdstrike_update_indicators',
name: 'CrowdStrike Update Indicators',
description:
'Update custom CrowdStrike Falcon indicators of compromise by ID (PATCH /iocs/entities/indicators/v1). DESTRUCTIVE: CrowdStrike blanks out any field you omit, so read each indicator with crowdstrike_get_indicator_details first and resend its full field set with your edits applied. Changing action or scope changes prevention behavior fleet-wide. type and value are immutable. Requires the "IOC Management: Write" API scope.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon API client secret',
},
cloud: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'CrowdStrike Falcon cloud region',
},
indicators: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'JSON array of indicators to update. Each entry requires id, and must also repeat every field it wants to keep: CrowdStrike blanks out any updatable field the entry omits. Updatable fields: action, severity, description, source, tags (array), platforms (array), applied_globally (boolean), host_groups (array), expiration (ISO 8601), mobile_action, metadata ({ filename }). type and value cannot be changed.',
},
comment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Audit comment explaining why these indicators were updated',
},
retrodetects: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to generate retroactive detections for the updated indicators',
},
ignoreWarnings: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to apply the updates even when CrowdStrike returns warnings',
},
},
request: {
url: '/api/tools/crowdstrike/query',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
cloud: params.cloud,
clientId: params.clientId,
clientSecret: params.clientSecret,
comment: params.comment,
ignoreWarnings: params.ignoreWarnings,
indicators: params.indicators,
operation: 'crowdstrike_update_indicators',
retrodetects: params.retrodetects,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok || data.success === false) {
throw new Error(data.error || 'Failed to update CrowdStrike indicators')
}
return {
success: true,
output: data.output,
}
},
outputs: {
indicators: {
type: 'array',
description: 'Updated CrowdStrike indicator records',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Indicator identifier', optional: true },
type: { type: 'string', description: 'Indicator type', optional: true },
value: { type: 'string', description: 'Indicator value', optional: true },
action: {
type: 'string',
description: 'Action taken when the indicator matches',
optional: true,
},
mobileAction: {
type: 'string',
description: 'Action taken on mobile platforms when the indicator matches',
optional: true,
},
severity: { type: 'string', description: 'Indicator severity', optional: true },
description: { type: 'string', description: 'Indicator description', optional: true },
source: { type: 'string', description: 'Indicator source', optional: true },
appliedGlobally: {
type: 'boolean',
description: 'Whether the indicator applies to all hosts',
optional: true,
},
platforms: {
type: 'array',
description: 'Platforms the indicator applies to',
optional: true,
items: { type: 'string' },
},
hostGroups: {
type: 'array',
description: 'Host group IDs the indicator is scoped to',
optional: true,
items: { type: 'string' },
},
tags: {
type: 'array',
description: 'Tags applied to the indicator',
optional: true,
items: { type: 'string' },
},
expiration: {
type: 'string',
description: 'Indicator expiration timestamp',
optional: true,
},
expired: {
type: 'boolean',
description: 'Whether the indicator has expired',
optional: true,
},
deleted: {
type: 'boolean',
description: 'Whether the indicator is deleted',
optional: true,
},
fromParent: {
type: 'boolean',
description: 'Whether the indicator was inherited from a parent CID',
optional: true,
},
parentCidName: { type: 'string', description: 'Parent CID name', optional: true },
createdBy: {
type: 'string',
description: 'User who created the indicator',
optional: true,
},
createdOn: {
type: 'string',
description: 'Indicator creation timestamp',
optional: true,
},
modifiedBy: {
type: 'string',
description: 'User who last modified the indicator',
optional: true,
},
modifiedOn: {
type: 'string',
description: 'Indicator modification timestamp',
optional: true,
},
metadata: {
type: 'json',
description: 'File metadata CrowdStrike resolved for the indicator',
optional: true,
properties: {
avHits: { type: 'number', description: 'Antivirus hit count', optional: true },
companyName: { type: 'string', description: 'Company name', optional: true },
fileDescription: { type: 'string', description: 'File description', optional: true },
fileVersion: { type: 'string', description: 'File version', optional: true },
filename: { type: 'string', description: 'File name', optional: true },
originalFilename: {
type: 'string',
description: 'Original file name',
optional: true,
},
productName: { type: 'string', description: 'Product name', optional: true },
productVersion: { type: 'string', description: 'Product version', optional: true },
signed: {
type: 'boolean',
description: 'Whether the file is signed',
optional: true,
},
},
},
},
},
},
count: {
type: 'number',
description: 'Number of indicators updated',
},
errors: {
type: 'array',
description: 'Errors CrowdStrike returned alongside a partially successful response',
optional: true,
items: {
type: 'object',
properties: {
code: { type: 'number', description: 'CrowdStrike error code', optional: true },
id: { type: 'string', description: 'Identifier the error applies to', optional: true },
message: { type: 'string', description: 'Error message', optional: true },
},
},
},
},
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+40
View File
@@ -719,9 +719,29 @@ import {
convexRunFunctionTool,
} from '@/tools/convex'
import {
crowdstrikeCreateIndicatorsTool,
crowdstrikeDeleteIndicatorsTool,
crowdstrikeDeleteRtrSessionTool,
crowdstrikeExecuteRtrCommandTool,
crowdstrikeGetAlertDetailsTool,
crowdstrikeGetCaseDetailsTool,
crowdstrikeGetHostGroupDetailsTool,
crowdstrikeGetIndicatorDetailsTool,
crowdstrikeGetRtrCommandStatusTool,
crowdstrikeGetSensorAggregatesTool,
crowdstrikeGetSensorDetailsTool,
crowdstrikeGetVulnerabilityDetailsTool,
crowdstrikeInitRtrSessionTool,
crowdstrikePerformHostActionTool,
crowdstrikePerformHostGroupActionTool,
crowdstrikeQueryAlertsTool,
crowdstrikeQueryCasesTool,
crowdstrikeQueryHostGroupsTool,
crowdstrikeQueryIndicatorsTool,
crowdstrikeQuerySensorsTool,
crowdstrikeQueryVulnerabilitiesTool,
crowdstrikeUpdateAlertsTool,
crowdstrikeUpdateIndicatorsTool,
} from '@/tools/crowdstrike'
import {
cursorAddFollowupTool,
@@ -6624,9 +6644,29 @@ export const tools: Record<string, ToolConfig> = {
convex_list_tables: convexListTablesTool,
convex_list_documents: convexListDocumentsTool,
convex_document_deltas: convexDocumentDeltasTool,
crowdstrike_create_indicators: crowdstrikeCreateIndicatorsTool,
crowdstrike_delete_indicators: crowdstrikeDeleteIndicatorsTool,
crowdstrike_delete_rtr_session: crowdstrikeDeleteRtrSessionTool,
crowdstrike_execute_rtr_command: crowdstrikeExecuteRtrCommandTool,
crowdstrike_get_alert_details: crowdstrikeGetAlertDetailsTool,
crowdstrike_get_case_details: crowdstrikeGetCaseDetailsTool,
crowdstrike_get_host_group_details: crowdstrikeGetHostGroupDetailsTool,
crowdstrike_get_indicator_details: crowdstrikeGetIndicatorDetailsTool,
crowdstrike_get_rtr_command_status: crowdstrikeGetRtrCommandStatusTool,
crowdstrike_get_sensor_aggregates: crowdstrikeGetSensorAggregatesTool,
crowdstrike_get_sensor_details: crowdstrikeGetSensorDetailsTool,
crowdstrike_get_vulnerability_details: crowdstrikeGetVulnerabilityDetailsTool,
crowdstrike_init_rtr_session: crowdstrikeInitRtrSessionTool,
crowdstrike_perform_host_action: crowdstrikePerformHostActionTool,
crowdstrike_perform_host_group_action: crowdstrikePerformHostGroupActionTool,
crowdstrike_query_alerts: crowdstrikeQueryAlertsTool,
crowdstrike_query_cases: crowdstrikeQueryCasesTool,
crowdstrike_query_host_groups: crowdstrikeQueryHostGroupsTool,
crowdstrike_query_indicators: crowdstrikeQueryIndicatorsTool,
crowdstrike_query_sensors: crowdstrikeQuerySensorsTool,
crowdstrike_query_vulnerabilities: crowdstrikeQueryVulnerabilitiesTool,
crowdstrike_update_alerts: crowdstrikeUpdateAlertsTool,
crowdstrike_update_indicators: crowdstrikeUpdateIndicatorsTool,
dynamodb_get: dynamodbGetTool,
dynamodb_put: dynamodbPutTool,
dynamodb_query: dynamodbQueryTool,