CrowdstrikeFalcon

The CrowdStrike Falcon OAuth 2 API (formerly the Falcon Firehose API), enables fetching and resolving detections, searching devices, getting behaviors by ID, containing hosts, and lifting host containment.

Endpoint · CrowdStrike Falcon

Details

IDCrowdstrikeFalcon
ProviderCrowdStrike
CategoryEndpoint
From Version5.0.0
Docker Imagedemisto/py3-tools:1.0.0.10120494
Supported ModulesAgentix XSIAM EDR Cortex Cloud Cloud Runtime Security Exposure Management

README

The CrowdStrike Falcon OAuth 2 API (formerly the Falcon Firehose API), enables fetching and resolving detections, searching devices, getting behaviors by ID, containing hosts, and lifting host containment.

Configure CrowdStrike Falcon in Cortex

Parameter Description Required
Server URL (e.g., https://api.crowdstrike.com)   True
Client ID   False
Secret   False
Source Reliability Reliability of the source providing the intelligence data. Currently used for “CVE” reputation command. False
Fetch incidents   False
Incident Fetch types Choose what incident types to fetch - You can choose any combination. Note:
Records from the detection endpoint of the CrowdStrike Falcon UI could be of types: ‘Endpoint Detection’ and ‘OFP Detection’.
False
Incident type   False
First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days) Supported in Cortex XSOAR and Cortex Platform. False
Max incidents per fetch Supported in Cortex XSOAR and Cortex Platform. Input a value between 1-500. Default is 15. False
Incidents Fetch Interval Supported in Cortex XSOAR and Cortex Platform. False
Advanced: Time in minutes to look back when fetching incidents and detections Use this parameter to determine the look-back period for searching for incidents that were created before the last run time and did not match the query when they were created. False
Endpoint Detections filter query Use the Falcon Query Language to refine the data collected. For more information, refer to the FQL syntax documentation: https://www.falconpy.io/Usage/Falcon-Query-Language.html False
IDP Detections filter query Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). False
Mobile Detections filter query Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). False
IOM filter query Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). False
IOA filter query The Indicator of Attack (IOA) fetch query. This is an FQL filter that is combined with the time-range filter using AND. For example: cloud_provider:’aws’. For more information, see the Falcon Query Language (FQL) documentation at: https://www.falconpy.io/Usage/Falcon-Query-Language.html. False
Detections from On-Demand Scans filter query Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). False
OFP Detections filter query Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). False
Third Party Detection fetch query Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). False
NGSIEM Detection fetch query Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). False
NGSIEM automated leads fetch query Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). False
NGSIEM cases fetch query Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). False
NGSIEM incidents fetch query Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). False
Recon filter query Use Falcon Query Language (FQL) to filter results. For more information, see the [FQL Syntax Documentation](https://www.falconpy.io/Usage/Falcon-Query-Language.html). False
Mirroring Direction Choose the direction to mirror the detection: Incoming (from CrowdStrike Falcon to Cortex XSOAR), Outgoing (from Cortex XSOAR to CrowdStrike Falcon), or Incoming and Outgoing (to/from CrowdStrike Falcon and Cortex XSOAR). False
Close Mirrored XSOAR Incident When selected, closing the CrowdStrike Falcon incident is mirrored in Cortex XSOAR. False
Close Mirrored CrowdStrike Falcon Incident or Detection When selected, closing the Cortex XSOAR incident is mirrored in CrowdStrike Falcon, according to the types that were chosen to be fetched and mirrored. False
Reopen Statuses CrowdStrike Falcon statuses that will reopen an incident in Cortex XSOAR if closed. You can choose any combination. False
Fetch events   False
Event Fetch types Choose what event types to fetch - You can choose any combination. Note: Records from the detection endpoint of the CrowdStrike Falcon UI could be of types: ‘Endpoint Detection’ and ‘OFP Detection’. False
Events Fetch Interval   False
Advanced: Time in minutes to look back when fetching events and detections Use this parameter to determine the look-back period for searching for events that were created before the last run time and did not match the query when they were created. False
Fetch assets and vulnerabilities   False
Fetch Asset types The asset sources to ingest into the Cortex Unified Asset Inventory. False
Assets Fetch Interval The fetch interval for assets and vulnerabilities. It is recommended to set it to 1 hour. False
Trust any certificate (not secure)   False
Use system proxy settings   False

Required API client scope

In order to use the CrowdStrike Falcon integration, the API client must have the following scope and permissions:

  • Real Time Response - Read and Write
  • Real Time Response Admin - Write
  • Alerts - Read and Write
  • IOC Manager - Read and Write
  • IOCs - Read and Write
  • IOA Exclusions - Read and Write
  • Machine Learning Exclusions - Read and Write
  • Detections - Read and Write
  • Hosts - Read and Write
  • Host Groups - Read and Write
  • Incidents - Read and Write
  • Spotlight Vulnerabilities - Read
  • User Management - Read
  • On-Demand Scans (ODS) - Read and Write
  • Identity Protection Entities - Read and Write
  • Identity Protection Detections - Read and Write
  • Identity Protection Timeline - Read
  • Identity Protection Assessment - Read
  • Falcon Container Image - Read
  • Recon - Read and Write
  • Workflow - Read and Write

Incident Mirroring (Cortex XSOAR Only)

You can enable incident mirroring between Cortex XSOAR incidents and CrowdStrike Falcon corresponding events (available from Cortex XSOAR version 6.0.0).
To set up the mirroring:

  1. Enable Fetching incidents in your instance configuration.
  2. In the Fetch types integration parameter, select what types to mirror.
  3. Optional: You can go to one of the fetch query parameters and select the query to fetch the events from CrowdStrike Falcon.
  4. In the Mirroring Direction integration parameter, select in which direction the incidents should be mirrored:

    Option Description
    None Turns off incident mirroring.
    Incoming Any changes in CrowdStrike Falcon events (mirroring incoming fields) will be reflected in Cortex XSOAR incidents.
    Outgoing Any changes in Cortex XSOAR incidents will be reflected in CrowdStrike Falcon events (outgoing mirrored fields).
    Incoming And Outgoing Changes in Cortex XSOAR incidents and CrowdStrike Falcon events will be reflected in both directions.
  5. Optional: Check the Close Mirrored XSOAR Incident integration parameter to close the Cortex XSOAR incident when the corresponding event is closed in CrowdStrike Falcon.
  6. Optional: Check the Close Mirrored CrowdStrike Falcon Incident or Detection integration parameter to close the CrowdStrike Falcon incident or detection when the corresponding Cortex XSOAR incident is closed.

Newly fetched Cortex XSOAR incidents will be mirrored in the chosen direction. However, this selection does not affect existing incidents.

Important Notes

  • To ensure the mirroring works as expected, mappers are required, both for incoming and outgoing, to map the expected fields in Cortex XSOAR and CrowdStrike Falcon.
  • When mirroring in incidents from CrowdStrike Falcon to Cortex XSOAR:
    • For the tags field, tags can only be added from the remote system.
    • When enabling the Close Mirrored XSOAR Incident integration parameter, the field in CrowdStrike Falcon that determines whether the incident was closed is the status field.
    • In case the look-back parameter is initialized with a certain value and during a time that incidents were fetched, if changing
      the lookback to a number that is greater than the previous value, then in the initial incident fetching there will be incidents duplications.
      If the integration was already set with lookback > 0, and the lookback is not being increased at any point of time, then those incident duplications would not occur.

Fetch Incidents (Cortex XSOAR Only)

CrowdStrike Falcon incidents or detections can be fetched as incidents in Cortex XSOAR.
Users can specify a fetch query per CrowdStrike Falcon fetch type when configuring the integration instance to control which records are fetched.

Indicator of Misconfiguration (IOM) Fetch Query

The IOM Fetch query relies on an FQL filter expression.

Available filters:

  • use_current_scan_ids (use this to get records for latest scans)
  • account_name
  • account_id
  • agent_id
  • attack_types
  • azure_subscription_id
  • cloud_provider
  • cloud_service_keyword
  • custom_policy_id
  • is_managed
  • policy_id
  • policy_type
  • resource_id
  • region
  • status
  • severity
  • severity_string

For example: cloud_provider: 'aws'+account_id: 'my_id'

Indicator of Attack (IOA) Fetch Query

Indicator of Attack (IOA) fetch query. An FQL filter that will be combined with the time-range filter using AND. For example: cloud_provider:'aws'. For more information, see the FQL Syntax Documentation.

Fetch Assets

CrowdStrike Falcon assets and vulnerabilities can be fetched and ingested into the Cortex XSIAM Unified Asset Inventory (UAI).
Select the desired method in the Fetch Assets Type parameter:

  • Spotlight: Fetches vulnerabilities from the Spotlight Vulnerabilities Endpoint and enriches them with the associated host details. Both the vulnerabilities and the corresponding assets are ingested into the Unified Asset Inventory. Only vulnerabilities updated within the last 100 days are retrieved, keeping each collection focused on recent data.
  • CNAPP Alerts: Fetches Cloud Native Application Protection Platform (CNAPP) alerts as assets.

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

cs-falcon-search-device


Searches for a device that matches the query.

Base Command

cs-falcon-search-device

Input

Argument Name Description Required
extended_data Whether or not to get additional data about the device. Possible values are: Yes, No. Optional
filter The query by which to filter the device. The query format should be filter_parameter_name:'filter_value'. Optional
limit The maximum records to return [1-5000]. Default is 50. Optional
offset The offset to start retrieving records from. Optional
ids A comma-separated list of device IDs to limit the results. Optional
status The status of the device. Possible values are: normal, containment_pending, contained, lift_containment_pending. Optional
hostname The hostname of the device. Optional
platform_name The platform name of the device. Possible values are: Windows, Mac, Linux. Optional
site_name The site name of the device. Optional
sort The property to sort by (e.g., status.desc or hostname.asc). Optional

Context Output

Path Type Description
CrowdStrike.Device.ID String The ID of the device.
CrowdStrike.Device.LocalIP String The local IP address of the device.
CrowdStrike.Device.ExternalIP String The external IP address of the device.
CrowdStrike.Device.Hostname String The hostname of the device.
CrowdStrike.Device.OS String The operating system of the device.
CrowdStrike.Device.MacAddress String The MAC address of the device.
CrowdStrike.Device.FirstSeen String The first time the device was seen.
CrowdStrike.Device.LastSeen String The last time the device was seen.
CrowdStrike.Device.PolicyType String The policy type of the device.
CrowdStrike.Device.Status String The device status.
Endpoint.Hostname String The endpoint hostname.
Endpoint.OS String The endpoint operation system.
Endpoint.IPAddress String The endpoint IP address.
Endpoint.ID String The endpoint ID.
Endpoint.Status String The endpoint status.
Endpoint.IsIsolated String The endpoint isolation status.
Endpoint.MACAddress String The endpoint MAC address.
Endpoint.Vendor String The integration name of the endpoint vendor.
Endpoint.OSVersion String The endpoint operation system version.

Command Example

!cs-falcon-search-device ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1,a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Context Example

{
   "CrowdStrike": {
        "Device": [
            {
                "ExternalIP": "94.188.164.68",
                "MacAddress": "8c-85-90-3d-ed-3e",
                "Hostname": "154.132.82-test-co.in-addr.arpa",
                "LocalIP": "192.168.1.76",
                "LastSeen": "2019-03-28T02:36:41Z",
                "OS": "Mojave (10.14)",
                "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "FirstSeen": "2017-12-28T22:38:11Z",
                "Status": "contained"
            },
            {
                "ExternalIP": "94.188.164.68",
                "MacAddress": "f0-18-98-74-8c-31",
                "Hostname": "154.132.82-test-co.in-addr.arpa",
                "LocalIP": "172.22.14.237",
                "LastSeen": "2019-03-17T10:03:17Z",
                "OS": "Mojave (10.14)",
                "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "FirstSeen": "2017-12-10T11:01:20Z",
                "Status": "contained"
            }
        ]
    },
    "Endpoint": [
        {
            "Hostname": "154.132.82-test-co.in-addr.arpa",
            "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "IPAddress": "192.168.1.76",
            "OS": "Mojave (10.14)",
            "Status": "Online",
            "Vendor": "CrowdStrike Falcon",
            "MACAddress": "1-1-1-1"
        },
        {
            "Hostname": "154.132.82-test-co.in-addr.arpa",
            "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "IPAddress": "172.22.14.237",
            "OS": "Mojave (10.14)",
            "Status": "Online",
            "Vendor": "CrowdStrike Falcon",
            "MACAddress": "1-1-1-1"
        }
    ]
}

Human Readable Output

Devices

ID Hostname OS Mac Address Local IP External IP First Seen Last Seen Status
a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 154.132.82-test-co.in-addr.arpa Mojave (10.14) 8c-85-90-3d-ed-3e 192.168.1.76 94.188.164.68 2017-12-28T22:38:11Z 2019-03-28T02:36:41Z contained
a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 154.132.82-test-co.in-addr.arpa Mojave (10.14) f0-18-98-74-8c-31 172.22.14.237 94.188.164.68 2017-12-10T11:01:20Z 2019-03-17T10:03:17Z contained

cs-falcon-search-detection


Search for details of specific detections, either using a filter query, or by providing the IDs of the detections.

Base Command

cs-falcon-search-detection

Input

Argument Name Description Required
ids A comma-separated list of IDs of the detections to search. If provided, will override other arguments. Optional
filter Filter detections using a query in Falcon Query Language (FQL).
For example, filter=”device.hostname:’CS-SE-TG-W7-01’“
For a full list of valid filter options, see: https://falcon.crowdstrike.com/support/documentation/2/query-api-reference#detectionsearch. Default is product:’epp’+type:’ldt’.
Optional
extended_data Whether to get additional data such as device and behaviors processed. Possible values are: Yes, No. Optional

Context Output

Path Type Description
CrowdStrike.Detection.Behavior.FileName String The filename of the behavior.
CrowdStrike.Detection.Behavior.Scenario String The scenario name of the behavior.
CrowdStrike.Detection.Behavior.MD5 String The MD5 hash of the IOC of the behavior.
CrowdStrike.Detection.Behavior.SHA256 String The SHA256 hash of the IOC of the behavior.
CrowdStrike.Detection.Behavior.IOCType String The type of the IOC.
CrowdStrike.Detection.Behavior.IOCValue String The value of the IOC.
CrowdStrike.Detection.Behavior.CommandLine String The command line executed in the behavior.
CrowdStrike.Detection.Behavior.UserName String The username related to the behavior.
CrowdStrike.Detection.Behavior.SensorID String The sensor ID related to the behavior.
CrowdStrike.Detection.Behavior.ParentProcessID String The ID of the parent process.
CrowdStrike.Detection.Behavior.ProcessID String The process ID of the behavior.
CrowdStrike.Detection.Behavior.ID String The ID of the behavior.
CrowdStrike.Detection.System String The system name of the detection.
CrowdStrike.Detection.CustomerID String The ID of the customer (CID).
CrowdStrike.Detection.MachineDomain String The name of the domain of the detection machine.
CrowdStrike.Detection.ID String The detection ID.
CrowdStrike.Detection.ProcessStartTime Date The start time of the process that generated the detection.

Command Example

!cs-falcon-search-detection filter="product:'idp'"

!cs-falcon-search-detection filter="product:'mobile'"

!cs-falcon-search-detection filter="product:'ngsiem'"

!cs-falcon-search-detection filter="product:'thirdparty'"

!cs-falcon-search-detection ids=ldt:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1:1898376850347,ldt:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1:1092318056279064902

Context Example

{
    "CrowdStrike": {
        "Detection": [
            { 
                "Status": "false_positive", 
                "ProcessStartTime": "2019-03-21T20:32:55.654489974Z", 
                "Behavior": [
                    {
                        "IOCType": "domain", 
                        "ProcessID": "2279170016592", 
                        "Scenario": "intel_detection", 
                        "ParentProcessID": "2257232915544", 
                        "CommandLine": "C:\\Python27\\pythonw.exe -c __import__('idlelib.run').run.main(True) 1250", 
                        "UserName": "josh", 
                        "FileName": "pythonw.exe", 
                        "SensorID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", 
                        "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", 
                        "ID": "4900", 
                        "IOCValue": "systemlowcheck.com", 
                        "MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
                    }, 
                    {
                        "IOCType": "domain", 
                        "ProcessID": "2283087267593", 
                        "Scenario": "intel_detection", 
                        "ParentProcessID": "2279170016592", 
                        "CommandLine": "ping.exe systemlowcheck.com", 
                        "UserName": "josh", 
                        "FileName": "PING.EXE", 
                        "SensorID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", 
                        "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", 
                        "ID": "4900", 
                        "IOCValue": "systemlowcheck.com", 
                        "MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
                    }
                ], 
                "MaxSeverity": 70, 
                "System": "DESKTOP-S49VMIL", 
                "ID": "ldt:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1:1898376850347", 
                "MachineDomain": "", 
                "ShowInUi": true, 
                "CustomerID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
            }, 
            {
                "Status": "new", 
                "ProcessStartTime": "2019-02-04T07:05:57.083205971Z", 
                "Behavior": [
                    {
                        "IOCType": "sha256", 
                        "ProcessID": "201917905370426448", 
                        "Scenario": "known_malware", 
                        "ParentProcessID": "201917902773103685", 
                        "CommandLine": "./xSf", 
                        "UserName": "user@u-MacBook-Pro-2.local", 
                        "FileName": "xSf", 
                        "SensorID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", 
                        "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", 
                        "ID": "3206", 
                        "IOCValue": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", 
                        "MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
                    }, 
                    {
                        "IOCType": "sha256", 
                        "ProcessID": "201917905370426448", 
                        "Scenario": "known_malware", 
                        "ParentProcessID": "201917902773103685", 
                        "CommandLine": "./xSf", 
                        "UserName": "user@u-MacBook-Pro-2.local", 
                        "FileName": "xSf", 
                        "SensorID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", 
                        "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", 
                        "ID": "3206", 
                        "IOCValue": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", 
                        "MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
                    }
                ], 
                "MaxSeverity": 30, 
                "System": "u-MacBook-Pro-2.local", 
                "ID": "ldt:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1:1092318056279064902", 
                "MachineDomain": "", 
                "ShowInUi": true, 
                "CustomerID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
            }
        ]
    }
}

Human Readable Output

Detections Found

ID Status System Process Start Time Customer ID Max Severity
ldt:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1:1898376850347 false_positive DESKTOP-S49VMIL 2019-03-21T20:32:55.654489974Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 70
ldt:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1:1092318056279064902 new u-MacBook-Pro-2.local 2019-02-04T07:05:57.083205971Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 30

cs-falcon-resolve-detection


Resolves and updates a detection using the provided arguments. At least one optional argument must be passed, otherwise no change will take place. Note that IDP detections are not supported.

Base Command

cs-falcon-resolve-detection

Input

Argument Name Description Required
ids A comma-separated list of one or more IDs to resolve. Required
status The status to transition a detection to. Possible values are: new, in_progress, closed, reopened. Optional
assigned_to_uuid A user ID, for example: 1234567855512345678. username and assigned_to_uuid are mutually exclusive. Optional
comment Optional comment to add to the detection. Comments are displayed with the detection in CrowdStrike Falcon and provide context or notes for other Falcon users. Optional
show_in_ui If true, displays the detection in the UI. Possible values are: true, false. Optional
username Username to assign the detections to. (This is usually the user’s email address, but may vary based on your configuration). username and assigned_to_uuid are mutually exclusive. Optional
tag The tag to add to the detection, supported only for API V3. Optional

Context Output

There is no context output for this command.

cs-falcon-contain-host


Contains containment for a specified host. When contained, a host can only communicate with the CrowdStrike cloud and any IPs specified in your containment policy.

Base Command

cs-falcon-contain-host

Input

Argument Name Description Required
ids A comma-separated list of host agent IDs (AID) of the host to contain. Get an agent ID from a detection. Required

Context Output

There is no context output for this command.

cs-falcon-lift-host-containment


Lifts containment on the host, which returns its network communications to normal. When lift_filesystem_containment_all is set to true, lifts filesystem containment instead.

Base Command

cs-falcon-lift-host-containment

Input

Argument Name Description Required
ids A comma-separated list of host agent IDs (AIDs) of the hosts to contain. Get an agent ID from a detection. Required
lift_filesystem_containment_all Whether to lift filesystem containment instead of network containment. When set to true, uses the lift_filesystem_containment_all action to remove filesystem containment from the specified hosts. Possible values are: true, false. Default is false. Optional

Context Output

There is no context output for this command.

Command Example

!cs-falcon-lift-host-containment ids="a]1234567890abcdef12345678"

Human Readable Output

Containment has been lifted off host ‘a]1234567890abcdef12345678’

cs-falcon-run-command


Sends commands to hosts.

Base Command

cs-falcon-run-command

Input

Argument Name Description Required
queue_offline Any commands run against an offline-queued session will be queued up and executed when the host comes online. Optional
host_ids A comma-separated list of host agent IDs to run commands for. The list of host agent IDs can be retrieved by running the ‘cs-falcon-search-device’ command. Required
command_type The type of command to run. Required
full_command The full command to run. Required
scope The scope to run the command for. (NOTE: In order to run the CrowdStrike RTR put command, it is necessary to pass scope=admin). Possible values are: read, write, admin. Default is read. Optional
timeout The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. Default is 180. Optional
target The target to run the command for. Possible values are: batch, single. Default is batch. Optional
batch_id A batch ID to execute the command on. Optional

Context Output

Path Type Description
CrowdStrike.Command.HostID String The ID of the host the command was running for.
CrowdStrike.Command.SessionID string The session ID of the host.
CrowdStrike.Command.Stdout String The standard output of the command.
CrowdStrike.Command.Stderr String The standard error of the command.
CrowdStrike.Command.BaseCommand String The base command.
CrowdStrike.Command.FullCommand String The full command.
CrowdStrike.Command.TaskID string (For single host) The ID of the command request which has been accepted.
CrowdStrike.Command.Complete boolean (For single host) True if the command completed.
CrowdStrike.Command.NextSequenceID number (For single host) The next sequence ID.
CrowdStrike.Command.BatchID String The Batch ID that the command was executed on.

Command Example

cs-falcon-run-command host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 command_type=ls full_command="ls C:\\"

Context Example

{
    'CrowdStrike': {
        'Command': [{
            'HostID': 'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1',
            'Stdout': 'Directory listing for C:\\ -\n\n'
            'BatchID': 'batch_id'
            'Name                                     Type         Size (bytes)    Size (MB)       '
            'Last Modified (UTC-5)     Created (UTC-5)          \n----                             '
            '        ----         ------------    ---------       ---------------------     -------'
            '--------          \n$Recycle.Bin                             <Directory>  --          '
            '    --              11/27/2018 10:54:44 AM    9/15/2017 3:33:40 AM     \nITAYDI       '
            '                            <Directory>  --              --              11/19/2018 1:'
            '31:42 PM     11/19/2018 1:31:42 PM    ',
            'Stderr': '',
            'BaseCommand': 'ls',
            'Command': 'ls C:\\'
        }]
}

Human Readable Output

Command ls C:\ results

BaseCommand Command HostID Stderr Stdout BatchID
ls ls C:\ a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1   Directory listing for C:\ -

Name Type Size (bytes) Size (MB) Last Modified (UTC-5) Created (UTC-5)
—- —- ———— ——— ——————— —————
$Recycle.Bin <Directory> – – 11/27/2018 10:54:44 AM 9/15/2017 3:33:40 AM
ITAYDI <Directory> – – 11/19/2018 1:31:42 PM 11/19/2018 1:31:42 PM
batch_id

cs-falcon-upload-script


Uploads a script to Falcon CrowdStrike.

Base Command

cs-falcon-upload-script

Input

Argument Name Description Required
name The script name to upload. Required
permission_type The permission type for the custom script. Possible values are: “private”, which is used only by the user who uploaded it, “group”, which is used by all RTR Admins, and “public”, which is used by all active-responders and RTR admins. Possible values are: private, group, public. Default is private. Optional
content The content of the PowerShell script. Required

Context Output

There is no context output for this command.

Command Example

!cs-falcon-upload-script name=greatscript content="Write-Output 'Hello, World!'"

Human Readable Output

The script was uploaded successfully.

Context Output

There is no context output for this command.

cs-falcon-upload-file


Uploads a file to the CrowdStrike cloud. (Can be used for the RTR ‘put’ command).

Base Command

cs-falcon-upload-file

Input

Argument Name Description Required
entry_id The file entry ID to upload. Required

Context Output

There is no context output for this command.

Command Example

!cs-falcon-upload-file entry_id=4@4

Human Readable Output

The file was uploaded successfully.

Context Output

There is no context output for this command.

cs-falcon-delete-file


Deletes a file based on the provided ID or name. Can delete only one file at a time.

Base Command

cs-falcon-delete-file

Input

Argument Name Description Required
file_id The ID of the file to delete. Either this argument or file_name is required. When both are specified, file_id takes precedence. Optional
file_name The name of the file to delete. Either this argument or file_id is required. When both are specified, file_id takes precedence. Optional

Context Output

There is no context output for this command.

Command Example

!cs-falcon-delete-file file_id=le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Human Readable Output

File le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 was deleted successfully.

Context Output

There is no context output for this command.

cs-falcon-get-file


Returns files based on the provided IDs. These files are used for the RTR ‘put’ command.

Base Command

cs-falcon-get-file

Input

Argument Name Description Required
file_id A comma-separated list of file IDs to get. The list of file IDs can be retrieved by running the ‘cs-falcon-list-files’ command. Required

Context Output

Path Type Description
CrowdStrike.File.ID String The ID of the file.
CrowdStrike.File.CreatedBy String The email address of the user who created the file.
CrowdStrike.File.CreatedTime Date The datetime the file was created.
CrowdStrike.File.Description String The description of the file.
CrowdStrike.File.Type String The type of the file. For example, script.
CrowdStrike.File.ModifiedBy String The email address of the user who modified the file.
CrowdStrike.File.ModifiedTime Date The datetime the file was modified.
CrowdStrike.File.Name String The full name of the file.
CrowdStrike.File.Permission String The permission type of the file. Possible values are: “private”, which is used only by the user who uploaded it, “group”, which is used by all RTR Admins, and “public”, which is used by all active-responders and RTR admins.
CrowdStrike.File.SHA256 String The SHA-256 hash of the file.
File.Type String The file type.
File.Name String The full name of the file.
File.SHA256 String The SHA-256 hash of the file.
File.Size Number The size of the file in bytes.

Command Example

!cs-falcon-get-file file_id=le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Context Example

{
   "CrowdStrike": {
      "File": {
            "CreatedBy": "spongobob@demisto.com",
            "CreatedTime": "2019-10-17T13:41:48.487520845Z",
            "Description": "Demisto",
            "ID": "le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "ModifiedBy": "spongobob@demisto.com",
            "ModifiedTime": "2019-10-17T13:41:48.487521161Z",
            "Name": "Demisto",
            "Permission": "private",
            "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "Type": "script"
        }
   }
}

Human Readable Output

CrowdStrike Falcon file le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

CreatedBy CreatedTime Description ID ModifiedBy ModifiedTime Name Permission SHA256 Type
spongobob@demisto.com 2019-10-17T13:41:48.487520845Z Demisto le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 spongobob@demisto.com 2019-10-17T13:41:48.487521161Z Demisto private a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 script

cs-falcon-list-files


Returns a list of put-file IDs that are available for the user in the ‘put’ command. Due to an API limitation, the maximum number of files returned is 100.

Base Command

cs-falcon-list-files

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
CrowdStrike.File.ID String The ID of the file.
CrowdStrike.File.CreatedBy String The email address of the user who created the file.
CrowdStrike.File.CreatedTime Date The datetime the file was created.
CrowdStrike.File.Description String The description of the file.
CrowdStrike.File.Type String The type of the file. For example, script.
CrowdStrike.File.ModifiedBy String The email address of the user who modified the file.
CrowdStrike.File.ModifiedTime Date The datetime the file was modified.
CrowdStrike.File.Name String The full name of the file.
CrowdStrike.File.Permission String The permission type of the file. Possible values are: “private”, which is used only by the user who uploaded it, “group”, which is used by all RTR Admins, and “public”, which is used by all active-responders and RTR admins.
CrowdStrike.File.SHA256 String The SHA-256 hash of the file.
File.Type String The file type.
File.Name String The full name of the file.
File.SHA256 String The SHA-256 hash of the file.
File.Size Number The size of the file in bytes.

Command Example

!cs-falcon-list-files

Context Example

{
   "CrowdStrike": {
      "File": [
         {
            "CreatedBy": "spongobob@demisto.com",
            "CreatedTime": "2019-10-17T13:41:48.487520845Z",
            "Description": "Demisto",
            "ID": "le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "ModifiedBy": "spongobob@demisto.com",
            "ModifiedTime": "2019-10-17T13:41:48.487521161Z",
            "Name": "Demisto",
            "Permission": "private",
            "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "Type": "script"
         }
      ]
   }
}

Human Readable Output

CrowdStrike Falcon files

CreatedBy CreatedTime Description ID ModifiedBy ModifiedTime Name Permission SHA256 Type
spongobob@demisto.com 2019-10-17T13:41:48.487520845Z Demisto le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 spongobob@demisto.com 2019-10-17T13:41:48.487521161Z Demisto private a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 script

cs-falcon-get-script


Returns custom scripts based on the provided ID. Used for the RTR ‘runscript’ command.

Base Command

cs-falcon-get-script

Input

Argument Name Description Required
script_id A comma-separated list of script IDs to return. The script IDs can be retrieved by running the ‘cs-falcon-list-scripts’ command. Required

Context Output

Path Type Description
CrowdStrike.Script.ID String The ID of the script.
CrowdStrike.Script.CreatedBy String The email address of the user who created the script.
CrowdStrike.Script.CreatedTime Date The datetime the script was created.
CrowdStrike.Script.Description String The description of the script.
CrowdStrike.Script.ModifiedBy String The email address of the user who modified the script.
CrowdStrike.Script.ModifiedTime Date The datetime the script was modified.
CrowdStrike.Script.Name String The script name.
CrowdStrike.Script.Permission String Permission type of the script. Possible values are: “private”, which is used only by the user who uploaded it, “group”, which is used by all RTR Admins, and “public”, which is used by all active-responders and RTR admins.
CrowdStrike.Script.SHA256 String The SHA-256 hash of the script file.
CrowdStrike.Script.RunAttemptCount Number The number of times the script attempted to run.
CrowdStrike.Script.RunSuccessCount Number The number of times the script ran successfully.
CrowdStrike.Script.Platform String The list of operating system platforms on which the script can run. For example, Windows.
CrowdStrike.Script.WriteAccess Boolean Whether the user has write access to the script.

Command Example

!cs-falcon-get-script file_id=le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Context Example

{
    "CrowdStrike": {
        "Script": [
            {
                "CreatedBy": "spongobob@demisto.com",
                "CreatedTime": "2019-10-17T13:41:48.487520845Z",
                "Description": "Demisto",
                "ID": "le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "ModifiedBy": "spongobob@demisto.com",
                "ModifiedTime": "2019-10-17T13:41:48.487521161Z",
                "Name": "Demisto",
                "Permission": "private",
                "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "RunAttemptCount": 0,
                "RunSuccessCount": 0,
                "WriteAccess": true
            }
        ]
    }
}

Human Readable Output

CrowdStrike Falcon script le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

CreatedBy CreatedTime Description ID ModifiedBy ModifiedTime Name Permission SHA256
spongobob@demisto.com 2019-10-17T13:41:48.487520845Z Demisto le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 spongobob@demisto.com 2019-10-17T13:41:48.487521161Z Demisto private a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

cs-falcon-delete-script


Deletes a custom-script based on the provided ID. Can delete only one script at a time.

Base Command

cs-falcon-delete-script

Input

Argument Name Description Required
script_id The script ID to delete. The script IDs can be retrieved by running the ‘cs-falcon-list-scripts’ command. Required

Context Output

There is no context output for this command.

Command Example

!cs-falcon-delete-script script_id=le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Human Readable Output

Script le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 was deleted successfully.

Context Output

There is no context output for this command.

cs-falcon-list-scripts


Returns a list of custom script IDs that are available for the user in the ‘runscript’ command.

Base Command

cs-falcon-list-scripts

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
CrowdStrike.Script.ID String The ID of the script.
CrowdStrike.Script.CreatedBy String The email address of the user who created the script.
CrowdStrike.Script.CreatedTime Date The datetime the script was created.
CrowdStrike.Script.Description String The description of the script.
CrowdStrike.Script.ModifiedBy String The email address of the user who modified the script.
CrowdStrike.Script.ModifiedTime Date The datetime the script was modified.
CrowdStrike.Script.Name String The script name.
CrowdStrike.Script.Permission String Permission type of the script. Possible values are: “private”, which is used only by the user who uploaded it, “group”, which is used by all RTR Admins, and “public”, which is used by all active-responders and RTR admins.
CrowdStrike.Script.SHA256 String The SHA-256 hash of the script file.
CrowdStrike.Script.RunAttemptCount Number The number of times the script attempted to run.
CrowdStrike.Script.RunSuccessCount Number The number of times the script ran successfully.
CrowdStrike.Script.Platform String The list of operating system platforms on which the script can run. For example, Windows.
CrowdStrike.Script.WriteAccess Boolean Whether the user has write access to the script.

Command Example

!cs-falcon-list-scripts

Context Example

{
    "CrowdStrike": {
        "Script": [
            {
                "CreatedBy": "spongobob@demisto.com",
                "CreatedTime": "2019-10-17T13:41:48.487520845Z",
                "Description": "Demisto",
                "ID": "le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "ModifiedBy": "spongobob@demisto.com",
                "ModifiedTime": "2019-10-17T13:41:48.487521161Z",
                "Name": "Demisto",
                "Permission": "private",
                "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "RunAttemptCount": 0,
                "RunSuccessCount": 0,
                "WriteAccess": true
            }
        ]
    }
}

Human Readable Output

CrowdStrike Falcon scripts

CreatedBy CreatedTime Description ID ModifiedBy ModifiedTime Name Permission SHA256
spongobob@demisto.com 2019-10-17T13:41:48.487520845Z Demisto le10098bf0e311e989190662caec3daa_a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 spongobob@demisto.com 2019-10-17T13:41:48.487521161Z Demisto private a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

cs-falcon-run-script


Runs a script on the agent host.

Base Command

cs-falcon-run-script

Input

Argument Name Description Required
script_name The name of the script to run. Optional
host_ids A comma-separated list of host agent IDs to run commands. The list of host agent IDs can be retrieved by running the ‘cs-falcon-search-device’ command. Required
raw The PowerShell script code to run. Optional
timeout Timeout for how long to wait for the request in seconds. Maximum is 600 (10 minutes). Default is 30. Optional
queue_offline Whether the command will run against an offline-queued session and be queued for execution when the host comes online. Optional

Context Output

Path Type Description
CrowdStrike.Command.HostID String The ID of the host for which the command was running.
CrowdStrike.Command.SessionID String The ID of the session of the host.
CrowdStrike.Command.Stdout String The standard output of the command.
CrowdStrike.Command.Stderr String The standard error of the command.
CrowdStrike.Command.BaseCommand String The base command.
CrowdStrike.Command.FullCommand String The full command.

Command Example

cs-falcon-run-script host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 raw="Write-Output 'Hello, World!'"

Context Example

{
    "CrowdStrike": {
        "Command": [
            {
                "HostID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "Stdout": "Hello, World!",
                "Stderr": "",
                "BaseCommand": "runscript",
                "Command": "runscript -Raw=Write-Output 'Hello, World!'"
            }
        ]
    }
}

Human Readable Output

Command runscript -Raw=Write-Output ‘Hello, World! results

BaseCommand Command HostID Stderr Stdout  
runscript runscript -Raw=Write-Output ‘Hello, World! a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1   Hello, World! Type Size (bytes) Size (MB) Last Modified (UTC-5) Created (UTC-5)
—- —- ———— ——— ——————— —————
$Recycle.Bin <Directory> – – 11/27/2018 10:54:44 AM 9/15/2017 3:33:40 AM
ITAYDI <Directory> – – 11/19/2018 1:31:42 PM 11/19/2018 1:31:42 PM

cs-falcon-run-get-command


Batch executes ‘get’ command across hosts to retrieve files.

Base Command

cs-falcon-run-get-command

Input

Argument Name Description Required
host_ids A comma-separated list of host agent IDs on which to run the RTR command. Required
file_path Full path to the file that will be retrieved from each host in the batch. Required
optional_hosts A comma-separated list of a subset of hosts on which to run the command. Optional
timeout The number of seconds to wait for the request before it times out. In ISO time format. For example: 2019-10-17T13:41:48.487520845Z. Optional
timeout_duration The amount of time to wait for the request before it times out. In duration syntax. For example, 10s. Valid units are: ns, us, ms, s, m, h. Maximum value is 10 minutes. Optional

Context Output

Path Type Description
CrowdStrike.Command.HostID string The ID of the host on which the command was running.
CrowdStrike.Command.Stdout string The standard output of the command.
CrowdStrike.Command.Stderr string The standard error of the command.
CrowdStrike.Command.BaseCommand string The base command.
CrowdStrike.Command.TaskID string The ID of the command that was running on the host.
CrowdStrike.Command.GetRequestID string The ID of the command request that was accepted.
CrowdStrike.Command.Complete boolean True if the command completed.
CrowdStrike.Command.FilePath string The file path.

Command Example

cs-falcon-run-get-command host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 file_path="""c:\Windows\notepad.exe"""

Context Example

{
    "CrowdStrike": {
        "Command": [
            {
                "BaseCommand": "get",
                "Complete": true,
                "FilePath": "c:\\Windows\\notepad.exe",
                "GetRequestID": "84ee4d50-f499-482e-bac6-b0e296149bbf",
                "HostID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "Stderr": "",
                "Stdout": "C:\\Windows\\notepad.exe",
                "TaskID": "b5c8f140-280b-43fd-8501-9900f837510b"
            }
        ]
    }
}

Human Readable Output

Get command has requested for a file c:\Windows\notepad.exe

BaseCommand Complete FilePath GetRequestID HostID Stderr Stdout TaskID
get true c:\Windows\notepad.exe 107199bc-544c-4b0c-8f20-3094c062a115 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1   C:\Windows\notepad.exe 9c820b97-6a60-4238-bc23-f63513970ec8

cs-falcon-status-get-command


Retrieves the status of the specified batch ‘get’ command.

Base Command

cs-falcon-status-get-command

Input

Argument Name Description Required
request_ids A comma-separated list of IDs of the command requested. Required
timeout The number of seconds to wait for the request before it times out. In ISO time format. For example: 2019-10-17T13:41:48.487520845Z. Optional
timeout_duration The amount of time to wait for the request before it times out. In duration syntax. For example, 10s. Valid units are: ns, us, ms, s, m, h. Maximum value is 10 minutes. Optional

Context Output

Path Type Description
CrowdStrike.File.ID string The ID of the file.
CrowdStrike.File.TaskID string The ID of the command that is running.
CrowdStrike.File.CreatedAt date The date the file was created.
CrowdStrike.File.DeletedAt date The date the file was deleted.
CrowdStrike.File.UpdatedAt date The date the file was last updated.
CrowdStrike.File.Name string The full name of the file.
CrowdStrike.File.SHA256 string The SHA256 hash of the file.
CrowdStrike.File.Size number The size of the file in bytes.
File.Name string The full name of the file.
File.Size number The size of the file in bytes.
File.SHA256 string The SHA256 hash of the file.

Command Example

!cs-falcon-status-get-command request_ids="84ee4d50-f499-482e-bac6-b0e296149bbf"

Context Example

{
   "CrowdStrike": {
      "File": {
         "CreatedAt": "2020-05-01T16:09:00Z",
         "DeletedAt": null,
         "ID": 185596,
         "Name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
         "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
         "Size": 0,
         "TaskID": "b5c8f140-280b-43fd-8501-9900f837510b",
         "UpdatedAt": "2020-05-01T16:09:00Z"
      }
   },
   "File": {
      "Name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
      "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
      "Size": 0
   }
}

Human Readable Output

CrowdStrike Falcon files

CreatedAt DeletedAt ID Name SHA256 Size TaskID UpdatedAt
2020-05-01T16:09:00Z   185596 \Device\HarddiskVolume2\Windows\notepad.exe a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 0 b5c8f140-280b-43fd-8501-9900f837510b 2020-05-01T16:09:00Z

cs-falcon-status-command


Gets the status of a command executed on a host.

Base Command

cs-falcon-status-command

Input

Argument Name Description Required
request_id The ID of the command requested. Required
sequence_id The sequence ID in chunk requests. Optional
scope The scope to run the command for. Possible values are: read, write, admin. Default is read. Optional

Context Output

Path Type Description
CrowdStrike.Command.TaskID string The ID of the command request that was accepted.
CrowdStrike.Command.Stdout string The standard output of the command.
CrowdStrike.Command.Stderr string The standard error of the command.
CrowdStrike.Command.BaseCommand string The base command.
CrowdStrike.Command.Complete boolean True if the command completed.
CrowdStrike.Command.SequenceID number The sequence ID in the current request.
CrowdStrike.Command.NextSequenceID number The sequence ID for the next request in the chunk request.

Command Example

!cs-falcon-status-command request_id="ae323961-5aa8-442e-8461-8d05c4541d7d"

Context Example

{
  "CrowdStrike": {
    "Command": [
        {
            "BaseCommand": "ls",
            "Complete": true,
            "NextSequenceID": null,
            "SequenceID": null,
            "Stderr": "",
            "Stdout": "Directory listing for C:\\ -\n\nName                                     Type         Size (bytes)    Size (MB)       Last Modified (UTC+9)     Created (UTC+9)          \n----                                     ----         ------------    ---------       ---------------------     ---------------          \n$Recycle.Bin                             \u003cDirectory\u003e  --              --              2020/01/10 16:05:59       2019/03/19 13:52:43      \nConfig.Msi                               \u003cDirectory\u003e  --              --              2020/05/01 23:12:50       2020/01/10 16:52:09      \nDocuments and Settings                   \u003cDirectory\u003e  --              --              2019/09/12 15:03:21       2019/09/12 15:03:21      \nPerfLogs                                 \u003cDirectory\u003e  --              --              2019/03/19 13:52:43       2019/03/19 13:52:43      \nProgram Files                            \u003cDirectory\u003e  --              --              2020/01/10 17:11:47       2019/03/19 13:52:43      \nProgram Files (x86)                      \u003cDirectory\u003e  --              --              2020/05/01 23:12:53       2019/03/19 13:52:44      \nProgramData                              \u003cDirectory\u003e  --              --              2020/01/10 17:16:51       2019/03/19 13:52:44      \nRecovery                                 \u003cDirectory\u003e  --              --              2019/09/11 20:13:59       2019/09/11 20:13:59      \nSystem Volume Information                \u003cDirectory\u003e  --              --              2019/09/12 15:08:21       2019/09/11 20:08:43      \nUsers                                    \u003cDirectory\u003e  --              --              2019/09/22 22:26:11       2019/03/19 13:37:22      \nWindows                                  \u003cDirectory\u003e  --              --              2020/05/01 23:09:08       2019/03/19 13:37:22      \npagefile.sys                             .sys         2334928896      2226.762        2020/05/02 2:10:05        2019/09/11 20:08:44      \nswapfile.sys                             .sys         268435456       256             2020/05/01 23:09:13       2019/09/11 20:08:44      \n",
            "TaskID": "ae323961-5aa8-442e-8461-8d05c4541d7d"
            }
        ]
    }
}

Human Readable Output

Command status results

BaseCommand Complete Stdout TaskID
ls true Directory listing for C:\ …… ae323961-5aa8-442e-8461-8d05c4541d7d

cs-falcon-get-extracted-file


Gets the RTR extracted file contents for the specified session and SHA256 hash.

Base Command

cs-falcon-get-extracted-file

Input

Argument Name Description Required
host_id The host agent ID. Required
sha256 The SHA256 hash of the file. Required
filename The filename to use for the archive name and the file within the archive. Optional

Context Output

There is no context output for this command.

Command Example

!cs-falcon-get-extracted-file host_id="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1" sha256="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"

Context Output

There is no context output for this command.

cs-falcon-list-host-files


Gets a list of files for the specified RTR session on a host.

Base Command

cs-falcon-list-host-files

Input

Argument Name Description Required
host_id The ID of the host agent that lists files in the session. Required
session_id The ID of the existing session with the agent. Optional

Context Output

Path Type Description
CrowdStrike.Command.HostID string The ID of the host the command was running for.
CrowdStrike.Command.TaskID string The ID of the command request that was accepted.
CrowdStrike.Command.SessionID string The ID of the session of the host.
CrowdStrike.File.ID string The ID of the file.
CrowdStrike.File.CreatedAt date The date the file was created.
CrowdStrike.File.DeletedAt date The date the file was deleted.
CrowdStrike.File.UpdatedAt date The date the file was last updated.
CrowdStrike.File.Name string The full name of the file.
CrowdStrike.File.SHA256 string The SHA256 hash of the file.
CrowdStrike.File.Size number The size of the file in bytes.
File.Name string The full name of the file.
File.Size number The size of the file in bytes.
File.SHA256 string The SHA256 hash of the file.

Command Example

!cs-falcon-list-host-files host_id="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"

Context Example

{
  "CrowdStrike": {
    "Command": {
        "HostID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
        "SessionID": "fdd6408f-6688-441b-8659-41bcad25441c",
        "TaskID": "1269ad9e-c11f-4e38-8aba-1a0275304f9c"
    },
    "File": {
        "CreatedAt": "2020-05-01T17:57:42Z",
        "DeletedAt": null,
        "ID": 186811,
        "Name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
        "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
        "Size": 0,
        "Stderr": null,
        "Stdout": null,
        "UpdatedAt": "2020-05-01T17:57:42Z"
    }
  },
  "File": {
      "Name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
      "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
      "Size": 0
    }
}

Human Readable Output

CrowdStrike Falcon files

CreatedAt DeletedAt ID Name SHA256 Size Stderr Stdout UpdatedAt
2020-05-01T17:57:42Z   186811 \Device\HarddiskVolume2\Windows\notepad.exe a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 0     2020-05-01T17:57:42Z

cs-falcon-refresh-session


Refresh a session timeout on a single host.

Base Command

cs-falcon-refresh-session

Input

Argument Name Description Required
host_id The ID of the host to extend the session for. Required

Context Output

There is no context output for this command.

Command Example

!cs-falcon-refresh-session host_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Human Readable Output

CrowdStrike Session Refreshed: fdd6408f-6688-441b-8659-41bcad25441c

Context Output

There is no context output for this command.

cs-falcon-search-custom-iocs


Returns a list of your uploaded IOCs that match the search criteria.

Base Command

cs-falcon-search-custom-iocs

Input

Argument Name Description Required
types A comma-separated list of indicator types. Possible values are: sha256, sha1, md5, domain, ipv4, ipv6. Optional
values A comma-separated list of indicator values. Optional
sources A comma-separated list of IOC sources. Optional
expiration The datetime the indicator will become inactive (ISO 8601 format, i.e., YYYY-MM-DDThh:mm:ssZ). Optional
limit The maximum number of records to return. The minimum is 1 and the maximum is 500. Default is 50. Optional
sort The order the results are returned in. Possible values are: type.asc, type.desc, value.asc, value.desc, policy.asc, policy.desc, share_level.asc, share_level.desc, expiration_timestamp.asc, expiration_timestamp.desc. Optional
offset The offset to begin the list from. For example, start from the 10th record and return the list. Optional
next_page_token A pagination token used with the limit parameter to manage pagination of results. Matching the ‘after’ parameter in the API. Use instead of offset. Optional

Context Output

Path Type Description
CrowdStrike.IOC.Type string The type of the IOC.
CrowdStrike.IOC.Value string The string representation of the indicator.
CrowdStrike.IOC.ID string The full ID of the indicator.
CrowdStrike.IOC.Severity string The severity level to apply to this indicator.
CrowdStrike.IOC.Source string The source of the IOC.
CrowdStrike.IOC.Action string Action to take when a host observes the custom IOC.
CrowdStrike.IOC.Expiration string The datetime the indicator will expire.
CrowdStrike.IOC.Description string The description of the IOC.
CrowdStrike.IOC.CreatedTime date The datetime the IOC was created.
CrowdStrike.IOC.CreatedBy string The identity of the user/process who created the IOC.
CrowdStrike.IOC.ModifiedTime date The datetime the indicator was last modified.
CrowdStrike.IOC.ModifiedBy string The identity of the user/process who last updated the IOC.
CrowdStrike.IOC.MobileAction string The action to take on mobile devices when a host observes the custom IOC.
CrowdStrike.NextPageToken unknown A pagination token used with the limit parameter to manage pagination of results.

Command Example

!cs-falcon-search-custom-iocs limit=2

Context Example

{
    "CrowdStrike": {
        "IOC": [
            {
                "Action": "no_action",
                "CreatedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "CreatedTime": "2022-02-16T17:17:25.992164453Z",
                "Description": "test",
                "Expiration": "2022-02-17T13:47:57Z",
                "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "ModifiedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "ModifiedTime": "2022-02-16T17:17:25.992164453Z",
                "Platforms": [
                    "mac"
                ],
                "Severity": "informational",
                "Source": "Cortex",
                "Type": "ipv4",
                "Value": "1.1.8.9"
            },
            {
                "Action": "no_action",
                "CreatedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "CreatedTime": "2022-02-16T17:16:44.514398876Z",
                "Description": "test",
                "Expiration": "2022-02-17T13:47:57Z",
                "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "ModifiedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "ModifiedTime": "2022-02-16T17:16:44.514398876Z",
                "Platforms": [
                    "mac"
                ],
                "Severity": "informational",
                "Source": "Cortex",
                "Type": "ipv4",
                "Value": "4.1.8.9"
            }
        ]
    }
}

Human Readable Output

Indicators of Compromise

ID Action Severity Type Value Expiration CreatedBy CreatedTime Description ModifiedBy ModifiedTime Platforms Policy ShareLevel Source Tags
a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 no_action informational ipv4 1.1.8.9 2022-02-17T13:47:57Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T17:17:25.992164453Z test a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T17:17:25.992164453Z mac     Cortex  
a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 no_action informational ipv4 4.1.8.9 2022-02-17T13:47:57Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T17:16:44.514398876Z test a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T17:16:44.514398876Z mac     Cortex  

cs-falcon-get-custom-ioc


Gets the full definition of one or more indicators that you are watching.

Base Command

cs-falcon-get-custom-ioc

Input

Argument Name Description Required
type The IOC type to retrieve. Either ioc_id or ioc_type and value must be provided. Possible values are: sha256, sha1, md5, domain, ipv4, ipv6. Optional
value The string representation of the indicator. Either ioc_id or ioc_type and value must be provided. Optional
ioc_id The ID of the IOC to get. The ID of the IOC can be retrieved by running the ‘cs-falcon-search-custom-iocs’ command. Either ioc_id or ioc_type and value must be provided. Optional

Context Output

Path Type Description
CrowdStrike.IOC.Type string The type of the IOC.
CrowdStrike.IOC.Value string The string representation of the indicator.
CrowdStrike.IOC.ID string The full ID of the indicator.
CrowdStrike.IOC.Severity string The severity level to apply to this indicator.
CrowdStrike.IOC.Source string The source of the IOC.
CrowdStrike.IOC.Action string Action to take when a host observes the custom IOC.
CrowdStrike.IOC.Expiration string The datetime when the indicator will expire.
CrowdStrike.IOC.Description string The description of the IOC.
CrowdStrike.IOC.CreatedTime date The datetime the IOC was created.
CrowdStrike.IOC.CreatedBy string The identity of the user/process who created the IOC.
CrowdStrike.IOC.ModifiedTime date The datetime the indicator was last modified.
CrowdStrike.IOC.ModifiedBy string The identity of the user/process who last updated the IOC.
CrowdStrike.IOC.MobileAction string The action to take on mobile devices when a host observes the custom IOC.

Command Example

!cs-falcon-get-custom-ioc type=ipv4 value=7.5.9.8

Context Example

{
    "CrowdStrike": {
        "IOC": {
            "Action": "no_action",
            "MobileAction": "no_action",
            "CreatedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "CreatedTime": "2022-02-16T14:25:22.968603813Z",
            "Expiration": "2022-02-17T17:55:09Z",
            "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "ModifiedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "ModifiedTime": "2022-02-16T14:25:22.968603813Z",
            "Platforms": [
                "linux"
            ],
            "Severity": "informational",
            "Source": "Cortex",
            "Tags": [
                "test",
                "test1"
            ],
            "Type": "ipv4",
            "Value": "7.5.9.8"
        }
    }
}

Human Readable Output

Indicator of Compromise

ID Action Severity Type Value Expiration CreatedBy CreatedTime Description ModifiedBy ModifiedTime Platforms Policy ShareLevel Source Tags
a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 no_action informational ipv4 7.5.9.8 2022-02-17T17:55:09Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T14:25:22.968603813Z   a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T14:25:22.968603813Z linux     Cortex test,
test1

cs-falcon-upload-custom-ioc


Uploads an indicator for CrowdStrike to monitor.

Base Command

cs-falcon-upload-custom-ioc

Input

Argument Name Description Required
ioc_type The type of the indicator. Possible values are: sha256, md5, domain, ipv4, ipv6. Required
value A comma-separated list of indicators.
More than one value can be supplied to upload multiple IOCs of the same type but with different values. Note that the uploaded IOCs will have the same properties (as supplied in other arguments).
Required
action Action to take when a host observes the custom IOC. Possible values are: no_action - Save the indicator for future use, but take no action. No severity required. allow - Applies to hashes only. Allow the indicator and do not detect it. Severity does not apply and should not be provided. prevent_no_ui - Applies to hashes only. Block and detect the indicator, but hide it from Activity > Detections. Has a default severity value. prevent - Applies to hashes only. Block the indicator and show it as a detection at the selected severity. detect - Enable detections for the indicator at the selected severity. Possible values are: no_action, allow, prevent_no_ui, prevent, detect. Required
platforms A comma-separated list of the platforms that the indicator applies to. Possible values are: mac, windows, linux, android, ios. Required
severity The severity level to apply to this indicator. Required for the prevent and detect actions. Optional for no_action. Possible values are: informational, low, medium, high, critical. Optional
expiration The datetime the indicator will become inactive (ISO 8601 format, i.e., YYYY-MM-DDThh:mm:ssZ). Optional
source The source where this indicator originated. This can be used for tracking where this indicator was defined. Limited to 200 characters. Optional
description A meaningful description of the indicator. Limited to 200 characters. Optional
applied_globally Whether the indicator is applied globally. Either applied_globally or host_groups must be provided. Possible values are: true, false. Optional
host_groups A comma-separated list of host group IDs that the indicator applies to. The list of host group IDs can be retrieved by running the ‘cs-falcon-list-host-groups’ command. Either applied_globally or host_groups must be provided. Optional
tags A comma-separated list of tags to apply to the indicator. Optional
file_name Name of the file for file indicators. Applies to hashes only. A common filename, or a filename in your environment. Filenames can be helpful for identifying hashes or filtering IOCs. Optional
mobile_action The action to take on mobile devices when a host observes the custom IOC.
Note: To use this argument, a mobile platform (android or ios) must be included in the platforms argument.
- no_action: The indicator is saved for future use, but no action is taken (no severity required).
- allow: The indicator is allowed and not detected (severity does not apply and should not be provided).
- detect: The connection is allowed and a detection is generated.
- prevent: The indicator is blocked and shown as a detection. Possible values are: no_action, allow, detect, prevent.
Optional

Context Output

Path Type Description
CrowdStrike.IOC.Type string The type of the IOC.
CrowdStrike.IOC.Value string The string representation of the indicator.
CrowdStrike.IOC.ID string The full ID of the indicator.
CrowdStrike.IOC.Severity string The severity level to apply to this indicator.
CrowdStrike.IOC.Source string The source of the IOC.
CrowdStrike.IOC.Action string Action to take when a host observes the custom IOC.
CrowdStrike.IOC.Expiration string The datetime when the indicator will expire.
CrowdStrike.IOC.Description string The description of the IOC.
CrowdStrike.IOC.CreatedTime date The datetime the IOC was created.
CrowdStrike.IOC.CreatedBy string The identity of the user/process who created the IOC.
CrowdStrike.IOC.ModifiedTime date The datetime the indicator was last modified.
CrowdStrike.IOC.ModifiedBy string The identity of the user/process who last updated the IOC.
CrowdStrike.IOC.Tags Unknown The tags of the IOC.
CrowdStrike.IOC.Platforms Unknown The platforms of the IOC.
CrowdStrike.IOC.Filename string Name of the file for file indicators. Applies to hashes only. A common filename, or a filename in your environment. Filenames can be helpful for identifying hashes or filtering IOCs.
CrowdStrike.IOC.MobileAction string The action to take on mobile devices when a host observes the custom IOC.

Command Example

!cs-falcon-upload-custom-ioc ioc_type="domain" value="test.domain.com" action="prevent" severity="high" source="Demisto playbook" description="Test ioc" platforms="mac" mobile_action="no_action"

Context Example

{
    "CrowdStrike": {
        "IOC": {
            "CreatedTime": "2020-10-02T13:55:26Z",
            "Description": "Test ioc",
            "Expiration": "2020-11-01T00:00:00Z",
            "ID": "4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r",
            "ModifiedTime": "2020-10-02T13:55:26Z",
            "Action": "prevent",
            "MobileAction": "no_action",
            "Severity": "high",
            "Source": "Demisto playbook",
            "Type": "domain",
            "Value": "test.domain.com",
            "Platforms": ["mac"]
        }
    }
}

Human Readable Output

Custom IOC was created successfully

CreatedTime Description Expiration ID ModifiedTime Action Severity Source Type Value
2020-10-02T13:55:26Z Test ioc 2020-11-01T00:00:00Z 4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r 2020-10-02T13:55:26Z prevent high Demisto playbook domain test.domain.com

cs-falcon-update-custom-ioc


Updates an indicator for CrowdStrike to monitor.

Base Command

cs-falcon-update-custom-ioc

Input

Argument Name Description Required
ioc_id The ID of the IOC to update. The ID of the IOC can be retrieved by running the ‘cs-falcon-search-custom-iocs’ command. Required
action Action to take when a host observes the custom IOC. Possible values are: no_action - Save the indicator for future use, but take no action. No severity required. allow - Applies to hashes only. Allow the indicator and do not detect it. Severity does not apply and should not be provided. prevent_no_ui - Applies to hashes only. Block and detect the indicator, but hide it from Activity > Detections. Has a default severity value. prevent - Applies to hashes only. Block the indicator and show it as a detection at the selected severity. detect - Enable detections for the indicator at the selected severity. Possible values are: no_action, allow, prevent_no_ui, prevent, detect. Optional
platforms A comma-separated list of the platforms that the indicator applies to. Possible values are: mac, windows, linux. Optional
severity The severity level to apply to this indicator. Required for the prevent and detect actions. Optional for no_action. Possible values are: informational, low, medium, high, critical. Optional
expiration The datetime the indicator will become inactive (ISO 8601 format, i.e., YYYY-MM-DDThh:mm:ssZ). Optional
source The source where this indicator originated. This can be used for tracking where this indicator was defined. Limited to 200 characters. Optional
description A meaningful description of the indicator. Limited to 200 characters. Optional
file_name Name of the file for file indicators. Applies to hashes only. A common filename, or a filename in your environment. Filenames can be helpful for identifying hashes or filtering IOCs. Optional
mobile_action The action to take on mobile devices when a host observes the custom IOC.
Note: To use this argument, a mobile platform (android or ios) must be included in the platforms argument.
- no_action: The indicator is saved for future use, but no action is taken (no severity required).
- allow: The indicator is allowed and not detected (severity does not apply and should not be provided).
- detect: The connection is allowed and a detection is generated.
- prevent: The indicator is blocked and shown as a detection. Possible values are: no_action, allow, detect, prevent.
Optional

Context Output

Path Type Description
CrowdStrike.IOC.Type string The type of the IOC.
CrowdStrike.IOC.Value string The string representation of the indicator.
CrowdStrike.IOC.ID string The full ID of the indicator.
CrowdStrike.IOC.Severity string The severity level to apply to this indicator.
CrowdStrike.IOC.Source string The source of the IOC.
CrowdStrike.IOC.Action string Action to take when a host observes the custom IOC.
CrowdStrike.IOC.Expiration string The datetime when the indicator will expire.
CrowdStrike.IOC.Description string The description of the IOC.
CrowdStrike.IOC.CreatedTime date The datetime the IOC was created.
CrowdStrike.IOC.CreatedBy string The identity of the user/process who created the IOC.
CrowdStrike.IOC.ModifiedTime date The datetime the indicator was last modified.
CrowdStrike.IOC.ModifiedBy string The identity of the user/process who last updated the IOC.
CrowdStrike.IOC.Filename string Name of the file for file indicators. Applies to hashes only. A common filename, or a filename in your environment. Filenames can be helpful for identifying hashes or filtering IOCs.
CrowdStrike.IOC.MobileAction string The action to take on mobile devices when a host observes the custom IOC.

Command Example

!cs-falcon-update-custom-ioc ioc_id="4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r" severity="high"

Context Example

{
    "CrowdStrike": {
        "IOC": {
            "CreatedTime": "2020-10-02T13:55:26Z",
            "Description": "Test ioc",
            "Expiration": "2020-11-01T00:00:00Z",
            "ID": "4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r",
            "ModifiedTime": "2020-10-02T13:55:26Z",
            "Action": "prevent",
            "MobileAction": "no_action",
            "Severity": "high",
            "Source": "Demisto playbook",
            "Type": "domain",
            "Value": "test.domain.com"
        }
    }
}

Human Readable Output

Custom IOC was updated successfully

CreatedTime Description Expiration ID ModifiedTime Action Severity Source Type Value
2020-10-02T13:55:26Z Test ioc 2020-11-01T00:00:00Z 4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r 2020-10-02T13:55:26Z prevent high Demisto playbook domain test.domain.com

cs-falcon-delete-custom-ioc


Deletes a monitored indicator.

Base Command

cs-falcon-delete-custom-ioc

Input

Argument Name Description Required
ioc_id The ID of the IOC to delete. The ID of the IOC can be retrieved by running the ‘cs-falcon-search-custom-iocs’ command. Required

Context Output

There is no context output for this command.

Command Example

!cs-falcon-delete-custom-ioc ioc_id="4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r"

Human Readable Output

Custom IOC 4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r was successfully deleted.

cs-falcon-device-count-ioc


The number of hosts that observed the provided IOC.

Base Command

cs-falcon-device-count-ioc

Input

Argument Name Description Required
type The IOC type. Possible values are: sha256, sha1, md5, domain, ipv4, ipv6. Required
value The string representation of the indicator. Required

Context Output

Path Type Description
CrowdStrike.IOC.Type string The type of the IOC.
CrowdStrike.IOC.Value string The string representation of the indicator.
CrowdStrike.IOC.ID string The full ID of the indicator (type:value).
CrowdStrike.IOC.DeviceCount number The number of devices the IOC ran on.

Command Example

!cs-falcon-device-count-ioc type="domain" value="value"

Context Example

{
    "CrowdStrike": {
        "IOC": {
            "DeviceCount": 1,
            "ID": "domain:value",
            "Type": "domain",
            "Value": "value"
        }
    }
}

Human Readable Output

Indicator of Compromise domain:value device count: 1

cs-falcon-processes-ran-on


Get processes associated with a given IOC.

Base Command

cs-falcon-processes-ran-on

Input

Argument Name Description Required
type The IOC type. Possible values are: sha256, sha1, md5, domain, ipv4, ipv6. Required
value The string representation of the indicator. Required
device_id The device ID to check against. Required

Context Output

Path Type Description
CrowdStrike.IOC.Type string The type of the IOC.
CrowdStrike.IOC.Value string The string representation of the indicator.
CrowdStrike.IOC.ID string The full ID of the indicator (type:value).
CrowdStrike.IOC.Process.ID number The processes IDs associated with the given IOC.
CrowdStrike.IOC.Process.DeviceID number The device the process ran on.

Command Example

!cs-falcon-processes-ran-on device_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 type=domain value=value

Context Example

{
    "CrowdStrike": {
        "IOC": {
            "ID": "domain:value",
            "Process": {
                "DeviceID": "pid",
                "ID": [
                    "pid:pid:650164094720"
                ]
            },
            "Type": "domain",
            "Value": "value"
        }
    }
}

Human Readable Output

Processes with custom IOC domain:value on device device_id

Process ID
pid:pid:650164094720

cs-falcon-process-details


Retrieves the details of a process, according to the process ID that is running or that previously ran.

Base Command

cs-falcon-process-details

Input

Argument Name Description Required
ids A comma-separated list of process IDs. Required

Context Output

Path Type Description
CrowdStrike.Process.process_id String The process ID.
CrowdStrike.Process.process_id_local String Local ID of the process.
CrowdStrike.Process.device_id String The device the process ran on.
CrowdStrike.Process.file_name String The path of the file that ran the process.
CrowdStrike.Process.command_line String The command line command execution.
CrowdStrike.Process.start_timestamp_raw String The start datetime of the process in Unix time format. For example: 132460167512852140.
CrowdStrike.Process.start_timestamp String The start datetime of the process in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.
CrowdStrike.Process.stop_timestamp_raw Date The stop datetime of the process in Unix time format. For example: 132460167512852140.
CrowdStrike.Process.stop_timestamp Date The stop datetime of the process in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.

Command Example

!cs-falcon-process-details ids="pid:pid:pid"

Context Example

{
    "CrowdStrike": {
        "Process": {
            "command_line": "\"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe\"",
            "device_id": "deviceId",
            "file_name": "\\Device\\HarddiskVolume1\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
            "process_id": "deviceId:pid",
            "process_id_local": "pid",
            "start_timestamp": "2020-10-01T09:05:51Z",
            "start_timestamp_raw": "132460167512852140",
            "stop_timestamp": "2020-10-02T06:43:45Z",
            "stop_timestamp_raw": "132460946259334768"
        }
    }
}

Human Readable Output

Details for process: pid:pid:pid

command_line device_id file_name process_id process_id_local start_timestamp start_timestamp_raw stop_timestamp stop_timestamp_raw
“C:\Program Files (x86)\Google\Chrome\Application\chrome.exe” deviceId \Device\HarddiskVolume1\Program Files (x86)\Google\Chrome\Application\chrome.exe device_id:pid pid 2020-10-01T09:05:51Z 132460167512852140 2020-10-02T06:43:45Z 132460946259334768

cs-falcon-device-ran-on


Returns a list of device IDs an indicator ran on.

Base Command

cs-falcon-device-ran-on

Input

Argument Name Description Required
type The type of indicator. Possible values are: domain, ipv4, ipv6, md5, sha1, sha256. Required
value The string representation of the indicator. Required

Context Output

Path Type Description
CrowdStrike.DeviceID string Device IDs an indicator ran on.

Command Example

!cs-falcon-device-ran-on type=domain value=value

Context Example

{
    "CrowdStrike": {
        "DeviceID": [
            "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
        ]
    }
}

Human Readable Output

Devices that encountered the IOC domain:value

Device ID
a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

cs-falcon-list-detection-summaries


Lists detection summaries.

Base Command

cs-falcon-list-detection-summaries

Input

Argument Name Description Required
fetch_query The query used to filter the results. Optional
ids A comma-separated list of detection IDs. For example, ldt:1234:1234,ldt:5678:5678. If you use this argument, the fetch_query argument will be ignored. Optional

Context Output

Path Type Description
CrowdStrike.Detections.cid String The organization’s customer ID (CID).
CrowdStrike.Detections.created_timestamp Date The datetime the detection occurred in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.
CrowdStrike.Detections.detection_id String The ID of the detection.
CrowdStrike.Detections.device.device_id String The device ID as seen by CrowdStrike Falcon.
CrowdStrike.Detections.device.cid String The CrowdStrike Customer ID (CID) to which the device belongs.
CrowdStrike.Detections.device.agent_load_flags String The CrowdStrike Falcon agent load flags.
CrowdStrike.Detections.device.agent_local_time Date The local time of the sensor.
CrowdStrike.Detections.device.agent_version String The version of the agent that the device is running. For example: 5.32.11406.0.
CrowdStrike.Detections.device.bios_manufacturer String The BIOS manufacturer.
CrowdStrike.Detections.device.bios_version String The device’s BIOS version.
CrowdStrike.Detections.device.config_id_base String The base of the sensor that the device is running.
CrowdStrike.Detections.device.config_id_build String The version of the sensor that the device is running. For example: 11406.
CrowdStrike.Detections.device.config_id_platform String The platform ID of the sensor that the device is running.
CrowdStrike.Detections.device.external_ip String The external IP address of the device.
CrowdStrike.Detections.device.hostname String The hostname of the device.
CrowdStrike.Detections.device.first_seen Date The datetime the host was first seen by CrowdStrike Falcon.
CrowdStrike.Detections.device.last_seen Date The datetime the host was last seen by CrowdStrike Falcon.
CrowdStrike.Detections.device.local_ip String The local IP address of the device.
CrowdStrike.Detections.device.mac_address String The MAC address of the device.
CrowdStrike.Detections.device.major_version String The major version of the operating system.
CrowdStrike.Detections.device.minor_version String The minor version of the operating system.
CrowdStrike.Detections.device.os_version String The operating system of the device.
CrowdStrike.Detections.device.platform_id String The platform ID of the device that runs the sensor.
CrowdStrike.Detections.device.platform_name String The platform name of the device.
CrowdStrike.Detections.device.product_type_desc String The value indicating the product type. For example, 1 = Workstation, 2 = Domain Controller, 3 = Server.
CrowdStrike.Detections.device.status String The containment status of the machine. Possible values are: “normal”, “containment_pending”, “contained”, and “lift_containment_pending”.
CrowdStrike.Detections.device.system_manufacturer String The system manufacturer of the device.
CrowdStrike.Detections.device.system_product_name String The product name of the system.
CrowdStrike.Detections.device.modified_timestamp Date The datetime the device was last modified in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.
CrowdStrike.Detections.behaviors.device_id String The ID of the device associated with the behavior.
CrowdStrike.Detections.behaviors.timestamp Date The datetime the behavior detection occurred in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.
CrowdStrike.Detections.behaviors.filename String The filename of the triggering process.
CrowdStrike.Detections.behaviors.alleged_filetype String The file extension of the behavior’s filename.
CrowdStrike.Detections.behaviors.cmdline String The command line of the triggering process.
CrowdStrike.Detections.behaviors.scenario String The name of the scenario the behavior belongs to.
CrowdStrike.Detections.behaviors.objective String The name of the objective associated with the behavior.
CrowdStrike.Detections.behaviors.tactic String The name of the tactic associated with the behavior.
CrowdStrike.Detections.behaviors.technique String The name of the technique associated with the behavior.
CrowdStrike.Detections.behaviors.severity Number The severity rating for the behavior. The value can be any integer between 1-100.
CrowdStrike.Detections.behaviors.confidence Number The true positive confidence rating for the behavior. The value can be any integer between 1-100.
CrowdStrike.Detections.behaviors.ioc_type String The type of the triggering IOC. Possible values are: “hash_sha256”, “hash_md5”, “domain”, “filename”, “registry_key”, “command_line”, and “behavior”.
CrowdStrike.Detections.behaviors.ioc_value String The IOC value.
CrowdStrike.Detections.behaviors.user_name String The user name.
CrowdStrike.Detections.behaviors.user_id String The Security Identifier (SID) of the user in Windows.
CrowdStrike.Detections.behaviors.control_graph_id String The behavior hit key for the Threat Graph API.
CrowdStrike.Detections.behaviors.triggering_process_graph_id String The ID of the process that triggered the behavior detection.
CrowdStrike.Detections.behaviors.sha256 String The SHA256 of the triggering process.
CrowdStrike.Detections.behaviors.md5 String The MD5 hash of the triggering process.
CrowdStrike.Detections.behaviors.parent_details.parent_sha256 String The SHA256 hash of the parent process.
CrowdStrike.Detections.behaviors.parent_details.parent_md5 String The MD5 hash of the parent process.
CrowdStrike.Detections.behaviors.parent_details.parent_cmdline String The command line of the parent process.
CrowdStrike.Detections.behaviors.parent_details.parent_process_graph_id String The process graph ID of the parent process.
CrowdStrike.Detections.behaviors.pattern_disposition Number The pattern associated with the action performed on the behavior.
CrowdStrike.Detections.behaviors.pattern_disposition_details.indicator Boolean Whether the detection behavior is similar to an indicator.
CrowdStrike.Detections.behaviors.pattern_disposition_details.detect Boolean Whether this behavior is detected.
CrowdStrike.Detections.behaviors.pattern_disposition_details.inddet_mask Boolean Whether this behavior is an inddet mask.
CrowdStrike.Detections.behaviors.pattern_disposition_details.sensor_only Boolean Whether this detection is sensor only.
CrowdStrike.Detections.behaviors.pattern_disposition_details.rooting Boolean Whether this behavior is rooting.
CrowdStrike.Detections.behaviors.pattern_disposition_details.kill_process Boolean Whether this detection kills the process.
CrowdStrike.Detections.behaviors.pattern_disposition_details.kill_subprocess Boolean Whether this detection kills the subprocess.
CrowdStrike.Detections.behaviors.pattern_disposition_details.quarantine_machine Boolean Whether this detection was on a quarantined machine.
CrowdStrike.Detections.behaviors.pattern_disposition_details.quarantine_file Boolean Whether this detection was on a quarantined file.
CrowdStrike.Detections.behaviors.pattern_disposition_details.policy_disabled Boolean Whether this policy is disabled.
CrowdStrike.Detections.behaviors.pattern_disposition_details.kill_parent Boolean Whether this detection kills the parent process.
CrowdStrike.Detections.behaviors.pattern_disposition_details.operation_blocked Boolean Whether the operation is blocked.
CrowdStrike.Detections.behaviors.pattern_disposition_details.process_blocked Boolean Whether the process is blocked.
CrowdStrike.Detections.behaviors.pattern_disposition_details.registry_operation_blocked Boolean Whether the registry operation is blocked.
CrowdStrike.Detections.email_sent Boolean Whether an email is sent about this detection.
CrowdStrike.Detections.show_in_ui Boolean Whether the detection displays in the UI.
CrowdStrike.Detections.status String The status of the detection.
CrowdStrike.Detections.hostinfo.domain String The domain of the Active Directory.
CrowdStrike.Detections.seconds_to_triaged Number The amount of time it took to move a detection from “new” to “in_progress”.
CrowdStrike.Detections.seconds_to_resolved Number The amount of time it took to move a detection from new to a resolved state (“true_positive”, “false_positive”, and “ignored”).

Command Example


#### Context Example

```json
{
    "CrowdStrike": {
        "Detections": [
            {
                "behaviors": [
                    {
                        "alleged_filetype": "exe",
                        "behavior_id": "10197",
                        "cmdline": "choice  /m crowdstrike_sample_detection",
                        "confidence": 80,
                        "control_graph_id": "ctg:ctg:ctg",
                        "device_id": "deviceid",
                        "display_name": "",
                        "filename": "choice.exe",
                        "filepath": "",
                        "ioc_description": "",
                        "ioc_source": "",
                        "ioc_type": "",
                        "ioc_value": "",
                        "md5": "md5",
                        "objective": "Falcon Detection Method",
                        "parent_details": {
                            "parent_cmdline": "\"C:\\Windows\\system32\\cmd.exe\" ",
                            "parent_md5": "md5",
                            "parent_process_graph_id": "pid:pid:pid",
                            "parent_sha256": "sha256"
                        },
                        "pattern_disposition": 0,
                        "pattern_disposition_details": {
                            "bootup_safeguard_enabled": false,
                            "critical_process_disabled": false,
                            "detect": false,
                            "fs_operation_blocked": false,
                            "inddet_mask": false,
                            "indicator": false,
                            "kill_parent": false,
                            "kill_process": false,
                            "kill_subprocess": false,
                            "operation_blocked": false,
                            "policy_disabled": false,
                            "process_blocked": false,
                            "quarantine_file": false,
                            "quarantine_machine": false,
                            "registry_operation_blocked": false,
                            "rooting": false,
                            "sensor_only": false
                        },
                        "scenario": "suspicious_activity",
                        "severity": 30,
                        "sha256": "sha256",
                        "tactic": "Malware",
                        "tactic_id": "",
                        "technique": "Malicious File",
                        "technique_id": "",
                        "template_instance_id": "382",
                        "timestamp": "2020-07-06T08:10:44Z",
                        "triggering_process_graph_id": "pid:pid:pid",
                        "user_id": "user_id",
                        "user_name": "user_name"
                    }
                ],
                "behaviors_processed": [
                    "pid:pid:pid:10197"
                ],
                "cid": "cid",
                "created_timestamp": "2020-07-06T08:10:55.538668036Z",
                "detection_id": "ldt:ldt:ldt",
                "device": {
                    "agent_load_flags": "0",
                    "agent_local_time": "2020-07-02T01:42:07.640Z",
                    "agent_version": "5.32.11406.0",
                    "bios_manufacturer": "Google",
                    "bios_version": "Google",
                    "cid": "cid",
                    "config_id_base": "id",
                    "config_id_build": "id",
                    "config_id_platform": "3",
                    "device_id": "device_id",
                    "external_ip": "external_ip",
                    "first_seen": "2020-02-10T12:40:18Z",
                    "hostname": "FALCON-CROWDSTR",
                    "last_seen": "2020-07-06T07:59:12Z",
                    "local_ip": "local_ip",
                    "mac_address": "mac_address",
                    "major_version": "major_version",
                    "minor_version": "minor_version",
                    "modified_timestamp": "modified_timestamp",
                    "os_version": "os_version",
                    "platform_id": "platform_id",
                    "platform_name": "platform_name",
                    "product_type": "product_type",
                    "product_type_desc": "product_type_desc",
                    "status": "status",
                    "system_manufacturer": "system_manufacturer",
                    "system_product_name": "system_product_name"
                },
                "email_sent": false,
                "first_behavior": "2020-07-06T08:10:44Z",
                "hostinfo": {
                    "domain": ""
                },
                "last_behavior": "2020-07-06T08:10:44Z",
                "max_confidence": 80,
                "max_severity": 30,
                "max_severity_displayname": "Low",
                "seconds_to_resolved": 0,
                "seconds_to_triaged": 0,
                "show_in_ui": true,
                "status": "new"
            }
        ]
    }
}

Human Readable Output

CrowdStrike Detections

detection_id created_time status max_severity
ldt:ldt:ldt 2020-07-06T08:10:55.538668036Z new Low

endpoint


Returns information about an endpoint. Does not support regex.

Base Command

endpoint

Input

Argument Name Description Required
id The endpoint ID. Optional
ip The endpoint IP address. Optional
hostname The endpoint hostname. Optional

Context Output

Path Type Description
Endpoint.Hostname String The endpoint’s hostname.
Endpoint.OS String The endpoint’s operation system.
Endpoint.IPAddress String The endpoint’s IP address.
Endpoint.ID String The endpoint’s ID.
Endpoint.Status String The endpoint’s status.
Endpoint.IsIsolated String The endpoint’s isolation status.
Endpoint.MACAddress String The endpoint’s MAC address.
Endpoint.Vendor String The integration name of the endpoint vendor.
Endpoint.OSVersion String The endpoint’s operation system version.

Command Example

!endpoint id=15dbb9d5fe9f61eb46e829d986

Context Example

{
  "Endpoint":
    {
      "Hostname": "Hostname",
      "ID": "15dbb9d5fe9f61eb46e829d986",
      "IPAddress": "1.1.1.1",
      "OS": "Windows",
      "OSVersion": "Windows Server 2019",
      "Status": "Online",
      "￿Vendor": "CrowdStrike Falcon",
      "￿MACAddress": "1-1-1-1"
    }
}

Human Readable Output

Endpoints

ID IPAddress OS OSVersion Hostname Status MACAddress Vendor  
a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 1.1.1.1 Windows Windows Server 2019 Hostname Online 1-1-1-1 CrowdStrike Falcon \n”

cs-falcon-create-host-group


Create a host group.

Base Command

cs-falcon-create-host-group

Input

Argument Name Description Required
name The name of the host. Required
group_type The group type of the group. Possible values are: static, dynamic. Required
description The description of the host. Optional
assignment_rule The assignment rule. Optional

Context Output

Path Type Description
CrowdStrike.HostGroup.id String The ID of the host group.
CrowdStrike.HostGroup.group_type String The group type of the host group.
CrowdStrike.HostGroup.name String The name of the host group.
CrowdStrike.HostGroup.description String The description of the host group.
CrowdStrike.HostGroup.created_by String The client that created the host group.
CrowdStrike.HostGroup.created_timestamp Date The datetime the host group was created in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.
CrowdStrike.HostGroup.modified_by String The client that modified the host group.
CrowdStrike.HostGroup.modified_timestamp Date The datetime the host group was last modified in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.

Command Example

!cs-falcon-create-host-group name="test_name_1" description="test_description" group_type=static

Context Example

{
    "CrowdStrike": {
        "HostGroup": {
            "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "created_timestamp": "2021-08-25T08:02:02.060242909Z",
            "description": "test_description",
            "group_type": "static",
            "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "modified_timestamp": "2021-08-25T08:02:02.060242909Z",
            "name": "test_name_1"
        }
    }
}

Human Readable Output

Results

created_by created_timestamp description group_type id modified_by modified_timestamp name
api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-25T08:02:02.060242909Z test_description static a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-25T08:02:02.060242909Z test_name_1

cs-falcon-list-host-groups


List the available host groups.

Base Command

cs-falcon-list-host-groups

Input

Argument Name Description Required
filter The query by which to filter the devices that belong to the host group. Optional
offset Page offset. Optional
limit Maximum number of results on a page. Default is 50. Optional

Context Output

Path Type Description
CrowdStrike.HostGroup.id String The ID of the host group.
CrowdStrike.HostGroup.group_type String The group type of the host group.
CrowdStrike.HostGroup.name String The name of the host group.
CrowdStrike.HostGroup.description String The description of the host group.
CrowdStrike.HostGroup.created_by String The client that created the host group.
CrowdStrike.HostGroup.created_timestamp Date The datetime the host group was created in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.
CrowdStrike.HostGroup.modified_by String The client that modified the host group.
CrowdStrike.HostGroup.modified_timestamp Date The datetime the host group was last modified in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.

Command Example


#### Context Example

```json
{
    "CrowdStrike": {
        "HostGroup": [
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T14:35:23.765624811Z",
                "description": "description",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T14:35:23.765624811Z",
                "name": "InnerServicesModuleMon Aug 23 2021"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T14:35:25.506030441Z",
                "description": "description",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T14:35:25.506030441Z",
                "name": "Rasterize_default_instanceMon Aug 23 2021"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['','FALCON-CROWDSTR']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-07-27T12:34:59.13917402Z",
                "description": "",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-07-27T12:34:59.13917402Z",
                "name": "Static by id group test"
            },
            {
                "assignment_rule": "device_id:[],hostname:[]",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-07-27T12:24:18.364057533Z",
                "description": "Group test",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-07-27T12:24:18.364057533Z",
                "name": "Static group test"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T14:35:26.069515348Z",
                "description": "description",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T14:35:26.069515348Z",
                "name": "ad-loginMon Aug 23 2021"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T14:35:25.556897468Z",
                "description": "description",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T14:35:25.556897468Z",
                "name": "ad-queryMon Aug 23 2021"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T14:35:23.737307612Z",
                "description": "description",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T14:35:23.737307612Z",
                "name": "d2Mon Aug 23 2021"
            },
            {
                "created_by": "someone@email.com",
                "created_timestamp": "2021-07-27T12:27:43.503021999Z",
                "description": "dhfh",
                "group_type": "staticByID",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "someone@email.com",
                "modified_timestamp": "2021-07-27T12:27:43.503021999Z",
                "name": "ddfxgh"
            },
            {
                "assignment_rule": "device.hostname:'FALCON-CROWDSTR'",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-07-27T12:46:39.058352326Z",
                "description": "",
                "group_type": "dynamic",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-07-27T12:46:39.058352326Z",
                "name": "dynamic 1 group test"
            },
            {
                "assignment_rule": "lkjlk:'FalconGroupingTags/example_tag'",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T13:12:56.338590022Z",
                "description": "",
                "group_type": "dynamic",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T13:12:56.338590022Z",
                "name": "dynamic 13523 group test"
            },
            {
                "assignment_rule": "lkjlk:'FalconGroupingTags/example_tag'",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-07-27T14:02:05.538065349Z",
                "description": "",
                "group_type": "dynamic",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-07-27T14:02:05.538065349Z",
                "name": "dynamic 1353 group test"
            },
            {
                "assignment_rule": "tags:'FalconGroupingTags/example_tag'",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-07-27T12:41:33.127997409Z",
                "description": "",
                "group_type": "dynamic",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-07-27T12:41:33.127997409Z",
                "name": "dynamic 2 group test"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T14:35:23.7402217Z",
                "description": "description",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T14:35:23.7402217Z",
                "name": "fcm_default_instanceMon Aug 23 2021"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-11T09:55:23.801049103Z",
                "description": "ilan test",
                "group_type": "dynamic",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-11T09:55:23.801049103Z",
                "name": "ilan"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-12T11:24:51.434863056Z",
                "description": "ilan test",
                "group_type": "dynamic",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-12T11:24:51.434863056Z",
                "name": "ilan 2"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['FALCON-CROWDSTR']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-12T11:55:57.943490809Z",
                "description": "ilan test",
                "group_type": "dynamic",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-12T11:55:57.943490809Z",
                "name": "ilan 23"
            },
            {
                "assignment_rule": "",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-17T11:28:39.855075106Z",
                "description": "after change",
                "group_type": "dynamic",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T09:26:15.351650252Z",
                "name": "ilan 2345"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-17T11:58:42.453661998Z",
                "description": "ilan test",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-17T11:58:42.453661998Z",
                "name": "ilan 23e"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-11T13:54:59.695821727Z",
                "description": "",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-11T13:54:59.695821727Z",
                "name": "ilan test 2"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-12T10:56:49.2127345Z",
                "description": "ilan test",
                "group_type": "dynamic",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-12T11:35:35.76509212Z",
                "name": "ilan2"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T14:35:23.766284685Z",
                "description": "description",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T14:35:23.766284685Z",
                "name": "splunkMon Aug 23 2021"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:09:15.36414377Z",
                "description": "description",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:09:15.36414377Z",
                "name": "test_1629731353498"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:12:20.69203954Z",
                "description": "description",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:12:20.69203954Z",
                "name": "test_1629731538458"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:14:20.650781714Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:14:23.026511269Z",
                "name": "test_16297316587261629731658726"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:18:53.896505566Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:18:56.2598933Z",
                "name": "test_16297319320381629731932038"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:19:51.91067257Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:19:54.269898808Z",
                "name": "test_16297319902371629731990237"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:25:42.99601887Z",
                "description": "description",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:25:42.99601887Z",
                "name": "test_1629732339973"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:26:12.280379354Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:26:14.973676462Z",
                "name": "test_16297323698941629732369894"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:26:58.717706381Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:27:01.648623079Z",
                "name": "test_16297324168771629732416877"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:28:18.674512647Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:28:21.781563212Z",
                "name": "test_16297324965761629732496576"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['FALCON-CROWDSTR','INSTANCE-1','falcon-crowdstrike-sensor-centos7']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:31:41.142748214Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:31:43.800147323Z",
                "name": "test_16297326990981629732699098"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:34:20.195778795Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:34:23.212828317Z",
                "name": "test_16297328579781629732857978"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:34:55.837119719Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:34:58.490114093Z",
                "name": "test_16297328938791629732893879"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-23T15:37:42.911344704Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-23T15:37:45.620464598Z",
                "name": "test_16297330605301629733060530"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-24T07:05:55.813475476Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-24T07:05:58.805702883Z",
                "name": "test_16297887501421629788750142"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-24T07:07:30.422517324Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-24T07:07:34.291988227Z",
                "name": "test_16297888481381629788848138"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-24T08:03:15.522772079Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-24T08:03:18.622015517Z",
                "name": "test_16297921932741629792193274"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T09:09:52.379925975Z",
                "description": "description",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T09:09:52.379925975Z",
                "name": "test_1629967211800"
            },
            {
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T12:34:36.934507422Z",
                "description": "description",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T12:34:36.934507422Z",
                "name": "test_162996721180000"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T08:46:09.996065663Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T08:46:11.572092204Z",
                "name": "test_16299675695531629967569553"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T08:53:15.35181954Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T08:53:17.041535905Z",
                "name": "test_16299679949831629967994983"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T08:59:52.639696743Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T08:59:54.538170036Z",
                "name": "test_16299683923121629968392312"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T09:06:21.891707157Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T09:06:23.846219163Z",
                "name": "test_16299687814871629968781487"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T09:12:53.982989Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T09:12:55.571265187Z",
                "name": "test_16299691732871629969173287"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T09:17:58.206157753Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T09:17:59.659515838Z",
                "name": "test_16299694779051629969477905"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T09:19:23.276267291Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T09:19:25.318976241Z",
                "name": "test_16299695623981629969562398"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T09:26:22.538367707Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T09:26:25.085214782Z",
                "name": "test_16299699813871629969981387"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T09:33:46.303790983Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T09:33:48.288311235Z",
                "name": "test_16299704254441629970425444"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T09:55:09.157561612Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T09:55:10.741852436Z",
                "name": "test_16299717065381629971706538"
            },
            {
                "assignment_rule": "device_id:[''],hostname:['']",
                "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "created_timestamp": "2021-08-26T10:02:50.175530821Z",
                "description": "description2",
                "group_type": "static",
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "modified_timestamp": "2021-08-26T10:02:52.026307768Z",
                "name": "test_16299721694081629972169408"
            }
        ]
    }
}

Human Readable Output

Results

assignment_rule created_by created_timestamp description group_type id modified_by modified_timestamp name
device_id:[’’],hostname:[’’] api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-26T10:02:50.175530821Z description2 static a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-26T10:02:52.026307768Z test_16299721694081629972169408

cs-falcon-delete-host-groups


Deletes the requested host groups.

Base Command

cs-falcon-delete-host-groups

Input

Argument Name Description Required
host_group_id A comma-separated list of the IDs of the host groups to be deleted. Required

Context Output

There is no context output for this command.

Command Example

!cs-falcon-delete-host-groups host_group_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1,a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Human Readable Output

host group id a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 deleted successfully
host group id a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 deleted successfully

cs-falcon-update-host-group


Updates a host group.

Base Command

cs-falcon-update-host-group

Input

Argument Name Description Required
host_group_id The ID of the host group. Required
name The name of the host group. Optional
description The description of the host group. Optional
assignment_rule The assignment rule. Optional

Context Output

Path Type Description
CrowdStrike.HostGroup.id String The ID of the host group.
CrowdStrike.HostGroup.group_type String The group type of the host group.
CrowdStrike.HostGroup.name String The name of the host group.
CrowdStrike.HostGroup.description String The description of the host group.
CrowdStrike.HostGroup.created_by String The client that created the host group.
CrowdStrike.HostGroup.created_timestamp Date The datetime the host group was created in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.
CrowdStrike.HostGroup.modified_by String The client that modified the host group.
CrowdStrike.HostGroup.modified_timestamp Date The datetime the host group was last modified in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.

Command Example

!cs-falcon-update-host-group host_group_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 name="test_name_update_1" description="test_description_update"

Context Example

{
    "CrowdStrike": {
        "HostGroup": {
            "assignment_rule": "device_id:[''],hostname:['']",
            "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "created_timestamp": "2021-08-22T07:48:35.111070562Z",
            "description": "test_description_update",
            "group_type": "static",
            "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "modified_timestamp": "2021-08-25T08:02:05.295663156Z",
            "name": "test_name_update_1"
        }
    }
}

Human Readable Output

Results

assignment_rule created_by created_timestamp description group_type id modified_by modified_timestamp name
device_id:[’’],hostname:[’’] api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-22T07:48:35.111070562Z test_description_update static a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-25T08:02:05.295663156Z test_name_update_1

cs-falcon-list-host-group-members


Gets the list of host group members.

Base Command

cs-falcon-list-host-group-members

Input

Argument Name Description Required
host_group_id The ID of the host group. Optional
filter The query to filter the devices that belong to the host group. Optional
offset Page offset. Optional
limit The maximum number of results on a page. Default is 50. Optional
sort The property to sort by (e.g., status.desc or hostname.asc). Optional

Context Output

Path Type Description
CrowdStrike.Device.ID String The ID of the device.
CrowdStrike.Device.LocalIP String The local IP address of the device.
CrowdStrike.Device.ExternalIP String The external IP address of the device.
CrowdStrike.Device.Hostname String The hostname of the device.
CrowdStrike.Device.OS String The operating system of the device.
CrowdStrike.Device.MacAddress String The MAC address of the device.
CrowdStrike.Device.FirstSeen String The first time the device was seen.
CrowdStrike.Device.LastSeen String The last time the device was seen.
CrowdStrike.Device.Status String The device status.

Command Example


#### Context Example

```json
{
    "CrowdStrike": {
        "Device": [
            {
                "ExternalIP": "35.224.136.145",
                "FirstSeen": "2021-08-12T16:13:26Z",
                "Hostname": "FALCON-CROWDSTR",
                "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "LastSeen": "2021-08-23T04:59:48Z",
                "LocalIP": "10.128.0.21",
                "MacAddress": "42-01-0a-80-00-15",
                "OS": "Windows Server 2019",
                "Status": "normal"
            },
            {
                "ExternalIP": "35.224.136.145",
                "FirstSeen": "2020-02-10T12:40:18Z",
                "Hostname": "FALCON-CROWDSTR",
                "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "LastSeen": "2021-08-25T07:42:47Z",
                "LocalIP": "10.128.0.7",
                "MacAddress": "42-01-0a-80-00-07",
                "OS": "Windows Server 2019",
                "Status": "contained"
            },
            {
                "ExternalIP": "35.224.136.145",
                "FirstSeen": "2021-08-23T05:04:41Z",
                "Hostname": "INSTANCE-1",
                "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "LastSeen": "2021-08-25T07:49:06Z",
                "LocalIP": "10.128.0.20",
                "MacAddress": "42-01-0a-80-00-14",
                "OS": "Windows Server 2019",
                "Status": "normal"
            },
            {
                "ExternalIP": "35.224.136.145",
                "FirstSeen": "2021-08-11T13:57:29Z",
                "Hostname": "INSTANCE-1",
                "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "LastSeen": "2021-08-23T04:45:37Z",
                "LocalIP": "10.128.0.20",
                "MacAddress": "42-01-0a-80-00-14",
                "OS": "Windows Server 2019",
                "Status": "normal"
            },
            {
                "ExternalIP": "35.224.136.145",
                "FirstSeen": "2021-08-08T11:33:21Z",
                "Hostname": "falcon-crowdstrike-sensor-centos7",
                "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "LastSeen": "2021-08-25T07:50:47Z",
                "LocalIP": "10.128.0.19",
                "MacAddress": "42-01-0a-80-00-13",
                "OS": "CentOS 7.9",
                "Status": "normal"
            }
        ]
    }
}

Human Readable Output

Devices

ID External IP Local IP Hostname OS Mac Address First Seen Last Seen Status
a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 35.224.136.145 10.128.0.19 falcon-crowdstrike-sensor-centos7 CentOS 7.9 42-01-0a-80-00-13 2021-08-08T11:33:21Z 2021-08-25T07:50:47Z normal

cs-falcon-add-host-group-members


Add host group members.

Base Command

cs-falcon-add-host-group-members

Input

Argument Name Description Required
host_group_id The ID of the host group. Required
host_ids A comma-separated list of host agent IDs to run commands. The list of host agent IDs can be retrieved by running the ‘cs-falcon-search-device’ command. Required

Context Output

Path Type Description
CrowdStrike.HostGroup.id String The ID of the host group.
CrowdStrike.HostGroup.group_type String The group type of the host group.
CrowdStrike.HostGroup.name String The name of the host group.
CrowdStrike.HostGroup.description String The description of the host group.
CrowdStrike.HostGroup.created_by String The client that created the host group.
CrowdStrike.HostGroup.created_timestamp Date The datetime the host group was created in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.
CrowdStrike.HostGroup.modified_by String The client that modified the host group.
CrowdStrike.HostGroup.modified_timestamp Date The datetime the host group was last modified in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.

Command Example

!cs-falcon-add-host-group-members host_group_id="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1" host_ids="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"

Context Example

{
    "CrowdStrike": {
        "HostGroup": {
            "assignment_rule": "device_id:[''],hostname:['falcon-crowdstrike-sensor-centos7','']",
            "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "created_timestamp": "2021-08-22T07:48:35.111070562Z",
            "description": "test_description_update",
            "group_type": "static",
            "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "modified_timestamp": "2021-08-25T08:02:05.295663156Z",
            "name": "test_name_update_1"
        }
    }
}

Human Readable Output

Results

assignment_rule created_by created_timestamp description group_type id modified_by modified_timestamp name
device_id:[’’],hostname:[‘falcon-crowdstrike-sensor-centos7’,’’] api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-22T07:48:35.111070562Z test_description_update static a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-25T08:02:05.295663156Z test_name_update_1

cs-falcon-remove-host-group-members


Remove host group members.

Base Command

cs-falcon-remove-host-group-members

Input

Argument Name Description Required
host_group_id The ID of the host group. Required
host_ids A comma-separated list of host agent IDs to run commands. The list of host agent IDs can be retrieved by running the ‘cs-falcon-search-device’ command. Required

Context Output

Path Type Description
CrowdStrike.HostGroup.id String The ID of the host group.
CrowdStrike.HostGroup.group_type String The group type of the host group.
CrowdStrike.HostGroup.name String The name of the host group.
CrowdStrike.HostGroup.description String The description of the host group.
CrowdStrike.HostGroup.created_by String The client that created the host group.
CrowdStrike.HostGroup.created_timestamp Date The datetime the host group was created in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.
CrowdStrike.HostGroup.modified_by String The client that modified the host group.
CrowdStrike.HostGroup.modified_timestamp Date The datetime the host group was last modified in ISO time format. For example: 2019-10-17T13:41:48.487520845Z.

Command Example

!cs-falcon-remove-host-group-members host_group_id="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1" host_ids="a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"

Context Example

{
    "CrowdStrike": {
        "HostGroup": {
            "assignment_rule": "device_id:[''],hostname:['']",
            "created_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "created_timestamp": "2021-08-22T07:48:35.111070562Z",
            "description": "test_description_update",
            "group_type": "static",
            "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "modified_by": "api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "modified_timestamp": "2021-08-25T08:02:05.295663156Z",
            "name": "test_name_update_1"
        }
    }
}

Human Readable Output

Results

assignment_rule created_by created_timestamp description group_type id modified_by modified_timestamp name
device_id:[’’],hostname:[’’] api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-22T07:48:35.111070562Z test_description_update static a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 api-client-id:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2021-08-25T08:02:05.295663156Z test_name_update_1

cs-falcon-batch-upload-custom-ioc


Uploads a batch of indicators.

Base Command

cs-falcon-batch-upload-custom-ioc

Input

Argument Name Description Required
multiple_indicators_json A JSON object with a list of CrowdStrike Falcon indicators to upload. Required
timeout The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. Default is 180. Optional

Context Output

Path Type Description
CrowdStrike.IOC.Type string The type of the IOC.
CrowdStrike.IOC.Value string The string representation of the indicator.
CrowdStrike.IOC.ID string The full ID of the indicator.
CrowdStrike.IOC.Severity string The severity level to apply to this indicator.
CrowdStrike.IOC.Source string The source of the IOC.
CrowdStrike.IOC.Action string The action to take when a host observes the custom IOC.
CrowdStrike.IOC.Expiration string The datetime the indicator will expire.
CrowdStrike.IOC.Description string The description of the IOC.
CrowdStrike.IOC.CreatedTime date The datetime the IOC was created.
CrowdStrike.IOC.CreatedBy string The identity of the user/process who created the IOC.
CrowdStrike.IOC.ModifiedTime date The datetime the indicator was last modified.
CrowdStrike.IOC.ModifiedBy string The identity of the user/process who last updated the IOC.
CrowdStrike.IOC.Tags Unknown The tags of the IOC.
CrowdStrike.IOC.Platforms Unknown The platforms of the IOC.
CrowdStrike.IOC.MobileAction string The action to take on mobile devices when a host observes the custom IOC.

Command Example

!cs-falcon-batch-upload-custom-ioc multiple_indicators_json=`[{"description": "test", "expiration": "2022-02-17T13:47:57Z", "type": "ipv4", "severity": "Informational", "value": "1.1.8.9", "action": "no_action", "platforms": ["mac"], "source": "Cortex", "applied_globally": true}]`

Context Example

{
    "CrowdStrike": {
        "IOC": {
            "Action": "no_action",
            "CreatedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "CreatedTime": "2022-02-16T17:17:25.992164453Z",
            "Description": "test",
            "Expiration": "2022-02-17T13:47:57Z",
            "ID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "ModifiedBy": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "ModifiedTime": "2022-02-16T17:17:25.992164453Z",
            "Platforms": [
                "mac"
            ],
            "Severity": "informational",
            "Source": "Cortex",
            "Type": "ipv4",
            "Value": "1.1.8.9"
        }
    }
}

Human Readable Output

Custom IOC 1.1.8.9 was created successfully

Action CreatedBy CreatedTime Description Expiration ID ModifiedBy ModifiedTime Platforms Severity Source Type Value
no_action a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T17:17:25.992164453Z test 2022-02-17T13:47:57Z “a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2022-02-16T17:17:25.992164453Z mac informational Cortex ipv4 1.1.8.9

cs-falcon-rtr-kill-process


Execute an active responder kill command on a single host.

Base Command

cs-falcon-rtr-kill-process

Input

Argument Name Description Required
host_id The host ID to kill the given process for. Required
process_ids A comma-separated list of process IDs to kill. Required
queue_offline Whether the command will run against an offline-queued session and be queued for execution when the host comes online. Optional
timeout The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. Optional

Context Output

Path Type Description
CrowdStrike.Command.kill.ProcessID String The process ID that was killed.
CrowdStrike.Command.kill.Error String The error message raised if the command failed.
CrowdStrike.Command.kill.HostID String The host ID.

Command Example

!cs-falcon-rtr-kill-process host_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 process_ids=5260,123

Context Example

{
  "CrowdStrike": {
    "Command": {
      "kill": [
        {
          "Error": "Cannot find a process with the process identifier 123.",
          "HostID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
          "ProcessID": "123"
        },
        {
          "Error": "Success",
          "HostID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
          "ProcessID": "5260"
        }
      ]
    }
  }
}

Human Readable Output

CrowdStrike Falcon kill command on host a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

|ProcessID|Error|
|—|—|
| 123 | Cannot find a process with the process identifier 123. |
| 5260 | Success |
Note: you don’t see the following IDs in the results as the request was failed for them.
ID 123 failed as it was not found.

cs-falcon-rtr-remove-file


Batch executes an RTR active-responder remove file across the hosts mapped to the given batch ID.

Base Command

cs-falcon-rtr-remove-file

Input

Argument Name Description Required
host_ids A comma-separated list of the hosts IDs to remove the file for. Required
file_path The path to a file or a directory to remove. Required
os The operating system of the hosts given. Since the remove command is different in each operating system, you can choose only one operating system. Possible values are: Windows, Linux, Mac. Required
queue_offline Whether the command will run against an offline-queued session and be queued for execution when the host comes online. Optional
timeout The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. Optional

Context Output

Path Type Description
CrowdStrike.Command.rm.HostID String The host ID.
CrowdStrike.Command.rm.Error String The error message raised if the command failed.

Command Example

!cs-falcon-rtr-remove-file file_path="c:\\testfolder" host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 os=Windows

Context Example

{
  "CrowdStrike": {
    "Command": {
      "rm": {
        "Error": "Success",
        "HostID": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
      }
    }
  }
}

Human Readable Output

CrowdStrike Falcon rm over the file: c:\testfolder

HostID Error
a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 Success

cs-falcon-rtr-list-processes


Executes an RTR active-responder ps command to get a list of active processes across the given host.

Base Command

cs-falcon-rtr-list-processes

Input

Argument Name Description Required
host_id The host ID to get the processes list from. Required
queue_offline Whether the command will run against an offline-queued session and be queued for execution when the host comes online. Optional
timeout The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. Optional

Context Output

Path Type Description
CrowdStrike.Command.ps.Filename String The name of the result file to be returned.

Command Example

!cs-falcon-rtr-list-processes host_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Context Example

{
  "CrowdStrike": {
    "Command": {
      "ps": {
        "Filename": "ps-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
      }
    }
  },
  "File": {
    "EntryID": "1792@5e02fcd0-37ad-4124-836d-7e769ba0ae86",
    "Info": "text/plain",
    "MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
    "Name": "ps-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
    "SHA1": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a115919af3",
    "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
    "SHA512": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
    "SSDeep": "768:4jcAkTBaZ61QUEcDBdMoFwIxVvroYrohrbY2akHLnsa5fbqFEJtPNObzVj0ff+3K:4IraZ61QUEcDBdMoFwIxRJEbY2akHLnr",
    "Size": 30798,
    "Type": "ASCII text"
  }
}

Human Readable Output

CrowdStrike Falcon ps command on host a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Stdout
TOO MUCH INFO TO DISPLAY

cs-falcon-rtr-list-network-stats


Executes an RTR active-responder netstat command to get a list of network status and protocol statistics across the given host.

Base Command

cs-falcon-rtr-list-network-stats

Input

Argument Name Description Required
host_id The host ID to get the network status and protocol statistics list from. Required
queue_offline Whether the command will run against an offline-queued session and be queued for execution when the host comes online. Optional
timeout The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. Optional

Context Output

Path Type Description
CrowdStrike.Command.netstat.Filename String The name of the result file to be returned.

Command Example

!cs-falcon-rtr-list-network-stats host_id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Context Example

{
  "CrowdStrike": {
    "Command": {
      "netstat": {
        "Filename": "netstat-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
      }
    }
  },
  "File": {
    "EntryID": "1797@5e02fcd0-37ad-4124-836d-7e769ba0ae86",
    "Info": "text/plain",
    "MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
    "Name": "netstat-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
    "SHA1": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1864ce595",
    "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
    "SHA512": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
    "SSDeep": "48:XSvprPoeCfd8saowYL8zjt6yjjRchg24OI58RtTLvWptl6TtCla5n1lEtClMw/u:CRQeCxRmxVpIHUchCIvsCo",
    "Size": 4987,
    "Type": "ASCII text, with CRLF line terminators"
  }
}

Human Readable Output

CrowdStrike Falcon netstat command on host a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Stdout
TOO MUCH INFO TO DISPLAY

cs-falcon-rtr-read-registry


Executes an RTR active-responder read registry keys command across the given hosts. This command is valid only for Windows hosts.

Base Command

cs-falcon-rtr-read-registry

Input

Argument Name Description Required
host_ids A comma-separated list of the host IDs to get the registry keys from. Required
registry_keys A comma-separated list of the registry keys, sub-keys, or value to get. Required
queue_offline Whether the command will run against an offline-queued session and be queued for execution when the host comes online. Optional
timeout The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. Optional

Context Output

There is no context output for this command.

Command Example

!cs-falcon-rtr-read-registry host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 registry_keys=` HKEY_LOCAL_MACHINE,HKEY_USERS`

Context Example

{
  "File": [
    {
      "EntryID": "1806@5e02fcd0-37ad-4124-836d-7e769ba0ae86",
      "Info": "text/plain",
      "MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
      "Name": "reg-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1HKEY_USERS",
      "SHA1": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a139dd0333",
      "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
      "SHA512": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
      "SSDeep": "12:uSn3PtdoI1pZI2WUNI2e6NI2vboI2vbP3I2zd:uSQIpZIII1aIUMIUjIcd",
      "Size": 656,
      "Type": "ASCII text, with CRLF, LF line terminators"
    },
    {
      "EntryID": "1807@5e02fcd0-37ad-4124-836d-7e769ba0ae86",
      "Info": "text/plain",
      "MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
      "Name": "reg-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1HKEY_LOCAL_MACHINE",
      "SHA1": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a18e3b4919",
      "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
      "SHA512": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
      "SSDeep": "6:zYuSugMQEYPtdWCMwdiwf2Jai2FU42DGE25/:zYuSnMQXPtd9/eJqy7yfh",
      "Size": 320,
      "Type": "ASCII text, with CRLF, LF line terminators"
    }
  ]
}

Human Readable Output

CrowdStrike Falcon reg command on hosts [‘a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1’]

FileName Stdout
reg-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1HKEY_USERS TOO MUCH INFO TO DISPLAY
reg-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1HKEY_LOCAL_MACHINE TOO MUCH INFO TO DISPLAY

cs-falcon-rtr-list-scheduled-tasks


Executes an RTR active-responder netstat command to get a list of scheduled tasks across the given host. This command is valid only for Windows hosts.

Base Command

cs-falcon-rtr-list-scheduled-tasks

Input

Argument Name Description Required
host_ids A comma-separated list of the hosts IDs to get the list of scheduled tasks from. Required
queue_offline Whether the command will run against an offline-queued session and be queued for execution when the host comes online. Optional
timeout The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. Optional

Context Output

There is no context output for this command.

Command Example

!cs-falcon-rtr-list-scheduled-tasks host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Context Example

{
  "CrowdStrike": {
    "Command": {
      "runscript": {
        "Filename": "runscript-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
      }
    }
  },
  "File": {
    "EntryID": "1812@5e02fcd0-37ad-4124-836d-7e769ba0ae86",
    "Info": "text/plain",
    "MD5": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
    "Name": "runscript-a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
    "SHA1": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1c589bf80",
    "SHA256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
    "SHA512": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
    "SSDeep": "3072:zjQ3/3YHGa8dbXbpbItbo4W444ibNb9MTf2Wat4cuuEqk4W4ybmF54c4eEEEjX6f:EXN8Nbw",
    "Size": 299252,
    "Type": "ASCII text"
  }
}

Human Readable Output

CrowdStrike Falcon runscript command on host a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Stdout
TOO MUCH INFO TO DISPLAY

cs-falcon-rtr-retrieve-file


Gets the RTR extracted file contents for the specified file path.

Base Command

cs-falcon-rtr-retrieve-file

Input

Argument Name Description Required
host_ids A comma-separated list of the hosts IDs to get the file from. Required
file_path The file path of the required file to extract. Required
filename The filename to use for the archive name and the file within the archive. Optional
interval_in_seconds Interval between polling. Default is 60 seconds. Must be higher than 10. Optional
hosts_and_requests_ids This is an internal argument used for the polling process, not to be used by the user. Optional
SHA256 This is an internal argument used for the polling process, not to be used by the user. Optional
queue_offline Whether the command will run against an offline-queued session and be queued for execution when the host comes online. Optional
timeout The amount of time (in seconds) that a request will wait for a client to establish a connection to a remote machine before a timeout occurs. Optional
polling_timeout Timeout for polling. Default is 600 seconds. Optional

Context Output

Path Type Description
CrowdStrike.File.FileName String The filename.
CrowdStrike.File.HostID String The host ID.
File.Size Number The size of the file.
File.SHA1 String The SHA1 hash of the file.
File.SHA256 String The SHA256 hash of the file.
File.SHA512 String The SHA512 hash of the file.
File.Name String The name of the file.
File.SSDeep String The SSDeep hash of the file.
File.EntryID String The entry ID of the file.
File.Info String Information about the file.
File.Type String The file type.
File.MD5 String The MD5 hash of the file.
File.Extension String The extension of the file.

Command Example

!cs-falcon-rtr-retrieve-file file_path=`C:\Windows\System32\Windows.Media.FaceAnalysis.dll` host_ids=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1,a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

Human Readable Output

Waiting for the polling execution

get-mapping-fields


Returns the list of fields to map in outgoing mirroring. This command is only used for debugging purposes. Note that this command is supported in Cortex XSOAR only.

Base Command

get-mapping-fields

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

There is no context output for this command.

get-remote-data


Gets remote data from a remote incident or detection. This method does not update the current incident or detection, and should be used for debugging purposes only. Note that this command is supported in Cortex XSOAR only.

Base Command

get-remote-data

Input

Argument Name Description Required
id The remote incident or detection ID. Required
lastUpdate The UTC timestamp in seconds of the last update. The incident or detection is only updated if it was modified after the last update time. Default is 0. Optional

Context Output

There is no context output for this command.

get-modified-remote-data


Gets the list of incidents and detections that were modified since the last update time. This method is used for debugging purposes. The get-modified-remote-data command is used as part of the Mirroring feature that was introduced in Cortex XSOAR version 6.1. Note that this command is supported in Cortex XSOAR only.

Base Command

get-modified-remote-data

Input

Argument Name Description Required
lastUpdate Date string representing the local time in UTC timestamp in seconds. The incident or detection is only returned if it was modified after the last update time. Optional

Context Output

There is no context output for this command.

update-remote-system


Updates the remote incident or detection with local incident or detection changes. This method is only used for debugging purposes and will not update the current incident or detection. Note that this command is supported in Cortex XSOAR only.

Base Command

update-remote-system

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

There is no context output for this command.

cve


Retrieve vulnerability details according to the selected filter. Each request requires at least one filter parameter. Supported with the CrowdStrike Spotlight license.

Base Command

cve

Input

Argument Name Description Required
cve_id Deprecated. Use cve instead. Optional
cve Unique identifier for a vulnerability as cataloged in the National Vulnerability Database (NVD). This filter supports multiple values and negation. Optional

Context Output

Path Type Description
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.

Command Example

cve cve=CVE-2021-2222

Human Readable Output

ID Severity Published Date Base Score
CVE-2021-2222 HIGH 2021-09-16T15:12:42Z 1

cs-falcon-create-ml-exclusion


Create an ML exclusion.

Base Command

cs-falcon-create-ml-exclusion

Input

Argument Name Description Required
value Value to match for exclusion. Required
excluded_from A comma-separated list from where to exclude the exclusion. Possible values are: blocking, extraction. Required
comment Comment describing why the exclusions were created. Optional
groups A comma-separated list of group ID(s) impacted by the exclusion OR all if empty. Optional

Context Output

Path Type Description
CrowdStrike.MLExclusion.id String The ML exclusion ID.
CrowdStrike.MLExclusion.value String The ML exclusion value.
CrowdStrike.MLExclusion.regexp_value String A regular expression for matching the excluded value.
CrowdStrike.MLExclusion.value_hash String An hash of the value field.
CrowdStrike.MLExclusion.excluded_from String What the exclusion applies to (e.g., a specific ML model).
CrowdStrike.MLExclusion.groups.id String Group ID that the exclusion rule is associated with.
CrowdStrike.MLExclusion.groups.group_type String Group type that the exclusion rule is associated with.
CrowdStrike.MLExclusion.groups.name String Group name that the exclusion rule is associated with.
CrowdStrike.MLExclusion.groups.description String Group description that the exclusion rule is associated with.
CrowdStrike.MLExclusion.groups.assignment_rule String Group assignment rule that the exclusion is associated with.
CrowdStrike.MLExclusion.groups.created_by String Indicate who created the group.
CrowdStrike.MLExclusion.groups.created_timestamp Date The date when the group was created.
CrowdStrike.MLExclusion.groups.modified_by String Indicate who last modified the group.
CrowdStrike.MLExclusion.groups.modified_timestamp Date The date when the group was last modified.
CrowdStrike.MLExclusion.applied_globally Boolean Whether the exclusion rule applies globally or only to specific entities.
CrowdStrike.MLExclusion.last_modified Date The date when the exclusion rule was last modified.
CrowdStrike.MLExclusion.modified_by String Indicate who last modified the rule.
CrowdStrike.MLExclusion.created_on Date The date when the exclusion rule was created.
CrowdStrike.MLExclusion.created_by String Indicate who created the rule.

Command Example

!cs-falcon-create-ml-exclusion value=/demo-test excluded_from=blocking groups=999999

Context Example

{
    "CrowdStrike": {
        "MLExclusion": {
            "applied_globally": false,
            "created_by": "api-client-id:123456",
            "created_on": "2023-03-06T13:57:14.853546312Z",
            "excluded_from": [
                "blocking"
            ],
            "groups": [
                {
                    "assignment_rule": "device_id",
                    "created_by": "admin@test.com",
                    "created_timestamp": "2023-01-23T15:01:11.846726918Z",
                    "description": "",
                    "group_type": "static",
                    "id": "999999",
                    "modified_by": "admin@test.com",
                    "modified_timestamp": "2023-01-23T15:18:52.316882546Z",
                    "name": "Lab env"
                }
            ],
            "id": "123456",
            "last_modified": "2023-03-06T13:57:14.853546312Z",
            "modified_by": "api-client-id:123456",
            "regexp_value": "\\/demo-test",
            "value": "/demo-test",
            "value_hash": "abcdef123456"
        }
    }
}

Human Readable Output

CrowdStrike Falcon machine learning exclusion

Id Value RegexpValue ValueHash ExcludedFrom Groups AppliedGlobally LastModified ModifiedBy CreatedOn CreatedBy
123456 /demo-test \/demo-test abcdef123456 values: blocking - id: 999999
group_type: static
name: Lab env
description:
assignment_rule: device_id:
created_by: admin@test.com
created_timestamp: 2023-01-23T15:01:11.846726918Z
modified_by: admin@test.com
modified_timestamp: 2023-01-23T15:18:52.316882546Z
  2023-03-06T13:57:14.853546312Z api-client-id:123456 2023-03-06T13:57:14.853546312Z api-client-id:123456

cs-falcon-update-ml-exclusion


Updates an ML exclusion. At least one argument is required in addition to the id argument.

Base Command

cs-falcon-update-ml-exclusion

Input

Argument Name Description Required
id The ID of the exclusion to update. Required
value Value to match for the exclusion (the exclusion pattern). Optional
comment Comment describing why the exclusions were created. Optional
groups A comma-separated list of group ID(s) impacted by the exclusion. Optional

Context Output

Path Type Description
CrowdStrike.MLExclusion.id String The ML exclusion ID.
CrowdStrike.MLExclusion.value String The ML exclusion value.
CrowdStrike.MLExclusion.regexp_value String A regular expression for matching the excluded value.
CrowdStrike.MLExclusion.value_hash String A hash of the value field.
CrowdStrike.MLExclusion.excluded_from String What the exclusion applies to (e.g., a specific ML model).
CrowdStrike.MLExclusion.groups.id String Group ID that the exclusion rule is associated with.
CrowdStrike.MLExclusion.groups.group_type String Group type that the exclusion rule is associated with.
CrowdStrike.MLExclusion.groups.name String Group name that the exclusion rule is associated with.
CrowdStrike.MLExclusion.groups.description String Group description that the exclusion rule is associated with.
CrowdStrike.MLExclusion.groups.assignment_rule String Group assignment rule that the exclusion is associated with.
CrowdStrike.MLExclusion.groups.created_by String Indicate who created the group.
CrowdStrike.MLExclusion.groups.created_timestamp Date The date when the group was created.
CrowdStrike.MLExclusion.groups.modified_by String Indicate who last modified the group.
CrowdStrike.MLExclusion.groups.modified_timestamp Date The date when the group was last modified.
CrowdStrike.MLExclusion.applied_globally Boolean Whether the exclusion rule applies globally or only to specific entities.
CrowdStrike.MLExclusion.last_modified Date The date when the exclusion rule was last modified.
CrowdStrike.MLExclusion.modified_by String Indicate who last modified the rule.
CrowdStrike.MLExclusion.created_on Date The date when the exclusion rule was created.
CrowdStrike.MLExclusion.created_by String Indicate who created the rule.

Command Example

!cs-falcon-update-ml-exclusion id=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 comment=demo-comment

Context Example

{
    "CrowdStrike": {
        "MLExclusion": {
            "applied_globally": false,
            "created_by": "api-client-id:123456",
            "created_on": "2023-03-06T13:56:25.940685483Z",
            "excluded_from": [
                "extraction",
                "blocking"
            ],
            "groups": [
                {
                    "assignment_rule": "device_id:",
                    "created_by": "admin@test.com",
                    "created_timestamp": "2023-01-23T15:01:11.846726918Z",
                    "description": "",
                    "group_type": "static",
                    "id": "999999",
                    "modified_by": "admin@test.com",
                    "modified_timestamp": "2023-01-23T15:18:52.316882546Z",
                    "name": "Lab env"
                }
            ],
            "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "last_modified": "2023-03-06T13:57:21.57829431Z",
            "modified_by": "api-client-id:123456",
            "regexp_value": "\\/demo",
            "value": "/demo",
            "value_hash": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
        }
    }
}

Human Readable Output

CrowdStrike Falcon machine learning exclusion

Id Value RegexpValue ValueHash ExcludedFrom Groups AppliedGlobally LastModified ModifiedBy CreatedOn CreatedBy
a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 /demo \/demo a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 values: extraction, blocking - id: 999999
group_type: static
name: Lab env
description:
assignment_rule: device_id:
created_by: admin@test.com
created_timestamp: 2023-01-23T15:01:11.846726918Z
modified_by: admin@test.com
modified_timestamp: 2023-01-23T15:18:52.316882546Z
  2023-03-06T13:57:21.57829431Z api-client-id:123456 2023-03-06T13:56:25.940685483Z api-client-id:123456

cs-falcon-delete-ml-exclusion


Delete the ML exclusions by ID.

Base Command

cs-falcon-delete-ml-exclusion

Input

Argument Name Description Required
ids A comma-separated list of exclusion IDs to delete. Required

Context Output

There is no context output for this command.

Command Example

!cs-falcon-delete-ml-exclusion ids=123456

Human Readable Output

‘The machine learning exclusions with IDs ‘123456’ was successfully deleted.’

cs-falcon-search-ml-exclusion


Get a list of ML exclusions by specifying their IDs, value, or a specific filter.

Base Command

cs-falcon-search-ml-exclusion

Input

Argument Name Description Required
filter A custom filter by which the exclusions should be filtered.
The syntax follows the pattern &lt;property&gt;:[operator]'&lt;value&gt;'. For example: value:’test’.
Available filters: applied_globally, created_by, created_on, last_modified, modified_by, value.
For more information, see: https://falcon.crowdstrike.com/documentation/page/d3c84a1b/falcon-query-language-fql.
Optional
value The value by which the exclusions should be filtered. Optional
ids A comma-separated list of exclusion IDs to retrieve. The IDs overwrite the filter and value. Optional
limit The maximum number of records to return. [1-500]. Applies only if the ids argument is not supplied. Optional
offset The offset to start retrieving records from. Applies only if the ids argument is not supplied. Optional
sort How to sort the retrieved exclusions. Possible values are: applied_globally.asc, applied_globally.desc, created_by.asc, created_by.desc, created_on.asc, created_on.desc, last_modified.asc, last_modified.desc, modified_by.asc, modified_by.desc, value.asc, value.desc. Optional

Context Output

Path Type Description
CrowdStrike.MLExclusion.id String The ML exclusion ID.
CrowdStrike.MLExclusion.value String The ML exclusion value.
CrowdStrike.MLExclusion.regexp_value String A regular expression for matching the excluded value.
CrowdStrike.MLExclusion.value_hash String A hash of the value field.
CrowdStrike.MLExclusion.excluded_from String What the exclusion applies to (e.g., a specific ML model).
CrowdStrike.MLExclusion.groups.id String Group ID that the exclusion rule is associated with.
CrowdStrike.MLExclusion.groups.group_type String Group type that the exclusion rule is associated with.
CrowdStrike.MLExclusion.groups.name String Group name that the exclusion rule is associated with.
CrowdStrike.MLExclusion.groups.description String Group description that the exclusion rule is associated with.
CrowdStrike.MLExclusion.groups.assignment_rule String Group assignment rule that the exclusion is associated with.
CrowdStrike.MLExclusion.groups.created_by String Indicate who created the group.
CrowdStrike.MLExclusion.groups.created_timestamp Date The date when the group was created.
CrowdStrike.MLExclusion.groups.modified_by String Indicate who last modified the group.
CrowdStrike.MLExclusion.groups.modified_timestamp Date The date when the group was last modified.
CrowdStrike.MLExclusion.applied_globally Boolean Whether the exclusion rule applies globally or only to specific entities.
CrowdStrike.MLExclusion.last_modified Date The date when the exclusion rule was last modified.
CrowdStrike.MLExclusion.modified_by String Indicate who last modified the rule.
CrowdStrike.MLExclusion.created_on Date The date when the exclusion rule was created.
CrowdStrike.MLExclusion.created_by String Indicate who created the rule.

Command Example

!cs-falcon-search-ml-exclusion limit=1

Context Example

{
    "CrowdStrike": {
        "MLExclusion": {
            "applied_globally": false,
            "created_by": "api-client-id:123456",
            "created_on": "2023-03-01T18:51:07.196018144Z",
            "excluded_from": [
                "blocking"
            ],
            "groups": [
                {
                    "assignment_rule": "device_id",
                    "created_by": "admin@test.com",
                    "created_timestamp": "2023-01-23T15:01:11.846726918Z",
                    "description": "",
                    "group_type": "static",
                    "id": "999999",
                    "modified_by": "admin@test.com",
                    "modified_timestamp": "2023-01-23T15:18:52.316882546Z",
                    "name": "Lab env"
                }
            ],
            "id": "123456",
            "last_modified": "2023-03-01T18:51:07.196018144Z",
            "modified_by": "api-client-id:123456",
            "regexp_value": "\\/MosheTest2-432",
            "value": "/MosheTest2-432",
            "value_hash": "abcdef123456"
        }
    }
}

Human Readable Output

CrowdStrike Falcon machine learning exclusions

Id Value RegexpValue ValueHash ExcludedFrom Groups AppliedGlobally LastModified ModifiedBy CreatedOn CreatedBy
123456 /MosheTest2-432 \/MosheTest2-432 abcdef123456 values: blocking - id: 999999
group_type: static
name: Lab env
description:
assignment_rule: device_id
created_by: admin@test.com
created_timestamp: 2023-01-23T15:01:11.846726918Z
modified_by: admin@test.com
modified_timestamp: 2023-01-23T15:18:52.316882546Z
  2023-03-01T18:51:07.196018144Z api-client-id:123456 2023-03-01T18:51:07.196018144Z api-client-id:123456

cs-falcon-create-ioa-exclusion


Create an IOA exclusion.

Base Command

cs-falcon-create-ioa-exclusion

Input

Argument Name Description Required
exclusion_name Name of the exclusion. Required
pattern_name Name of the exclusion pattern. Optional
pattern_id ID of the exclusion pattern. Required
cl_regex Command line regular expression. Required
ifn_regex Image filename regular expression. Required
comment Comment describing why the exclusions were created. Optional
description Exclusion description. Optional
detection_json JSON formatted detection template. Optional
groups A comma-separated list of group ID(s) impacted by the exclusion OR all if empty. Required

Context Output

Path Type Description
CrowdStrike.IOAExclusion.id String A unique identifier for the IOA exclusion.
CrowdStrike.IOAExclusion.name String The name of the IOA exclusion.
CrowdStrike.IOAExclusion.description String A description of the IOA exclusion.
CrowdStrike.IOAExclusion.pattern_id String The identifier of the pattern associated with the IOA exclusion.
CrowdStrike.IOAExclusion.pattern_name String The name of the pattern associated with the IOA exclusion.
CrowdStrike.IOAExclusion.ifn_regex String A regular expression used for filename matching.
CrowdStrike.IOAExclusion.cl_regex String A regular expression used for command line matching.
CrowdStrike.IOAExclusion.detection_json String A JSON string that describes the detection logic for the IOA exclusion.
CrowdStrike.IOAExclusion.groups.id String Group ID that the exclusion rule is associated with.
CrowdStrike.IOAExclusion.groups.group_type String Group type that the exclusion rule is associated with.
CrowdStrike.IOAExclusion.groups.name String Group name that the exclusion rule is associated with.
CrowdStrike.IOAExclusion.groups.description String Group description that the exclusion rule is associated with.
CrowdStrike.IOAExclusion.groups.assignment_rule String Group assignment rule that the exclusion is associated with.
CrowdStrike.IOAExclusion.groups.created_by String Indicate who created the group.
CrowdStrike.IOAExclusion.groups.created_timestamp Date The date when the group was created.
CrowdStrike.IOAExclusion.groups.modified_by String Indicate who last modified the group.
CrowdStrike.IOAExclusion.groups.modified_timestamp Date The date when the group was last modified.
CrowdStrike.IOAExclusion.applied_globally Boolean Whether the exclusion rule applies globally or only to specific entities.
CrowdStrike.IOAExclusion.last_modified Date The date when the exclusion rule was last modified.
CrowdStrike.IOAExclusion.modified_by String Indicate who last modified the rule.
CrowdStrike.IOAExclusion.created_on Date The date when the exclusion rule was created.
CrowdStrike.IOAExclusion.created_by String Indicate who created the rule.

Command Example

!cs-falcon-create-ioa-exclusion exclusion_name=demo-test pattern_id=101010 cl_regex=.* ifn_regex="c:\\\\windows\\\\system32\\\\test.exe" groups=999999

Context Example

{
    "CrowdStrike": {
        "IOAExclusion": {
            "applied_globally": false,
            "cl_regex": ".*",
            "created_by": "api-client-id:123456",
            "created_on": "2023-03-06T13:57:41.746172897Z",
            "description": "",
            "detection_json": "",
            "groups": [
                {
                    "assignment_rule": "device_id",
                    "created_by": "admin@test.com",
                    "created_timestamp": "2023-01-23T15:01:11.846726918Z",
                    "description": "",
                    "group_type": "static",
                    "id": "999999",
                    "modified_by": "admin@test.com",
                    "modified_timestamp": "2023-01-23T15:18:52.316882546Z",
                    "name": "Lab env"
                }
            ],
            "id": "123456",
            "ifn_regex": "c:\\\\windows\\\\system32\\\\test.exe",
            "last_modified": "2023-03-06T13:57:41.746172897Z",
            "modified_by": "api-client-id:123456",
            "name": "demo-test",
            "pattern_id": "101010",
            "pattern_name": ""
        }
    }
}

Human Readable Output

CrowdStrike Falcon IOA exclusion

Id Name PatternId IfnRegex ClRegex Groups AppliedGlobally LastModified ModifiedBy CreatedOn CreatedBy
123456 demo-test 101010 c:\windows\system32\poqexec.exe .* - id: 999999
group_type: static
name: Lab env
description:
assignment_rule: device_id
created_by: admin@test.com
created_timestamp: 2023-01-23T15:01:11.846726918Z
modified_by: admin@test.com
modified_timestamp: 2023-01-23T15:18:52.316882546Z
  2023-03-06T13:57:41.746172897Z api-client-id:123456 2023-03-06T13:57:41.746172897Z api-client-id:123456

cs-falcon-update-ioa-exclusion


Updates an IOA exclusion. At least one argument is required in addition to the id argument.

Base Command

cs-falcon-update-ioa-exclusion

Input

Argument Name Description Required
id ID of the exclusion to update. Required
exclusion_name Name of the exclusion. Optional
pattern_id ID of the exclusion pattern to update. Optional
pattern_name Name of the exclusion pattern. Optional
cl_regex Command line regular expression. Optional
ifn_regex Image filename regular expression. Optional
comment Comment describing why the exclusions was created. Optional
description Exclusion description. Optional
detection_json JSON formatted detection template. Optional
groups A comma-separated list of group ID(s) impacted by the exclusion. Optional

Context Output

Path Type Description
CrowdStrike.IOAExclusion.id String A unique identifier for the IOA exclusion.
CrowdStrike.IOAExclusion.name String The name of the IOA exclusion.
CrowdStrike.IOAExclusion.description String A description of the IOA exclusion.
CrowdStrike.IOAExclusion.pattern_id String The identifier of the pattern associated with the IOA exclusion.
CrowdStrike.IOAExclusion.pattern_name String The name of the pattern associated with the IOA exclusion.
CrowdStrike.IOAExclusion.ifn_regex String A regular expression used for filename matching.
CrowdStrike.IOAExclusion.cl_regex String A regular expression used for command line matching.
CrowdStrike.IOAExclusion.detection_json String A JSON string that describes the detection logic for the IOA exclusion.
CrowdStrike.IOAExclusion.groups.id String Group ID that the exclusion rule is associated with.
CrowdStrike.IOAExclusion.groups.group_type String Group type that the exclusion rule is associated with.
CrowdStrike.IOAExclusion.groups.name String Group name that the exclusion rule is associated with.
CrowdStrike.IOAExclusion.groups.description String Group description that the exclusion rule is associated with.
CrowdStrike.IOAExclusion.groups.assignment_rule String Group assignment rule that the exclusion is associated with.
CrowdStrike.IOAExclusion.groups.created_by String Indicate who created the group.
CrowdStrike.IOAExclusion.groups.created_timestamp Date The date when the group was created.
CrowdStrike.IOAExclusion.groups.modified_by String Indicate who last modified the group.
CrowdStrike.IOAExclusion.groups.modified_timestamp Date The date when the group was last modified.
CrowdStrike.IOAExclusion.applied_globally Boolean Whether the exclusion rule applies globally or only to specific entities.
CrowdStrike.IOAExclusion.last_modified Date The date when the exclusion rule was last modified.
CrowdStrike.IOAExclusion.modified_by String Indicate who last modified the rule.
CrowdStrike.IOAExclusion.created_on Date The date when the exclusion rule was created.
CrowdStrike.IOAExclusion.created_by String Indicate who created the rule.

Command Example

!cs-falcon-update-ioa-exclusion id=123456 description=demo-description

Context Example

{
    "CrowdStrike": {
        "IOAExclusion": {
            "applied_globally": false,
            "cl_regex": ".*",
            "created_by": "api-client-id:123456",
            "created_on": "2023-03-06T13:46:58.137122925Z",
            "description": "demo-description",
            "detection_json": "",
            "groups": [
                {
                    "assignment_rule": "device_id",
                    "created_by": "admin@test.com",
                    "created_timestamp": "2023-01-23T15:01:11.846726918Z",
                    "description": "",
                    "group_type": "static",
                    "id": "999999",
                    "modified_by": "admin@test.com",
                    "modified_timestamp": "2023-01-23T15:18:52.316882546Z",
                    "name": "Lab env"
                }
            ],
            "id": "123456",
            "ifn_regex": "c:\\\\windows\\\\system32\\\\poqexec\\.exe",
            "last_modified": "2023-03-06T13:57:49.086458198Z",
            "modified_by": "api-client-id:123456",
            "name": "demo",
            "pattern_id": "101010",
            "pattern_name": ""
        }
    }
}

Human Readable Output

CrowdStrike Falcon IOA exclusion

Id Name Description PatternId IfnRegex ClRegex Groups AppliedGlobally LastModified ModifiedBy CreatedOn CreatedBy
123456 demo demo-description 101010 c:\windows\system32\poqexec.exe .* - id: 999999
group_type: static
name: Lab env
description:
assignment_rule: device_id
created_by: admin@test.com
created_timestamp: 2023-01-23T15:01:11.846726918Z
modified_by: admin@test.com
modified_timestamp: 2023-01-23T15:18:52.316882546Z
  2023-03-06T13:57:49.086458198Z api-client-id:123456 2023-03-06T13:46:58.137122925Z api-client-id:123456

cs-falcon-delete-ioa-exclusion


Delete the IOA exclusions by ID.

Base Command

cs-falcon-delete-ioa-exclusion

Input

Argument Name Description Required
ids A comma-separated list of exclusion IDs to delete. Required

Context Output

There is no context output for this command.

Command Example

!cs-falcon-delete-ioa-exclusion ids=123456

Human Readable Output

‘The IOA exclusions with IDs ‘123456’ was successfully deleted.’

cs-falcon-search-ioa-exclusion


Get a list of IOA exclusions by specifying their IDs or a filter.

Base Command

cs-falcon-search-ioa-exclusion

Input

Argument Name Description Required
filter A custom filter by which the exclusions should be filtered.
The syntax follows the pattern &lt;property&gt;:[operator]'&lt;value&gt;'. For example: name:’test’.
Available filters: applied_globally, created_by, created_on, name, last_modified, modified_by, value, pattern.
For more information, see: https://www.falconpy.io/Service-Collections/Falcon-Query-Language.
Optional
name The name by which the exclusions should be filtered. Optional
ids A comma-separated list of exclusion IDs to retrieve. The IDs overwrite the filter and name. Optional
limit The limit of how many exclusions to retrieve. Default is 50. Applies only if the ids argument is not supplied. Optional
offset The offset of how many exclusions to skip. Default is 0. Applies only if the ids argument is not supplied. Optional

Context Output

Path Type Description
CrowdStrike.IOAExclusion.id String A unique identifier for the IOA exclusion.
CrowdStrike.IOAExclusion.name String The name of the IOA exclusion.
CrowdStrike.IOAExclusion.description String A description of the IOA exclusion.
CrowdStrike.IOAExclusion.pattern_id String The identifier of the pattern associated with the IOA exclusion.
CrowdStrike.IOAExclusion.pattern_name String The name of the pattern associated with the IOA exclusion.
CrowdStrike.IOAExclusion.ifn_regex String A regular expression used for filename matching.
CrowdStrike.IOAExclusion.cl_regex String A regular expression used for command line matching.
CrowdStrike.IOAExclusion.detection_json String A JSON string that describes the detection logic for the IOA exclusion.
CrowdStrike.IOAExclusion.groups.id String Group ID that the exclusion rule is associated with.
CrowdStrike.IOAExclusion.groups.group_type String Group type that the exclusion rule is associated with.
CrowdStrike.IOAExclusion.groups.name String Group name that the exclusion rule is associated with.
CrowdStrike.IOAExclusion.groups.description String Group description that the exclusion rule is associated with.
CrowdStrike.IOAExclusion.groups.assignment_rule String Group assignment rule that the exclusion is associated with.
CrowdStrike.IOAExclusion.groups.created_by String Indicate who created the group.
CrowdStrike.IOAExclusion.groups.created_timestamp Date The date when the group was created.
CrowdStrike.IOAExclusion.groups.modified_by String Indicate who last modified the group.
CrowdStrike.IOAExclusion.groups.modified_timestamp Date The date when the group was last modified.
CrowdStrike.IOAExclusion.applied_globally Boolean Whether the exclusion rule applies globally or only to specific entities.
CrowdStrike.IOAExclusion.last_modified Date The date when the exclusion rule was last modified.
CrowdStrike.IOAExclusion.modified_by String Indicate who last modified the rule.
CrowdStrike.IOAExclusion.created_on Date The date when the exclusion rule was created.
CrowdStrike.IOAExclusion.created_by String Indicate who created the rule.

Command Example

!cs-falcon-search-ioa-exclusion limit=1

Context Example

{
    "CrowdStrike": {
        "IOAExclusion": {
            "applied_globally": true,
            "cl_regex": "regex",
            "created_by": "user@test.com",
            "created_on": "2023-02-06T16:42:19.29906839Z",
            "description": "demo description",
            "detection_json": "",
            "groups": [],
            "id": "123456",
            "ifn_regex": ".*\\\\Windows\\\\System32\\\\choice\\.exe",
            "last_modified": "2023-02-26T15:30:04.554767735Z",
            "modified_by": "api-client-id:123456",
            "name": "My IOA Exclusion",
            "pattern_id": "101010",
            "pattern_name": "P_name"
        }
    }
}

Human Readable Output

CrowdStrike Falcon IOA exclusions

Id Name Description PatternId PatternName IfnRegex ClRegex AppliedGlobally LastModified ModifiedBy CreatedOn CreatedBy
123456 My IOA Exclusion demo description 101010 P_name .*\Windows\System32\choice.exe choice\s+/m\s+crowdstrike_sample_detection   2023-02-26T15:30:04.554767735Z api-client-id:123456 2023-02-06T16:42:19.29906839Z user@test.com

cs-falcon-list-quarantined-file


Get quarantine file metadata by specified IDs or filter.

Base Command

cs-falcon-list-quarantined-file

Input

Argument Name Description Required
ids A comma-separated list of quarantined file IDs to retrieve. Optional
filter A custom filter by which the retrieved quarantined file should be filtered. Optional
sha256 A comma-separated list of SHA256 hash of the files to retrieve. Optional
filename A comma-separated list of the name of the files to retrieve. Optional
state Filter the retrieved files by state. Optional
hostname A comma-separated list of the hostnames of the files to retrieve. Optional
username A comma-separated list of the usernames of the files to retrieve. Optional
limit Maximum number of IDs to return. Max 5000. Default 50. Optional
offset Starting index of the overall result set from which to return IDs. Default 0. Optional

Context Output

Path Type Description
CrowdStrike.QuarantinedFile.id String A unique identifier for the quarantined file.
CrowdStrike.QuarantinedFile.aid String The agent identifier of the agent that quarantined the file.
CrowdStrike.QuarantinedFile.cid String The unique customer identifier of the agent that quarantined the file.
CrowdStrike.QuarantinedFile.sha256 String The SHA256 hash value of the quarantined file.
CrowdStrike.QuarantinedFile.paths.path String The full path of the quarantined file.
CrowdStrike.QuarantinedFile.paths.filename String The name of the quarantined file.
CrowdStrike.QuarantinedFile.paths.state String The current state of the quarantined file path (e.g., “purged”).
CrowdStrike.QuarantinedFile.state String The current state of the quarantined file (e.g., “unrelease_pending”).
CrowdStrike.QuarantinedFile.detect_ids String The detection identifiers associated with the quarantined file.
CrowdStrike.QuarantinedFile.hostname String The hostname of the agent that quarantined the file.
CrowdStrike.QuarantinedFile.username String The username associated with the quarantined file.
CrowdStrike.QuarantinedFile.date_updated Date The date the quarantined file was last updated.
CrowdStrike.QuarantinedFile.date_created Date The date the quarantined file was created.

Command Example

!cs-falcon-list-quarantined-file limit=1

Context Example

{
    "CrowdStrike": {
        "QuarantinedFile": {
            "aid": "a123456",
            "cid": "c123456",
            "date_created": "2022-12-13T14:23:49Z",
            "date_updated": "2023-03-06T13:47:30Z",
            "detect_ids": [
                "ldt:a123456:456789"
            ],
            "hostname": "INSTANCE-1",
            "id": "a123456_sha123456",
            "paths": [
                {
                    "filename": "nc.exe",
                    "path": "\\Device\\HarddiskVolume3\\Users\\admin\\Downloads\\hamuzim\\test.exe",
                    "state": "quarantined"
                }
            ],
            "sha256": "sha123456",
            "state": "deleted",
            "username": "admin"
        }
    }
}

Human Readable Output

CrowdStrike Falcon Quarantined File

Id Aid Cid Sha256 Paths State DetectIds Hostname Username DateUpdated DateCreated
a123456_sha123456 a123456 c123456 sha123456 - path: \Device\HarddiskVolume3\Users\admin\Downloads\hamuzim\netcat-1.11\nc.exe
filename: nc.exe
state: quarantined
deleted values: ldt:a123456:456789 INSTANCE-1 admin 2023-03-06T13:47:30Z 2022-12-13T14:23:49Z

cs-falcon-apply-quarantine-file-action


Apply action to quarantined files by file IDs or filter.

Base Command

cs-falcon-apply-quarantine-file-action

Input

Argument Name Description Required
ids A comma-separated list of quarantined file IDs to update. Optional
action Action to perform against the quarantined file. Possible values are: delete, release, unrelease. Required
comment Comment to appear along with the action taken. Required
filter Update files based on a custom filter. Optional
sha256 A comma-separated list of quarantined SHA256 files to update. Optional
filename A comma-separated list of quarantined filenames to update. Optional
state Update files based on the state. Optional
hostname A comma-separated list of quarantined file hostnames to update. Optional
username A comma-separated list of quarantined file usernames to update. Optional

Context Output

There is no context output for this command.

Command Example

!cs-falcon-apply-quarantine-file-action filename=nc.exe action=delete comment=demo-comment

Human Readable Output

The Quarantined File with IDs [‘a123456_sha123456’] was successfully updated.

cs-falcon-ods-query-scan


Retrieve ODS scan details.

Base Command

cs-falcon-ods-query-scan

Input

Argument Name Description Required
filter Valid CS-Falcon-FQL filter to query with. Optional
ids Comma-separated list of scan IDs to retrieve details about. If set, will override all other arguments. Optional
initiated_from Comma-separated list of scan initiation sources to filter by. Optional
status Comma-separated list of scan statuses to filter by. Optional
severity Comma-separated list of scan severities to filter by. Optional
scan_started_on UTC-format of the scan start time to filter by. Optional
scan_completed_on UTC-format of the scan completion time to filter by. Optional
offset Starting index of overall result set from which to return IDs. Optional
limit Maximum number of resources to return. Optional

Context Output

Path Type Description
CrowdStrike.ODSScan.id String A unique identifier for the scan event.
CrowdStrike.ODSScan.cid String A unique identifier for the client that triggered the scan.
CrowdStrike.ODSScan.profile_id String A unique identifier for the scan profile used in the scan.
CrowdStrike.ODSScan.description String The ID of the description of the scan.
CrowdStrike.ODSScan.scan_inclusions String The files or folders included in the scan.
CrowdStrike.ODSScan.initiated_from String The source of the scan initiation.
CrowdStrike.ODSScan.quarantine Boolean Whether the scan was set to quarantine.
CrowdStrike.ODSScan.cpu_priority Number The CPU priority for the scan (1-5).
CrowdStrike.ODSScan.preemption_priority Number The preemption priority for the scan.
CrowdStrike.ODSScan.metadata.host_id String A unique identifier for the host that was scanned.
CrowdStrike.ODSScan.metadata.host_scan_id String A unique identifier for the scan that was performed on the host.
CrowdStrike.ODSScan.metadata.scan_host_metadata_id String A unique identifier for the metadata associated with the host scan.
CrowdStrike.ODSScan.metadata.filecount.scanned Number The number of files that were scanned.
CrowdStrike.ODSScan.metadata.filecount.malicious Number The number of files that were identified as malicious.
CrowdStrike.ODSScan.metadata.filecount.quarantined Number The number of files that were quarantined.
CrowdStrike.ODSScan.metadata.filecount.skipped Number The number of files that were skipped during the scan.
CrowdStrike.ODSScan.metadata.filecount.traversed Number The number of files that were traversed during the scan.
CrowdStrike.ODSScan.metadata.status String The status of the scan on this host. (e.g., “pending”, “running”, “completed”, or “failed”).
CrowdStrike.ODSScan.metadata.started_on Date The date and time that the scan started.
CrowdStrike.ODSScan.metadata.completed_on Date The date and time that the scan completed.
CrowdStrike.ODSScan.metadata.last_updated Date The date and time that the metadata was last updated.
CrowdStrike.ODSScan.status String The status of the scan (e.g., “pending”, “running”, “completed”, or “failed”).
CrowdStrike.ODSScan.hosts String A list of the host IDs that were scanned.
CrowdStrike.ODSScan.endpoint_notification Boolean Indicates whether endpoint notifications are enabled.
CrowdStrike.ODSScan.pause_duration Number The number of hours to pause between scanning each file.
CrowdStrike.ODSScan.max_duration Number The maximum amount of time to allow for the scan job in hours.
CrowdStrike.ODSScan.max_file_size Number The maximum file size (in MB) to scan.
CrowdStrike.ODSScan.sensor_ml_level_detection Number The level of detection sensitivity for the local sensor machine learning model.
CrowdStrike.ODSScan.sensor_ml_level_prevention Number The level of prevention sensitivity for the local sensor machine learning model.
CrowdStrike.ODSScan.cloud_ml_level_detection Number The level of detection sensitivity for the cloud machine learning model.
CrowdStrike.ODSScan.cloud_ml_level_prevention Number The level of prevention sensitivity for the cloud machine learning model.
CrowdStrike.ODSScan.policy_setting Number A list of policy setting IDs for the scan job (these correspond to specific policy settings in the Falcon console).
CrowdStrike.ODSScan.scan_started_on Date The timestamp when the scan was started.
CrowdStrike.ODSScan.scan_completed_on Date The timestamp when the scan was completed.
CrowdStrike.ODSScan.created_on Date The timestamp when the scan was created.
CrowdStrike.ODSScan.created_by String The ID of the user who created the scan job.
CrowdStrike.ODSScan.last_updated Date The timestamp when the scan job was last updated.

Command Example

!cs-falcon-ods-query-scan initiated_from=some_admin_name severity=high scan_started_on=2023-02-27T09:51:33.91608286Z

Context Example

{
    "CrowdStrike": {
        "ODSScan": [
            {
                "cid": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "cloud_ml_level_detection": 4,
                "cloud_ml_level_prevention": 4,
                "cpu_priority": 5,
                "created_by": "someone@email.com",
                "created_on": "2023-05-03T08:45:41.688556439Z",
                "endpoint_notification": true,
                "file_paths": [
                    "C:\\Users\\admin\\Downloads\\hamuzim\\netcat-1.11\\eicar_com.exe"
                ],
                "filecount": {},
                "hosts": [
                    "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
                ],
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "initiated_from": "some_admin_name",
                "last_updated": "2023-05-03T08:45:43.348230927Z",
                "max_duration": 0,
                "max_file_size": 60,
                "metadata": [
                    {
                        "completed_on": "2023-05-03T08:45:43.274953782Z",
                        "filecount": {
                            "malicious": 0,
                            "quarantined": 0,
                            "scanned": 0,
                            "skipped": 0,
                            "traversed": 0
                        },
                        "host_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                        "host_scan_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                        "last_updated": "2023-05-03T08:45:43.61797613Z",
                        "scan_host_metadata_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                        "started_on": "2023-05-03T08:45:43.069273028Z",
                        "status": "completed"
                    }
                ],
                "pause_duration": 2,
                "policy_setting": [
                    26439818675190,
                    26405458936832,
                    26405458936833,
                    26405458936834,
                    26405458936835,
                    26405458936840,
                    26405458936841,
                    26405458936842,
                    26405458936843,
                    26456998543793,
                    26456998544045,
                    26456998543652,
                    26456998543653,
                    26456998543656,
                    26456998543654,
                    26456998543950,
                    26456998543963
                ],
                "preemption_priority": 1,
                "profile_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "quarantine": true,
                "scan_completed_on": "2023-05-03T08:45:43.274953782Z",
                "scan_inclusions": [
                    "**\\Downloads\\**"
                ],
                "scan_started_on": "2023-02-27T09:51:33.91608286Z",
                "sensor_ml_level_detection": 4,
                "sensor_ml_level_prevention": 4,
                "status": "completed"
            },
            {
                "cid": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "cloud_ml_level_detection": 3,
                "cloud_ml_level_prevention": 3,
                "cpu_priority": 4,
                "created_by": "someone@email.com",
                "created_on": "2023-03-12T14:54:43.659773852Z",
                "endpoint_notification": true,
                "filecount": {},
                "hosts": [
                    "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
                ],
                "id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "initiated_from": "some_admin_name",
                "last_updated": "2023-04-05T16:56:14.972317443Z",
                "max_duration": 2,
                "max_file_size": 60,
                "metadata": [
                    {
                        "completed_on": "2023-03-12T14:57:37.338506965Z",
                        "filecount": {
                            "malicious": 0,
                            "quarantined": 0,
                            "scanned": 0,
                            "skipped": 0,
                            "traversed": 518485
                        },
                        "host_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                        "host_scan_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                        "last_updated": "2023-03-12T14:57:37.338585331Z",
                        "scan_host_metadata_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                        "started_on": "2023-02-27T09:51:33.91608286Z",
                        "status": "completed"
                    }
                ],
                "pause_duration": 2,
                "policy_setting": [
                    26439818674573,
                    26439818674574,
                    26439818674575,
                    26405458936832,
                    26456998543653,
                    26456998543656,
                    26456998543654,
                    26456998543950,
                    26456998543963
                ],
                "preemption_priority": 1,
                "profile_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "quarantine": true,
                "scan_completed_on": "2023-03-12T14:57:37.338506965Z",
                "scan_inclusions": [
                    "*"
                ],
                "scan_started_on": "2023-03-12T14:54:45.210172175Z",
                "sensor_ml_level_detection": 3,
                "sensor_ml_level_prevention": 3,
                "status": "failed"
            }
        ]
    }
}

Human Readable Output

CrowdStrike Falcon ODS Scans

ID Status Severity File Count Description Hosts/Host groups End time Start time Run by
a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 failed   scanned: 0
malicious: 0
quarantined: 0
skipped: 0
traversed: 518464
desc3456346 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1   2023-02-27T09:51:33.91608286Z a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1
a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 failed   scanned: 0
malicious: 0
quarantined: 0
skipped: 0
traversed: 518511
  a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2023-03-13T14:50:26.259846586Z 2023-02-27T09:51:33.91608286Z someone@email.com

cs-falcon-ods-query-scheduled-scan


Retrieve ODS scheduled scan details.

Base Command

cs-falcon-ods-query-scheduled-scan

Input

Argument Name Description Required
filter Valid CS-Falcon-FQL filter to query with. Optional
ids Comma-separated list of scan IDs to retrieve details about. If set, will override all other arguments. Optional
initiated_from Comma-separated list of scan initiation sources to filter by. Optional
status Comma-separated list of scan statuses to filter by. Optional
created_on UTC-format of the scan creation time to filter by. Optional
created_by UTC-format time of the scan creator to filter by. Optional
start_timestamp UTC-format of scan start time to filter by. Optional
deleted Deleted scans only. Optional
offset Starting index of overall result set from which to return IDs. Optional
limit Maximum number of resources to return. Optional

Context Output

Path Type Description
CrowdStrike.ODSScheduledScan.id String Unique identifier for the scan.
CrowdStrike.ODSScheduledScan.cid String Identifier for the customer or organization that owns the scan.
CrowdStrike.ODSScheduledScan.description String The ID of the description of the scan.
CrowdStrike.ODSScheduledScan.file_paths String The file or folder paths scanned.
CrowdStrike.ODSScheduledScan.scan_exclusions String The file or folder exclusions from the scan.
CrowdStrike.ODSScheduledScan.initiated_from String The source of the scan initiation.
CrowdStrike.ODSScheduledScan.cpu_priority Number The CPU priority for the scan (1-5).
CrowdStrike.ODSScheduledScan.preemption_priority Number The preemption priority for the scan.
CrowdStrike.ODSScheduledScan.status String The status of the scan, whether it’s “scheduled”, “running”, “completed”, etc.
CrowdStrike.ODSScheduledScan.host_groups String The host groups targeted by the scan.
CrowdStrike.ODSScheduledScan.endpoint_notification Boolean Whether notifications of the scan were sent to endpoints.
CrowdStrike.ODSScheduledScan.pause_duration Number The pause duration of the scan in hours.
CrowdStrike.ODSScheduledScan.max_duration Number The maximum duration of the scan in hours.
CrowdStrike.ODSScheduledScan.max_file_size Number The maximum file size that the scan can handle in MB.
CrowdStrike.ODSScheduledScan.sensor_ml_level_detection Number The machine learning detection level for the sensor.
CrowdStrike.ODSScheduledScan.cloud_ml_level_detection Number The machine learning detection level for the cloud.
CrowdStrike.ODSScheduledScan.schedule.start_timestamp Date The timestamp when the first scan was created.
CrowdStrike.ODSScheduledScan.schedule.interval Number The interval between scans.
CrowdStrike.ODSScheduledScan.created_on Date The timestamp when the scan was created.
CrowdStrike.ODSScheduledScan.created_by String The user who created the scan.
CrowdStrike.ODSScheduledScan.last_updated Date The timestamp when the scan was last updated.
CrowdStrike.ODSScheduledScan.deleted Boolean Whether the scan was deleted.
CrowdStrike.ODSScheduledScan.quarantine Boolean Whether the scan was set to quarantine.
CrowdStrike.ODSScheduledScan.metadata.host_id String Scan host IDs.
CrowdStrike.ODSScheduledScan.metadata.last_updated Date The date and time when the detection event was last updated.
CrowdStrike.ODSScheduledScan.sensor_ml_level_prevention Number The machine learning prevention level for the sensor.
CrowdStrike.ODSScheduledScan.cloud_ml_level_prevention Number The machine learning prevention level for the cloud.

Command Example

!cs-falcon-ods-query-scheduled-scan ids=123456789

Context Example

{
    "CrowdStrike": {
        "ODSScheduledScan": {
            "cid": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "cloud_ml_level_detection": 2,
            "cloud_ml_level_prevention": 2,
            "cpu_priority": 3,
            "created_by": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
            "created_on": "2023-05-08T09:04:20.8414225Z",
            "deleted": false,
            "endpoint_notification": true,
            "host_groups": [
                "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
            ],
            "id": "123456789",
            "initiated_from": "cloud_scheduled",
            "last_updated": "2023-05-08T09:22:48.408487143Z",
            "max_duration": 2,
            "max_file_size": 60,
            "metadata": [
                {
                    "host_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                    "last_updated": "2023-05-08T09:22:48.408487143Z"
                }
            ],
            "pause_duration": 3,
            "policy_setting": [
                26439818674573,
                26439818674574,
                26439818675074,
                26405458936702,
                26405458936703,
                26405458936707,
                26439818675124,
                26439818675125,
                26439818675157,
                26439818675158,
                26439818675182,
                26439818675183,
                26439818675190,
                26439818675191,
                26439818675196,
                26439818675197,
                26439818675204,
                26439818675205,
                26405458936760,
                26405458936761,
                26405458936793,
                26405458936794,
                26405458936818,
                26405458936819,
                26405458936825,
                26405458936826,
                26405458936832,
                26405458936833,
                26405458936840,
                26405458936841,
                26456998543793,
                26456998544045,
                26456998543652,
                26456998543653,
                26456998543656,
                26456998543654,
                26456998543950,
                26456998543963
            ],
            "preemption_priority": 15,
            "quarantine": true,
            "scan_inclusions": [
                "*"
            ],
            "schedule": {
                "interval": 14,
                "start_timestamp": "2023-05-20T06:49"
            },
            "sensor_ml_level_detection": 2,
            "sensor_ml_level_prevention": 2,
            "status": "scheduled"
        }
    }
}

Human Readable Output

CrowdStrike Falcon ODS Scheduled Scans

ID Hosts targeted Description Host groups Start time Created by
123456789 1   a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 2023-05-20T06:49 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1

cs-falcon-ods-query-scan-host


Retrieve ODS scan host details.

Base Command

cs-falcon-ods-query-scan-host

Input

Argument Name Description Required
filter Valid CS-Falcon-FQL filter to query with. Optional
host_ids Comma-separated list of host IDs to filter by. Optional
scan_ids Comma-separated list of scan IDs to filter by. Optional
status Comma-separated list of scan statuses to filter by. Optional
started_on UTC-format of scan start time to filter by. Optional
completed_on UTC-format of scan completion time to filter by. Optional
offset Starting index of the overall result set from which to return IDs. Optional
limit Maximum number of resources to return. Optional

Context Output

Path Type Description
CrowdStrike.ODSScanHost.id String A unique identifier for the scan event.
CrowdStrike.ODSScanHost.cid String A unique identifier for the client that triggered the scan.
CrowdStrike.ODSScanHost.scan_id String A unique identifier for the scan.
CrowdStrike.ODSScanHost.profile_id String A unique identifier for the scan profile used in the scan.
CrowdStrike.ODSScanHost.host_id String A unique identifier for the host that was scanned.
CrowdStrike.ODSScanHost.host_scan_id String A unique identifier for the scan that was performed on the host.
CrowdStrike.ODSScanHost.filecount.scanned Number The number of files that were scanned during the scan.
CrowdStrike.ODSScanHost.filecount.malicious Number The number of files that were detected as malicious during the scan.
CrowdStrike.ODSScanHost.filecount.quarantined Number The number of files that were quarantined during the scan.
CrowdStrike.ODSScanHost.filecount.skipped Number The number of files that were skipped during the scan.
CrowdStrike.ODSScanHost.status String The status of the scan. (e.g., “completed”, “pending”, “cancelled”, “running”, or “failed”).
CrowdStrike.ODSScanHost.severity Number A severity score assigned to the scan, ranging from 0 to 100.
CrowdStrike.ODSScanHost.started_on Date The date and time when the scan started.
CrowdStrike.ODSScanHost.completed_on Date The date and time when the scan completed.
CrowdStrike.ODSScanHost.last_updated Date The date and time when the scan event was last updated.

Command Example

!cs-falcon-ods-query-scan-host filter="scan_id:[\"123456789\",\"987654321\"]"

Context Example

{
    "CrowdStrike": {
        "ODSScanHost": [
            {
                "cid": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "filecount": {},
                "host_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "id": "123456789",
                "last_updated": "2022-11-27T17:15:50.056840267Z",
                "profile_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "scan_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "status": "pending"
            },
            {
                "cid": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "completed_on": "2023-05-07T08:28:56.856506979Z",
                "filecount": {
                    "malicious": 0,
                    "quarantined": 0,
                    "scanned": 0,
                    "skipped": 0,
                    "traversed": 524581
                },
                "host_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "host_scan_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "id": "987654321",
                "last_updated": "2023-05-07T08:28:56.856575358Z",
                "profile_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "scan_id": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                "started_on": "2023-05-07T08:25:48.336234188Z",
                "status": "completed"
            }
        ]
    }
}

Human Readable Output

CrowdStrike Falcon ODS Scan Hosts

ID Scan ID Host ID Filecount Status Severity Started on
123456789 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1   pending    
987654321 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 scanned: 0
malicious: 0
quarantined: 0
skipped: 0
traversed: 524581
completed   2023-05-07T08:25:48.336234188Z

cs-falcon-ods-query-malicious-files


Retrieve ODS malicious file details.

Base Command

cs-falcon-ods-query-malicious-files

Input

Argument Name Description Required
filter Valid CS-Falcon-FQL filter to query with. Optional
file_ids Comma-separated list of malicious file IDs to retrieve details about. If set, will override all other arguments. Optional
host_ids Comma-separated list of host IDs to filter by. Optional
scan_ids Comma-separated list of scan IDs to filter by. Optional
file_paths Comma-separated list of file paths to filter by. Optional
file_names Comma-separated list of filenames to filter by. Optional
hash Comma-separated list of hashes to filter by. Optional
offset Starting index of the overall result set from which to return IDs. Optional
limit Maximum number of resources to return. Optional

Context Output

Path Type Description
CrowdStrike.ODSMaliciousFile.id String A unique identifier of the detection event.
CrowdStrike.ODSMaliciousFile.cid String A unique identifier for the client that triggered the detection event.
CrowdStrike.ODSMaliciousFile.scan_id String A unique identifier for the scan that triggered the detection event.
CrowdStrike.ODSMaliciousFile.host_id String A unique identifier for the host that was scanned.
CrowdStrike.ODSMaliciousFile.host_scan_id String A unique identifier for the scan that detected the file on the host.
CrowdStrike.ODSMaliciousFile.filepath String The full path to the malicious file on the host system.
CrowdStrike.ODSMaliciousFile.filename String The name of the malicious file.
CrowdStrike.ODSMaliciousFile.hash String A SHA256 hash of the malicious file, which can be used to identify it.
CrowdStrike.ODSMaliciousFile.pattern_id Number The identifier of the pattern used to detect the malicious file.
CrowdStrike.ODSMaliciousFile.severity Number A severity score assigned to the detection event, ranging from 0 to 100.
CrowdStrike.ODSMaliciousFile.quarantined Boolean Indicates whether the file was quarantined.
CrowdStrike.ODSMaliciousFile.last_updated Date The date and time when the detection event was last updated.

Command Example


#### Human Readable Output

>No malicious files match the arguments/filter.

### cs-falcon-ods-create-scan

***
Create an ODS scan and wait for the results.

#### Base Command

`cs-falcon-ods-create-scan`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| hosts | A comma-separated list of hosts to be scanned. "hosts" OR "host_groups" must be set. | Optional |
| host_groups | A comma-separated list of host groups to be scanned. "hosts" OR "host_groups" must be set. | Optional |
| file_paths | A comma-separated list of file paths to be scanned. "file_paths" OR "scan_inclusions" must be set. | Optional |
| scan_inclusions | A comma-separated list of included files or locations for this scan. "file_paths" OR "scan_inclusions" must be set. | Optional |
| scan_exclusions | A comma-separated list of excluded files or locations for this scan. | Optional |
| initiated_from | Scan origin. | Optional |
| cpu_priority | The scan CPU priority. Possible values are: Highest, High, Medium, Low, Lowest. Default is Low. | Optional |
| description | Scan description. | Optional |
| quarantine | Flag indicating if identified threats should be quarantined. | Optional |
| pause_duration | Amount of time (in hours) for scan pauses. Default is 2. | Optional |
| sensor_ml_level_detection | Sensor ML detection level. | Optional |
| sensor_ml_level_prevention | Sensor ML prevention level. | Optional |
| cloud_ml_level_detection | Cloud ML detection level for the scan. | Optional |
| cloud_ml_level_prevention | Cloud ML prevention level for the scan. | Optional |
| max_duration | Maximum time (in hours) the scan is allowed to execute. Default is 2. | Optional |
| interval_in_seconds | The interval in seconds between each poll. Default is 30. | Optional |
| timeout_in_seconds | The timeout in seconds until polling ends. Default is 600. | Optional |
| cloud_pup_adware_level_detection | Potentially unwanted programs (PUPs) adware detection level. Possible values are 0–4 (0 = Disabled). If not specified, CrowdStrike applies the default behavior (Disabled). Possible values are: 0, 1, 2, 3, 4. | Optional |
| cloud_pup_adware_level_prevention | Potentially unwanted programs (PUPs) adware prevention level. Possible values are 0–4 (0 = Disabled). If not specified, CrowdStrike applies the default behavior (Disabled). Possible values are: 0, 1, 2, 3, 4. | Optional |
| polling | Whether to use polling to wait for the scan result. If set to false, the command will only create the scan and return immediately without waiting for results. Possible values are: true, false. Default is true. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| CrowdStrike.ODSScan.id | String | A unique identifier for the scan event. |
| CrowdStrike.ODSScan.cid | String | A unique identifier for the client that triggered the scan. |
| CrowdStrike.ODSScan.profile_id | String | A unique identifier for the scan profile used in the scan. |
| CrowdStrike.ODSScan.description | String | The ID of the description of the scan. |
| CrowdStrike.ODSScan.scan_inclusions | String | The files or folders included in the scan. |
| CrowdStrike.ODSScan.initiated_from | String | The source of the scan initiation. |
| CrowdStrike.ODSScan.quarantine | Boolean | Whether the scan was set to quarantine. |
| CrowdStrike.ODSScan.cpu_priority | Number | The CPU priority for the scan \(1-5\). |
| CrowdStrike.ODSScan.preemption_priority | Number | The preemption priority for the scan. |
| CrowdStrike.ODSScan.metadata.host_id | String | A unique identifier for the host that was scanned. |
| CrowdStrike.ODSScan.metadata.host_scan_id | String | A unique identifier for the scan that was performed on the host. |
| CrowdStrike.ODSScan.metadata.scan_host_metadata_id | String | A unique identifier for the metadata associated with the host scan. |
| CrowdStrike.ODSScan.metadata.filecount.scanned | Number | The number of files that were scanned. |
| CrowdStrike.ODSScan.metadata.filecount.malicious | Number | The number of files that were identified as malicious. |
| CrowdStrike.ODSScan.metadata.filecount.quarantined | Number | The number of files that were quarantined. |
| CrowdStrike.ODSScan.metadata.filecount.skipped | Number | The number of files that were skipped during the scan. |
| CrowdStrike.ODSScan.metadata.filecount.traversed | Number | The number of files that were traversed during the scan. |
| CrowdStrike.ODSScan.metadata.status | String | The status of the scan on this host \(e.g., "pending", "running", "completed", or "failed"\). |
| CrowdStrike.ODSScan.metadata.started_on | Date | The date and time that the scan started. |
| CrowdStrike.ODSScan.metadata.completed_on | Date | The date and time that the scan completed. |
| CrowdStrike.ODSScan.metadata.last_updated | Date | The date and time that the metadata was last updated. |
| CrowdStrike.ODSScan.status | String | The status of the scan \(e.g., "pending", "running", "completed", or "failed"\). |
| CrowdStrike.ODSScan.hosts | String | A list of the host IDs that were scanned. |
| CrowdStrike.ODSScan.endpoint_notification | Boolean | Indicates whether endpoint notifications are enabled. |
| CrowdStrike.ODSScan.pause_duration | Number | The number of hours to pause between scanning each file. |
| CrowdStrike.ODSScan.max_duration | Number | The maximum amount of time to allow for the scan job in hours. |
| CrowdStrike.ODSScan.max_file_size | Number | The maximum file size \(in MB\) to scan. |
| CrowdStrike.ODSScan.sensor_ml_level_detection | Number | The level of detection sensitivity for the local sensor machine learning model. |
| CrowdStrike.ODSScan.sensor_ml_level_prevention | Number | The level of prevention sensitivity for the local sensor machine learning model. |
| CrowdStrike.ODSScan.cloud_ml_level_detection | Number | The level of detection sensitivity for the cloud machine learning model. |
| CrowdStrike.ODSScan.cloud_ml_level_prevention | Number | The level of prevention sensitivity for the cloud machine learning model. |
| CrowdStrike.ODSScan.policy_setting | Number | A list of policy setting IDs for the scan job \(these correspond to specific policy settings in the Falcon console\). |
| CrowdStrike.ODSScan.scan_started_on | Date | The timestamp when the scan was started. |
| CrowdStrike.ODSScan.scan_completed_on | Date | The timestamp when the scan was completed. |
| CrowdStrike.ODSScan.created_on | Date | The timestamp when the scan was created. |
| CrowdStrike.ODSScan.created_by | String | The ID of the user who created the scan job. |
| CrowdStrike.ODSScan.last_updated | Date | The timestamp when the scan job was last updated. |
| CrowdStrike.ODSScan.cloud_pup_adware_level_detection | Number | Potentially unwanted programs \(PUPs\) Adware detection level. |
| CrowdStrike.ODSScan.cloud_pup_adware_level_prevention | Number | Potentially unwanted programs \(PUPs\) Adware prevention level. |

#### Command Example

```!cs-falcon-ods-create-scan host_groups=7471ba0636b34cbb8c65fae7979a6a9b scan_inclusions=* cpu_priority=Highest max_duration=1 pause_duration=1```

#### Context Example

```json
{
    "CrowdStrike": {
        "ODSScan": {
            "cid": "20879a8064904ecfbb62c118a6a19411",
            "cloud_ml_level_detection": 2,
            "cloud_ml_level_prevention": 2,
            "cpu_priority": 5,
            "created_by": "f7acf1bd5d3d4b40afe77546cbbaefde",
            "created_on": "2023-06-11T13:23:05.139153881Z",
            "filecount": {
                "malicious": 0,
                "quarantined": 0,
                "scanned": 0,
                "skipped": 0,
                "traversed": 0
            },
            "host_groups": [
                "7471ba0636b34cbb8c65fae7979a6a9b"
            ],
            "id": "9ba8489e9f604b61bf9b4a2c5f95ede7",
            "initiated_from": "cloud_adhoc",
            "last_updated": "2023-06-11T13:23:05.139153881Z",
            "max_duration": 1,
            "max_file_size": 60,
            "metadata": [
                {
                    "filecount": {},
                    "host_id": "046761c46ec84f40b27b6f79ce7cd32c",
                    "last_updated": "2023-06-11T13:23:05.139153881Z",
                    "scan_host_metadata_id": "31052e821a5a4189a1a9a2814cc88e4e",
                    "status": "complete"
                }
            ],
            "pause_duration": 1,
            "policy_setting": [
                26439818674573,
                26439818674574,
                26439818675074,
                26405458936702,
                26405458936703,
                26456998543654,
                26456998543950,
                26456998543963
            ],
            "preemption_priority": 1,
            "profile_id": "335198a96e1a4a6b880d62b2e7ccbb91",
            "quarantine": true,
            "scan_inclusions": [
                "*"
            ],
            "sensor_ml_level_detection": 2,
            "sensor_ml_level_prevention": 2,
            "cloud_pup_adware_level_prevention": 1,
            "cloud_pup_adware_level_detection": 1,
            "status": "complete"
        }
    }
}

Human Readable Output

CrowdStrike Falcon ODS Scans

ID Status Severity File Count Description Hosts/Host groups End time Start time Run by
9ba8489e9f604b61bf9b4a2c5f95ede7 complete       7471ba0636b34cbb8c65fae7979a6a9b     f7acf1bd5d3d4b40afe77546cbbaefde

cs-falcon-ods-create-scheduled-scan


Create an ODS scheduled scan.

Base Command

cs-falcon-ods-create-scheduled-scan

Input

Argument Name Description Required
host_groups A comma-separated list of host groups to be scanned. Required
file_paths A comma-separated list of file paths to be scanned. “file_paths” OR “scan_inclusions” must be set. Optional
scan_inclusions A comma-separated list of included files or locations for this scan. “file_paths” OR “scan_inclusions” must be set. Optional
scan_exclusions A comma-separated list of excluded files or locations for this scan. Optional
initiated_from Scan origin. Optional
cpu_priority The scan CPU priority. Possible values are: Highest, High, Medium, Low, Lowest. Default is Low. Optional
description Scan description. Optional
quarantine Flag indicating if identified threats should be quarantined. Optional
pause_duration Amount of time (in hours) for scan pauses. Default is 2. Optional
sensor_ml_level_detection Sensor ML detection level. Optional
sensor_ml_level_prevention Sensor ML prevention level. Optional
cloud_ml_level_detection Cloud ML detection level for the scan. Optional
cloud_ml_level_prevention Cloud ML prevention level for the scan. Optional
max_duration Maximum time (in hours) the scan is allowed to execute. Default is 2. Optional
schedule_start_timestamp When to start the first scan. Supports english expressions such as “tomorrow” or “in an hour”. Required
schedule_interval The schedule interval. Possible values are: Never, Daily, Weekly, Every other week, Every four weeks, Monthly. Required
cloud_pup_adware_level_detection Potentially unwanted programs (PUPs) adware detection level. Possible values are 0–4 (0 = Disabled). If not specified, CrowdStrike applies the default behavior (Disabled). Possible values are: 0, 1, 2, 3, 4. Optional
cloud_pup_adware_level_prevention Potentially unwanted programs (PUPs) adware prevention level. Possible values are 0–4 (0 = Disabled). If not specified, CrowdStrike applies the default behavior (Disabled). Possible values are: 0, 1, 2, 3, 4. Optional

Context Output

Path Type Description
CrowdStrike.ODSScheduledScan.id String Unique identifier for the scan.
CrowdStrike.ODSScheduledScan.cid String Identifier for the customer or organization that owns the scan.
CrowdStrike.ODSScheduledScan.description String The ID of the description of the scan.
CrowdStrike.ODSScheduledScan.file_paths String The file or folder paths scanned.
CrowdStrike.ODSScheduledScan.scan_exclusions String The file or folder exclusions from the scan.
CrowdStrike.ODSScheduledScan.initiated_from String The source of the scan initiation.
CrowdStrike.ODSScheduledScan.cpu_priority Number The CPU priority for the scan (1-5).
CrowdStrike.ODSScheduledScan.preemption_priority Number The preemption priority for the scan.
CrowdStrike.ODSScheduledScan.status String The status of the scan, whether it’s “scheduled”, “running”, “completed”, etc.
CrowdStrike.ODSScheduledScan.host_groups String The host groups targeted by the scan.
CrowdStrike.ODSScheduledScan.endpoint_notification Boolean Whether notifications of the scan were sent to endpoints.
CrowdStrike.ODSScheduledScan.pause_duration Number The pause duration of the scan in hours.
CrowdStrike.ODSScheduledScan.max_duration Number The maximum duration of the scan in hours.
CrowdStrike.ODSScheduledScan.max_file_size Number The maximum file size that the scan can handle in MB.
CrowdStrike.ODSScheduledScan.sensor_ml_level_detection Number The machine learning detection level for the sensor.
CrowdStrike.ODSScheduledScan.cloud_ml_level_detection Number The machine learning detection level for the cloud.
CrowdStrike.ODSScheduledScan.schedule.start_timestamp Date The timestamp when the first scan was created.
CrowdStrike.ODSScheduledScan.schedule.interval Number The interval between scans.
CrowdStrike.ODSScheduledScan.created_on Date The timestamp when the scan was created.
CrowdStrike.ODSScheduledScan.created_by String The user who created the scan.
CrowdStrike.ODSScheduledScan.last_updated Date The timestamp when the scan was last updated.
CrowdStrike.ODSScheduledScan.deleted Boolean Whether the scan was deleted.
CrowdStrike.ODSScheduledScan.quarantine Boolean Whether the scan was set to quarantine.
CrowdStrike.ODSScheduledScan.metadata.host_id String Scan host IDs.
CrowdStrike.ODSScheduledScan.metadata.last_updated Date The date and time when the detection event was last updated.
CrowdStrike.ODSScheduledScan.sensor_ml_level_prevention Number The machine learning prevention level for the sensor.
CrowdStrike.ODSScheduledScan.cloud_ml_level_prevention Number The machine learning prevention level for the cloud.
CrowdStrike.ODSScheduledScan.cloud_pup_adware_level_detection Number Potentially unwanted programs (PUPs) Adware detection level.
CrowdStrike.ODSScheduledScan.cloud_pup_adware_level_prevention Number Potentially unwanted programs (PUPs) Adware prevention level.

Command Example

!cs-falcon-ods-create-scheduled-scan host_groups=7471ba0636b34cbb8c65fae7979a6a9b schedule_interval=daily schedule_start_timestamp=tomorrow cpu_priority=Highest scan_inclusions=*

Context Example

{
    "CrowdStrike": {
        "ODSScan": {
            "cid": "20879a8064904ecfbb62c118a6a19411",
            "cloud_ml_level_detection": 2,
            "cloud_ml_level_prevention": 2,
            "cpu_priority": 5,
            "created_by": "f7acf1bd5d3d4b40afe77546cbbaefde",
            "created_on": "2023-06-11T13:23:10.564070276Z",
            "deleted": false,
            "host_groups": [
                "7471ba0636b34cbb8c65fae7979a6a9b"
            ],
            "id": "7d08d9a3088f49b3aa20efafc355aef0",
            "initiated_from": "cloud_scheduled",
            "last_updated": "2023-06-11T13:23:10.564070276Z",
            "max_duration": 2,
            "max_file_size": 60,
            "metadata": [
                {
                    "host_id": "046761c46ec84f40b27b6f79ce7cd32c",
                    "last_updated": "2023-06-11T13:23:10.564070276Z"
                }
            ],
            "pause_duration": 2,
            "policy_setting": [
                26439818674573,
                26439818674574,
                26439818675074,
                26405458936702,
                26405458936703,
                26405458936707,
                26439818675124,
                26439818675125,
                26439818675157,
                26439818675158,
                26439818675182,
                26439818675183,
                26439818675190,
                26439818675191,
                26439818675196,
                26439818675197,
                26439818675204,
                26439818675205,
                26405458936760,
                26405458936761,
                26405458936793,
                26405458936794,
                26405458936818,
                26405458936819,
                26405458936825,
                26405458936826,
                26405458936832,
                26405458936833,
                26405458936840,
                26405458936841,
                26456998543793,
                26456998544045,
                26456998543652,
                26456998543653,
                26456998543656,
                26456998543654,
                26456998543950,
                26456998543963
            ],
            "preemption_priority": 15,
            "quarantine": true,
            "scan_inclusions": [
                "*"
            ],
            "schedule": {
                "interval": 1,
                "start_timestamp": "2023-06-12T13:23"
            },
            "sensor_ml_level_detection": 2,
            "sensor_ml_level_prevention": 2,
            "cloud_pup_adware_level_prevention": 1,
            "cloud_pup_adware_level_detection": 1,
            "status": "scheduled"
        }
    }
}

Human Readable Output

Scheduled Scan Created

Scan ID
7d08d9a3088f49b3aa20efafc355aef0

cs-falcon-ods-delete-scheduled-scan


Delete ODS scheduled scans.

Base Command

cs-falcon-ods-delete-scheduled-scan

Input

Argument Name Description Required
ids Comma-separated list of scheduled scan IDs to delete. Optional
filter Valid CS-Falcon-FQL filter to delete scans by. Optional

Context Output

There is no context output for this command.

Command Example

!cs-falcon-ods-delete-scheduled-scan ids=9acf0c069d3d4a5b82badb170966e77c

Human Readable Output

Deleted Scans

Scan ID
9acf0c069d3d4a5b82badb170966e77c

cs-falcon-list-identity-entities


List identity entities.

Base Command

cs-falcon-list-identity-entities

Input

Argument Name Description Required
type API type. Possible values are: USER, ENDPOINT. Required
sort_key The key to sort by. Possible values are: RISK_SCORE, PRIMARY_DISPLAY_NAME, SECONDARY_DISPLAY_NAME, MOST_RECENT_ACTIVITY, ENTITY_ID. Optional
sort_order The sort order. Possible values are: DESCENDING, ASCENDING. Default is ASCENDING. Optional
entity_id A comma-separated list of entity IDs to look for. Optional
primary_display_name A comma-separated list of primary display names to filter by. Optional
secondary_display_name A comma-separated list of secondary display names to filter by. Optional
max_risk_score_severity The maximum risk score severity to filter by. Possible values are: NORMAL, MEDIUM, HIGH. Optional
min_risk_score_severity The minimum risk score severity to filter by. Possible values are: NORMAL, MEDIUM, HIGH. Optional
enabled Whether to get only enabled or disabled identity entities. Possible values are: true, false. Optional
email Email to filter by. Optional
next_token The hash for the next page. Optional
page_size The maximum number of items to fetch per page. The maximum value allowed is 1000. Default is 50. Optional
page The page number. Default is 1. Optional
limit The maximum number of identity entities to list. Optional

Context Output

Path Type Description
CrowdStrike.IDPEntity.Ishuman Boolean Whether the identity entity is human made.
CrowdStrike.IDPEntity.Isprogrammatic Boolean Whether the identity entity is programmatic made.
CrowdStrike.IDPEntity.Isadmin String Whether the identity entity is admin made.
CrowdStrike.IDPEntity.Primarydisplayname String The identity entity primary display name.
CrowdStrike.IDPEntity.Riskfactors.Type Unknown The identity entity risk factor type.
CrowdStrike.IDPEntity.Riskfactors.Severity Unknown The identity entity risk factor severity.
CrowdStrike.IDPEntity.Riskscore Number The identity entity risk score.
CrowdStrike.IDPEntity.Riskscoreseverity String The identity entity risk score severity.
CrowdStrike.IDPEntity.Secondarydisplayname String The identity entity secondary display name.
CrowdStrike.IDPEntity.Emailaddresses String The identity entity email address.

cs-falcon-cspm-list-policy-details


Given a CSV list of policy IDs, returns detailed policy information.

Base Command

cs-falcon-cspm-list-policy-details

Input

Argument Name Description Required
policy_ids Comma-separated list of policy IDs to look for. Required

Context Output

Path Type Description
CrowdStrike.CSPMPolicy.ID Integer The policy ID.
CrowdStrike.CSPMPolicy.CreatedAt Date The creation date.
CrowdStrike.CSPMPolicy.UpdatedAt Date The update date.
CrowdStrike.CSPMPolicy.DeletedAt Date The deletion date.
CrowdStrike.CSPMPolicy.description String The policy description.
CrowdStrike.CSPMPolicy.policy_statement String The policy statement.
CrowdStrike.CSPMPolicy.policy_remediation String The policy remediation.
CrowdStrike.CSPMPolicy.cloud_service_subtype String The cloud service subtype.
CrowdStrike.CSPMPolicy.cloud_document String The cloud document.
CrowdStrike.CSPMPolicy.mitre_attack_cloud_matrix String URL to the MITRE attack tactics.
CrowdStrike.CSPMPolicy.mitre_attack_cloud_subtype String URL to the MITRE attack techniques.
CrowdStrike.CSPMPolicy.alert_logic String The alert logic.
CrowdStrike.CSPMPolicy.api_command String The API command.
CrowdStrike.CSPMPolicy.cli_command String The CLI command.
CrowdStrike.CSPMPolicy.cloud_platform_type String The cloud platform type.
CrowdStrike.CSPMPolicy.cloud_service_type String The cloud service type.
CrowdStrike.CSPMPolicy.default_severity String The default severity.
CrowdStrike.CSPMPolicy.cis_benchmark_ids Array The CIS benchmark IDs.
CrowdStrike.CSPMPolicy.nist_benchmark_ids Array The NIST benchmark IDs.
CrowdStrike.CSPMPolicy.pci_benchmark_ids Array The PCI benchmark IDs.
CrowdStrike.CSPMPolicy.policy_type String The policy type.
CrowdStrike.CSPMPolicy.tactic_url String The tactic URL.
CrowdStrike.CSPMPolicy.technique_url String The technique URL.
CrowdStrike.CSPMPolicy.tactic String The tactic used.
CrowdStrike.CSPMPolicy.technique String The technique used.
CrowdStrike.CSPMPolicy.tactic_id String The tactic ID.
CrowdStrike.CSPMPolicy.technique_id String The technique ID.
CrowdStrike.CSPMPolicy.attack_types Array The attack types.
CrowdStrike.CSPMPolicy.asset_type_id Integer The asset type ID.
CrowdStrike.CSPMPolicy.cloud_asset_type String The cloud asset type.
CrowdStrike.CSPMPolicy.is_remediable Boolean Whether the policy is remediable or not.
CrowdStrike.CSPMPolicy.is_enabled Boolean Whether the policy is enabled or not.
CrowdStrike.CSPMPolicy.account_scope String The account scope.

Command Example

!cs-falcon-cspm-list-policy-details policy_ids=1,2

Context Example

{
    "CrowdStrike": {
        "CSPMPolicy": [
            {
                "CreatedAt": "2020-08-18T08:30:21.760579Z",
                "DeletedAt": null,
                "ID": 1,
                "UpdatedAt": "2023-06-21T18:47:44.371539Z",
                "account_scope": "",
                "alert_logic": "1. List all IAM users.|\n2. Filter on users with active access keys.|\n3. Filter on access keys that have not been rotated in 90 days.|\n4. Alert on each user.",
                "api_command": "ListUsers, ListAccessKeys",
                "asset_type_id": 8,
                "attack_types": [
                    "Credential policy violation"
                ],
                "cis_benchmark_ids": [
                    108,
                    641,
                    740
                ],
                "cli_command": "aws2 iam list-users, aws2 iam list-access-keys",
                "cloud_asset_type": "user",
                "cloud_document": "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html",
                "cloud_platform_type": "aws",
                "cloud_service_subtype": "Access Keys",
                "cloud_service_type": "IAM",
                "default_severity": "informational",
                "description": "Because IAM access keys are long-term credentials, as time goes on, the risk of these keys being exposed is increased.\n\nKeys are often left on old servers, accidentally published through Git, or stolen from developer machines. The longer the keys are valid, the more likely they are to be discovered in one of these places. By ensuring keys are rotated at least every 90 days, you can be confident that if those keys are discovered, they cannot be abused.",
                "is_enabled": true,
                "is_remediable": false,
                "mitre_attack_cloud_matrix": "https://attack.mitre.org/tactics/TA0006/",
                "mitre_attack_cloud_subtype": "https://attack.mitre.org/techniques/T1528/",
                "nist_benchmark_ids": [
                    2,
                    3,
                    281,
                    941
                ],
                "pci_benchmark_ids": [
                    120
                ],
                "policy_remediation": "Step 1. From the AWS Console, navigate to the IAM page.|\nStep 2. Locate and click on the offending IAM User.|\nStep 3. Click on the Security Credentials tab.|\nStep 4. Navigate to the Access Keys section and choose between making the access key inactive, deleting the key, or rotating the key.",
                "policy_statement": "IAM user access key active longer than 90 days",
                "policy_type": "Configuration",
                "tactic": "Credential Access",
                "tactic_id": "TA0006",
                "tactic_url": "https://attack.mitre.org/tactics/TA0006/",
                "technique": "Steal Application Access Token",
                "technique_id": "T1528",
                "technique_url": "https://attack.mitre.org/techniques/T1528/"
            },
            {
                "CreatedAt": "2022-10-19T15:00:00Z",
                "DeletedAt": null,
                "ID": 2,
                "UpdatedAt": "2023-07-20T19:14:47.972998Z",
                "account_scope": "",
                "alert_logic": "1. List Launch Configurations.|\n2. Decode Base64-encoded User Data field.|\n3. Search for strings indicating credentials are present.|\n4. Alert on each instance.",
                "api_command": "DescribeLaunchConfigurations",
                "asset_type_id": 81,
                "cis_benchmark_ids": [
                    714
                ],
                "cisa_benchmark_ids": [
                    16
                ],
                "cli_command": "aws autoscaling describe-launch-configurations",
                "cloud_asset_type": "launchconfig",
                "cloud_document": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html",
                "cloud_platform_type": "aws",
                "cloud_service_type": "Auto Scaling",
                "default_severity": "informational",
                "description": "EC2 instance data is used to pass start up information into the EC2 instance. This User Data must not contain any sort of credentials. Instead, use an IAM Instance Profile assigned to the instance to grant access to other AWS Services.",
                "is_enabled": true,
                "is_remediable": false,
                "iso_benchmark_ids": [
                    10,
                    15,
                    62,
                    63
                ],
                "mitre_attack_cloud_matrix": "https://attack.mitre.org/tactics/TA0006/",
                "mitre_attack_cloud_subtype": "https://attack.mitre.org/techniques/T1552/005/",
                "nist_benchmark_ids": [
                    16,
                    65,
                    66,
                    531
                ],
                "policy_remediation": "Step 1. From the console navigate to the EC2 page.|\nStep 2. From the sub-menu, click on 'Launch Configurations' under 'Auto Scaling'.|\nStep 3. Select the offending launch configuration.|\nStep 4. Scroll down to the 'Details' section and click 'View user data'.|\nStep 5. Validate whether or not any credentials are present.|\nStep 6. If credentials are present, re-create the launch configuration without exposing any credentials in the user data field.",
                "policy_statement": "Auto Scaling group launch configuration User Data with potential credentials exposed",
                "policy_type": "Configuration",
                "soc2_benchmark_ids": [
                    15,
                    17
                ],
                "tactic": "Credential Access",
                "tactic_id": "TA0006",
                "tactic_url": "https://attack.mitre.org/tactics/TA0006/",
                "technique": "Unsecured Credentials: Cloud Instance Metadata API",
                "technique_id": "T1552.005",
                "technique_url": "https://attack.mitre.org/techniques/T1552/005/"
            }
        ]
    }
}

Human Readable Output

CSPM Policy Details

Id Description Policy Statement Policy Remediation Cloud Service Subtype Cloud Platform Type Cloud Service Type Default Severity Policy Type Tactic Technique
1 Because IAM access keys are long-term credentials, as time goes on, the risk of these keys being exposed is increased.

Keys are often left on old servers, accidentally published through Git, or stolen from developer machines. The longer the keys are valid, the more likely they are to be discovered in one of these places. By ensuring keys are rotated at least every 90 days, you can be confident that if those keys are discovered, they cannot be abused.
IAM user access key active longer than 90 days Step 1. From the AWS Console, navigate to the IAM page.|
Step 2. Locate and click on the offending IAM User.|
Step 3. Click on the Security Credentials tab.|
Step 4. Navigate to the Access Keys section and choose between making the access key inactive, deleting the key, or rotating the key.
Access Keys aws IAM informational Configuration Credential Access Steal Application Access Token
2 EC2 instance data is used to pass start up information into the EC2 instance. This User Data must not contain any sort of credentials. Instead, use an IAM Instance Profile assigned to the instance to grant access to other AWS Services. Auto Scaling group launch configuration User Data with potential credentials exposed Step 1. From the console navigate to the EC2 page.|
Step 2. From the sub-menu, click on ‘Launch Configurations’ under ‘Auto Scaling’.|
Step 3. Select the offending launch configuration.|
Step 4. Scroll down to the ‘Details’ section and click ‘View user data’.|
Step 5. Validate whether or not any credentials are present.|
Step 6. If credentials are present, re-create the launch configuration without exposing any credentials in the user data field.
  aws Auto Scaling informational Configuration Credential Access Unsecured Credentials: Cloud Instance Metadata API

cs-falcon-cspm-list-service-policy-settings


Returns information about current policy settings.

Base Command

cs-falcon-cspm-list-service-policy-settings

Input

Argument Name Description Required
policy_id The policy ID. Optional
cloud_platform The cloud provider. Possible values are: aws, gcp, azure. Default is aws. Optional
service Service type to filter by. Optional
limit The maximum number of entities to list. Default is 50. Optional

Context Output

Path Type Description
CrowdStrike.CSPMPolicySetting.is_remediable Boolean Whether the policy setting is remediable or not.
CrowdStrike.CSPMPolicySetting.created_at String The creation date.
CrowdStrike.CSPMPolicySetting.updated_at String The update date.
CrowdStrike.CSPMPolicySetting.policy_id Integer The policy ID.
CrowdStrike.CSPMPolicySetting.name String The policy setting name.
CrowdStrike.CSPMPolicySetting.policy_type String The policy type.
CrowdStrike.CSPMPolicySetting.cloud_service_subtype String The cloud service subtype.
CrowdStrike.CSPMPolicySetting.cloud_service String The cloud service.
CrowdStrike.CSPMPolicySetting.cloud_service_friendly String The cloud friendly service.
CrowdStrike.CSPMPolicySetting.cloud_asset_type String The cloud asset type.
CrowdStrike.CSPMPolicySetting.cloud_asset_type_id Integer The cloud asset type ID.
CrowdStrike.CSPMPolicySetting.cloud_provider String The cloud provider.
CrowdStrike.CSPMPolicySetting.default_severity String The default severity.
CrowdStrike.CSPMPolicySetting.policy_timestamp Date The policy timestamp.
CrowdStrike.CSPMPolicySetting.policy_settings Array An array that holds policy settings.
CrowdStrike.CSPMPolicySetting.policy_settings.account_id String The account ID correlated to the policy.
CrowdStrike.CSPMPolicySetting.policy_settings.regions Array The regions in which the policy is configured.
CrowdStrike.CSPMPolicySetting.policy_settings.severity String The severity of the policy.
CrowdStrike.CSPMPolicySetting.policy_settings.enabled Boolean Whether the policy settings are enabled or not.
CrowdStrike.CSPMPolicySetting.policy_settings.tag_excluded Boolean Whether the tag is excluded or not.
CrowdStrike.CSPMPolicySetting.cis_benchmark Array An array of CIS benchmark details.
CrowdStrike.CSPMPolicySetting.cis_benchmark.id Integer The CIS benchmark ID.
CrowdStrike.CSPMPolicySetting.cis_benchmark.benchmark_short String The CIS benchmark shortname.
CrowdStrike.CSPMPolicySetting.cis_benchmark.recommendation_number String The CIS benchmark recommendation number.
CrowdStrike.CSPMPolicySetting.pci_benchmark Array An array of PCI benchmark details.
CrowdStrike.CSPMPolicySetting.pci_benchmark.id Integer The PCI benchmark ID.
CrowdStrike.CSPMPolicySetting.pci_benchmark.benchmark_short String The PCI benchmark shortname.
CrowdStrike.CSPMPolicySetting.pci_benchmark.recommendation_number String The PCI benchmark recommendation number.
CrowdStrike.CSPMPolicySetting.nist_benchmark Array An array of NIST benchmark details.
CrowdStrike.CSPMPolicySetting.nist_benchmark.id Integer The NIST benchmark ID.
CrowdStrike.CSPMPolicySetting.nist_benchmark.benchmark_short String The NIST benchmark shortname.
CrowdStrike.CSPMPolicySetting.nist_benchmark.recommendation_number String The NIST benchmark recommendation number.
CrowdStrike.CSPMPolicySetting.attack_types Array The attack types.

Command Example

!cs-falcon-cspm-list-service-policy-settings limit=2

Context Example

{
    "CrowdStrike": {
        "CSPMPolicySetting": [
            {
                "cis_benchmark": [
                    {
                        "benchmark_short": "CIS Controls v8",
                        "id": 722,
                        "recommendation_number": "3.11"
                    }
                ],
                "cloud_asset_type": "filesystem",
                "cloud_asset_type_id": 107,
                "cloud_provider": "aws",
                "cloud_service": "efs",
                "cloud_service_friendly": "EFS",
                "cloud_service_subtype": "N/A",
                "created_at": "2022-08-02T22:17:56.53081Z",
                "default_severity": "informational",
                "fql_policy": "aws_encrypted:['true']+aws_kms_key_id:['']",
                "is_remediable": false,
                "name": "EFS File System is encrypted without CMK",
                "nist_benchmark": [
                    {
                        "benchmark_short": "NIST 800-53 REV 5",
                        "id": 932,
                        "recommendation_number": "SC-8(1)"
                    },
                    {
                        "benchmark_short": "NIST 800-53 REV 5",
                        "id": 989,
                        "recommendation_number": "SC-28(1)"
                    }
                ],
                "pci_benchmark": [
                    {
                        "benchmark_short": "PCI DSS v3.2.1",
                        "id": 41,
                        "recommendation_number": "3.4"
                    }
                ],
                "policy_id": 1,
                "policy_settings": [
                    {
                        "account_id": "537409938058",
                        "enabled": true,
                        "regions": [
                            "af-south-1",
                            "ap-east-1",
                            "ap-northeast-1",
                            "ap-northeast-2",
                            "ap-northeast-3"
                        ],
                        "severity": "informational",
                        "tag_excluded": false
                    }
                ],
                "policy_timestamp": "0001-01-01T00:00:00Z",
                "policy_type": "Configuration",
                "updated_at": "2023-07-19T17:31:45.372476Z"
            },
            {
                "cis_benchmark": [
                    {
                        "benchmark_short": "CIS 1.4.0 AWS Foundations",
                        "id": 143,
                        "recommendation_number": "4.13"
                    }
                ],
                "cloud_asset_type": "awsaccount",
                "cloud_asset_type_id": 116,
                "cloud_provider": "aws",
                "cloud_service": "awsaccount",
                "cloud_service_friendly": "CloudWatch",
                "cloud_service_subtype": "Route Table",
                "created_at": "2023-01-04T19:57:23.897865Z",
                "default_severity": "informational",
                "is_remediable": false,
                "iso_benchmark": [
                    {
                        "benchmark_short": "ISO",
                        "id": 25,
                        "recommendation_number": "5.25"
                    }
                ],
                "name": "CloudWatch log metric filter and alarm missing for changes to route tables",
                "nist_benchmark": [
                    {
                        "benchmark_short": "NIST 800-53 REV 5",
                        "id": 184,
                        "recommendation_number": "AU-6(1)"
                    },
                    {
                        "benchmark_short": "NIST 800-53 REV 5",
                        "id": 183,
                        "recommendation_number": "AU-6"
                    }
                ],
                "pci_benchmark": [
                    {
                        "benchmark_short": "PCI DSS v4.0",
                        "id": 428,
                        "recommendation_number": "10.4.1"
                    }
                ],
                "policy_id": 2,
                "policy_settings": [
                    {
                        "account_id": "537409938058",
                        "enabled": true,
                        "regions": [
                            "af-south-1",
                            "ap-east-1"
                        ],
                        "severity": "informational",
                        "tag_excluded": false
                    }
                ],
                "policy_timestamp": "0001-01-01T00:00:00Z",
                "policy_type": "Configuration",
                "soc2_benchmark": [
                    {
                        "benchmark_short": "TSC 2017 rev 2020",
                        "id": 27,
                        "recommendation_number": "CC7.3"
                    }
                ],
                "updated_at": "2023-09-18T16:11:58.369644Z"
            }
        ]
    }
}

Human Readable Output

CSPM Policy Settings

Policy Id Is Remediable Remediation Summary Name Policy Type Cloud Service Subtype Cloud Service Default Severity
1 false   EFS File System is encrypted without CMK Configuration N/A efs informational
2 false   CloudWatch log metric filter and alarm missing for changes to route tables Configuration Route Table awsaccount informational

cs-falcon-cspm-update-policy_settings


Updates a policy setting. Can be used to override policy severity or to disable a policy entirely.

Base Command

cs-falcon-cspm-update-policy_settings

Input

Argument Name Description Required
policy_id Policy ID to be updated. Required
account_id Cloud account ID to impact. Optional
enabled Flag indicating if this policy is enabled. Possible values are: false, true. Default is true. Optional
regions A comma-separated list of regions where this policy is enforced. Optional
severity Policy severity value. Possible values are: critical, high, medium, informational. Optional
tag_excluded Tag exclusion flag. Possible values are: false, true. Optional

Context Output

There is no context output for this command.

Command Example

!cs-falcon-cspm-update-policy_settings policy_id=1 enabled=true regions="eu-central-1,eu-central-2" severity=high tag_excluded=false

Human Readable Output

Policy 1 was updated successfully

cs-falcon-resolve-identity-detection


Perform actions on identity detection alerts.

Base Command

cs-falcon-resolve-identity-detection

Input

Argument Name Description Required
ids A comma-separated list of IDs of the alerts to update. Required
assign_to_name Assign the specified detections to a user based on their username. Optional
assign_to_user_id Assign the specified detections to a user based on their user ID (Email). Optional
assign_to_uuid Assign the specified detections to a user based on their UUID. Optional
append_comment Appends a new comment to any existing comments for the specified detections. Optional
add_tag Add a tag to the specified detections. Optional
remove_tag Remove a tag from the specified detections. Optional
update_status Update the status of the alert to the specified value. Possible values are: new, in_progress, closed, reopened. Optional
unassign Whether to unassign any assigned users to the specified detections. Possible values are: false, true. Optional
show_in_ui If true, displays the detection in the UI. Possible values are: false, true. Optional

Context Output

There is no context output for this command.

cs-falcon-resolve-mobile-detection


Perform actions on mobile detection alerts.

Base Command

cs-falcon-resolve-mobile-detection

Input

Argument Name Description Required
ids A comma-separated list of IDs of the alerts to update. Required
assign_to_name Assign the specified detections to a user based on their username. Optional
assign_to_user_id Assign the specified detections to a user based on their user ID (Email). Optional
assign_to_uuid Assign the specified detections to a user based on their UUID. Optional
append_comment Appends a new comment to any existing comments for the specified detections. Optional
add_tag Add a tag to the specified detections. Optional
remove_tag Remove a tag from the specified detections. Optional
update_status Update the status of the alert to the specified value. Possible values are: new, in_progress, closed, reopened. Optional
unassign Whether to unassign any assigned users to the specified detections. Possible values are: false, true. Optional
show_in_ui If true, displays the detection in the UI. Possible values are: false, true. Optional

Context Output

There is no context output for this command.

cs-falcon-list-users


List users.

Base Command

cs-falcon-list-users

Input

Argument Name Description Required
id A comma-separated list of IDs (UUIDs) of specific users to list. Optional
filter The filter expression that should be used to limit the results. FQL syntax. Available values: assigned_cids, cid, first_name, last_name, name, uid. Example: “first_name:’John’”. Optional
offset The integer offset to start retrieving records from. Optional
limit The maximum number of records to return. Default is 50. Optional

Context Output

Path Type Description
CrowdStrike.Users.uuid String The user’s UUID.
CrowdStrike.Users.cid String The customer ID.
CrowdStrike.Users.uid String The user’s ID.
CrowdStrike.Users.first_name String The user’s first name.
CrowdStrike.Users.last_name String The user’s last name.
CrowdStrike.Users.last_login_at String The timestamp of the user’s last login.
CrowdStrike.Users.created_at String The timestamp of the user’s creation.

cs-falcon-get-ioarules


Get IOA Rules.

Base Command

cs-falcon-get-ioarules

Input

Argument Name Description Required
rule_ids A comma-separated list of rule IDs to get IOA rules for. Required

Context Output

Path Type Description
CrowdStrike.IOARules.instance_id String The IOA rule’s instance ID.
CrowdStrike.IOARules.customer_id String The customer ID.
CrowdStrike.IOARules.action_label String The IOA rule’s action label.
CrowdStrike.IOARules.comment String The IOA rule’s comment.
CrowdStrike.IOARules.committed_on String The timestamp of the IOA rule’s commitment.
CrowdStrike.IOARules.created_by String The IOA rule’s creator.
CrowdStrike.IOARules.created_on String The timestamp of the IOA rule’s creation.
CrowdStrike.IOARules.deleted Boolean Whether the IOA rule is in a deleted status.
CrowdStrike.IOARules.description String The IOA rule’s description.
CrowdStrike.IOARules.disposition_id String The disposition ID used by the IOA rule.
CrowdStrike.IOARules.enabled Boolean Whether the IOA rule is enabled.
CrowdStrike.IOARules.field_values String The IOA rule’s field values.
CrowdStrike.IOARules.instance_version String The IOA rule’s instance version.
CrowdStrike.IOARules.magic_cookie String The IOA rule’s magic cookie.
CrowdStrike.IOARules.modified_by String The last user who modified the IOA rule.
CrowdStrike.IOARules.modified_on String The timestamp of the IOA rule’s last modification.
CrowdStrike.IOARules.name String The IOA rule name.
CrowdStrike.IOARules.pattern_id String The IOA rule’s pattern ID.
CrowdStrike.IOARules.pattern_severity String The IOA rule’s pattern severity.
CrowdStrike.IOARules.rulegroup_id String The IOA rule’s rule group ID.
CrowdStrike.IOARules.ruletype_id String The IOA rule’s rule type ID.
CrowdStrike.IOARules.ruletype_name String The IOA rule’s rule type name.
CrowdStrike.IOARules.version_ids String The IOA rule’s version ID.

Using Spotlight APIs

Spotlight identifies and gives info about specific vulnerabilities on your hosts using the Falcon sensor.

Required API client scope

To access the Spotlight API, your API client must be assigned the spotlight-vulnerabilities:read scope.

Validating API data

The Falcon sensor continuously monitors hosts for any changes and reports them as they occur.
Depending on the timing of requests, Spotlight APIs can return values that are different from those shown by the Falcon console or an external source.
There are other factors that can cause differences between API responses and other data sources.

API query syntax

If an API query doesn’t exactly match the query used on the Spotlight Vulnerabilities page, the values might differ.

Expired vulnerabilities in Spotlight APIs

If a host is deleted or inactive for 45 days, the status of vulnerabilities on that host changes to expired. Expired vulnerabilities are removed from Spotlight after 3 days.
Expired vulnerabilities are only visible in API responses and are not included in reports or the Falcon console.
An external data source might not use the same data retention policy, which can lead to discrepancies with Spotlight APIs. For more info, see Data retention in Spotlight [https://falcon.crowdstrike.com/login/?next=%2Fdocumentation%2F43%2Ffalcon-spotlight-overview#data-retention-in-spotlight].

The following commands uses the Spotlight API

cs-falcon-spotlight-search-vulnerability


Retrieve vulnerability details according to the selected filter. Each request requires at least one filter parameter. Supported with the CrowdStrike Spotlight license.

Base Command

cs-falcon-spotlight-search-vulnerability

Input

Argument Name Description Required
filter Limit the vulnerabilities returned to specific properties. Each value must be enclosed in single quotes and placed immediately after the colon with no space. For example, ‘filter=status:’open’+cve.id:[‘CVE-2013-3900’,’CVE-2021-1675’]’. Optional
aid A comma-separated list of unique agent identifiers (AIDs) of a sensor. Optional
cve_id A comma-separated list of unique identifiers for a vulnerability as cataloged in the National Vulnerability Database (NVD). This filter supports multiple values and negation. Optional
cve_severity A comma-separated list of severities of the CVE. The possible values are: CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN, or NONE. Optional
tags A comma-separated list of names of a tag assigned to a host. Retrieve tags from Host Tags APIs. Optional
status Status of a vulnerability. This filter supports multiple values and negation. The possible values are: open, closed, reopen, expired. Optional
platform_name Operating system platform. This filter supports negation. The possible values are: Windows, Mac, Linux. Optional
host_group A comma-separated list of unique system-assigned IDs of a host group. Retrieve the host group ID from Host Group APIs. Optional
host_type A comma-separated list of types of hosts a sensor is running on. Optional
last_seen_within Filter for vulnerabilities based on the number of days since a host last connected to CrowdStrike Falcon. Enter a numeric value from 3 to 45 to indicate the number of days to look back. For example, last_seen_within:10. Optional
is_suppressed Indicates if the vulnerability is suppressed by a suppression rule. Possible values are: true, false. Optional
display_remediation_info Display remediation information type of data to be returned for each vulnerability entity. Possible values are: True, False. Default is True. Optional
display_evaluation_logic_info Whether to return logic information type of data for each vulnerability entity. Possible values are: True, False. Default is True. Optional
display_host_info Whether to return host information type of data for each vulnerability entity. Possible values are: True, False. Default is False. Optional
limit The maximum number of items to return (1-2500). Use next_token to retrieve additional pages. Default is 50. Optional
next_token Pagination cursor from a previous run’s CrowdStrike.VulnerabilityNextToken output. Provide this value to fetch the next page of vulnerabilities. Expires in 120 seconds. Optional

Context Output

Path Type Description
CrowdStrike.Vulnerability.id String Unique system-assigned ID of the vulnerability.
CrowdStrike.Vulnerability.cid String Unique system-generated customer identifier (CID) of the account.
CrowdStrike.Vulnerability.aid String Unique agent identifier (AID) of the sensor where the vulnerability was found.
CrowdStrike.Vulnerability.created_timestamp Date UTC date and time of when the vulnerability was created in Spotlight.
CrowdStrike.Vulnerability.updated_timestamp Date UTC date and time of the last update made on the vulnerability.
CrowdStrike.Vulnerability.status String Vulnerability’s current status. Possible values are: open, closed, reopen, or expired.
CrowdStrike.Vulnerability.apps.product_name_version String Name and version of the product associated with the vulnerability.
CrowdStrike.Vulnerability.apps.sub_status String Status of each product associated with the vulnerability. Possible values are: open, closed, or reopen.
CrowdStrike.Vulnerability.apps.remediation.ids String Remediation ID of each product associated with the vulnerability.
CrowdStrike.Vulnerability.host_info.hostname String Name of the machine.
CrowdStrike.Vulnerability.host_info.instance_id String Cloud instance ID of the host.
CrowdStrike.Vulnerability.host_info.service_provider_account_id String Cloud service provider account ID for the host.
CrowdStrike.Vulnerability.host_info.service_provider String Cloud service provider for the host.
CrowdStrike.Vulnerability.host_info.os_build String Operating system build.
CrowdStrike.Vulnerability.host_info.product_type_desc String Type of host a sensor is running on.
CrowdStrike.Vulnerability.host_info.local_ip String Device’s local IP address.
CrowdStrike.Vulnerability.host_info.machine_domain String Active directory domain name.
CrowdStrike.Vulnerability.host_info.os_version String Operating system version.
CrowdStrike.Vulnerability.host_info.ou String Active directory organizational unit name.
CrowdStrike.Vulnerability.host_info.site_name String Active directory site name.
CrowdStrike.Vulnerability.host_info.system_manufacturer String Name of the system manufacturer.
CrowdStrike.Vulnerability.host_info.groups.id String Array of host group IDs that the host is assigned to.
CrowdStrike.Vulnerability.host_info.groups.name String Array of host group names that the host is assigned to.
CrowdStrike.Vulnerability.host_info.tags String Name of a tag assigned to a host.
CrowdStrike.Vulnerability.host_info.platform String Operating system platform. This filter supports negation.
CrowdStrike.Vulnerability.remediation.entities.id String Unique ID of the remediation.
CrowdStrike.Vulnerability.remediation.entities.reference String Relevant reference for the remediation that can be used to get additional details for the remediation.
CrowdStrike.Vulnerability.remediation.entities.title String Short description of the remediation.
CrowdStrike.Vulnerability.remediation.entities.action String Expanded description of the remediation.
CrowdStrike.Vulnerability.remediation.entities.link String Link to the remediation page for the vendor. In certain cases, this field is null.
CrowdStrike.Vulnerability.cve.id String Unique identifier for a vulnerability as cataloged in the National Vulnerability Database (NVD).
CrowdStrike.Vulnerability.cve.base_score Number Base score of the CVE (float value between 1 and 10).
CrowdStrike.Vulnerability.cve.severity String CVSS severity rating of the vulnerability.
CrowdStrike.Vulnerability.cve.exploit_status Number Numeric value of the most severe known exploit.
CrowdStrike.Vulnerability.cve.exprt_rating String ExPRT rating assigned by CrowdStrike’s predictive AI rating system.
CrowdStrike.Vulnerability.cve.description String Brief description of the CVE.
CrowdStrike.Vulnerability.cve.published_date Date UTC timestamp with the date and time of when the vendor published the CVE.
CrowdStrike.Vulnerability.cve.vendor_advisory String Link to the vendor page where the CVE was disclosed.
CrowdStrike.Vulnerability.cve.exploitability_score Number Exploitability score of the CVE (float values from 1-4).
CrowdStrike.Vulnerability.cve.impact_score Number Impact score of the CVE (float values from 1-6).
CrowdStrike.Vulnerability.cve.vector String Textual representation of the metric values used to score the vulnerability.
CrowdStrike.Vulnerability.cve.remediation_level String CVSS remediation level of the vulnerability (U = Unavailable, or O = Official fix).
CrowdStrike.Vulnerability.cve.cisa_info.is_cisa_kev Boolean Whether to filter for vulnerabilities that are in the CISA Known Exploited Vulnerabilities (KEV) catalog.
CrowdStrike.Vulnerability.cve.cisa_info.due_date Date Date before which CISA mandates subject organizations to patch the vulnerability.
CrowdStrike.Vulnerability.cve.spotlight_published_date Date UTC timestamp with the date and time Spotlight enabled coverage for the vulnerability.
CrowdStrike.Vulnerability.cve.actors String Adversaries associated with the vulnerability.
CrowdStrike.Vulnerability.cve.name String The vulnerability name.
CrowdStrike.VulnerabilityNextToken String Pagination cursor returned by CrowdStrike when more results are available. Pass this value back as the `next_token` argument on the next invocation. Absent when no more pages exist. Expires in 120 seconds.

Command Example

cs-falcon-spotlight-search-vulnerability filter=status:['open','closed'] cve_id=CVE-2021-2222 cve_severity='LOW,HIGH' display_host_info=false display_evaluation_logic_info=false display_remediation_info=false limit=1

Context Example

{
    "resources": [
        {
            "id": "id_num",
            "cid": "cid_num",
            "aid": "aid_num",
            "created_timestamp": "2021-07-13T01:12:57Z",
            "updated_timestamp": "2022-10-27T18:32:21Z",
            "status": "open",
            "apps": [
                {
                    "product_name_version": "product",
                    "sub_status": "open",
                    "remediation": {
                        "ids": [
                            "1234"
                        ]
                    },
                    "evaluation_logic": {
                        "id": "1234"
                    }
                }
            ],
            "suppression_info": {
                "is_suppressed": false
            },
            "cve": {
                "id": "CVE-2021-2222",
                "base_score": 5.5,
                "severity": "MEDIUM",
                "exploit_status": 0,
                "exprt_rating": "LOW",
                "remediation_level": "O",
                "cisa_info": {
                    "is_cisa_kev": false
                },
                "spotlight_published_date": "2021-05-10T17:08:00Z",
                "description": "description\n",
                "published_date": "2021-02-25T23:15:00Z",
                "vendor_advisory": [
                    "web address"
                ],
                "exploitability_score": 1.8,
                "impact_score": 3.6,
                "vector": "vendor"
            }
        }
    ],
    "VulnerabilityNextToken": "next_token"
    
}

Human Readable Output

CVE ID CVE Severity CVE Base Score CVE Published Date CVE Impact Score CVE Exploitability Score CVE Vector
CVE-2021-2222 LOW 5.5 2021-05-10T17:08:00Z 3.6 0 vendor

cs-falcon-spotlight-list-host-by-vulnerability


Retrieve vulnerability details for a specific ID and host. Supported with the CrowdStrike Spotlight license.

Base Command

cs-falcon-spotlight-list-host-by-vulnerability

Input

Argument Name Description Required
limit Maximum number of items to return (1-5000). Default is 50. Optional
cve_ids Unique identifier for a vulnerability as cataloged in the National Vulnerability Database (NVD). This filter supports multiple values and negation. Required

Context Output

Path Type Description
CrowdStrike.VulnerabilityHost.id String Unique system-assigned ID of the vulnerability.
CrowdStrike.VulnerabilityHost.cid String Unique system-generated customer identifier (CID) of the account.
CrowdStrike.VulnerabilityHost.aid String Unique agent identifier (AID) of the sensor where the vulnerability was found.
CrowdStrike.VulnerabilityHost.created_timestamp Date UTC date and time of when the vulnerability was created in Spotlight.
CrowdStrike.VulnerabilityHost.updated_timestamp Date UTC date and time of the last update made on the vulnerability.
CrowdStrike.VulnerabilityHost.status String Vulnerability’s current status. Possible values are: open, closed, reopen, or expired.
CrowdStrike.VulnerabilityHost.apps.product_name_version String Name and version of the product associated with the vulnerability.
CrowdStrike.VulnerabilityHost.apps.sub_status String Status of each product associated with the vulnerability. Possible values are: open, closed, or reopen.
CrowdStrike.VulnerabilityHost.apps.remediation.ids String Remediation ID of each product associated with the vulnerability.
CrowdStrike.VulnerabilityHost.apps.evaluation_logic.id String Unique system-assigned ID of the vulnerability evaluation logic.
CrowdStrike.VulnerabilityHost.suppression_info.is_suppressed Boolean Indicates if the vulnerability is suppressed by a suppression rule.
CrowdStrike.VulnerabilityHost.host_info.hostname String Name of the machine.
CrowdStrike.VulnerabilityHost.host_info.local_ip String Device’s local IP address.
CrowdStrike.VulnerabilityHost.host_info.machine_domain String Active directory domain name.
CrowdStrike.VulnerabilityHost.host_info.os_version String Operating system version.
CrowdStrike.VulnerabilityHost.host_info.ou String Active directory organizational unit name.
CrowdStrike.VulnerabilityHost.host_info.site_name String Active directory site name.
CrowdStrike.VulnerabilityHost.host_info.system_manufacturer String Name of the system manufacturer.
CrowdStrike.VulnerabilityHost.host_info.platform String Operating system platform. This filter supports negation.
CrowdStrike.VulnerabilityHost.host_info.instance_id String Cloud instance ID of the host.
CrowdStrike.VulnerabilityHost.host_info.service_provider_account_id String Cloud service provider account ID for the host.
CrowdStrike.VulnerabilityHost.host_info.service_provider String Cloud service provider for the host.
CrowdStrike.VulnerabilityHost.host_info.os_build String Operating system build.
CrowdStrike.VulnerabilityHost.host_info.product_type_desc String Type of host a sensor is running on.
CrowdStrike.VulnerabilityHost.cve.id String Unique identifier for a vulnerability as cataloged in the National Vulnerability Database (NVD).

Command Example

cs-falcon-spotlight-list-host-by-vulnerability cve_ids=CVE-2021-2222

Context Example

{
        {
            "id": "id",
            "cid": "cid",
            "aid": "aid",
            "created_timestamp": "2021-09-16T15:12:42Z",
            "updated_timestamp": "2022-10-19T00:54:43Z",
            "status": "open",
            "apps": [
                {
                    "product_name_version": "prod",
                    "sub_status": "open",
                    "remediation": {
                        "ids": [
                            "id"
                        ]
                    },
                    "evaluation_logic": {
                        "id": "id"
                    }
                }
            ],
            "suppression_info": {
                "is_suppressed": false
            },
            "host_info": {
                "hostname": "host",
                "local_ip": "10.128.0.7",
                "machine_domain": "",
                "os_version": "version",
                "ou": "",
                "site_name": "",
                "system_manufacturer": "manufactor",
                "tags": [],
                "platform": "Windows",
                "instance_id": "instance id",
                "service_provider_account_id": "id",
                "service_provider": "id",
                "os_build": "os build",
                "product_type_desc": "Server"
            },
            "cve": {
                "id": "CVE-20212-2222"
            }
        }
    
}

Human Readable Output

CVE ID Host Info hostname Host Info os Version Host Info Product Type Desc Host Info Local IP Host Info ou Host Info Machine Domain Host Info Site Name CVE Exploitability Score CVE Vector
CVE-20212-2222 host 1 Server ip     site 5.5  

cs-falcon-list-cnapp-alerts


Returns a list of CNAPP alerts. Used for debugging fetch-assets.

Base Command

cs-falcon-list-cnapp-alerts

Input

Argument Name Description Required
filter The filter to use for the query. Optional

Context Output

Path Type Description
CrowdStrike.CnappAlert.containers_impacted_count String The number of containers impacted by the alert.
CrowdStrike.CnappAlert.containers_impacted_ids Array The list of the ids of containers impacted by the alert.
CrowdStrike.CnappAlert.detection_description String The description of the alert.
CrowdStrike.CnappAlert.detection_event_simple_name String The simple name of the alert.
CrowdStrike.CnappAlert.detection_name String The name of the alert.
CrowdStrike.CnappAlert.first_seen_timestamp String The first time the alert was seen.
CrowdStrike.CnappAlert.last_seen_timestamp String The last time the alert was seen.
CrowdStrike.CnappAlert.severity String The severity of the alert.

Command Example

cs-falcon-list-cnapp-alerts

Context Example

{
    "severity": "Critical",
    "first_seen_timestamp": "2025-11-24T11:04:03Z",
    "last_seen_timestamp": "2025-11-26T10:18:35Z",
    "detection_name": "PotentialKernelTampering",
    "detection_event_simple_name": "BPFCommandIssued",
    "detection_description": "some decription.",
    "containers_impacted_count": "1",
    "containers_impacted_ids": ["id1", "id2"],
}

Human Readable Output

severity first_seen_timestamp last_seen_timestamp detection_name detection_event_simple_name detection_description containers_impacted_count containers_impacted_ids
Critical 2025-11-24T11:04:03Z 2025-11-26T10:18:35Z PotentialKernelTampering BPFCommandIssued alert description. 1 test

Troubleshooting

  • In the different fetch query configuration parameters such as “Endpoint Detections fetch query” and “Endpoint Incidents fetch query”, to query for multiple values in the same field use the following format: field:['value1','value2','value3'].
    • For example, filtering by “severity_name” equal to “Medium”, “High”, or “Critical” can be achieved by specifying severity_name:['Medium','High','Critical']
  • When encountering the error “400 - Reason: Bad Request: Invalid element in the request”, ensure the integration instance is configured correctly and verify the command arguments.
    • For example, the error appears when using the ID of a detection prior to the Raptor release (legacy API) in an integration configured to run with Raptor.
  • When experiencing connectivity or authorization errors in Cortex XSOAR 8 or Cortex XSIAM, ensure that the IP addresses associated with the relevant CrowdStrike Falcon region are added to the allow list for the Cortex tenant. For detailed instructions, refer to the Egress section or search for Egress in the product documentation:
  • When encountering HTTP 429 errors from CrowdStrike Falcon, install custom engine on the the Cortex tenant and use it in the configuration of the integration instance:
  • When encountering missing incidents in Cortex XSOAR, make sure that the ‘Fetch Type’ integration parameter includes the type of the missing incidents.
    • Optional types are:
      • Endpoint Incident
      • Endpoint Detection
      • IDP Detection
      • Indicator of Misconfiguration
      • Indicator of Attack
      • Mobile Detection
      • On-Demand Scans Detection
      • OFP Detection
    • Notes:
      • Records from the detection endpoint of the CrowdStrike Falcon UI could be of types: “Endpoint Detection” and “OFP Detection”.
      • For CNAPP Alerts, you will need one of the following subscriptions:
        • Falcon Cloud Security with Containers CNAPP
        • Falcon Cloud Security CNAPP
        • Falcon Cloud Security with Containers Runtime Protection
        • Falcon for Managed Containers Runtime Protection
        • Falcon Cloud Security Proactive
        • Falcon Cloud Security with Containers
        • Falcon for Managed Containers

cs-falcon-list-case-summaries


Lists case summaries.

Base Command

cs-falcon-list-case-summaries

Input

Argument Name Description Required
ids A comma-separated list of case IDs. Optional

Context Output

Path Type Description
CrowdStrike.Case.id String The ID of the case.
CrowdStrike.Case.name String The name of the case.
CrowdStrike.Case.created_timestamp Date The date and time the case was created.
CrowdStrike.Case.status String The status of the case.
CrowdStrike.Case.version String The version of the case.
CrowdStrike.Case.description String The description of the case.
CrowdStrike.Case.severity String The severity of the case.
CrowdStrike.Case.assigned_to String The name of the user assigned to the case.
CrowdStrike.Case.tags String The tags of the case.

cs-falcon-add-case-tag


Adds tags to the specified case.

Base Command

cs-falcon-add-case-tag

Input

Argument Name Description Required
tags A comma-separated list of tags. Required
id The ID of the case the tags will be added to. Required

Context Output

There is no context output for this command.

cs-falcon-get-evidence-for-case


Get evidence for a specific case.

Base Command

cs-falcon-get-evidence-for-case

Input

Argument Name Description Required
id The ID of the case to retrieve evidence for. Required

Context Output

Path Type Description
CrowdStrike.CaseEvidence.alerts Array The alerts associated with the case.
CrowdStrike.CaseEvidence.events Array The events associated with the case.
CrowdStrike.CaseEvidence.leads Array The leads associated with the case.

cs-falcon-resolve-case


Resolves or updates a case.

Base Command

cs-falcon-resolve-case

Input

Argument Name Description Required
id The ID of the case to resolve. Required
status The status to set for the case. Possible values are: new, in_progress, closed, reopened. Optional
assigned_to_uuid A UUID of a user to assign the case to. Optional
description A new description for the case. Optional
remove_user_assignment Whether to remove case assignment from the current user.
If set to true and assigned_to_uuid is not provided, the case becomes unassigned.
If set to false and assigned_to_uuid is provided, the case is reassigned to the specified user UUID.
If set to true and assigned_to_uuid is provided, the case is reassigned to the specified user UUID.
If this field is omitted and assigned_to_uuid is provided, the case is reassigned to the specified user UUID. Possible values are: true, false. Default is false.
Optional
severity The new case severity rating (10-100). Optional
template_id The unique ID of the template to apply to the case. Optional
name The new name for the case. Optional

Context Output

There is no context output for this command.

cs-falcon-delete-case-tag


Deletes a tag from the specified case.

Base Command

cs-falcon-delete-case-tag

Input

Argument Name Description Required
id The ID of the case the tags will be deleted from. Required
tag The tag to delete. Required

Context Output

There is no context output for this command.

cs-falcon-search-ngsiem-events


Search NGSIEM historical events. Requires NGSIEM scope with read and write permissions.

Base Command

cs-falcon-search-ngsiem-events

Input

Argument Name Description Required
repository The repository to run the query against.
. Possible values are: search-all, third-party, falcon_for_it_view, forensics_view, investigate_view. Default is search-all.
Optional
query The CQL query to use for the search. Note: Double quotes (“) and backslashes () in the queryString must be escaped with a backslash to ensure they are properly interpreted. Example: query=”#event_simpleName = "Event_name"””, For more details see: https://library.humio.com/data-analysis/syntax.html. Required
start The start of the search window, based on the event timestamp.
Note: ‘end’ must be later than ‘start’.
If both start/end and ingest_start/ingest_end are provided, the server applies BOTH windows (AND).
Supports relative durations (e.g., “1d”, “2h”, “30m”, “1month”), ISO8601 timestamps (e.g., “2026-01-01T00:00:00Z”; if no time zone is provided, assumes UTC), and epoch timestamps (e.g., 1767225600000).
.
Optional
end The end of the search window, based on the event timestamp.
Note: ‘end’ must be later than ‘start’.
If both start/end and ingest_start/ingest_end are provided, the server applies BOTH windows (AND).
Supports relative durations (e.g., “1d”, “2h”, “30m”, “1month”), ISO8601 timestamps (e.g., “2026-01-01T00:00:00Z”; if no time zone is provided, assumes UTC), and epoch timestamps (e.g., 1767225600000).
.
Optional
around_event_id The ID of the event to search around. Must be provided together with around_timestamp. Optional
around_number_events_before Number of events to return before the target event. Requires around_event_id and around_timestamp. Optional
around_number_events_after Number of events to return after the target event. Requires around_event_id and around_timestamp. Optional
around_timestamp Timestamp for around search. Must be provided together with around_event_id. Optional
ingest_start The start of the search window, based on the event ingesttimestamp.
Note: ‘ingest_end’ must be later than ‘ingest_start’.
If both start/end and ingest_start/ingest_end are provided, the server applies BOTH windows (AND).
Supports relative durations (e.g., “1d”, “2h”, “30m”, “1month”), ISO8601 timestamps (e.g., “2026-01-01T00:00:00Z”; if no time zone is provided, assumes UTC), and epoch timestamps (e.g., 1767225600000).
.
Optional
ingest_end The end of the search window, based on the event ingesttimestamp.
Note: ‘ingest_end’ must be later than ‘ingest_start’.
If both start/end and ingest_start/ingest_end are provided, the server applies BOTH windows (AND).
Supports relative durations (e.g., “1d”, “2h”, “30m”, “1month”), ISO8601 timestamps (e.g., “2026-01-01T00:00:00Z”; if no time zone is provided, assumes UTC), and epoch timestamps (e.g., 1767225600000).
.
Optional
use_ingest_time When true, the server uses ingest_start/ingest_end as the query window. when false (or not set), it uses start/end. If both windows are provided, results are constrained by BOTH (AND). Possible values are: true, false. Optional
limit Maximum number of events to return. Ignored when around_number_events_before or around_number_events_after parameters are specified. Default is 50. Optional
interval_in_seconds Interval between polling attempts in seconds. To prevent search timeouts, set this value within the 60–90 second range. Default is 60. Optional
timeout_in_seconds Timeout for polling in seconds. Default is 600. Optional

Context Output

Path Type Description
CrowdStrike.NGSiemEvent Array The list of all events returned from the search.
CrowdStrike.NGSiemEvent.id String The event ID.
CrowdStrike.NGSiemEvent.timestamp String Event timestamp.

Command Example

!cs-falcon-search-ngsiem-events query="#event_simpleName = \"Event_name\"" repository="search-all" start="1d" end="now" limit=2 interval=60 timeout=120

!cs-falcon-search-ngsiem-events query="*" repository="search-all" start="7d" end="now" around_event_id="aaa" around_number_events_before=1 around_number_events_after=1 around_timestamp=1700000000000 interval=60 timeout=120

!cs-falcon-search-ngsiem-events query="id=aaaaa_anchor_event" repository="search-all" ingest_start=1700000000000 ingest_end=1700000002000 use_ingest_time=true limit=2 interval=60 timeout=120

Context Example

{
  "CrowdStrike": {
    "NGSiemEvent": [
      {
        "@id": "aaaaa_event_1",
        "@timestamp": 1700000000000,
        "@ingesttimestamp": "1700000001000",
        "id": "aaaaa_event_1",
        "event_simpleName": "Event_APIActivityAuditEvent"
      },
      {
        "@id": "aaaaa_event_2",
        "@timestamp": 1700000002000,
        "@ingesttimestamp": "1700000003000",
        "id": "aaaaa_event_2",
        "event_simpleName": "Event_APIActivityAuditEvent"
      }
    ]
  }
}

Workflow Commands

The following commands are available from the CrowdStrike Falcon integration. Note that for these commands, the required API client scope is: Workflows - Read and Write.

cs-falcon-list-workflow-definitions


Lists workflow definitions from CrowdStrike Falcon.

Base Command

cs-falcon-list-workflow-definitions

Input

Argument Name Description Required
filter Filters results using a query in Falcon Query Language (FQL). For more information, see the FQL Syntax Documentation: https://www.falconpy.io/Usage/Falcon-Query-Language.html. For a list of available properties, see Workflow execution FQL filters - https://falcon.crowdstrike.com/documentation/page/z028de1a/fusion-workflow-apis#zec519e3. Optional
definition_id The workflow definition ID to filter by. If ‘filter’ is provided, this argument is ignored. Optional
activity_id The activity ID to filter by. If ‘filter’ is provided, this argument is ignored. Optional
name The workflow name to filter by. If ‘filter’ is provided, this argument is ignored. Optional
description The workflow description to filter by. If ‘filter’ is provided, this argument is ignored. Optional
offset The offset to start retrieving records from. Default is 0. Optional
limit The maximum number of records to return. Max is 500. Default is 50. Optional
sort A comma-separated list of properties to sort by in the format property.direction (for example name.desc, time.asc, created_at.desc). Optional

Context Output

There is no context output for this command.

Command example

!cs-falcon-list-workflow-definitions limit=5

cs-falcon-workflow-execute


Executes an on-demand workflow. Use cs-falcon-list-workflow-definitions to find workflows to run. Note: This command executes on-demand workflows only.

Base Command

cs-falcon-workflow-execute

Input

Argument Name Description Required
definition_id A comma-separated list of workflow definition IDs to execute. Either definition_id or name must be provided. Optional
name The workflow name to execute. Either definition_id or name must be provided. Optional
execution_cid A comma-separated list of CID(s) to execute the workflow on. Optional
key The key used for deduplication of workflow executions. If not set, a new UUID is used. Optional
source_event_url The URL of the source that triggered the workflow execution. Optional
body The JSON body to pass to the workflow execution. Can be an empty object {}. Optional

Context Output

There is no context output for this command.

Command example

!cs-falcon-workflow-execute definition_id="abc123" body="{}"

cs-falcon-list-workflow-executions


Lists workflow executions from CrowdStrike Falcon. Use cs-falcon-workflow-execute to find executions IDs.

Base Command

cs-falcon-list-workflow-executions

Input

Argument Name Description Required
filter Filters results using a query in Falcon Query Language (FQL). For more information, see the FQL Syntax Documentation: https://www.falconpy.io/Usage/Falcon-Query-Language.html. For a list of available properties, see Workflow execution FQL filters - https://falcon.crowdstrike.com/documentation/page/z028de1a/fusion-workflow-apis#zec519e3. Optional
definition_id The workflow definition ID to filter by. If ‘filter’ is provided, this argument is ignored. Optional
definition_name The workflow definition name to filter by. If ‘filter’ is provided, this argument is ignored. Optional
execution_id The execution ID to filter by. If ‘filter’ is provided, this argument is ignored. Optional
offset The offset to start retrieving records from. Default is 0. Optional
limit The maximum number of records to return. Max is 500. Default is 50. Optional
sort A comma-separated list of properties to sort by in the format property.direction (for example name.desc, time.asc, created_at.desc). Optional

Context Output

There is no context output for this command.

Command example

!cs-falcon-list-workflow-executions limit=10

cs-falcon-list-workflow-execution-results


Gets detailed results for specific workflow executions. Use cs-falcon-list-workflow-executions to find execution IDs.

Base Command

cs-falcon-list-workflow-execution-results

Input

Argument Name Description Required
ids A comma-separated list of workflow execution IDs to retrieve results for. Required

Context Output

There is no context output for this command.

Command example

!cs-falcon-list-workflow-execution-results ids="exec_id_1,exec_id_2"

cs-falcon-workflow-execution-action


Performs an action (cancel or resume) on one or more workflow executions. Use cs-falcon-list-workflow-execution-results to find execution activity status.

Base Command

cs-falcon-workflow-execution-action

Input

Argument Name Description Required
ids A comma-separated list of workflow execution IDs to perform the action on. Get the workflow execution ID using cs-falcon-list-workflow-executions. Required
action_name The action to perform on the workflow executions. Possible values are: cancel, resume. Required

Context Output

There is no context output for this command.

Command example

!cs-falcon-workflow-execution-action ids="exec_id_1" action_name="cancel"

<~PLATFORM>

License Requirements

The following configuration parameters require the Cortex XSIAM license:

  • Fetch events

The following configuration parameters require Cortex XSIAM with the Exposure Management add-on:

  • Fetch assets and vulnerabilities

The following configuration parameters require one of these licenses: Cortex XSIAM or Agentix:

  • Fetch incidents

</~PLATFORM>

Configuration parameters

  • legacy_version — Use legacy API
  • url — Server URL (e.g., https://api.crowdstrike.com) (required)
  • credentials — Client ID
  • client_id — Client ID
  • secret — Secret
  • Reliability — Source Reliability
  • isFetch — Fetch incidents
  • fetch_incidents_or_detections — Incident Fetch types
  • incidentType — Incident type
  • fetch_time — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)
  • incidents_per_fetch — Max incidents per fetch
  • incidentFetchInterval — Incidents Fetch Interval
  • look_back — Advanced: Time in minutes to look back when fetching incidents and detections
  • fetch_query — Endpoint Detections filter query
  • idp_detections_fetch_query — IDP Detections filter query
  • mobile_detections_fetch_query — Mobile Detections filter query
  • iom_fetch_query — IOM filter query
  • ioa_fetch_query — IOA filter query
  • on_demand_fetch_query — Detections from On-Demand Scans filter query
  • ofp_detection_fetch_query — OFP Detections filter query
  • third_party_detection_fetch_query — Third Party Detection fetch query
  • ngsiem_detection_fetch_query — NGSIEM Detection fetch query
  • automated_leads_fetch_query — NGSIEM automated leads fetch query
  • ngsiem_cases_fetch_query — NGSIEM cases fetch query
  • ngsiem_incidents_fetch_query — NGSIEM incidents fetch query
  • recon_fetch_query — Recon filter query
  • mirror_direction — Mirroring Direction
  • close_incident — Close Mirrored XSOAR Incident
  • close_in_cs_falcon — Close Mirrored CrowdStrike Falcon Incident or Detection
  • reopen_statuses — Reopen Statuses
  • isFetchEvents — Fetch events
  • fetch_events_or_detections — Event Fetch types
  • eventFetchInterval — Events Fetch Interval
  • look_back_xsiam — Advanced: Time in minutes to look back when fetching events and detections
  • isFetchAssets — Fetch assets and vulnerabilities
  • fetch_assets_type — Fetch Asset types
  • assetsFetchInterval — Assets Fetch Interval
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (98)

  • cs-device-ran-on Deprecated

    Returns a list of device IDs an indicator ran on.

  • cs-falcon-add-case-tag

    Adds tags to the specified case.

  • cs-falcon-add-host-group-members

    Add host group members.

  • cs-falcon-apply-quarantine-file-action

    Apply action to quarantined files by file IDs or filter.

  • cs-falcon-batch-upload-custom-ioc

    Uploads a batch of indicators.

  • cs-falcon-contain-host

    Contains containment for a specified host. When contained, a host can only communicate with the CrowdStrike cloud and any IPs specified in your containment policy.

  • cs-falcon-create-host-group

    Create a host group.

  • cs-falcon-create-ioa-exclusion

    Create an IOA exclusion.

  • cs-falcon-create-ml-exclusion

    Create an ML exclusion.

  • cs-falcon-cspm-list-policy-details

    Given a CSV list of policy IDs, returns detailed policy information.

  • cs-falcon-cspm-list-service-policy-settings

    Returns information about current policy settings.

  • cs-falcon-cspm-update-policy_settings

    Updates a policy setting. Can be used to override policy severity or to disable a policy entirely.

  • cs-falcon-delete-case-tag

    Deletes a tag from the specified case.

  • cs-falcon-delete-custom-ioc

    Deletes a monitored indicator.

  • cs-falcon-delete-file

    Deletes a file based on the provided ID or name. Can delete only one file at a time.

  • cs-falcon-delete-host-groups

    Deletes the requested host groups.

  • cs-falcon-delete-ioa-exclusion

    Delete the IOA exclusions by ID.

  • cs-falcon-delete-ioc Deprecated

    Deprecated. Use the cs-falcon-delete-custom-ioc command instead.

  • cs-falcon-delete-ml-exclusion

    Delete the ML exclusions by ID.

  • cs-falcon-delete-script

    Deletes a custom-script based on the provided ID. Can delete only one script at a time.

  • cs-falcon-device-count-ioc

    The number of hosts that observed the provided IOC.

  • cs-falcon-device-ran-on

    Returns a list of device IDs an indicator ran on.

  • cs-falcon-get-behavior Deprecated

    Searches for and fetches the behavior that matches the query. Deprecated - No replacement available.

  • cs-falcon-get-custom-ioc

    Gets the full definition of one or more indicators that you are watching.

  • cs-falcon-get-evidence-for-case

    Get evidence for a specific case.

  • cs-falcon-get-extracted-file

    Gets the RTR extracted file contents for the specified session and SHA256 hash.

  • cs-falcon-get-file

    Returns files based on the provided IDs. These files are used for the RTR 'put' command.

  • cs-falcon-get-ioarules

    Get IOA Rules.

  • cs-falcon-get-ioc Deprecated

    Deprecated. Use the cs-falcon-get-custom-ioc command instead.

  • cs-falcon-get-script

    Returns custom scripts based on the provided ID. Used for the RTR 'runscript' command.

  • cs-falcon-lift-host-containment

    Lifts containment on the host, which returns its network communications to normal. When lift_filesystem_containment_all is set to true, lifts filesystem containment instead.

  • cs-falcon-list-case-summaries

    Lists case summaries.

  • cs-falcon-list-cnapp-alerts

    Returns a list of CNAPP alerts. Used for debugging fetch-assets.

  • cs-falcon-list-detection-summaries

    Lists detection summaries.

  • cs-falcon-list-files

    Returns a list of put-file IDs that are available for the user in the 'put' command. Due to an API limitation, the maximum number of files returned is 100.

  • cs-falcon-list-host-files

    Gets a list of files for the specified RTR session on a host.

  • cs-falcon-list-host-group-members

    Gets the list of host group members.

  • cs-falcon-list-host-groups

    List the available host groups.

  • cs-falcon-list-identity-entities

    List identity entities.

  • cs-falcon-list-quarantined-file

    Get quarantine file metadata by specified IDs or filter.

  • cs-falcon-list-scripts

    Returns a list of custom script IDs that are available for the user in the 'runscript' command.

  • cs-falcon-list-users

    List users.

  • cs-falcon-list-workflow-definitions

    Lists workflow definitions from CrowdStrike Falcon.

  • cs-falcon-list-workflow-execution-results

    Gets detailed results for specific workflow executions. Use cs-falcon-list-workflow-executions to find execution IDs.

  • cs-falcon-list-workflow-executions

    Lists workflow executions from CrowdStrike Falcon. Use cs-falcon-workflow-execute to find executions IDs.

  • cs-falcon-ods-create-scan

    Create an ODS scan and wait for the results.

  • cs-falcon-ods-create-scheduled-scan

    Create an ODS scheduled scan.

  • cs-falcon-ods-delete-scheduled-scan

    Delete ODS scheduled scans.

  • cs-falcon-ods-query-malicious-files

    Retrieve ODS malicious file details.

  • cs-falcon-ods-query-scan

    Retrieve ODS scan details.

  • cs-falcon-ods-query-scan-host

    Retrieve ODS scan host details.

  • cs-falcon-ods-query-scheduled-scan

    Retrieve ODS scheduled scan details.

  • cs-falcon-process-details

    Retrieves the details of a process, according to the process ID that is running or that previously ran.

  • cs-falcon-processes-ran-on

    Get processes associated with a given IOC.

  • cs-falcon-refresh-session

    Refresh a session timeout on a single host.

  • cs-falcon-remove-host-group-members

    Remove host group members.

  • cs-falcon-resolve-case

    Resolves or updates a case.

  • cs-falcon-resolve-detection

    Resolves and updates a detection using the provided arguments. At least one optional argument must be passed, otherwise no change will take place. Note that IDP detections are not supported.

  • cs-falcon-resolve-identity-detection

    Perform actions on identity detection alerts.

  • cs-falcon-resolve-mobile-detection

    Perform actions on mobile detection alerts.

  • cs-falcon-rtr-kill-process

    Execute an active responder kill command on a single host.

  • cs-falcon-rtr-list-network-stats

    Executes an RTR active-responder netstat command to get a list of network status and protocol statistics across the given host.

  • cs-falcon-rtr-list-processes

    Executes an RTR active-responder ps command to get a list of active processes across the given host.

  • cs-falcon-rtr-list-scheduled-tasks

    Executes an RTR active-responder netstat command to get a list of scheduled tasks across the given host. This command is valid only for Windows hosts.

  • cs-falcon-rtr-read-registry

    Executes an RTR active-responder read registry keys command across the given hosts. This command is valid only for Windows hosts.

  • cs-falcon-rtr-remove-file

    Batch executes an RTR active-responder remove file across the hosts mapped to the given batch ID.

  • cs-falcon-rtr-retrieve-file

    Gets the RTR extracted file contents for the specified file path.

  • cs-falcon-run-command

    Sends commands to hosts.

  • cs-falcon-run-get-command

    Batch executes 'get' command across hosts to retrieve files.

  • cs-falcon-run-script

    Runs a script on the agent host.

  • cs-falcon-search-custom-iocs

    Returns a list of your uploaded IOCs that match the search criteria.

  • cs-falcon-search-detection

    Search for details of specific detections, either using a filter query, or by providing the IDs of the detections.

  • cs-falcon-search-device

    Searches for a device that matches the query.

  • cs-falcon-search-ioa-exclusion

    Get a list of IOA exclusions by specifying their IDs or a filter.

  • cs-falcon-search-iocs Deprecated

    Deprecated. Use the cs-falcon-search-custom-iocs command instead.

  • cs-falcon-search-ml-exclusion

    Get a list of ML exclusions by specifying their IDs, value, or a specific filter.

  • cs-falcon-search-ngsiem-events

    Search NGSIEM historical events. Requires NGSIEM scope with read and write permissions.

  • cs-falcon-spotlight-list-host-by-vulnerability

    Retrieve vulnerability details for a specific ID and host. Supported with the CrowdStrike Spotlight license.

  • cs-falcon-spotlight-search-vulnerability

    Retrieve vulnerability details according to the selected filter. Each request requires at least one filter parameter. Supported with the CrowdStrike Spotlight license.

  • cs-falcon-status-command

    Gets the status of a command executed on a host.

  • cs-falcon-status-get-command

    Retrieves the status of the specified batch 'get' command.

  • cs-falcon-update-custom-ioc

    Updates an indicator for CrowdStrike to monitor.

  • cs-falcon-update-host-group

    Updates a host group.

  • cs-falcon-update-ioa-exclusion

    Updates an IOA exclusion. At least one argument is required in addition to the id argument.

  • cs-falcon-update-ioc Deprecated

    Deprecated. Use the cs-falcon-update-custom-ioc command instead.

  • cs-falcon-update-ml-exclusion

    Updates an ML exclusion. At least one argument is required in addition to the id argument.

  • cs-falcon-upload-custom-ioc

    Uploads an indicator for CrowdStrike to monitor.

  • cs-falcon-upload-file

    Uploads a file to the CrowdStrike cloud. (Can be used for the RTR 'put' command).

  • cs-falcon-upload-ioc Deprecated

    Deprecated. Use the cs-falcon-upload-custom-ioc command instead.

  • cs-falcon-upload-script

    Uploads a script to Falcon CrowdStrike.

  • cs-falcon-workflow-execute

    Executes an on-demand workflow. Use cs-falcon-list-workflow-definitions to find workflows to run. Note: This command executes on-demand workflows only.

  • cs-falcon-workflow-execution-action

    Performs an action (cancel or resume) on one or more workflow executions. Use cs-falcon-list-workflow-execution-results to find execution activity status.

  • cve

    Retrieve vulnerability details according to the selected filter. Each request requires at least one filter parameter. Supported with the CrowdStrike Spotlight license.

  • endpoint

    Returns information about an endpoint. Does not support regex.

  • get-mapping-fields

    Returns the list of fields to map in outgoing mirroring. This command is only used for debugging purposes. Note that this command is supported in Cortex XSOAR only.

  • get-modified-remote-data

    Gets the list of incidents and detections that were modified since the last update time. This method is used for debugging purposes. The get-modified-remote-data command is used as part of the Mirroring feature that was introduced in Cortex XSOAR version 6.1. Note that this command is supported in Cortex XSOAR only.

  • get-remote-data

    Gets remote data from a remote incident or detection. This method does not update the current incident or detection, and should be used for debugging purposes only. Note that this command is supported in Cortex XSOAR only.

  • update-remote-system

    Updates the remote incident or detection with local incident or detection changes. This method is only used for debugging purposes and will not update the current incident or detection. Note that this command is supported in Cortex XSOAR only.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *
from ContentClientApiModule import *

""" IMPORTS """
import base64
import email
import hashlib
import json
from collections.abc import Callable
from enum import Enum, IntEnum
from threading import Timer
from typing import Any
import urllib.parse

import requests
import asyncio
import aiohttp
import gzip

# Disable insecure warnings
import urllib3
from gql import Client, gql
from gql.transport.requests import RequestsHTTPTransport

urllib3.disable_warnings()
""" GLOBALS/PARAMS """
VENDOR = "CrowdStrike"
PRODUCT = "Falcon_Event"
CNAPP_PRODUCT = "Falcon_CNAPP"
SPOTLIGHT_VULN_PRODUCT = "Falcon_Spotlight_Vulnerabilities"
SPOTLIGHT_ASSETS_PRODUCT = "Falcon_Spotlight_Assets"
INTEGRATION_NAME = "CrowdStrike Falcon"

# Incidents Type names - use for debugging and context save.
IDP_DETECTION = "IDP detection"
MOBILE_DETECTION = "MOBILE detection"
ON_DEMAND_SCANS_DETECTION = "On-Demand Scans detection"
OFP_DETECTION = "OFP detection"
NGSIEM_DETECTION = "ngsiem_detection"
NGSIEM_INCIDENT = "ngsiem_incident"
NGSIEM_AUTOMATED_LEAD = "ngsiem_automated_lead"
NGSIEM_CASE = "ngsiem_case"
THIRD_PARTY_DETECTION = "thirdparty_detection"
IOA_DETECTION = "ioa_detection"
RECON_NOTIFICATION = "Recon notifications"

# Fetch type names as they appear in the .yml instance configurations
DETECTION_FETCH_TYPES = ["Detections", "Endpoint Detection"]
IDP_DETECTION_FETCH_TYPE = "IDP Detection"
MOBILE_DETECTION_FETCH_TYPE = "Mobile Detection"
ON_DEMAND_SCANS_DETECTION_TYPE = "On-Demand Scans Detection"
OFP_DETECTION_TYPE = "OFP Detection"
IOM_FETCH_TYPE = "Indicator of Misconfiguration"
IOA_FETCH_TYPE = "Indicator of Attack"
NGSIEM_DETECTION_FETCH_TYPE = "NGSIEM Detection"
NGSIEM_INCIDENT_FETCH_TYPE = "NGSIEM Incident (XDR Alert)"
NGSIEM_AUTOMATED_LEADS_FETCH_TYPE = "NGSIEM Automated Lead"
NGSIEM_CASES_FETCH_TYPE = "NGSIEM Case"
THIRD_PARTY_DETECTION_FETCH_TYPE = "Third Party Detection"
RECON_FETCH_TYPE = "Recon notifications"

ENDPOINT_DETECTION = "detection"

SUPPORTED_DETECTIONS_TYPES = [
    IDP_DETECTION_FETCH_TYPE,
    ON_DEMAND_SCANS_DETECTION_TYPE,
    OFP_DETECTION_TYPE,
    NGSIEM_DETECTION_FETCH_TYPE,
    NGSIEM_INCIDENT_FETCH_TYPE,
    NGSIEM_AUTOMATED_LEADS_FETCH_TYPE,
    NGSIEM_CASES_FETCH_TYPE,
    THIRD_PARTY_DETECTION_FETCH_TYPE,
]

PARAMS = demisto.params()
PROXY = PARAMS.get("proxy", False)
CLIENT_ID = PARAMS.get("credentials", {}).get("identifier") or PARAMS.get("client_id")
SECRET = PARAMS.get("credentials", {}).get("password") or PARAMS.get("secret")

# Remove trailing slash to prevent wrong URL path to service
SERVER = PARAMS["url"].removesuffix("/")

# Should we use SSL
USE_SSL = not PARAMS.get("insecure", False)

# How much time before the first fetch to retrieve incidents
FETCH_TIME = "now" if demisto.command() == "fetch-events" else PARAMS.get("fetch_time", "3 days")

MAX_FETCH_SIZE = 10000
MAX_FETCH_DETECTION_PER_API_CALL = 10000  # fetch limit for get ids call - detections
MAX_FETCH_DETECTION_PER_API_CALL_ENTITY = 1000  # fetch limit for get entities call - detections
MAX_FETCH_SPOTLIGHT_ASSETS = 5000
# Below the 5000 server-side maximum to keep payloads under XSOAR's auto-file threshold.
MAX_SPOTLIGHT_VULNERABILITY_PAGE_SIZE = 2500
MAX_PENDING_TASKS_PER_SEVERITY = 5  # Backpressure: max concurrent pending XSIAM send tasks per severity stream
SPOTLIGHT_LOOKBACK_DAYS = 100  # Only fetch vulnerabilities updated within this many days (bounds dataset size)
RECON_API_LIMIT = 100
MAX_FETCH_RECON = 100

# Spotlight vulnerability severity levels for parallel fetching
SPOTLIGHT_SEVERITIES = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "NONE", "UNKNOWN"]

BYTE_CREDS = f"{CLIENT_ID}:{SECRET}".encode()

# Headers to be sent in requests
HEADERS = {
    "Content-Type": "application/json",
    "Accept": "application/json",
    "Authorization": f"Basic {base64.b64encode(BYTE_CREDS).decode()}",
}

# Note: True life time of token is actually 30 mins
TOKEN_LIFE_TIME = 28
INCIDENTS_PER_FETCH = int(PARAMS.get("incidents_per_fetch", 15))
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
DETECTION_DATE_FORMAT = IOM_DATE_FORMAT = RECON_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
DEFAULT_TIMEOUT = 30

DEFAULT_INTERVAL = 60
DEFAULT_TIMEOUT_NGSIEM_SEARCH = 600

DEFAULT_TIMEOUT_ON_GENERIC_HTTP_REQUEST = 60
TOTAL_RETRIES_ON_ENRICHMENT = 0
TIMEOUT_ON_ENRICHMENT = 15

""" KEY DICTIONARY """

LEGACY_DETECTIONS_BASE_KEY_MAP = {
    "device.hostname": "System",
    "device.cid": "CustomerID",
    "hostinfo.domain": "MachineDomain",
    "detection_id": "ID",
    "created_timestamp": "ProcessStartTime",
    "max_severity": "MaxSeverity",
    "show_in_ui": "ShowInUi",
    "status": "Status",
    "first_behavior": "FirstBehavior",
    "last_behavior": "LastBehavior",
    "max_confidence": "MaxConfidence",
}

DETECTIONS_BASE_KEY_MAP = {
    "device.hostname": "System",
    "device.cid": "CustomerID",
    "device.hostinfo.domain": "MachineDomain",
    "composite_id": "ID",
    "created_timestamp": "ProcessStartTime",
    "severity": "MaxSeverity",
    "show_in_ui": "ShowInUi",
    "status": "Status",
    "confidence": "MaxConfidence",
}

DETECTIONS_BEHAVIORS_KEY_MAP = {
    "filename": "FileName",
    "scenario": "Scenario",
    "md5": "MD5",
    "sha256": "SHA256",
    "ioc_type": "IOCType",
    "ioc_value": "IOCValue",
    "cmdline": "CommandLine",
    "user_name": "UserName",
    "behavior_id": "ID",
    "alleged_filetype": "AllegedFiletype",
    "confidence": "Confidence",
    "description": "Description",
    "display_name": "DisplayName",
    "filepath": "Filepath",
    "parent_md5": "ParentMD5",
    "parent_sha256": "ParentSHA256",
    "pattern_disposition": "PatternDisposition",
    "pattern_disposition_details": "PatternDispositionDetails",
    "tactic": "Tactic",
    "tactic_id": "TacticID",
    "technique": "Technique",
    "technique_id": "TechniqueId",
}

IOC_KEY_MAP = {
    "type": "Type",
    "value": "Value",
    "policy": "Policy",
    "source": "Source",
    "share_level": "ShareLevel",
    "expiration": "Expiration",
    "description": "Description",
    "created_on": "CreatedTime",
    "created_by": "CreatedBy",
    "modified_on": "ModifiedTime",
    "modified_by": "ModifiedBy",
    "id": "ID",
    "platforms": "Platforms",
    "action": "Action",
    "severity": "Severity",
    "tags": "Tags",
    "mobile_action": "MobileAction",
}

IOC_HEADERS = [
    "ID",
    "Action",
    "MobileAction",
    "Severity",
    "Type",
    "Value",
    "Expiration",
    "CreatedBy",
    "CreatedTime",
    "Description",
    "ModifiedBy",
    "ModifiedTime",
    "Platforms",
    "Policy",
    "ShareLevel",
    "Source",
    "Tags",
]

IOC_DEVICE_COUNT_MAP = {"id": "ID", "type": "Type", "value": "Value", "device_count": "DeviceCount"}

SEARCH_DEVICE_KEY_MAP = {
    "device_id": "ID",
    "external_ip": "ExternalIP",
    "local_ip": "LocalIP",
    "hostname": "Hostname",
    "os_version": "OS",
    "mac_address": "MacAddress",
    "first_seen": "FirstSeen",
    "last_seen": "LastSeen",
    "status": "Status",
}

SEARCH_DEVICE_VERBOSE_KEY_MAP = {
    "agent_load_flags": "AgentLoadFlags",
    "agent_local_time": "AgentLocalTime",
    "agent_version": "AgentVersion",
    "bios_manufacturer": "BiosManufacturer",
    "bios_version": "BiosVersion",
    "cid": "CID",
    "config_id_base": "ConfigIdBase",
    "config_id_build": "ConfigIdBuild",
    "config_id_platform": "ConfigIdPlatform",
    "connection_ip": "ConnectionIp",
    "connection_mac_address": "ConnectionMacAddress",
    "cpu_signature": "CpuSignature",
    "default_gateway_ip": "DefaultGatewayIP",
    "device_id": "ID",
    "device_policies": "DevicePolicies",
    "external_ip": "ExternalIP",
    "first_seen": "FirstSeen",
    "group_hash": "GroupHash",
    "group_name": "GroupName",
    "group_names": "GroupNames",
    "groups": "Groups",
    "hostname": "Hostname",
    "kernel_version": "KernelVersion",
    "last_seen": "LastSeen",
    "local_ip": "LocalIP",
    "mac_address": "MacAddress",
    "major_version": "MajorVersion",
    "meta": "Meta",
    "minor_version": "MinorVersion",
    "modified_timestamp": "ModifiedTimestamp",
    "os_version": "OS",
    "platform_id": "PlatformID",
    "platform_name": "PlatformName",
    "policies": "Policies",
    "product_type_desc": "ProductTypeDesc",
    "provision_status": "ProvisionStatus",
    "reduced_functionality_mode": "ReducedFunctionalityMode",
    "serial_number": "SerialNumber",
    "status": "Status",
    "system_manufacturer": "SystemManufacturer",
    "system_product_name": "SystemProductName",
    "tags": "Tags",
}

ENDPOINT_KEY_MAP = {
    "device_id": "ID",
    "local_ip": "IPAddress",
    "os_version": "OS",
    "hostname": "Hostname",
    "status": "Status",
}

""" SPLIT KEY DICTIONARY """

"""
    Pattern:
    {
        'Path': 'Path to item',
        'NewKey': 'Value of output key',
        'Delim': 'Delimiter char',
        'Index': Split Array Index
    }
"""

DETECTIONS_BEHAVIORS_SPLIT_KEY_MAP = [
    {"Path": "parent_details.process_graph_id", "NewKey": "SensorID", "Delim": ":", "Index": 1},
    {"Path": "parent_details.process_graph_id", "NewKey": "ParentProcessID", "Delim": ":", "Index": 2},
    {"Path": "triggering_process_graph_id", "NewKey": "ProcessID", "Delim": ":", "Index": 2},
]

HOST_GROUP_HEADERS = [
    "id",
    "name",
    "group_type",
    "description",
    "assignment_rule",
    "created_by",
    "created_timestamp",
    "modified_by",
    "modified_timestamp",
]

""" MIRRORING DICTIONARIES & PARAMS """

STATUS_LIST_FOR_MULTIPLE_DETECTION_TYPES = {"new", "in_progress", "closed", "reopened"}

CS_FALCON_DETECTION_OUTGOING_ARGS = {
    "status": f'Updated detection status, one of {"/".join(STATUS_LIST_FOR_MULTIPLE_DETECTION_TYPES)}'
}

LEGACY_CS_FALCON_DETECTION_INCOMING_ARGS = [
    "status",
    "severity",
    "behaviors.tactic",
    "behaviors.scenario",
    "behaviors.objective",
    "behaviors.technique",
    "device.hostname",
    "detection_id",
    "behaviors.display_name",
]
CS_FALCON_DETECTION_INCOMING_ARGS = [
    "status",
    "severity",
    "tactic",
    "scenario",
    "objective",
    "technique",
    "device.hostname",
    "composite_id",
    "display_name",
    "tags",
    "comments",
    "assigned_to_uid",
]
CS_FALCON_DETECTION_INCOMING_ARGS_IDP = ["status", "id", "tags", "comments", "assigned_to_uid"]
NGSIEM_MIRRORING_FIELDS = ["status", "state"]
CS_FALCON_RECON_INCOMING_ARGS = ["notification.status"]

MIRROR_DIRECTION_DICT = {"None": None, "Incoming": "In", "Outgoing": "Out", "Incoming And Outgoing": "Both"}

HOST_STATUS_DICT = {"online": "Online", "offline": "Offline", "unknown": "Unknown"}

NO_QUARANTINED_FILES_MSG = "The arguments/filters you provided did not match any files."

QUARANTINE_FILES_OUTPUT_HEADERS = [
    "id",
    "aid",
    "cid",
    "sha256",
    "paths",
    "state",
    "detect_ids",
    "alert_ids",
    "hostname",
    "username",
    "date_updated",
    "date_created",
    "extracted",
    "release_path_for_removable_media",
    "primary_module",
    "is_on_removable_disk",
    "sandbox_report_id",
    "sandbox_report_state",
]

CPU_UTILITY_INT_TO_STR_KEY_MAP = {
    1: "Lowest",
    2: "Low",
    3: "Medium",
    4: "High",
    5: "Highest",
}
CPU_UTILITY_STR_TO_INT_KEY_MAP = {value: key for key, value in CPU_UTILITY_INT_TO_STR_KEY_MAP.items()}

SCHEDULE_INTERVAL_STR_TO_INT = {
    "never": 0,
    "daily": 1,
    "weekly": 7,
    "every other week": 14,
    "every four weeks": 28,
    "monthly": 30,
}

TOTAL_FETCH_TYPE_XSOAR = 14  # Matches the total number of fetch types for XSOAR in the LastRunIndex class
TOTAL_FETCH_TYPE_XSIAM = 6  # Matches the total number of fetch types for XSIAM in the LastRunIndex class


class LastRunIndex(IntEnum):
    """
    The last_run object is defined as a list of dictionaries.
    Each index in the list represents a different fetch type.
    The last_run object is supported only in the following scenario:
    The fetch_incidents command runs on XSOAR (and not on XSIAM),
    while the fetch_events command runs on XSIAM (and not on XSOAR).
    """

    # Common fetch types for fetch-incidents and fetch-events.
    DETECTIONS = 0
    _RESERVED_INCIDENTS = 1  # Formerly Endpoint Incidents. Do not reuse.
    IDP_DETECTIONS = 2
    MOBILE_DETECTIONS = 3
    ON_DEMAND_DETECTIONS = 4
    OFP_DETECTION = 5

    # Fetch types only for fetch-incidents
    IOM = 6
    IOA = 7
    THIRD_PARTY_DETECTIONS = 8
    NGSIEM_DETECTIONS = 9
    NGSIEM_INCIDENTS = 10
    NGSIEM_AUTOMATED_LEADS = 11
    NGSIEM_CASES = 12
    RECON_NOTIFICATIONS = 13


class IncidentType(Enum):
    LEGACY_ENDPOINT_DETECTION = "ldt"
    ENDPOINT_OR_IDP_OR_MOBILE_OR_OFP_DETECTION = ":ind:"  # OFP was joined here since it has ':ind:' too in its id
    IOM_CONFIGURATIONS = "iom_configurations"
    IOA_TYPE_TAG = "cloud-ioa"
    ON_DEMAND = "ods"
    OFP = "ofp"
    THIRD_PARTY = ":thirdparty:"
    RECON = ":recon:"
    NGSIEM_DETECTION = ":ngsiem:"
    NGSIEM_AUTOMATED_LEAD = ":automated-lead:"
    NGSIEM_CASE = ":case"


MIRROR_DIRECTION = MIRROR_DIRECTION_DICT.get(demisto.params().get("mirror_direction"))
INTEGRATION_INSTANCE = demisto.integrationInstance()

""" HELPER FUNCTIONS """


def is_detection_fetch_type_selected(selected_types: list):
    return any(detection_type in selected_types for detection_type in DETECTION_FETCH_TYPES)


def disable_for_xsiam():
    """Validates if command is not running on an unsupported Cortex platform.

    Raises:
        DemistoException: If command is being run on XSIAM.
    """
    if is_xsiam() or is_platform():
        raise DemistoException("This command is not supported on this Cortex platform.")


def truncate_long_time_str(detections: List[Dict], time_key: str) -> List[Dict]:
    """
    Truncates the time string in each detection to a maximum of 26 characters, to prevent an error when parsing the time.

    Args:
        detections (List[Dict]): The list of detections, each represented as a dictionary.
        time_key (str): The key in each detection dictionary that corresponds to the time string.

    Returns:
        List[Dict]: The list of detections with the time string truncated.
    """
    for event in detections:
        long_time_str = event.get(time_key)
        if long_time_str and len(long_time_str) > 26:
            event[time_key] = long_time_str[:26] + "Z"
    return detections


def modify_detection_outputs(detection):
    """
    Modifies the detection outputs in the newer version (raptor release) to be in the same format as the legacy version.
    Args:
        detection: The detection to modify.
    Returns:
        The nested modified detection.
    """
    behavior = {key: detection.pop(key, None) for key in DETECTIONS_BEHAVIORS_KEY_MAP}
    behavior.update(
        {
            "parent_details": detection.pop("parent_details", None),
            "triggering_process_graph_id": detection.pop("triggering_process_graph_id", None),
        }
    )
    detection["behaviors"] = [behavior]
    return detection


def error_handler(res):
    reason = res.reason
    demisto.debug(f"CrowdStrike Falcon error handler {res.status_code=} {reason=}")
    try:
        res_json = res.json()
    except ValueError:
        # Non-JSON response (common for NGSIEM errors: text/plain)
        body = (res.text or "").strip()
        # keep it short to avoid huge war-room errors
        body = body[:4000]
        raise DemistoException(f"Error in API call to CrowdStrike Falcon: code: {res.status_code} - reason: {reason}\n{body}")
    resources = res_json.get("resources", {})
    extracted_error_message = ""
    if resources:
        if isinstance(resources, list):
            extracted_error_message += f"\n{resources!s}"
        else:
            for host_id, resource in resources.items():
                errors = resource.get("errors", []) if isinstance(resource, dict) else ""  # type: ignore[union-attr]
                if errors:
                    error_message = errors[0].get("message")  # type: ignore[union-attr]
                    extracted_error_message += f"\nHost ID {host_id} - {error_message}"
    elif res_json.get("errors") and not extracted_error_message:
        errors = res_json.get("errors", [])
        for error in errors:
            extracted_error_message += f"\n{error.get('message')}"
    reason += extracted_error_message
    raise DemistoException(f"Error in API call to CrowdStrike Falcon: code: {res.status_code} - reason: {reason}")


def http_request(
    method,
    url_suffix,
    params=None,
    data=None,
    files=None,
    headers=HEADERS,
    get_token_flag=True,
    no_json=False,
    json=None,
    status_code=None,
    timeout=None,
):
    """
    A wrapper for requests lib to send our requests and handle requests and responses better.

    :param json: JSON body
    :type json ``dict`` or ``list``

    :type method: ``str``
    :param method: HTTP method for the request.

    :type url_suffix: ``str``
    :param url_suffix: The suffix of the URL (endpoint)

    :type params: ``dict``
    :param params: The URL params to be passed.

    :type data: ``str``
    :param data: The body data of the request.

    :type headers: ``dict``
    :param headers: Request headers

    :type get_token_flag: ``bool``
    :param get_token_flag: If set to True will call get_token()

    :type no_json: ``bool``
    :param no_json: If set to true will not parse the content and will return the raw response object for successful
    response

    :type status_code: ``int``
    :param: status_code: The request codes to accept as OK.

    :type timeout: ``float``
    :param: timeout: The timeout for the request.

    :return: Returns the http request response json
    :rtype: ``dict``
    """
    if get_token_flag:
        token = get_token()
        headers["Authorization"] = f"Bearer {token}"
        retries = 0
        status_list_to_retry = []
        # in case of 401,403,429 status codes we want to return the response, generate a new token and try again with retries.
        valid_status_codes = [200, 201, 202, 204, 401, 403, 429]
    else:
        # get_token_flag=False means that get_token_request() called http_request() with /oauth2/token, and we want to retry
        # to create the token in case of 429 in the first call to generic_http_request and not in the second call to avoid a
        # loop of calls to get_token_request().
        retries = 5
        # error code 401 - isn't relevant for requesting a token.
        # error code 403 - The IP is missing from the IP allowlist, no need to retry.
        status_list_to_retry = [429]
        valid_status_codes = [200, 201, 202, 204]
        demisto.debug(f"In http_request {get_token_flag=} updated retries, status_list_to_retry, valid_status_codes")

    headers["User-Agent"] = "PANW-XSOAR"

    if is_time_sensitive():
        demisto.debug("Changing timeout to 15 seconds and retries to 0 due to time_sensitive=True")
        retries = TOTAL_RETRIES_ON_ENRICHMENT
        request_timeout = TIMEOUT_ON_ENRICHMENT
    else:
        request_timeout = int(timeout) if timeout else DEFAULT_TIMEOUT_ON_GENERIC_HTTP_REQUEST

    # Handling a case when we want to return an entry for 404 status code.
    if status_code:
        # To cover the condition when status_code is a list of status codes
        if isinstance(status_code, list):
            valid_status_codes = valid_status_codes + status_code
        else:
            valid_status_codes.append(status_code)

    try:
        res = generic_http_request(
            method=method,
            server_url=SERVER,
            headers=headers,
            url_suffix=url_suffix,
            data=data,
            files=files,
            params=params,
            proxy=PROXY,
            resp_type="response",
            verify=USE_SSL,
            error_handler=error_handler,
            json_data=json,
            timeout=request_timeout,
            ok_codes=valid_status_codes,
            retries=retries,
            status_list_to_retry=status_list_to_retry,
        )
        demisto.debug(f"In http_request after the first call to generic_http_request {res=} {res.status_code=}")
    except requests.exceptions.RequestException as e:
        return_error(f"Error in connection to the server. Please make sure you entered the URL correctly. Exception is {e!s}.")
    try:
        if get_token_flag:
            # removing 401,403,429 status codes, now we want to generate a new token and try again
            valid_status_codes.remove(401)
            valid_status_codes.remove(403)
            valid_status_codes.remove(429)
        if res.status_code not in valid_status_codes:
            # try to create a new token
            if res.status_code in (401, 403, 429) and get_token_flag:
                demisto.debug(f"Try to create a new token because {res.status_code=}")
                token = get_token(new_token=True)
                headers["Authorization"] = f"Bearer {token}"
                demisto.debug(f"calling generic_http_request with retries={retries} and status_list_to_retry=[429]")  # noqa: E501
                res = generic_http_request(
                    method=method,
                    server_url=SERVER,
                    headers=headers,
                    url_suffix=url_suffix,
                    data=data,
                    files=files,
                    params=params,
                    proxy=PROXY,
                    retries=5,
                    status_list_to_retry=[429],
                    resp_type="response",
                    error_handler=error_handler,
                    json_data=json,
                    timeout=request_timeout,
                    ok_codes=valid_status_codes,
                )
                demisto.debug(f"In http_request after the second call to generic_http_request {res=} {res.status_code=}")
                return res if no_json else res.json()
            else:
                demisto.debug(f"In invalid status code and {get_token_flag=}")
                error_handler(res)
        demisto.debug("In http_request end")
        return res if no_json else res.json()
    except ValueError as exception:
        # type: ignore[str-bytes-safe]
        raise ValueError(f"Failed to parse json object from response: {exception} - {res.content}")


def create_relationships(cve: dict) -> list:
    """
    creates relationships between the cve and each actor from 'actors' field
    : args: cve contains the cve id and the actors field if it is exists.
    : return: a list of relationships by type THREAT_ACTOR.
    """
    list_with_actors_field = []
    if not cve.get("actors"):
        return []
    for actor in cve.get("actors", {}):
        list_with_actors_field.append(actor)
    relationships_list: list[EntityRelationship] = []
    # need to create entity
    for entity_b in list_with_actors_field:
        relationships_list.append(
            EntityRelationship(
                entity_a=cve.get("id"),
                entity_a_type=FeedIndicatorType.CVE,
                name=EntityRelationship.Relationships.TARGETED_BY,
                entity_b=entity_b,
                entity_b_type=ThreatIntel.ObjectsNames.THREAT_ACTOR,
                brand=INTEGRATION_NAME,
                reverse_name=EntityRelationship.Relationships.TARGETS,
            )
        )

    return relationships_list


def create_dbot_Score(cve: dict, reliability: str) -> Common.DBotScore:
    """
    Creates DBotScore CVE indicator, for get_cve_command.
    """
    return Common.DBotScore(
        indicator=cve.get("id"),
        indicator_type=DBotScoreType.CVE,
        integration_name=INTEGRATION_NAME,
        score=Common.DBotScore.NONE,
        reliability=reliability,
    )


def create_publications(cve: dict) -> list:
    """
    Creates publications list from CVE, while using get_cve_command.
    """
    publications = []
    if cve.get("references"):
        for reference in cve.get("references", {}):
            publications.append(Common.Publications(title="references", link=reference))
    if cve.get("vendor_advisory"):
        for vendor_advisory in cve.get("vendor_advisory", {}):
            publications.append(Common.Publications(title="vendor_advisory", link=vendor_advisory))
    return publications


def build_query_params(query_params: dict) -> str:
    r"""
    Gets a dict of {property: value} and returns a string to use as an FQL ``q`` parameter.

    For example::

        {}                                     => ""
        {'name': 'test', 'os_name': 'WINDOWS'} => "name:'test'+os_name:'WINDOWS'"
        {'filename': ['a.txt']}                => "filename:'a.txt'"
        {'filename': ['a.txt', 'b.txt']}       => "filename:['a.txt','b.txt']"
        {'filename': []}                       => ""        # empty list is skipped

    List values are unwrapped (single element) or rendered in FQL multi-value bracket
    notation (multiple elements). Without this, a list value would be interpolated as a
    Python ``repr`` (e.g. ``filename:'['a.txt']'``), which CrowdStrike's FQL parser does
    not match against. Single quotes inside values are escaped with a backslash so values
    like ``O'Brien.txt`` do not break the FQL syntax.

    Args:
        query_params: dict of property: value (value may be scalar or list).
            ``None`` values and empty lists are ignored.
    Returns:
        String to use as the FQL ``q`` query param (``""`` if no usable values).
    """

    def _fql_quote(v: Any) -> str:
        # Escape single quotes inside the value so they don't terminate the FQL string.
        return "'" + str(v).replace("'", "\\'") + "'"

    parts: list[str] = []

    for key, value in query_params.items():
        if value is None:
            continue
        if isinstance(value, list):
            if not value:
                # Empty list: nothing to filter on for this key.
                continue
            if len(value) == 1:
                parts.append(f"{key}:{_fql_quote(value[0])}")
            else:
                joined = ",".join(_fql_quote(v) for v in value)
                parts.append(f"{key}:[{joined}]")
        else:
            parts.append(f"{key}:{_fql_quote(value)}")

    return "+".join(parts)


def modify_detection_summaries_outputs(detection: dict):
    """
    Modifies the detection summaries outputs in the new version (raptor release) to be in the same format as the legacy version.

    Args:
        detection: The detection to modify.
    Returns:
        The modified detection.
    """
    keys_to_move = [
        "pattern_disposition_details",
        "timestamp",
        "device_id",
        "filename",
        "alleged_filetype",
        "cmdline",
        "scenario",
        "objective",
        "tactic",
        "technique",
        "severity",
        "confidence",
        "ioc_type",
        "ioc_value",
        "user_name",
        "user_id",
        "control_graph_id",
        "triggering_process_graph_id",
        "sha256",
        "pattern_disposition",
        "parent_details",
        "md5",
        "filepath",
    ]

    # rename before adding to a nested dict
    parent_details = detection.get("parent_details", {})
    parent_keys = ["sha256", "cmdline", "md5", "process_graph_id"]
    for key in parent_keys:
        if key in parent_details:
            new_key = f"parent_{key}"
            parent_details[new_key] = parent_details.pop(key)

    # change from a flat dict to nested dict
    nested_dict = {key: detection.pop(key, None) for key in keys_to_move if key in detection}
    nested_dict["device_id"] = detection.get("device", {}).get("device_id")
    detection["behaviors"] = nested_dict

    # change from nested to flat
    detection["hostinfo"] = detection.get("device", {}).get("hostinfo")

    # rename without moving to a nested dict
    detection["detection_id"] = detection.pop("composite_id", None)

    return detection


def log_falcon_assets(log_line: str, log_type="debug", asset="Spotlight"):
    """Wrapper for log line for spotlight asset collector"""
    full_log_line = f"[Falcon Asset Collector] [{asset}] {log_line}"
    if log_type == "debug":
        demisto.debug(full_log_line)
    elif log_type == "info":
        demisto.info(full_log_line)
    else:
        demisto.error(full_log_line)


def _get_process_memory_mb() -> str:
    """Get current process RSS memory usage and Python's tracked allocations.

    Reads VmRSS from /proc/self/status (Linux) to get the current resident set size,
    which reflects actual physical memory usage at this moment.
    Also reports tracemalloc's tracked Python allocations to quantify arena fragmentation
    (difference between OS RSS and Python's tracked memory = fragmentation overhead).

    Returns:
        Formatted string with current RSS, peak RSS, and Python tracked memory in MB.
    """
    # Import locally to avoid shadowing the `resource` loop variable used in other functions
    import resource as resource_mod  # noqa: F811
    import sys
    import tracemalloc

    # Current RSS: read from /proc/self/status (Linux only)
    # VmRSS shows the actual physical memory currently used by the process
    current_rss_mb = 0.0
    try:
        with open("/proc/self/status") as f:
            for line in f:
                if line.startswith("VmRSS:"):
                    # VmRSS is reported in KB in /proc/self/status
                    current_rss_mb = int(line.split()[1]) / 1024
                    break
    except (FileNotFoundError, ValueError):
        pass  # Not on Linux or parse error — current RSS will show 0

    # Peak RSS: the maximum RSS ever reached during the process lifetime
    # On Linux ru_maxrss is in KB, on macOS it's in bytes
    rusage = resource_mod.getrusage(resource_mod.RUSAGE_SELF)
    if sys.platform == "darwin":
        peak_rss_mb = rusage.ru_maxrss / (1024 * 1024)
    else:
        peak_rss_mb = rusage.ru_maxrss / 1024

    # Python tracked allocations via tracemalloc
    # The gap between RSS and traced = arena fragmentation + non-Python allocations
    traced_mb = 0.0
    traced_peak_mb = 0.0
    if tracemalloc.is_tracing():
        traced_current, traced_peak = tracemalloc.get_traced_memory()
        traced_mb = traced_current / (1024 * 1024)
        traced_peak_mb = traced_peak / (1024 * 1024)

    return f"current={current_rss_mb:.1f} MB, peak={peak_rss_mb:.1f} MB" + (
        f", py_traced={traced_mb:.1f} MB, py_peak={traced_peak_mb:.1f} MB" if traced_mb > 0 else ""
    )


def _normalize_data_to_str(data: Union[str, list, None], data_type: str) -> str | None:
    """Convert data to a newline-separated JSON string for XSIAM ingestion.

    Handles multiple input types (list of dicts, list of strings, raw string, or None)
    and returns a unified string representation ready for chunking and sending.

    Args:
        data: The data to normalize. Can be a list of dicts/strings, a raw string, or None.
        data_type: The type of data being sent (e.g., "assets", "events").

    Returns:
        The normalized string, or None if the data cannot be converted
        (signals the caller to skip sending).
    """
    if isinstance(data, list):
        log_falcon_assets(f"Sending {len(data)} {data_type} (data type) to XSIAM")
        if data and isinstance(data[0], dict):
            data = [json.dumps(item) for item in data]
        return "\n".join(data)
    elif isinstance(data, str):
        return data
    elif not data and data_type == "assets":
        # Handle explicit None for assets seal
        return ""
    # Unknown type or empty data for non-assets
    return None


""" API FUNCTIONS """


def create_entry_object(contents: list[Any] | dict[str, Any] = {}, ec: list[Any] | dict[str, Any] | None = None, hr: str = ""):
    """
    Creates an entry object

    :type contents: ``dict``
    :param contents: Raw response to output

    :type ec: ``dict``
    :param ec: Entry context of the entry object

    :type hr: ``str``
    :param hr: Human readable

    :return: Entry object
    :rtype: ``dict``
    """
    return {
        "Type": entryTypes["note"],
        "Contents": contents,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": hr,
        "EntryContext": ec,
    }


def add_mirroring_fields(incident: dict):
    """
    Updates the given incident to hold the needed mirroring fields.
    """
    incident["mirror_direction"] = MIRROR_DIRECTION
    incident["mirror_instance"] = INTEGRATION_INSTANCE


def extract_response_to_dataset_raw(resp: dict, raw: dict) -> None:
    """
    Adds response data info to specific dataset raw.

    This function processes response keys and adds them to the raw ad new column.
    The "name" column is typically used for displaying "(fetched type) ID: (id)" format,
    The function renames the "name" key to "_name" column to avoid conflicts.

    Args:
        resp (dict): Response dictionary to process (events/incident)
        raw (dict): Target dataset raw to update
    """
    for key, val in resp.items():
        column_name = "_name" if key == "name" else key
        value = val if isinstance(val, str) else json.dumps(val)
        raw[column_name] = value


def detection_to_incident(detection, is_fetch_events: bool = False):
    """
    Creates an incident of a detection.

    :type detection: ``dict``
    :param detection: Single detection object

    :return: Incident representation of a detection
    :rtype ``dict``
    """
    add_mirroring_fields(detection)
    # detection_id and severity key names change between the legacy and the new version
    detection_id = detection.get("detection_id") or detection.get("composite_id")
    severity = detection.get("max_severity_displayname") or detection.get("severity_name")
    incident = {
        "name": "Detection ID: " + str(detection_id),
        "occurred": str(detection.get("created_timestamp")),
        "severity": severity_string_to_int(severity),
        "rawJSON": json.dumps(detection),
    }
    if is_fetch_events:
        incident["_source_log_type"] = detection.get("incident_type")
        extract_response_to_dataset_raw(resp=detection, raw=incident)
        # new detection
        if not detection.get("updated_timestamp") or (detection.get("updated_timestamp") == detection.get("timestamp")):
            incident["_time"] = detection.get("timestamp")
            incident["_entry_status"] = "new"
        # updated detection
        else:
            incident["_time"] = detection.get("updated_timestamp")
            incident["_entry_status"] = "updated"
    return incident


def fix_time_field(detection: dict, time_key: str):
    """
    Fix the value of the date to have only 6 figures after the ".".
    The string representation of the created_timestamp value can contain from 6 to 9 figures after the dot,
    for example: 2024-02-22T14:16:04.973070837Z. The template supports only 6 digits, so there is a need to remove the extra
    digits to use datetime.strptime().

    Args:
        detection (dict): the detection.
        time_key (str): the key of the wanted date&time field.
    """
    demisto.debug(f"fix_time_field {time_key=}")
    str_date = detection[time_key]
    split_date = str_date.split(".")
    relevant_microseconds = split_date[1][:6]
    # if 'Z' isn't in relevant_microseconds it means that it was removed since there was more than 5 digits in the microseconds.
    fixed_date = f"{split_date[0]}.{relevant_microseconds}Z" if "Z" not in relevant_microseconds else str_date
    demisto.debug(f"fix_time_field, the original value in {time_key=} is {str_date} the updated value is {fixed_date} ")
    detection[time_key] = fixed_date


def detection_to_incident_context(detection, detection_type, start_time_key: str = "start_time", is_fetch_events: bool = False):
    """
    Creates an incident context from multiple detection types.

    :type detection: ``dict``
    :param detection: Single detection object.

    :return: The incident context for the detection.
    :rtype ``dict``
    """
    add_mirroring_fields(detection)
    demisto.debug(f"detection_to_incident_context, {detection_type=}")
    if detection_type in (
        IDP_DETECTION_FETCH_TYPE,
        NGSIEM_DETECTION_FETCH_TYPE,
        THIRD_PARTY_DETECTION_FETCH_TYPE,
        NGSIEM_INCIDENT_FETCH_TYPE,
        NGSIEM_AUTOMATED_LEADS_FETCH_TYPE,
        IOA_FETCH_TYPE,
    ):
        demisto.debug(f"detection_to_incident_context, {detection_type=} calling fix_time_field")
        fix_time_field(detection, start_time_key)

    incident_context = {"occurred": detection.get(start_time_key), "rawJSON": json.dumps(detection)}
    if detection_type in SUPPORTED_DETECTIONS_TYPES:
        incident_context["name"] = f'{detection_type} ID: {detection.get("composite_id")}'
        incident_context["last_updated"] = detection.get("updated_timestamp")
    elif detection_type == MOBILE_DETECTION_FETCH_TYPE:
        incident_context["name"] = f'{detection_type} ID: {detection.get("mobile_detection_id")}'
        incident_context["severity"] = detection.get("severity")
    elif detection_type == IOA_FETCH_TYPE:
        incident_context["name"] = f'{detection_type} ID: {detection.get("composite_id")}'
        incident_context["severity"] = severity_string_to_int(detection.get("severity_name"))

    if is_fetch_events:
        incident_context["_source_log_type"] = "detection"
        extract_response_to_dataset_raw(resp=detection, raw=incident_context)
        # new detection
        if not detection.get("updated_timestamp") or (detection.get("updated_timestamp") == detection.get("timestamp")):
            incident_context["_time"] = detection.get("timestamp")
            incident_context["_entry_status"] = "new"
        # updated detection
        else:
            incident_context["_time"] = detection.get("updated_timestamp")
            incident_context["_entry_status"] = "updated"
    return incident_context


def severity_string_to_int(severity):
    """
    Converts a severity string to DBot score representation

    :type severity: ``str``
    :param severity: String representation of a severity

    :return: DBot score representation of the severity
    :rtype ``int``
    """
    if severity in ("Critical", "High"):
        return 3
    elif severity in ("Medium", "Low"):
        return 2
    return 0


def get_trasnformed_dict(old_dict, transformation_dict):
    """
    Returns a dictionary with the same values as old_dict, with the correlating key:value in transformation_dict

    :type old_dict: ``dict``
    :param old_dict: Old dictionary to pull values from

    :type transformation_dict: ``dict``
    :param transformation_dict: Transformation dictionary that contains oldkeys:newkeys

    :return Transformed dictionart (according to transformation_dict values)
    :rtype ``dict``
    """
    new_dict = {}
    for k in list(old_dict.keys()):
        if k in transformation_dict:
            new_dict[transformation_dict[k]] = old_dict[k]
    return new_dict


def extract_transformed_dict_with_split(old_dict, transformation_dict_arr):
    """
    Extracts new values out of old_dict using a json structure of:
    {'Path': 'Path to item', 'NewKey': 'Value of output key', 'Delim': 'Delimiter char', 'Index': Split Array Index}
    """
    new_dict = {}
    for trans_dict in transformation_dict_arr:
        try:
            val = demisto.get(old_dict, trans_dict["Path"])
            if "split" in dir(val):
                i = trans_dict["Index"]
                new_dict[trans_dict["NewKey"]] = val.split(trans_dict["Delim"])[i]
        except Exception as ex:
            LOG(f"Error {ex} with: {trans_dict}")
    return new_dict


def get_passed_mins(start_time, end_time_str):
    """
    Returns the time passed in mins
    :param start_time: Start time in datetime
    :param end_time_str: End time in str
    :return: The passed mins in int
    """
    time_delta = start_time - datetime.fromtimestamp(end_time_str)
    return time_delta.seconds / 60


def handle_response_errors(raw_res: dict, err_msg: str | None = None):
    """
    Raise exception if raw_res is empty or contains errors
    """
    if not err_msg:
        err_msg = "The server was unable to return a result, please run the command again."
    if not raw_res:
        raise DemistoException(err_msg)
    if raw_res.get("errors"):
        raise DemistoException(raw_res.get("errors"))


def create_json_iocs_list(
    ioc_type: str,
    iocs_value: list[str],
    action: str,
    platforms: list[str],
    severity: str | None = None,
    source: str | None = None,
    description: str | None = None,
    expiration: str | None = None,
    applied_globally: bool | None = None,
    host_groups: list[str] | None = None,
    tags: list[str] | None = None,
    file_name: str | None = None,
    mobile_action: str | None = None,
) -> list[dict]:
    """
    Get a list of iocs values and create a list of Json objects with the iocs data.
    This function is used for uploading multiple indicator with same arguments with different values.
    :param ioc_type: The type of the indicator.
    :param iocs_value: List of the indicator.
    :param action: Action to take when a host observes the custom IOC.
    :param platforms: The platforms that the indicator applies to.
    :param severity: The severity level to apply to this indicator.
    :param source: The source where this indicator originated.
    :param description: A meaningful description of the indicator.
    :param expiration: The date on which the indicator will become inactive.
    :param applied_globally: Whether the indicator is applied globally.
    :param host_groups: List of host group IDs that the indicator applies to.
    :param tags: List of tags to apply to the indicator.
    :param file_name: Name of the file for file indicators.
    :param mobile_action: Action to take on mobile when a host observes the custom IOC.
    """
    iocs_list = []
    for ioc_value in iocs_value:
        iocs_list.append(
            assign_params(
                type=ioc_type,
                value=ioc_value,
                action=action,
                platforms=platforms,
                severity=severity,
                source=source,
                description=description,
                expiration=expiration,
                applied_globally=applied_globally,
                host_groups=host_groups,
                tags=tags,
                mobile_action=mobile_action,
                metadata=assign_params(filename=file_name) if ioc_type in {"sha256", "md5"} else None,
            )
        )

    return iocs_list


def list_workflow_definitions(filter_query: str = "", offset: str = "0", limit: int = 50, sort: str = "") -> dict:
    """
    List workflow definitions from CrowdStrike Falcon.

    Args:
        filter_query: FQL filter query string.
        offset: The offset to start retrieving records from.
        limit: The maximum number of records to return.
        sort: The property to sort by (e.g., name.desc).

    Returns:
        Response JSON containing workflow definitions.
    """
    params = assign_params(filter=filter_query, offset=offset, limit=limit, sort=sort)
    demisto.debug(f"[Workflow] list_workflow_definitions: calling API with {params=}")
    return http_request("GET", "/workflows/combined/definitions/v1", params=params)


def execute_workflow(
    definition_id: list[str] | None = None,
    name: str | None = None,
    execution_cid: list[str] | None = None,
    key: str | None = None,
    source_event_url: str | None = None,
    body: str = "{}",
) -> dict:
    """
    Execute an on-demand workflow.

    Args:
        definition_id: Workflow definition ID(s).
        name: Workflow name.
        execution_cid: CID(s) to execute the workflow on.
        key: Deduplication key.
        source_event_url: URL reference to the source that triggered the workflow.
        body: JSON body to pass to the workflow execution.

    Returns:
        Response JSON from the workflow execution.
    """
    params = assign_params(
        definition_id=definition_id,
        name=name,
        execution_cid=execution_cid,
        key=key,
        source_event_url=source_event_url,
    )
    try:
        json_body = json.loads(body)
    except json.JSONDecodeError as e:
        raise DemistoException(f"Invalid JSON in 'body' argument: {e}")
    demisto.debug(f"[Workflow] execute_workflow: calling API with {params=}, body_keys={list(json_body.keys())}")
    return http_request("POST", "/workflows/entities/execute/v1", params=params, json=json_body)


def list_workflow_executions(filter_query: str = "", offset: str = "0", limit: int = 50, sort: str = "") -> dict:
    """
    List workflow executions from CrowdStrike Falcon.

    Args:
        filter_query: FQL filter query string.
        offset: The offset to start retrieving records from.
        limit: The maximum number of records to return.
        sort: The property to sort by (e.g., created_at.desc).

    Returns:
        Response JSON containing workflow executions.
    """
    params = assign_params(filter=filter_query, offset=offset, limit=limit, sort=sort)
    demisto.debug(f"[Workflow] list_workflow_executions: calling API with {params=}")
    return http_request("GET", "/workflows/combined/executions/v1", params=params)


def get_workflow_execution_results(ids: list[str]) -> dict:
    """
    Get detailed results for specific workflow executions.

    Args:
        ids: List of workflow execution IDs.

    Returns:
        Response JSON containing execution results.
    """
    params = {"ids": ids}
    return http_request("GET", "/workflows/entities/execution-results/v1", params=params, status_code=404)


def perform_workflow_execution_action(ids: list[str], action_name: str) -> dict:
    """
    Perform an action (cancel or resume) on workflow executions.

    Args:
        ids: List of workflow execution IDs.
        action_name: The action to perform ('cancel' or 'resume').

    Returns:
        Response JSON from the action.
    """
    params = {"action_name": action_name}
    body = {"ids": ids}
    return http_request("POST", "/workflows/entities/execution-actions/v1", params=params, json=body, status_code=404)


""" COMMAND SPECIFIC FUNCTIONS """


def init_rtr_single_session(host_id: str, queue_offline: bool = False) -> str:
    """
    Start a session with single host.
    :param host_id: Host agent ID to initialize a RTR session on.
    :return: The session ID to execute the command on
    """
    endpoint_url = "/real-time-response/entities/sessions/v1"
    body = json.dumps({"device_id": host_id, "queue_offline": queue_offline})
    response = http_request("POST", endpoint_url, data=body)
    resources = response.get("resources")
    if resources and isinstance(resources, list) and isinstance(resources[0], dict):
        session_id = resources[0].get("session_id")
        if isinstance(session_id, str):
            return session_id
    raise ValueError("No session id found in the response")


def init_rtr_batch_session(host_ids: list, offline=False) -> str:
    """
    Start a session with one or more hosts
    :param host_ids: List of host agent ID’s to initialize a RTR session on.
    :return: The session batch ID to execute the command on
    """
    endpoint_url = "/real-time-response/combined/batch-init-session/v1"
    body = json.dumps({"host_ids": host_ids, "queue_offline": offline})
    response = http_request("POST", endpoint_url, data=body)
    return response.get("batch_id")


def refresh_session(host_id: str) -> dict:
    """
    Refresh a session timeout on a single host.
    :param host_id: Host agent ID to run RTR command on.
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/entities/refresh-session/v1"

    body = json.dumps({"device_id": host_id})
    response = http_request("POST", endpoint_url, data=body)
    return response


def batch_refresh_session(batch_id: str) -> None:
    """
    Batch refresh a RTR session on multiple hosts.
    :param batch_id:  Batch ID to execute the command on.
    """
    demisto.debug("Starting session refresh")
    endpoint_url = "/real-time-response/combined/batch-refresh-session/v1"

    body = json.dumps({"batch_id": batch_id})
    response = http_request("POST", endpoint_url, data=body)
    demisto.debug(f"Refresh session response: {response}")
    demisto.debug("Finished session refresh")


def run_batch_read_cmd(batch_id: str, command_type: str, full_command: str, timeout: int = 30) -> dict:
    """
    Sends RTR command scope with read access
    :param batch_id:  Batch ID to execute the command on.
    :param command_type: Read-only command type we are going to execute, for example: ls or cd.
    :param full_command: Full command string for the command.
    :param timeout: The timeout for the request.
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/combined/batch-command/v1"

    body = json.dumps({"base_command": command_type, "batch_id": batch_id, "command_string": full_command})
    params = {"timeout": timeout}
    response = http_request("POST", endpoint_url, data=body, params=params, timeout=timeout)
    return response


def run_batch_write_cmd(
    batch_id: str, command_type: str, full_command: str, optional_hosts: list | None = None, timeout: int = DEFAULT_TIMEOUT
) -> dict:
    """
    Sends RTR command scope with write access
    :param batch_id:  Batch ID to execute the command on.
    :param command_type: Read-only command type we are going to execute, for example: ls or cd.
    :param full_command: Full command string for the command.
    :param optional_hosts: The hosts ids to run the command on.
    :param timeout: The timeout for the request.
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/combined/batch-active-responder-command/v1"

    default_body = {"base_command": command_type, "batch_id": batch_id, "command_string": full_command}
    params = {"timeout": timeout if timeout else DEFAULT_TIMEOUT}
    if optional_hosts:
        default_body["optional_hosts"] = optional_hosts  # type:ignore

    body = json.dumps(default_body)
    response = http_request("POST", endpoint_url, data=body, timeout=timeout, params=params)
    return response


def run_batch_admin_cmd(
    batch_id: str, command_type: str, full_command: str, timeout: int = 30, optional_hosts: list | None = None
) -> dict:
    """
    Sends RTR command scope with write access
    :param batch_id:  Batch ID to execute the command on.
    :param command_type: Read-only command type we are going to execute, for example: ls or cd.
    :param full_command: Full command string for the command.
    :param timeout: Timeout for how long to wait for the request in seconds.
    :param optional_hosts: The hosts ids to run the command on.
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/combined/batch-admin-command/v1"

    params = {"timeout": timeout}

    default_body = {"base_command": command_type, "batch_id": batch_id, "command_string": full_command}
    if optional_hosts:
        default_body["optional_hosts"] = optional_hosts  # type:ignore

    body = json.dumps(default_body)
    response = http_request("POST", endpoint_url, data=body, params=params, timeout=timeout)
    return response


def run_batch_get_cmd(
    host_ids: list,
    file_path: str,
    optional_hosts: list | None = None,
    timeout: int | None = None,
    timeout_duration: str | None = None,
    offline: bool = False,
) -> dict:
    """
      Batch executes `get` command across hosts to retrieve files.
      After this call is made `/real-time-response/combined/batch-get-command/v1` is used to query for the results.

    :param host_ids: List of host agent ID’s to run RTR command on.
    :param file_path: Full path to the file that is to be retrieved from each host in the batch.
    :param optional_hosts: List of a subset of hosts we want to run the command on.
                           If this list is supplied, only these hosts will receive the command.
    :param timeout: Timeout for how long to wait for the request in seconds
    :param timeout_duration: Timeout duration for for how long to wait for the request in duration syntax
    :param offline: Whether the command will run against an offline-queued session for execution when the host comes online.
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/combined/batch-get-command/v1"
    batch_id = init_rtr_batch_session(host_ids, offline)

    body = assign_params(batch_id=batch_id, file_path=f'"{file_path}"', optional_hosts=optional_hosts)
    params = assign_params(timeout=timeout, timeout_duration=timeout_duration)
    response = http_request("POST", endpoint_url, data=json.dumps(body), params=params)
    return response


def status_get_cmd(request_id: str, timeout: int | None = None, timeout_duration: str | None = None) -> dict:
    """
      Retrieves the status of the specified batch get command. Will return successful files when they are finished processing.

    :param request_id: ID to the request of `get` command.
    :param timeout: Timeout for how long to wait for the request in seconds
    :param timeout_duration: Timeout duration for how long to wait for the request in duration syntax
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/combined/batch-get-command/v1"

    params = assign_params(timeout=timeout, timeout_duration=timeout_duration, batch_get_cmd_req_id=request_id)
    response = http_request("GET", endpoint_url, params=params)
    return response


def run_single_read_cmd(host_id: str, command_type: str, full_command: str, queue_offline: bool, timeout: int = 30) -> dict:
    """
    Sends RTR command scope with read access
    :param host_id: Host agent ID to run RTR command on.
    :param command_type: Active-Responder command type we are going to execute, for example: get or cp.
    :param full_command: Full command string for the command.
    :param queue_offline: Whether the command will run against an offline-queued session and be queued for execution
                          when the host comes online.  # noqa: E501
    :param timeout: The timeout for the request.
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/entities/command/v1"
    session_id = init_rtr_single_session(host_id, queue_offline)

    body = json.dumps({"base_command": command_type, "command_string": full_command, "session_id": session_id})
    params = {"timeout": timeout}
    response = http_request("POST", endpoint_url, data=body, timeout=timeout, params=params)
    return response


def run_single_write_cmd(host_id: str, command_type: str, full_command: str, queue_offline: bool, timeout: int = 30) -> dict:
    """
    Sends RTR command scope with write access
    :param host_id: Host agent ID to run RTR command on.
    :param command_type: Active-Responder command type we are going to execute, for example: get or cp.
    :param full_command: Full command string for the command.
    :param queue_offline: Whether the command will run against an offline-queued session and be queued for execution
                          when the host comes online.  # noqa: E501
    :param timeout: The timeout for the request.
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/entities/active-responder-command/v1"
    session_id = init_rtr_single_session(host_id, queue_offline)
    body = json.dumps({"base_command": command_type, "command_string": full_command, "session_id": session_id})
    params = {"timeout": timeout}
    response = http_request("POST", endpoint_url, data=body, timeout=timeout, params=params)
    return response


def run_single_admin_cmd(host_id: str, command_type: str, full_command: str, queue_offline: bool, timeout: int = 30) -> dict:
    """
    Sends RTR command scope with admin access
    :param host_id: Host agent ID to run RTR command on.
    :param command_type: Active-Responder command type we are going to execute, for example: get or cp.
    :param full_command: Full command string for the command.
    :param queue_offline: Whether the command will run against an offline-queued session and be queued for execution
                          when the host comes online.  # noqa: E501
    :param timeout: The timeout for the request.
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/entities/admin-command/v1"
    session_id = init_rtr_single_session(host_id, queue_offline)

    body = json.dumps({"base_command": command_type, "command_string": full_command, "session_id": session_id})
    params = {"timeout": timeout}
    response = http_request("POST", endpoint_url, data=body, timeout=timeout, params=params)
    return response


def status_read_cmd(request_id: str, sequence_id: int | None) -> dict:
    """
    Get status of an executed command with read access on a single host.

    :param request_id: Cloud Request ID of the executed command to query
    :param sequence_id: Sequence ID that we want to retrieve. Command responses are chunked across sequences
    """
    endpoint_url = "/real-time-response/entities/command/v1"

    params = {"cloud_request_id": request_id, "sequence_id": sequence_id or 0}

    response = http_request("GET", endpoint_url, params=params)
    return response


def status_write_cmd(request_id: str, sequence_id: int | None) -> dict:
    """
    Get status of an executed command with write access on a single host.

    :param request_id: Cloud Request ID of the executed command to query
    :param sequence_id: Sequence ID that we want to retrieve. Command responses are chunked across sequences
    """
    endpoint_url = "/real-time-response/entities/active-responder-command/v1"

    params = {"cloud_request_id": request_id, "sequence_id": sequence_id or 0}

    response = http_request("GET", endpoint_url, params=params)
    return response


def status_admin_cmd(request_id: str, sequence_id: int | None) -> dict:
    """
    Get status of an executed command with admin access on a single host.

    :param request_id: Cloud Request ID of the executed command to query
    :param sequence_id: Sequence ID that we want to retrieve. Command responses are chunked across sequences
    """
    endpoint_url = "/real-time-response/entities/admin-command/v1"

    params = {"cloud_request_id": request_id, "sequence_id": sequence_id or 0}

    response = http_request("GET", endpoint_url, params=params)
    return response


def list_host_files(host_id: str, session_id: str | None = None) -> dict:
    """
    Get a list of files for the specified RTR session on a host.
    :param host_id: Host agent ID to run RTR command on.
    :param session_id: optional session_id for the command, if not provided a new session_id will generate
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/entities/file/v2"
    if not session_id:
        session_id = init_rtr_single_session(host_id)

    params = {"session_id": session_id}
    response = http_request("GET", endpoint_url, params=params)
    return response


def upload_script(name: str, permission_type: str, content: str, entry_id: str) -> dict:
    """
    Uploads a script by either given content or file
    :param name: Script name to upload
    :param permission_type: Permissions type of script to upload
    :param content: PowerShell script content
    :param entry_id: Script file to upload
    :return: Response JSON which contains errors (if exist) and how many resources were affected
    """
    endpoint_url = "/real-time-response/entities/scripts/v1"
    body: dict[str, tuple[Any, Any]] = {"name": (None, name), "permission_type": (None, permission_type)}
    temp_file = None
    try:
        if content:
            body["content"] = (None, content)
        else:  # entry_id was provided
            file_ = demisto.getFilePath(entry_id)
            file_name = file_.get("name")  # pylint: disable=E1101
            temp_file = open(file_.get("path"), "rb")  # pylint: disable=E1101
            body["file"] = (file_name, temp_file)

        headers = {"Authorization": HEADERS["Authorization"], "Accept": "application/json"}

        response = http_request("POST", endpoint_url, files=body, headers=headers)

        return response
    finally:
        if temp_file:
            temp_file.close()


def get_script(script_id: list) -> dict:
    """
    Retrieves a script given its ID
    :param script_id: ID of script to get
    :return: Response JSON which contains errors (if exist) and retrieved resource
    """
    endpoint_url = "/real-time-response/entities/scripts/v2"
    params = {"ids": script_id}
    response = http_request("GET", endpoint_url, params=params)
    return response


def delete_script(script_id: str) -> dict:
    """
    Deletes a script given its ID
    :param script_id: ID of script to delete
    :return: Response JSON which contains errors (if exist) and how many resources were affected
    """
    endpoint_url = "/real-time-response/entities/scripts/v1"
    params = {"ids": script_id}
    response = http_request("DELETE", endpoint_url, params=params)
    return response


def list_scripts() -> dict:
    """
    Retrieves list of scripts
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/entities/scripts/v2"
    response = http_request("GET", endpoint_url)
    return response


def get_extracted_file(host_id: str, sha256: str, filename: str | None = None, timeout=None):
    """
    Get RTR extracted file contents for specified session and sha256.
    :param host_id: The host agent ID to initialize the RTR session on.
    :param sha256: Extracted SHA256
    :param filename: Filename to use for the archive name and the file within the archive.
    """
    endpoint_url = "/real-time-response/entities/extracted-file-contents/v1"
    session_id = init_rtr_single_session(host_id)
    params = {"session_id": session_id, "sha256": sha256}
    if filename:
        params["filename"] = filename

    response = http_request("GET", endpoint_url, params=params, no_json=True, timeout=timeout)
    return response


def upload_file(entry_id: str, description: str) -> tuple:
    """
    Uploads a file given entry ID
    :param entry_id: The entry ID of the file to upload
    :param description: String description of file to upload
    :return: Response JSON which contains errors (if exist) and how many resources were affected and the file name
    """
    endpoint_url = "/real-time-response/entities/put-files/v1"
    temp_file = None
    try:
        file_ = demisto.getFilePath(entry_id)
        file_name = file_.get("name")  # pylint: disable=E1101
        temp_file = open(file_.get("path"), "rb")  # pylint: disable=E1101
        body = {"name": (None, file_name), "description": (None, description), "file": (file_name, temp_file)}
        headers = {"Authorization": HEADERS["Authorization"], "Accept": "application/json"}
        response = http_request("POST", endpoint_url, files=body, headers=headers)
        return response, file_name
    finally:
        if temp_file:
            temp_file.close()


def delete_file(file_id: str) -> dict:
    """
    Delete a put-file based on the ID given
    :param file_id: ID of file to delete
    :return: Response JSON which contains errors (if exist) and how many resources were affected
    """
    endpoint_url = "/real-time-response/entities/put-files/v1"
    params = {"ids": file_id}
    response = http_request("DELETE", endpoint_url, params=params)
    return response


def get_file(file_id: list) -> dict:
    """
    Get put-files based on the ID's given
    :param file_id: ID of file to get
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/entities/put-files/v2"
    params = {"ids": file_id}
    response = http_request("GET", endpoint_url, params=params)
    return response


def get_file_id_by_name(file_name: str) -> str:
    """
    Retrieve the file ID for a put-file by its name.
    :param file_name: Name of the file to search for
    :return: File ID that matches the given name, or empty string if none found
    """
    endpoint_url = "/real-time-response/queries/put-files/v1"
    params = {"filter": f"name:'{file_name}'"}
    response = http_request("GET", endpoint_url, params=params)
    return response.get("resources", "")


def list_files() -> dict:
    """
    Get a list of put-file ID's that are available to the user for the put command.
    :return: Response JSON which contains errors (if exist) and retrieved resources
    """
    endpoint_url = "/real-time-response/entities/put-files/v2"
    response = http_request("GET", endpoint_url)
    return response


def get_token(new_token=False):
    """
    Retrieves the token from the server if it's expired and updates the global HEADERS to include it

    :param new_token: If set to True will generate a new token regardless of time passed

    :rtype: ``str``
    :return: Token
    """
    now = datetime.now()
    ctx = demisto.getIntegrationContext()
    if ctx and not new_token:
        passed_mins = get_passed_mins(now, ctx.get("time"))
        demisto.debug(f"{passed_mins=}")
        if passed_mins >= TOKEN_LIFE_TIME:
            # token expired
            demisto.debug("token expired")
            auth_token = get_token_request()
            demisto.setIntegrationContext({"auth_token": auth_token, "time": date_to_timestamp(now) / 1000})
        else:
            # token hasn't expired
            demisto.debug("token hasn't expired")
            auth_token = ctx.get("auth_token")
    else:
        # there is no token
        demisto.debug("there is no token")
        auth_token = get_token_request()
        demisto.setIntegrationContext({"auth_token": auth_token, "time": date_to_timestamp(now) / 1000})
    return auth_token


def get_token_request():
    """
    Sends token request

    :rtype ``str``
    :return: Access token
    """
    body = {"client_id": CLIENT_ID, "client_secret": SECRET}
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    token_res = http_request("POST", "/oauth2/token", data=body, headers=headers, get_token_flag=False)
    demisto.debug(f"In get_token_request, token_res is not None {token_res is not None}")
    if not token_res:
        err_msg = (
            "Authorization Error: User has no authorization to create a token. Please make sure you entered the"
            " credentials correctly."
        )
        raise Exception(err_msg)
    demisto.debug(f'{token_res.get("expires_in")=}')
    return token_res.get("access_token")


def get_ioarules(rule_ids: list[str]) -> dict:
    """
    Sends ioa rules entities request
    :param rule_ids: IDs of the requested ioa rule.
    :return: Response json of the get ioa rule entities endpoint (ioa rule objects)
    """
    params = {"ids": rule_ids}

    return http_request(
        "GET",
        "/ioarules/entities/rules/v1",
        params=params,
    )


def get_detections(last_behavior_time=None, behavior_id=None, filter_arg=None):
    """
    Sends detections request. The function will ignore the arguments passed according to priority:
    filter_arg > behavior_id > last_behavior_time

    :param last_behavior_time: 3rd priority. The last behavior time of results will be greater than this value
    :param behavior_id: 2nd priority. The result will only contain the detections with matching behavior id
    :param filter_arg: 1st priority. The result will be filtered using this argument.
    :return: Response json of the get detection endpoint (IDs of the detections)
    """
    params = {"sort": "first_behavior.asc"}
    if filter_arg:
        params["filter"] = filter_arg
    elif behavior_id:
        params["filter"] = f"behaviors.behavior_id:'{behavior_id}'"
    elif last_behavior_time:
        params["filter"] = f"first_behavior:>'{last_behavior_time}'"

    endpoint_url = "alerts/queries/alerts/v2?filter="
    if filter_arg:
        # in the new version we send only the filter_arg argument as encoded string without the params
        endpoint_url += urllib.parse.quote_plus(filter_arg)
    demisto.debug(f"In get_detections: {endpoint_url=}")
    return http_request("GET", endpoint_url, {"sort": "created_timestamp.asc"})


def get_fetch_detections(
    last_created_timestamp=None,
    filter_arg=None,
    offset: int = 0,
    last_updated_timestamp=None,
    has_limit=True,
    limit: int = INCIDENTS_PER_FETCH,
):
    """Sends detection request, based on the created_timestamp field. Used for fetch-incidents
    Args:
        last_created_timestamp: last created timestamp of the results will be greater than this value.
        filter_arg: The result will be filtered using this argument.
    Returns:
        Response json of the get detection endpoint (IDs of the detections)
    """
    sort_key = "created_timestamp.asc"
    params = {
        "sort": sort_key,
        "offset": offset,
    }
    if has_limit:
        params["limit"] = limit

    if filter_arg:
        params["filter"] = filter_arg
    elif last_created_timestamp:
        params["filter"] = f"created_timestamp:>'{last_created_timestamp}'"
    elif last_updated_timestamp:
        timestamp_key = "updated_timestamp"
        params["filter"] = f"{timestamp_key}:>'{last_updated_timestamp}'"

    endpoint_url = "/alerts/queries/alerts/v2?filter=product"

    if params.get("filter"):
        endpoint_url += urllib.parse.quote_plus(f":'epp'+type:'ldt'+{params.pop('filter')}")
    else:
        endpoint_url += urllib.parse.quote_plus(":'epp'+type:'ldt'")
    demisto.debug(f"In get_fetch_detections: {endpoint_url=}, {params=}")
    response = http_request("GET", endpoint_url, params)

    return response


def get_detections_entities(detections_ids: list):
    """
    Sends detection entities request
    :param detections_ids: IDs of the requested detections.
    :return: Response json of the get detection entities endpoint (detection objects)
    """
    if not detections_ids:
        return detections_ids

    combined_resources = []

    url = "/alerts/entities/alerts/v2"

    # Iterate through the detections_ids list in chunks of 1000 (According to API documentation).
    for i in range(0, len(detections_ids), MAX_FETCH_DETECTION_PER_API_CALL_ENTITY):
        batch_ids = detections_ids[i : i + MAX_FETCH_DETECTION_PER_API_CALL_ENTITY]

        ids_json = {"composite_ids": batch_ids}
        demisto.debug(f"Getting detections entities from {url} with {ids_json=} " f"with batch_ids len {len(batch_ids)}.")

        # Make the API call with the current batch.
        response = http_request("POST", url, data=json.dumps(ids_json))

        if "resources" in response:
            # Combine the resources from each response.
            combined_resources.extend(response["resources"])

    # Return the combined result.
    return {"resources": combined_resources}


def get_cases_data(url_filter: str = "", limit: int = 100, offset: int = 0) -> tuple[int, list[str]]:
    """
    Fetches NGSIEM Case ids with provided filter
    :param url_filter: URL filter
    :param limit: number of cases to fetch
    :param offset: the fetch offset

    Returns:
        tuple[int, list[str]]: The number of total cases in the filter and the list of cases ids.
    """
    params = {"sort": "created_timestamp.asc", "offset": offset, "limit": limit}
    if url_filter:
        params["filter"] = url_filter
    endpoint_url = "/cases/queries/cases/v1"
    response = http_request("GET", endpoint_url, params)
    total_cases: int = demisto.get(response, "meta.pagination.total")
    ids: list[str] = demisto.get(response, "resources", [])

    return total_cases, ids


def get_detections_ids(filter_arg=None, offset: int = 0, limit=INCIDENTS_PER_FETCH, product_type="idp"):
    """
    Send a request to retrieve IDP/ODS detections IDs.

    :type filter_arg: ``str``
    :param filter_arg: The filter to add to the query.
    :type offset: ``int``
    :param offset: The offset for the query.
    :type limit: ``int``
    :param limit: limit of idp/ods detections to retrieve each request.

    :return: The response.
    :rtype ``dict``
    """
    params = {"sort": "created_timestamp.asc", "offset": offset, "filter": filter_arg}
    if limit:
        params["limit"] = limit
    endpoint_url = "/alerts/queries/alerts/v2?filter="
    # in the new version we need to add the product type to the filter to the url as encoded string
    if params.get("filter"):
        endpoint_url += urllib.parse.quote_plus(params.pop("filter"))

    response = http_request("GET", endpoint_url, params)

    demisto.debug(f"CrowdStrikeFalconMsg: Getting {product_type} detections from {endpoint_url} with {params=}. {response=}.")

    return response


def get_cases_entities(cases_ids: list):
    """
    Sends case entities request
    :param cases_ids: IDs of the requested cases.
    :return: Response json of the get case entities endpoint (case objects)
    """
    ids_json = {"ids": cases_ids}
    raw_res = http_request("POST", "/cases/entities/cases/v2", data=json.dumps(ids_json))
    return raw_res["resources"]


def get_cases_details(ids: list[str]) -> list[dict[str, Any]]:
    """
    Get details on cases by providing case IDs
    Args:
        ids: List of case IDs to get details on
    Returns:
        list[dict[str, Any]]: Response data
    """
    full_cases = []

    for i in range(0, len(ids), MAX_FETCH_DETECTION_PER_API_CALL_ENTITY):
        batch_ids = ids[i : i + MAX_FETCH_DETECTION_PER_API_CALL_ENTITY]
        batch_cases = get_cases_entities(batch_ids)

        if batch_cases:
            # Combine the resources from each response.
            full_cases.extend(batch_cases)

    # Return the combined result.
    return full_cases


def add_case_tags(case_id: str, tags: list[str]) -> dict:
    """
    Add tags to a case.
    Args:
        case_id: The ID of the case to add tags to.
        tags: The list of tags to add.
    Returns:
        dict: The response from the API.
    """
    body = {"id": case_id, "tags": tags}
    return http_request("POST", "/cases/entities/case-tags/v1", json=body)


def delete_case_tags(case_id: str, tag: str) -> dict:
    """
    Delete a tag from a case.
    Args:
        case_id: The ID of the case to delete the tag from.
        tag: The tag to delete.
    Returns:
        dict: The response from the API.
    """
    params = {"id": case_id, "tag": tag}
    return http_request("DELETE", "/cases/entities/case-tags/v1", params=params)


def get_detection_entities(incidents_ids: list):
    """
    Send a request to retrieve IDP/ODS/OFP and mobile detection entities.

    :type incidents_ids: ``list``
    :param incidents_ids: The list of ids to search their entities.

    :return: The response.
    :rtype ``dict``
    """
    combined_resources = []

    url = "/alerts/entities/alerts/v2"

    for i in range(0, len(incidents_ids), MAX_FETCH_DETECTION_PER_API_CALL_ENTITY):
        batch_ids = incidents_ids[i : i + MAX_FETCH_DETECTION_PER_API_CALL_ENTITY]

        ids_json = {"composite_ids": batch_ids}
        demisto.debug(f"In get_detection_entities: Getting detection entities from\
            {url} with {ids_json=} and with batch_ids len {len(batch_ids)}.")

        # Make the API call with the current batch.
        raw_res = http_request("POST", url, data=json.dumps(ids_json))

        if "resources" in raw_res:
            # Combine the resources from each response.
            combined_resources.extend(raw_res["resources"])

    # Return the combined result.
    return {"resources": combined_resources}


def get_users(offset: int, limit: int, query_filter: str | None = None) -> dict:
    """
    Get a list of users using pagination.

    Note:
        The result will include all collected paginated data, but the 'meta' key will only include information of the first page.

    Args:
        offset (int): The offset to begin from.
        limit (int): The maximum number of records to return.
        query_filter (str): Filter to use for the API request.

    Returns:
        dict: The response from the API (a combination of all paginated data).
    """

    def generate_paginated_request(_offset: int, _limit: int) -> dict:
        result: dict = {
            "method": "GET",
            "url_suffix": "/user-management/queries/users/v1",
            "params": {
                "offset": _offset,
                "limit": _limit,
                # We need to use sort since the API doesn't guarantee a consistent order,
                # which can cause issues when using the offset parameter (repetitive & missing values)
                "sort": "uid",
            },
        }

        if query_filter:
            result["params"]["filter"] = query_filter

        return result

    response = http_request(**generate_paginated_request(_offset=offset, _limit=limit))

    total_results = response.get("meta", {}).get("pagination", {}).get("total", 0)
    fetched_results_count = len(response.get("resources", []))

    while fetched_results_count < limit and fetched_results_count + offset < total_results:
        current_offset = offset + fetched_results_count
        remaining_results_count = min(limit, total_results) - fetched_results_count

        if remaining_results_count > 500:
            current_limit = 500

        else:
            current_limit = remaining_results_count

        current_response = http_request(**generate_paginated_request(_offset=current_offset, _limit=current_limit))

        response["resources"].extend(current_response.get("resources", []))
        fetched_results_count += len(current_response.get("resources", []))

    return response


def get_users_data(user_ids: list[str]) -> dict:
    return http_request(
        "POST",
        "/user-management/entities/users/GET/v1",
        data=json.dumps({"ids": user_ids}),
    )


def upload_ioc(ioc_type, value, policy=None, expiration_days=None, share_level=None, description=None, source=None):
    """
    Create a new IOC (or replace an existing one)
    """
    payload = assign_params(
        type=ioc_type,
        value=value,
        policy=policy,
        share_level=share_level,
        expiration_days=expiration_days,
        source=source,
        description=description,
    )

    return http_request("POST", "/indicators/entities/iocs/v1", json=[payload])


def update_ioc(ioc_type, value, policy=None, expiration_days=None, share_level=None, description=None, source=None):
    """
    Update an existing IOC
    """
    body = assign_params(
        type=ioc_type,
        value=value,
        policy=policy,
        share_level=share_level,
        expiration_days=expiration_days,
        source=source,
        description=description,
    )
    params = assign_params(type=ioc_type, value=value)

    return http_request("PATCH", "/indicators/entities/iocs/v1", json=body, params=params)


def search_iocs(
    types=None,
    values=None,
    policies=None,
    sources=None,
    expiration_from=None,
    expiration_to=None,
    limit=None,
    share_levels=None,
    ids=None,
    sort=None,
    offset=None,
):
    """
    :param types: A list of indicator types. Separate multiple types by comma.
    :param values: Comma-separated list of indicator values
    :param policies: Comma-separated list of indicator policies
    :param sources: Comma-separated list of IOC sources
    :param expiration_from: Start of date range to search (YYYY-MM-DD format).
    :param expiration_to: End of date range to search (YYYY-MM-DD format).
    :param share_levels: A list of share levels. Only red is supported.
    :param limit: The maximum number of records to return. The minimum is 1 and the maximum is 500. Default is 100.
    :param sort: The order of the results. Format
    :param offset: The offset to begin the list from
    """
    if not ids:
        payload = assign_params(
            types=argToList(types),
            values=argToList(values),
            policies=argToList(policies),
            sources=argToList(sources),
            share_levels=argToList(share_levels),
            sort=sort,
            offset=offset,
            limit=limit or "50",
        )
        if expiration_from:
            payload["from.expiration_timestamp"] = expiration_from
        if expiration_to:
            payload["to.expiration_timestamp"] = expiration_to

        ids = http_request("GET", "/indicators/queries/iocs/v1", payload).get("resources")
        if not ids:
            return None
    else:
        ids = str(ids)
    payload = {"ids": ids}
    return http_request("GET", "/indicators/entities/iocs/v1", params=payload)


def enrich_ioc_dict_with_ids(ioc_dict):
    """
    Enriches the provided ioc_dict with IOC ID
    :param ioc_dict: IOC dict transformed using the SEARCH_IOC_KEY_MAP
    :return: ioc_dict with its ID key:value updated
    """
    for ioc in ioc_dict:
        ioc["ID"] = "{type}:{val}".format(type=ioc.get("Type"), val=ioc.get("Value"))
    return ioc_dict


def delete_ioc(ioc_type, value):
    """
    Delete an IOC
    """
    payload = assign_params(type=ioc_type, value=value)
    return http_request("DELETE", "/indicators/entities/iocs/v1", payload)


def search_custom_iocs(
    types: list | str | None = None,
    values: list | str | None = None,
    sources: list | str | None = None,
    expiration: str | None = None,
    limit: str = "50",
    sort: str | None = None,
    offset: str | None = None,
    after: str | None = None,
) -> dict:
    """
    :param types: A list of indicator types. Separate multiple types by comma.
    :param values: Comma-separated list of indicator values
    :param sources: Comma-separated list of IOC sources
    :param expiration: The date on which the indicator will become inactive. (YYYY-MM-DD format).
    :param limit: The maximum number of records to return. The minimum is 1 and the maximum is 500. Default is 100.
    :param sort: The order of the results. Format
    :param offset: The offset to begin the list from
    :param after: A pagination token used with the limit parameter to manage pagination of results.
                  On your first request, don't provide an 'after' token. On subsequent requests, provide
                  the 'after' token from the previous response to continue from that place in the results.
                  To access more than 10k indicators, use the 'after' parameter instead of 'offset'.
    """
    filter_list = []
    if types:
        filter_list.append(f"type:{types}")
    if values:
        filter_list.append(f"value:{values}")
    if sources:
        filter_list.append(f"source:{sources}")
    if expiration:
        filter_list.append(f'expiration:"{expiration}"')

    params = {
        "filter": "+".join(filter_list),
        "sort": sort,
        "offset": offset,
        "limit": limit,
        "after": after,
    }

    return http_request("GET", "/iocs/combined/indicator/v1", params=params)


def get_custom_ioc(ioc_id: str) -> dict:
    params = {"ids": ioc_id}
    return http_request("GET", "/iocs/entities/indicators/v1", params=params)


def update_custom_ioc(
    ioc_id: str,
    action: str | None = None,
    platforms: str | None = None,
    severity: str | None = None,
    source: str | None = None,
    description: str | None = None,
    expiration: str | None = None,
    file_name: str | None = None,
    mobile_action: str | None = None,
) -> dict:
    """
    Update an IOC
    """
    payload = {
        "indicators": [
            {
                "id": ioc_id,
            }
            | assign_params(
                action=action,
                platforms=platforms,
                severity=severity,
                source=source,
                description=description,
                expiration=expiration,
                mobile_action=mobile_action,
                metadata=assign_params(filename=file_name),
            )
        ]
    }

    return http_request("PATCH", "/iocs/entities/indicators/v1", json=payload)


def delete_custom_ioc(ids: str) -> dict:
    """
    Delete an IOC
    """
    params = {"ids": ids}
    return http_request("DELETE", "/iocs/entities/indicators/v1", params=params)


def get_ioc_device_count(ioc_type, value):
    """
    Gets the devices that encountered the IOC
    """
    payload = assign_params(type=ioc_type, value=value)
    response = http_request("GET", "/indicators/aggregates/devices-count/v1", payload, status_code=404)
    errors = response.get("errors", [])
    for error in errors:
        if error.get("code") == 404:
            return f"No results found for {ioc_type} - {value}"
    return response


def get_process_details(ids):
    """
    Get given processes details
    """
    payload = assign_params(ids=ids)
    return http_request("GET", "/processes/entities/processes/v1", payload)


def get_proccesses_ran_on(ioc_type, value, device_id):
    """
    Get processes ids that ran on the given device_id that encountered the ioc
    """
    payload = assign_params(type=ioc_type, value=value, device_id=device_id)
    return http_request("GET", "/indicators/queries/processes/v1", payload)


def search_device(filter_operator="AND"):
    """
    Searches for devices using the argument provided by the command execution. Returns empty
    result if no device was found

    :param: filter_operator: the operator that should be used between filters, default is 'AND'
    :param: exact_hostname: Whether to return exact hostname

    :return: Search device response json
    """
    args = demisto.args()
    input_arg_dict = {
        "device_id": str(args.get("ids", "")).split(","),
        "status": str(args.get("status", "")).split(","),
        "hostname": str(args.get("hostname", "")).split(","),
        "platform_name": str(args.get("platform_name", "")).split(","),
        "site_name": str(args.get("site_name", "")).split(","),
        "local_ip": str(args.get("ip", "")).split(","),
    }
    limit = int(args.get("limit", 50))
    offset = int(args.get("offset", 0))
    sort = args.get("sort", "")
    url_filter = "{}".format(str(args.get("filter", "")))
    op = "," if filter_operator == "OR" else "+"
    # In Falcon Query Language, '+' stands for AND and ',' for OR
    # (https://falcon.crowdstrike.com/documentation/45/falcon-query-language-fql)

    for k, arg in input_arg_dict.items():
        if arg:
            if type(arg) is list:
                arg_filter = ""
                for arg_elem in arg:
                    if arg_elem:
                        first_arg = f"{arg_filter},{k}" if arg_filter else k
                        arg_filter = f"{first_arg}:'{arg_elem}'"
                if arg_filter:
                    url_filter = "{url_filter}{arg_filter}".format(
                        url_filter=url_filter + op if url_filter else "", arg_filter=arg_filter
                    )
            else:
                # All args should be a list. this is a fallback
                url_filter = f"{url_filter}{op}{k}:'{arg}'"
    raw_res = http_request(
        "GET", "/devices/queries/devices/v1", params={"filter": url_filter, "limit": limit, "offset": offset, "sort": sort}
    )
    device_ids = raw_res.get("resources")
    if not device_ids:
        return None
    demisto.debug(f"number of devices returned from the api call is: {len(device_ids)}")
    return http_request("POST", "/devices/entities/devices/v2", json={"ids": device_ids})


def behavior_to_entry_context(behavior):
    """
    Transforms a behavior to entry context representation
    :param behavior: Behavior dict in the format of crowdstrike's API response
    :return: Behavior in entry context representation
    """
    raw_entry = get_trasnformed_dict(behavior, DETECTIONS_BEHAVIORS_KEY_MAP)
    split_key_map = DETECTIONS_BEHAVIORS_SPLIT_KEY_MAP
    raw_entry.update(extract_transformed_dict_with_split(behavior, split_key_map))
    return raw_entry


def resolve_detection(ids, status, assigned_to_uuid, username, show_in_ui, comment, tag):
    """
    Sends a resolve detection request
    :param ids: Single or multiple ids in an array string format.
    :param status: New status of the detection.
    :param assigned_to_uuid: uuid to assign the detection to.
    :param username: Username to assign the detection to.
    :param show_in_ui: Boolean flag in string format (true/false).
    :param comment: Optional comment to add to the detection.
    :param The tag to add.
    :return: Resolve detection response json
    """
    payload = {"ids": ids}
    if status:
        payload["status"] = status
    if assigned_to_uuid:
        payload["assigned_to_uuid"] = assigned_to_uuid
    if show_in_ui:
        payload["show_in_ui"] = show_in_ui
    if comment:
        payload["comment"] = comment
    demisto.debug(f"in resolve_detection: {payload=}")
    # modify the payload to match the Raptor API
    ids = payload.pop("ids")
    payload["assign_to_uuid"] = payload.pop("assigned_to_uuid") if "assigned_to_uuid" in payload else None
    payload["assign_to_user_id"] = username if username else None
    payload["update_status"] = payload.pop("status") if "status" in payload else None
    payload["append_comment"] = payload.pop("comment") if "comment" in payload else None
    if tag:
        payload["add_tag"] = tag

    data = json.dumps(resolve_detections_prepare_body_request(ids, payload))
    url = "/alerts/entities/alerts/v3"
    return http_request("PATCH", url, data=data)


def contain_host(ids):
    """
    Contains host(s) with matching ids
    :param ids: IDs of host to contain
    :return: Contain host response json
    """
    payload = {"ids": ids}
    data = json.dumps(payload)
    params = {"action_name": "contain"}
    return http_request("POST", "/devices/entities/devices-actions/v2", data=data, params=params)


def lift_host_containment(ids, action_name: str = "lift_containment"):
    """
    Lifts off containment from host(s) with matching ids
    :param ids: IDs of host to lift off containment from
    :param action_name: The action to perform. Either 'lift_containment' or 'lift_filesystem_containment_all'.
    :return: Lift off containment response json
    """
    payload = {"ids": ids}
    data = json.dumps(payload)
    params = {"action_name": action_name}
    return http_request("POST", "/devices/entities/devices-actions/v2", data=data, params=params)


def timestamp_length_equalization(timestamp1, timestamp2):
    """
        Makes sure the timestamps are of the same length.
    Args:
        timestamp1: First timestamp to compare.
        timestamp2: Second timestamp to compare.
    Returns:
        the two timestamps in the same length (the longer one)
    """
    diff_len = len(str(timestamp1)) - len(str(timestamp2))

    # no difference in length
    if diff_len == 0:
        return int(timestamp1), int(timestamp2)

    # length of timestamp1 > timestamp2
    if diff_len > 0:
        ten_times = pow(10, diff_len)
        timestamp2 = int(timestamp2) * ten_times

    # length of timestamp2 > timestamp1
    else:
        ten_times = pow(10, diff_len * -1)
        timestamp1 = int(timestamp1) * ten_times

    return int(timestamp1), int(timestamp2)


def change_host_group(
    is_post: bool,
    host_group_id: str | None = None,
    name: str | None = None,
    group_type: str | None = None,
    description: str | None = None,
    assignment_rule: str | None = None,
) -> dict:
    method = "POST" if is_post else "PATCH"
    data = {
        "resources": [
            {
                "id": host_group_id,
                "name": name,
                "description": description,
                "group_type": group_type,
                "assignment_rule": assignment_rule,
            }
        ]
    }
    response = http_request(method=method, url_suffix="/devices/entities/host-groups/v1", json=data)
    return response


def change_host_group_members(action_name: str, host_group_id: str, host_ids: list[str]) -> dict:
    allowed_actions = {"add-hosts", "remove-hosts"}
    if action_name not in allowed_actions:
        raise DemistoException(f"CrowdStrike Falcon error: action name should be in {allowed_actions}")
    data = {"action_parameters": [{"name": "filter", "value": f"(device_id:{host_ids!s})"}], "ids": [host_group_id]}
    response = http_request(
        method="POST", url_suffix="/devices/entities/host-group-actions/v1", params={"action_name": action_name}, json=data
    )
    return response


def host_group_members(filter: str | None, host_group_id: str | None, limit: str | None, offset: str | None, sort: str | None):
    params = {"id": host_group_id, "filter": filter, "offset": offset, "limit": limit, "sort": sort}
    response = http_request(method="GET", url_suffix="/devices/combined/host-group-members/v1", params=params)
    return response


def update_detection_request(ids: list[str], status: str) -> dict:
    list_of_stats = STATUS_LIST_FOR_MULTIPLE_DETECTION_TYPES
    if status not in list_of_stats:
        raise DemistoException(f"CrowdStrike Falcon Error: Status given is {status} and it is not in {list_of_stats}")
    return resolve_detection(
        ids=ids, status=status, assigned_to_uuid=None, username=None, show_in_ui=None, comment=None, tag=None
    )


def update_request_for_multiple_detection_types(ids: list[str], status: str) -> dict:
    """
    Manage the status to send to update to for IDP/Mobile detections.
    :type ids: ``list``
    :param ids: The list of ids to update.
    :type status: ``str``
    :param status: The new status to set.
    :return: The response.
    :rtype ``dict``
    """
    if status not in STATUS_LIST_FOR_MULTIPLE_DETECTION_TYPES:
        raise DemistoException(
            f"CrowdStrike Falcon Error: Status given is {status} and it is not in {STATUS_LIST_FOR_MULTIPLE_DETECTION_TYPES}"
        )
    return resolve_detections_request(ids=ids, update_status=status)


def list_host_groups(filter: str | None, limit: str | None, offset: str | None) -> dict:
    params = {"filter": filter, "offset": offset, "limit": limit}
    response = http_request(method="GET", url_suffix="/devices/combined/host-groups/v1", params=params)
    return response


def delete_host_groups(host_group_ids: list[str]) -> dict:
    params = {"ids": host_group_ids}
    response = http_request(method="DELETE", url_suffix="/devices/entities/host-groups/v1", params=params)
    return response


def upload_batch_custom_ioc(ioc_batch: list[dict], timeout: float | None = None) -> dict:
    """
    Upload a list of IOC
    """
    payload = {"indicators": ioc_batch}

    return http_request("POST", "/iocs/entities/indicators/v1", json=payload, timeout=timeout)


def create_exclusion(exclusion_type: str, body: dict) -> dict:
    """
    Creates an exclusions based on a given json object.

    Args:
        exclusion_type: The exclusion type can be either ml (machine learning) or IOA`.
        exclusion_ids: A dict contains the exclusion data.
    Returns:
        Info about the created exclusion.
    """
    return http_request(method="POST", url_suffix=f"/policy/entities/{exclusion_type}-exclusions/v1", json=body)


def update_exclusion(exclusion_type: str, body: dict) -> dict:
    """
    Updates an exclusions based on its ID and a given json object.

    Args:
        exclusion_type: The exclusion type can be either ml (machine learning) or IOA`.
        exclusion_ids: A dict contains the exclusion data.
    Returns:
        Info about the updated exclusion.
    """
    return http_request("PATCH", f"/policy/entities/{exclusion_type}-exclusions/v1", json=body)


def delete_exclusion(exclusion_type: str, exclusion_ids: list) -> dict:
    """
    Deletes an exclusions based on its ID.

    Args:
        exclusion_type: The exclusion type can be either ml (machine learning) or IOA`.
        exclusion_ids: A list of exclusion IDs to delete.
    Returns:
        Info about the deleted exclusion.
    """
    return http_request(
        method="DELETE", url_suffix=f'/policy/entities/{exclusion_type}-exclusions/v1{"?ids=" + "&ids=".join(exclusion_ids)}'
    )


def get_exclusions(exclusion_type: str, filter_query: str | None, params: dict) -> dict:
    """
    Returns IDs of exclusions that match the filter / value

    Args:
        exclusion_type: The exclusion type can be either ml (machine learning) or IOA`.
        filter_query: Custom filter, For example `value:'<value>'`.
        params: API query params (sort, limit, offset).
    Returns:
        List of exclusion IDs.
    """
    return http_request(
        method="GET",
        url_suffix=f"/policy/queries/{exclusion_type}-exclusions/v1",
        params=assign_params(filter=filter_query, **params),
    )


def get_exclusion_entities(exclusion_type: str, exclusion_ids: list) -> dict:
    """
    Returns the exclusions based on a list of IDs.

    Args:
        exclusion_type: The exclusion type can be either ml (machine learning) or IOA`.
        exclusion_ids: A list of exclusion IDs to retrieve.
    Returns:
        List of exclusions.
    """
    return http_request(
        method="GET", url_suffix=f'/policy/entities/{exclusion_type}-exclusions/v1{"?ids=" + "&ids=".join(exclusion_ids)}'
    )


def list_quarantined_files_id(files_filter: dict | None, query: dict, pagination: dict) -> dict:
    """
    Returns the files ID's that match the filter / value.

    Args:
        files_filter: The exclusion type can be either ml (machine learning) or IOA`.
        query: The exclusion type can be either ml (machine learning) or IOA`.
        pagination: API query params for pagination (limit, offset).
    Returns:
        list: List of exclusions.
    """

    return http_request(
        method="GET",
        url_suffix="/quarantine/queries/quarantined-files/v1",
        params=assign_params(filter=files_filter, q=build_query_params(query), **pagination),
    )


def list_quarantined_files(ids: list) -> dict:
    """
    Returns the file's metadata based a list of IDs.

    Args:
        ids: A list of the IDs of the files.
    Returns:
        A list contains metadata about the files.
    """
    return http_request(method="POST", url_suffix="/quarantine/entities/quarantined-files/GET/v1", json={"ids": ids})


def apply_quarantined_files_action(body: dict) -> dict:
    """
    Applies action to quarantined files.

    Args:
        body: The request body with the parameters to update.
    Returns:
        A list contains metadata about the updated files.
    """
    return http_request(method="PATCH", url_suffix="/quarantine/entities/quarantined-files/v1", json=body)


""" MIRRORING COMMANDS """


def get_remote_data_command(args: dict[str, Any]):
    """
    get-remote-data command: Returns an updated remote incident or detection.
    Args:
        args:
            id: incident or detection id to retrieve.
            lastUpdate: when was the last time we retrieved data.

    Returns:
        GetRemoteDataResponse object, which contain the incident or detection data to update.
    """
    remote_args = GetRemoteDataArgs(args)
    remote_incident_id = remote_args.remote_incident_id
    reopen_statuses_list = argToList(demisto.params().get("reopen_statuses", ""))
    demisto.debug(f"In get_remote_data_command {reopen_statuses_list=}")

    mirrored_data = {}
    entries: list = []
    try:
        demisto.debug(
            f"Performing get-remote-data command with incident or detection id: {remote_incident_id} "
            f"and last_update: {remote_args.last_update}"
        )
        incident_type = find_incident_type(remote_incident_id)
        demisto.debug(f"Successfully identified incident type: {incident_type} for remote incident id: {remote_incident_id}")
        # for legacy endpoint detections
        if incident_type == IncidentType.LEGACY_ENDPOINT_DETECTION:
            mirrored_data, updated_object = get_remote_detection_data(remote_incident_id)
            if updated_object:
                demisto.debug(f"Update detection {remote_incident_id} with fields: {updated_object}")
                detection_type = "Detection"
                set_xsoar_entries(
                    updated_object, entries, remote_incident_id, detection_type, reopen_statuses_list
                )  # sets in place
        elif incident_type == IncidentType.NGSIEM_CASE:
            mirrored_data, updated_object = get_remote_ngsiem_case_data(remote_incident_id)
            if updated_object:
                demisto.debug(f"Update ngsiem case {remote_incident_id} with fields: {updated_object}")
                set_xsoar_entries(updated_object, entries, remote_incident_id, NGSIEM_CASE, reopen_statuses_list)  # sets in place
        # for endpoint in the new version
        elif incident_type in (
            IncidentType.ENDPOINT_OR_IDP_OR_MOBILE_OR_OFP_DETECTION,
            IncidentType.ON_DEMAND,
            IncidentType.THIRD_PARTY,
            IncidentType.NGSIEM_DETECTION,
            IncidentType.NGSIEM_AUTOMATED_LEAD,
        ):
            mirrored_data, updated_object, detection_type = get_remote_detection_data_for_multiple_types(remote_incident_id)
            if updated_object:
                demisto.debug(f"Update {detection_type} detection {remote_incident_id} with fields: {updated_object}")
                set_xsoar_entries(
                    updated_object, entries, remote_incident_id, detection_type, reopen_statuses_list
                )  # sets in place
        elif incident_type == IncidentType.RECON:
            mirrored_data, updated_object, incident_type = get_remote_recon_data(remote_incident_id)
            if updated_object:
                demisto.debug(f"Recon-Log Update {incident_type} incident {remote_incident_id} with fields: {updated_object}")
                set_xsoar_entries(updated_object, entries, remote_incident_id, incident_type, reopen_statuses_list)
        elif incident_type is None and remote_incident_id.startswith("inc:"):
            demisto.debug(
                f"Skipping get-remote-data for deprecated Endpoint Incident {remote_incident_id}. "
                "Endpoint Incident mirroring is no longer supported."
            )
            return GetRemoteDataResponse(mirrored_object=mirrored_data, entries=entries)
        else:
            # this is here as prints can disrupt mirroring
            raise Exception(f"Executed get-remote-data command with undefined id: {remote_incident_id}")

        if not updated_object:
            demisto.debug(f"No delta was found for detection {remote_incident_id}.")
        return GetRemoteDataResponse(mirrored_object=updated_object, entries=entries)

    except Exception as e:
        demisto.debug(
            f"Error in CrowdStrike Falcon incoming mirror for incident or detection: {remote_incident_id}\n"
            f"Error message: {e!s}"
        )

        if not mirrored_data:
            mirrored_data = {"id": remote_incident_id}
        mirrored_data["in_mirror_error"] = str(e)

        return GetRemoteDataResponse(mirrored_object=mirrored_data, entries=[])


def find_incident_type(remote_incident_id: str):
    if IncidentType.LEGACY_ENDPOINT_DETECTION.value in remote_incident_id:
        return IncidentType.LEGACY_ENDPOINT_DETECTION
    if IncidentType.ENDPOINT_OR_IDP_OR_MOBILE_OR_OFP_DETECTION.value in remote_incident_id:
        return IncidentType.ENDPOINT_OR_IDP_OR_MOBILE_OR_OFP_DETECTION
    if IncidentType.ON_DEMAND.value in remote_incident_id:
        return IncidentType.ON_DEMAND
    if IncidentType.NGSIEM_DETECTION.value in remote_incident_id:
        return IncidentType.NGSIEM_DETECTION
    if IncidentType.THIRD_PARTY.value in remote_incident_id:
        return IncidentType.THIRD_PARTY
    if IncidentType.RECON.value in remote_incident_id:
        return IncidentType.RECON
    if IncidentType.NGSIEM_AUTOMATED_LEAD.value in remote_incident_id:
        return IncidentType.NGSIEM_AUTOMATED_LEAD
    if IncidentType.NGSIEM_CASE.value in remote_incident_id:
        return IncidentType.NGSIEM_CASE
    demisto.debug(f"Unable to determine incident type for remote incident id: {remote_incident_id}")
    return None


def get_remote_ngsiem_case_data(remote_case_id: str):
    """
    Called every time get-remote-data command runs on a NGSIEM case.
    Gets the relevant case entity from the remote system (CrowdStrike Falcon).
    We take from this entity only the relevant incoming mirroring fields, in order to do the mirroring.
    """
    # We remove the prefix IncidentType to make the API call, since the CS API does not recognize our internal prefix
    original_remote_case_id = remote_case_id.replace(f"{IncidentType.NGSIEM_CASE.value}:", "", 1)
    mirrored_case_list = get_cases_details([original_remote_case_id])
    if not mirrored_case_list:
        raise DemistoException(f"Could not find ngsiem case with {original_remote_case_id=}")
    mirrored_case = mirrored_case_list[0]
    updated_object = {"incident_type": NGSIEM_CASE}
    set_updated_object(updated_object, mirrored_case, NGSIEM_MIRRORING_FIELDS)
    return mirrored_case, updated_object


def get_remote_detection_data(remote_incident_id: str):
    """
    Called every time get-remote-data command runs on an detection.
    Gets the relevant detection entity from the remote system (CrowdStrike Falcon). The remote system returns a list with this
    entity in it. We take from this entity only the relevant incoming mirroring fields, in order to do the mirroring.
    """
    mirrored_data_list = get_detections_entities([remote_incident_id]).get("resources", [])  # a list with one dict in it
    mirrored_data = mirrored_data_list[0]
    # severity key name is different in the raptor version
    severity = mirrored_data.get("severity_name")
    mirrored_data["severity"] = severity_string_to_int(severity)
    demisto.debug(f"In get_remote_detection_data {remote_incident_id=} {mirrored_data=}")

    incoming_args = CS_FALCON_DETECTION_INCOMING_ARGS
    updated_object: dict[str, Any] = {"incident_type": "detection"}
    set_updated_object(updated_object, mirrored_data, incoming_args)
    demisto.debug(f"After set_updated_object {updated_object=}")
    return mirrored_data, updated_object


def get_remote_detection_data_for_multiple_types(remote_incident_id):
    """
    Gets the relevant detection entity from the remote system (CrowdStrike Falcon).
    This function handles the following detection types:
    - IDP (Identity Protection)
    - Mobile
    - Detection (not legacy)
    - OFP (Other File Protection)
    - ODS (On-Demand Scans)
    - NGSIEM (Next-Generation Security Information and Event Management)
    - THIRD PARTY Detection

    :type remote_incident_id: ``str``
    :param remote_incident_id: The incident id to return its information.

    :return: The detection entity.
    :rtype ``dict``
    :return: The object with the updated fields.
    :rtype ``dict``
    :return: The detection type.
    :rtype ``str``
    """
    mirrored_data_list = get_detection_entities([remote_incident_id]).get("resources", [])  # a list with one dict in it
    mirrored_data = mirrored_data_list[0]
    detection_type = ""
    mirroring_fields = ["status"]
    updated_object: dict[str, Any] = {}
    # Check type-based conditions first (more specific) before product-based conditions (more generic).
    # ODS and OFP detections carry product=epp but must be classified by their type, not their product.
    if "ofp" in mirrored_data["type"]:
        updated_object = {"incident_type": OFP_DETECTION}
        detection_type = "ofp"
        mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS
    elif "ods" in mirrored_data["type"]:
        updated_object = {"incident_type": ON_DEMAND_SCANS_DETECTION}
        detection_type = "ods"
        mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS
    elif "idp" in mirrored_data["product"]:
        updated_object = {"incident_type": IDP_DETECTION}
        detection_type = "IDP"
        mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS_IDP
    elif "mobile" in mirrored_data["product"]:
        updated_object = {"incident_type": MOBILE_DETECTION}
        detection_type = "Mobile"
        mirroring_fields.append("mobile_detection_id")
    elif "epp" in mirrored_data["product"]:
        updated_object = {"incident_type": ENDPOINT_DETECTION}
        detection_type = "Detection"
        mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS
    elif "ngsiem" in mirrored_data["product"]:
        updated_object = {"incident_type": NGSIEM_DETECTION}
        detection_type = "ngsiem"
        mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS
    elif "xdr" in mirrored_data["product"]:
        updated_object = {"incident_type": NGSIEM_INCIDENT}
        detection_type = "xdr"
        mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS
    elif "automated-lead" in mirrored_data["product"]:
        updated_object = {"incident_type": NGSIEM_AUTOMATED_LEAD}
        detection_type = "automated-lead"
        mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS
    elif "thirdparty" in mirrored_data["product"]:
        updated_object = {"incident_type": THIRD_PARTY_DETECTION}
        detection_type = "thirdparty"
        mirroring_fields = CS_FALCON_DETECTION_INCOMING_ARGS
    set_updated_object(updated_object, mirrored_data, mirroring_fields)
    demisto.debug(f"in get_remote_detection_data_for_multiple_types {mirrored_data=} { mirroring_fields=} {updated_object=}")
    return mirrored_data, updated_object, detection_type


def get_remote_recon_data(remote_incident_id: str):
    """
    Called every time get-remote-data command runs on a Recon notification.
    Gets the relevant Recon notification entity from the remote system (CrowdStrike Falcon).
    We take from this entity only the relevant incoming mirroring fields, in order to do the mirroring.

    :param remote_incident_id: The remote incident ID.
    :return: The mirrored data, the updated object, and the incident type.
    """
    demisto.debug(f"Recon-Log in get_remote_recon_data {remote_incident_id=}")
    remote_id = remote_incident_id.replace(f"{IncidentType.RECON.value}", "", 1)
    mirrored_data_list = get_recon_notifications_detailed([remote_id])
    if not mirrored_data_list:
        raise DemistoException(f"No Recon notification found for ID: {remote_incident_id}")
    mirrored_data = mirrored_data_list[0]
    demisto.debug(f"Recon-Log in get_remote_recon_data {mirrored_data=}")
    updated_object = {"incident_type": RECON_NOTIFICATION}
    set_updated_object(updated_object, mirrored_data, CS_FALCON_RECON_INCOMING_ARGS)
    if "notification.status" in updated_object:
        updated_object["status"] = updated_object["notification.status"]
    demisto.debug(f"Recon-Log in get_remote_recon_data {mirrored_data=} {CS_FALCON_RECON_INCOMING_ARGS=} {updated_object=}")
    return mirrored_data, updated_object, RECON_NOTIFICATION


def update_remote_recon_notification(delta: Dict[str, Any], inc_status: int, remote_incident_id: str) -> str:
    """
    Updates the status of a CrowdStrike Falcon Recon Notification via PATCH API call (Mirror Out)
    using the generic http_request function.

    :type delta: ``dict``
    :param delta: Dictionary of fields changed in the local XSOAR incident.
    :type inc_status: ``int``
    :param inc_status: The current status of the local XSOAR incident (0=Closed, 1=Active).
    :type remote_incident_id: ``str``
    :param remote_incident_id: The ID of the Recon Notification in CrowdStrike.
    :return: The API response payload on success, or empty string if no relevant change found.
    :type: ``str``
    """
    demisto.debug(f"Recon-Log in update_remote_recon_notification {delta=} {inc_status=} {remote_incident_id=}")
    remote_id = remote_incident_id.replace(f"{IncidentType.RECON.value}", "", 1)
    if inc_status == IncidentStatus.DONE and close_in_cs_falcon(delta):
        demisto.debug(f"Recon-Log Closing Recon Notification: {remote_id} in remote system.")
        close_reason = delta.get("closeReason")
        status = "closed-true-positive" if close_reason in ("True Positive", "Resolved") else "closed-false-positive"
        result = str(patch_remote_entity(remote_id, status=status, is_recon_type=True))
        demisto.debug(f"Recon-Log result closing Recon Notification: {remote_id} in remote system. {result=}")
        return result
    elif "status" in delta:
        demisto.debug(f"Recon-Log Updating Recon Notification: {remote_id} with status: {delta.get('status')} in remote system.")
        result = str(patch_remote_entity(remote_id, status=delta.get("status"), is_recon_type=True))
        demisto.debug(f"Recon-Log result closing Recon Notification: {remote_id} in remote system. {result=}")
        return result
    demisto.debug(f"Recon-Log No relevant change found for Recon Notification: {remote_id}")
    return ""


def get_modified_recon_ids(last_update_timestamp: str) -> List[str]:
    """
    Fetches the IDs of Recon notifications that have been modified (status update)
    since the last synchronization timestamp.

    :param last_update_timestamp: The last update timestamp.
    :return: A list of modified Recon notification IDs.
    """
    mirror_status_filter = (
        f"status:['in-progress','closed-false-positive','closed-true-positive']+updated_date:>'{last_update_timestamp}'"
    )
    demisto.debug(f"Recon-Log get_modified_recon_ids filter: {mirror_status_filter=}")

    try:
        ids, _, _ = recon_notifications_pagination(
            filter=mirror_status_filter,
            api_limit=RECON_API_LIMIT,
            recon_offset=0,
            fetch_limit=MAX_FETCH_RECON,
            is_fetch=False,
        )
        prefixed_incident_ids = [f"{IncidentType.RECON.value}{id}" for id in ids]
        demisto.debug(f"Recon-Log get_modified_recon_ids return: {prefixed_incident_ids}")
        return prefixed_incident_ids

    except Exception as e:
        error_msg = f"Failed to fetch modified Recon IDs. Filter: {mirror_status_filter}. Error: {str(e)}"
        demisto.error(error_msg)
        return []


def set_xsoar_entries(
    updated_object: dict[str, Any], entries: list, remote_detection_id: str, incident_type_name: str, reopen_statuses_list: list
):
    """
    Send the updated object to the relevant status handler

    :type updated_object: ``dict``
    :param updated_object: The updated object.
    :type entries: ``list``
    :param entries: The list of entries to add the new entry into.
    :type remote_detection_id: ``str``
    :param remote_detection_id: the remote detection id
    :type reopen_statuses_list: ``list``
    :param reopen_statuses_list: the set of statuses that should reopen an incident in XSOAR.

    :return: The response.
    :rtype ``dict``
    """
    reopen_statuses_set = {str(status).lower().strip().replace(" ", "_").replace("-", "_") for status in reopen_statuses_list}
    demisto.debug(f"In set_xsoar_entries {reopen_statuses_set=} {remote_detection_id=}")
    if demisto.params().get("close_incident"):
        status = updated_object.get("status", "").lower()
        if status.startswith("closed"):
            close_in_xsoar(entries, remote_detection_id, incident_type_name)
        elif updated_object.get("status", "").lower().replace("-", "_") in reopen_statuses_set:
            reopen_in_xsoar(entries, remote_detection_id, incident_type_name)
        else:
            demisto.debug(
                f"In set_xsoar_entries not closing and not reopening {remote_detection_id=}"
                f" since {updated_object.get('status')=} and {reopen_statuses_set=}."
            )


def close_in_xsoar(entries: list, remote_incident_id: str, incident_type_name: str):
    demisto.debug(f"{incident_type_name} is closed: {remote_incident_id}")
    entries.append(
        {
            "Type": EntryType.NOTE,
            "Contents": {"dbotIncidentClose": True, "closeReason": f"{incident_type_name} was closed on CrowdStrike Falcon"},
            "ContentsFormat": EntryFormat.JSON,
        }
    )


def reopen_in_xsoar(entries: list, remote_incident_id: str, incident_type_name: str):
    demisto.debug(f"{incident_type_name} is reopened: {remote_incident_id}")
    entries.append({"Type": EntryType.NOTE, "Contents": {"dbotIncidentReopen": True}, "ContentsFormat": EntryFormat.JSON})


def set_updated_object(updated_object: dict[str, Any], mirrored_data: dict[str, Any], mirroring_fields: list[str]):
    """
    Sets the updated object (in place) for the incident or detection we want to mirror in, from the mirrored data, according to
    the mirroring fields. In the mirrored data, the mirroring fields might be nested in a dict or in a dict inside a list (if so,
    their name will have a dot in it).
    Note that the fields that we mirror right now may have only one dot in them, so we only deal with this case.

    :param updated_object: The dictionary to set its values, so it will hold the fields we want to mirror in, with their values.
    :param mirrored_data: The data of the incident or detection we want to mirror in.
    :param mirroring_fields: The mirroring fields that we want to mirror in, given according to whether we want to mirror an
        incident or a detection.
    """
    for field in mirroring_fields:
        if mirrored_data.get(field):
            updated_object[field] = mirrored_data.get(field)

        # if the field is not in mirrored_data, it might be a nested field - that has a . in its name
        elif "." in field:
            field_name_parts = field.split(".")
            nested_mirrored_data = mirrored_data.get(field_name_parts[0])

            if isinstance(nested_mirrored_data, list):
                # if it is a list, it should hold a dictionary in it because it is a json structure
                for nested_field in nested_mirrored_data:
                    if nested_field.get(field_name_parts[1]):
                        updated_object[field] = nested_field.get(field_name_parts[1])
                        # finding the field in the first time it is satisfying
                        break
            elif isinstance(nested_mirrored_data, dict) and nested_mirrored_data.get(field_name_parts[1]):
                updated_object[field] = nested_mirrored_data.get(field_name_parts[1])


def get_modified_remote_data_command(args: dict[str, Any]):
    """
    Gets the modified remote incidents and detections IDs.
    Args:
        args:
            last_update: the last time we retrieved modified incidents and detections.

    Returns:
        GetModifiedRemoteDataResponse object, which contains a list of the retrieved incidents and detections IDs.
    """
    remote_args = GetModifiedRemoteDataArgs(args)

    last_update_utc = dateparser.parse(remote_args.last_update, settings={"TIMEZONE": "UTC"})  # convert to utc format
    assert last_update_utc is not None, f"could not parse{remote_args.last_update}"
    last_update_timestamp = last_update_utc.strftime("%Y-%m-%dT%H:%M:%SZ")
    demisto.debug(f"Remote arguments last_update in UTC is {last_update_timestamp}")
    fetch_types = demisto.params().get("fetch_incidents_or_detections", "")

    raw_ids = []

    if "Detections" in fetch_types or "Endpoint Detection" in fetch_types:
        raw_ids += get_fetch_detections(last_updated_timestamp=last_update_timestamp, has_limit=False).get("resources", [])
    if IDP_DETECTION_FETCH_TYPE in fetch_types:
        raw_ids += get_detections_ids(
            filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+product:'idp'"
        ).get("resources", [])

    if MOBILE_DETECTION_FETCH_TYPE in fetch_types:
        raw_ids += get_detections_ids(
            filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+product:'mobile'"
        ).get("resources", [])
    if ON_DEMAND_SCANS_DETECTION_TYPE in fetch_types:
        raw_ids += get_detections_ids(
            filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+type:'ods'"
        ).get("resources", [])
    if OFP_DETECTION_TYPE in fetch_types:
        raw_ids += get_detections_ids(
            filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+type:'ofp'"
        ).get("resources", [])
    if NGSIEM_DETECTION_FETCH_TYPE in fetch_types:
        raw_ids += get_detections_ids(
            filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+product:'ngsiem'"
        ).get("resources", [])
    if NGSIEM_INCIDENT_FETCH_TYPE in fetch_types:
        demisto.debug("fetching ngsiem incident ids")
        raw_ids += get_detections_ids(
            filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+product:'xdr'"
        ).get("resources", [])
        demisto.debug(f"new {raw_ids=}")
    if NGSIEM_AUTOMATED_LEADS_FETCH_TYPE in fetch_types:
        raw_ids += get_detections_ids(
            filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+product:'automated-lead'"
        ).get("resources", [])
    if THIRD_PARTY_DETECTION_FETCH_TYPE in fetch_types:
        raw_ids += get_detections_ids(
            filter_arg=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'+product:'thirdparty'"
        ).get("resources", [])
    if RECON_FETCH_TYPE in fetch_types:
        raw_ids += get_modified_recon_ids(last_update_timestamp=last_update_timestamp)
    if NGSIEM_CASES_FETCH_TYPE in fetch_types:
        _, case_ids = get_cases_data(
            url_filter=f"updated_timestamp:>'{last_update_utc.strftime(DETECTION_DATE_FORMAT)}'",
            limit=INCIDENTS_PER_FETCH,
            offset=0,
        )
        raw_ids += [f"{IncidentType.NGSIEM_CASE.value}:{case_id}" for case_id in case_ids]

    modified_ids_to_mirror = list(map(str, raw_ids))
    demisto.debug(f"All ids to mirror in are: {modified_ids_to_mirror}")
    return GetModifiedRemoteDataResponse(modified_ids_to_mirror)


def update_remote_system_command(args: dict[str, Any]) -> str:
    """
    Mirrors out local changes to the remote system.
    Args:
        args: A dictionary containing the data regarding a modified incident, including: data, entries, incident_changed,
         remote_incident_id, inc_status, delta

    Returns:
        The remote incident id that was modified. This is important when the incident is newly created remotely.
    """
    parsed_args = UpdateRemoteSystemArgs(args)
    delta = parsed_args.delta
    remote_incident_id = parsed_args.remote_incident_id
    demisto.debug(f"Got the following data {parsed_args.data}, and delta {delta} for the following {remote_incident_id=}.")
    if delta:
        demisto.debug(f"Got the following delta keys {list(delta.keys())}.")

    try:
        incident_type = find_incident_type(remote_incident_id)
        demisto.debug(f"Successfully identified incident type: {incident_type} for remote incident id: {remote_incident_id}")
        if parsed_args.incident_changed:
            if incident_type in (IncidentType.ON_DEMAND, IncidentType.LEGACY_ENDPOINT_DETECTION):
                result = update_remote_detection(delta, parsed_args.inc_status, remote_incident_id)
                if result:
                    demisto.debug(f"Detection updated successfully. Result: {result}")

            elif incident_type in (
                IncidentType.ENDPOINT_OR_IDP_OR_MOBILE_OR_OFP_DETECTION,
                IncidentType.NGSIEM_DETECTION,
                IncidentType.THIRD_PARTY,
                IncidentType.NGSIEM_AUTOMATED_LEAD,
            ):
                result = update_remote_for_multiple_detection_types(delta, parsed_args.inc_status, remote_incident_id)
                if result:
                    demisto.debug(f"IDP/Mobile/NGSIEM/Third Party Detection updated successfully. Result: {result}")
            elif incident_type == IncidentType.NGSIEM_CASE:
                result = update_remote_ngsiem_case(delta, parsed_args.inc_status, remote_incident_id)
                if result:
                    demisto.debug(f"NGSIEM case updated successfully. Result: {result}")
            elif incident_type == IncidentType.RECON:
                result = update_remote_recon_notification(delta, parsed_args.inc_status, remote_incident_id)
                if result:
                    demisto.debug(f"Recon-Log Recon notification updated successfully. Result: {result}")
            elif incident_type is None and remote_incident_id.startswith("inc:"):
                demisto.debug(
                    f"Skipping update-remote-system for deprecated Endpoint Incident {remote_incident_id}. "
                    "Endpoint Incident mirroring is no longer supported."
                )
            else:
                raise Exception(f"Executed update-remote-system command with undefined id: {remote_incident_id}")

        else:
            demisto.debug(f"Skipping updating remote incident or detection {remote_incident_id} as it didn't change.")

    except Exception as e:
        demisto.error(
            f"Error in CrowdStrike Falcon outgoing mirror for incident or detection {remote_incident_id}. "
            f"Error message: {e!s}"
        )

    return remote_incident_id


def close_in_cs_falcon(delta: dict[str, Any]) -> bool:
    """
    Closing in the remote system should happen only when both:
        1. The user asked for it
        2. One of the closing fields appears in the delta

    The second is mandatory so we will not send a closing request at all of the mirroring requests that happen after closing an
    incident (in case where the incident is updated so there is a delta, but it is not the status that was changed).
    """
    closing_fields = {"closeReason", "closingUserId", "closeNotes"}
    return demisto.params().get("close_in_cs_falcon") and any(field in delta for field in closing_fields)


def update_remote_detection(delta, inc_status: IncidentStatus, detection_id: str) -> str:
    if inc_status == IncidentStatus.DONE and close_in_cs_falcon(delta):
        demisto.debug(f"Closing detection with remote ID {detection_id} in remote system.")
        return str(update_detection_request([detection_id], "closed"))

    # status field in CS Falcon is mapped to State field in XSOAR
    elif "status" in delta:
        demisto.debug(f'Detection with remote ID {detection_id} status will change to "{delta.get("status")}" in remote system.')
        return str(update_detection_request([detection_id], delta.get("status")))

    return ""


def update_remote_ngsiem_case(delta, inc_status: IncidentStatus, ngsiem_case_id: str) -> str:
    """
    Sends the request to update the relevant NGSIEM case entity.
    :type delta: ``dict``
    :param delta: The modified fields.
    :type inc_status: ``IncidentStatus``
    :param inc_status: The NGSIEM case status.
    :type ngsiem_case_id: ``str``
    :param ngsiem_case_id: The NGSIEM case ID to update.
    :return: The response.
    :rtype ``str``
    """
    remote_id = ngsiem_case_id.replace(f"{IncidentType.NGSIEM_CASE.value}:", "", 1)
    if inc_status == IncidentStatus.DONE and close_in_cs_falcon(delta):
        demisto.debug(f"Closing case with remote ID {remote_id} in remote system.")
        return str(patch_remote_entity(remote_id, status="closed"))
    elif "status" in delta:
        return str(patch_remote_entity(remote_id, status=delta.get("status")))
    return ""


def update_remote_for_multiple_detection_types(delta, inc_status: IncidentStatus, detection_id: str) -> str:
    """
    Sends the request to update the relevant IDP/Mobile/NGSIEM/Third Party/Recon Notifications entity.

    :type delta: ``dict``
    :param delta: The modified fields.
    :type inc_status: ``IncidentStatus``
    :param inc_status: The IDP/Mobile/NGSIEM/Third Party/Recon Notifications status.
    :type detection_id: ``str``
    :param detection_id: The IDP/Mobile/NGSIEM/Third Party/Recon Notifications ID to update.
    """
    if inc_status == IncidentStatus.DONE and close_in_cs_falcon(delta):
        demisto.debug(
            f"Closing IDP/Mobile/NGSIEM/Third Party/Recon Notifications with remote ID {detection_id} in remote system."
        )
        return str(update_request_for_multiple_detection_types([detection_id], "closed"))

    # status field in CS Falcon is mapped to State field in XSOAR
    elif "status" in delta:
        demisto.debug(f'Detection with remote ID {detection_id} status will change to "{delta.get("status")}" in remote system.')
        return str(update_request_for_multiple_detection_types([detection_id], delta.get("status")))

    return ""


def get_mapping_fields_command() -> GetMappingFieldsResponse:
    """
    Returns the list of fields to map in outgoing mirroring, for incidents and detections.
    """
    mapping_response = GetMappingFieldsResponse()

    # Supported only in the new version (Raptor) and not in the legacy version
    detection_types = [
        "CrowdStrike Falcon Detection",
        "CrowdStrike Falcon OFP Detection",
        "CrowdStrike Falcon On-Demand Scans Detection",
    ]

    for detection_type in detection_types:
        detection_type_scheme = SchemeTypeMapping(type_name=detection_type)
        for argument, description in CS_FALCON_DETECTION_OUTGOING_ARGS.items():
            detection_type_scheme.add_field(name=argument, description=description)
        mapping_response.add_scheme_type(detection_type_scheme)
    return mapping_response


""" COMMANDS FUNCTIONS """


def migrate_last_run(last_run: dict[str, str] | list[dict], is_fetch_events: bool = False) -> list[dict]:
    """This function migrated from old last run object to new last run object

    Args:
        last_run (dict[str, str]): Old last run object.

    Returns:
        list[dict]: New last run object.
    """
    if isinstance(last_run, list):
        return last_run
    else:
        updated_last_run_detections: dict[str, str | None] = {}
        if (detection_time := last_run.get("first_behavior_detection_time")) and (
            detection_time_date := dateparser.parse(detection_time)
        ):
            updated_last_run_detections["time"] = detection_time_date.strftime(DATE_FORMAT)

        updated_last_run_incidents: dict[str, str | None] = {}
        if (incident_time := last_run.get("first_behavior_incident_time")) and (
            incident_time_date := dateparser.parse(incident_time)
        ):
            updated_last_run_incidents["time"] = incident_time_date.strftime(DATE_FORMAT)

        last_run_length = TOTAL_FETCH_TYPE_XSIAM if is_fetch_events else TOTAL_FETCH_TYPE_XSOAR
        result = [updated_last_run_detections, updated_last_run_incidents]
        current_length = len(result)
        result.extend([{} for _ in range(last_run_length - current_length)])
        return result


def fetch_endpoint_detections(current_fetch_info_detections, look_back, is_fetch_events):
    """
    Fetch detections from CrowdStrike Falcon api.

    Args:
        current_fetch_info_detections (dict): The last_run for detection fetch type, Contains information about the last fetch run
        look_back (int): Number of days to look back for detection
        is_fetch_events: Flag to determine whether it's for fetch-events command (XSIAM) or fetch-incidents command (XSOAR).

    Returns:
        tuple: A tuple containing a list of detections and the updated fetch information dictionary.
    """
    detections = []
    # The configured per-run limit (10000 for XSIAM, "Max incidents per fetch" for XSOAR).
    base_fetch_limit = MAX_FETCH_DETECTION_PER_API_CALL if is_fetch_events else INCIDENTS_PER_FETCH

    detections_offset: int = current_fetch_info_detections.get("offset") or 0
    start_fetch_time, end_fetch_time = get_fetch_run_time_range(
        last_run=current_fetch_info_detections, first_fetch=FETCH_TIME, look_back=look_back, date_format=DETECTION_DATE_FORMAT
    )
    fetch_limit = current_fetch_info_detections.get("limit") or base_fetch_limit
    incident_type = "detection"

    # The API rejects requests where offset + limit exceeds MAX_FETCH_SIZE. With look_back, fetch_limit can grow
    # past that bound, so cap the value sent to the API while keeping fetch_limit for dedup and last_run bookkeeping.
    api_limit = min(fetch_limit, MAX_FETCH_SIZE - detections_offset)

    fetch_query = demisto.params().get("fetch_query")
    if fetch_query:
        fetch_query = f"(created_timestamp:>'{start_fetch_time}')+({fetch_query})"
        response = get_fetch_detections(filter_arg=fetch_query, limit=api_limit, offset=detections_offset)
    else:
        response = get_fetch_detections(last_created_timestamp=start_fetch_time, limit=api_limit, offset=detections_offset)

    detections_ids: list[dict] = demisto.get(response, "resources", [])
    total_detections = demisto.get(response, "meta.pagination.total")
    detections_offset = calculate_new_offset(detections_offset, len(detections_ids), total_detections)
    if detections_offset:
        if detections_offset + fetch_limit > MAX_FETCH_SIZE:
            demisto.debug(
                f"CrowdStrikeFalconMsg: The new offset: {detections_offset} + limit: {fetch_limit} reached "
                f"{MAX_FETCH_SIZE}, resetting the offset to 0"
            )
            detections_offset = 0
        demisto.debug(f"CrowdStrikeFalconMsg: The new detections offset is {detections_offset}")
    raw_res = get_detections_entities(detections_ids)

    if raw_res is not None and "resources" in raw_res:
        full_detections = demisto.get(raw_res, "resources")
        # detection_id is for the old version of the API, composite_id is for the new version (Raptor)
        for detection in full_detections:
            detection_id = detection.get("composite_id")
            detection["incident_type"] = incident_type
            demisto.debug(
                f"CrowdStrikeFalconMsg: Detection {detection_id} "
                f"was fetched which was created in {detection['created_timestamp']}"
            )
            incident = detection_to_incident(detection, is_fetch_events=is_fetch_events)
            detections.append(incident)

    detections = filter_incidents_by_duplicates_and_limit(
        incidents_res=detections, last_run=current_fetch_info_detections, fetch_limit=fetch_limit, id_field="name"
    )

    for detection in detections:
        occurred = dateparser.parse(detection["occurred"])
        if occurred:
            detection["occurred"] = occurred.strftime(DETECTION_DATE_FORMAT)
            demisto.debug(f"CrowdStrikeFalconMsg: Detection {detection['name']} occurred at {detection['occurred']}")

    current_fetch_info_detections = update_last_run_object(
        last_run=current_fetch_info_detections,
        incidents=detections,
        fetch_limit=base_fetch_limit,
        start_fetch_time=start_fetch_time,
        end_fetch_time=end_fetch_time,
        look_back=look_back,
        created_time_field="occurred",
        id_field="name",
        date_format=DETECTION_DATE_FORMAT,
        new_offset=detections_offset,
    )
    demisto.debug(f"CrowdStrikeFalconMsg: Ending fetch endpoint_detections. Fetched {len(detections) if detections else 0}")

    return detections, current_fetch_info_detections


def fetch_iom_incidents(iom_last_run):
    demisto.debug("Fetching Indicator of Misconfiguration incidents")
    demisto.debug(f"{iom_last_run=}")
    fetch_query = demisto.params().get("iom_fetch_query", "")
    validate_iom_fetch_query(iom_fetch_query=fetch_query)

    last_resource_ids, iom_next_token, last_scan_time, first_fetch_timestamp = get_current_fetch_data(
        last_run_object=iom_last_run,
        date_format=IOM_DATE_FORMAT,
        last_date_key="last_scan_time",
        next_token_key="iom_next_token",
        last_fetched_ids_key="last_resource_ids",
    )
    filter = create_iom_filter(
        is_paginating=bool(iom_next_token),
        last_fetch_filter=iom_last_run.get("last_fetch_filter", ""),
        last_scan_time=last_scan_time,
        first_fetch_timestamp=first_fetch_timestamp,
        configured_fetch_query=fetch_query,
    )
    demisto.debug(f"IOM {filter=}")
    iom_resource_ids, iom_new_next_token = iom_ids_pagination(
        filter=filter, iom_next_token=iom_next_token, fetch_limit=INCIDENTS_PER_FETCH, api_limit=500
    )
    demisto.debug(f'Fetched the following IOM resource IDS: {", ".join(iom_resource_ids)}')
    iom_incidents, fetched_resource_ids, new_scan_time = parse_ioa_iom_incidents(
        fetched_data=get_iom_resources(iom_resource_ids=iom_resource_ids),
        last_date=last_scan_time,
        last_fetched_ids=last_resource_ids,
        date_key="scan_time",
        id_key="id",
        date_format=IOM_DATE_FORMAT,
        is_paginating=bool(iom_new_next_token or iom_next_token),
        to_incident_context=iom_resource_to_incident,
        incident_type="iom_configurations",
    )

    iom_last_run = {
        "iom_next_token": iom_new_next_token,
        "last_scan_time": new_scan_time,
        "last_fetch_filter": filter,
        "last_resource_ids": fetched_resource_ids or last_resource_ids,
    }

    return iom_incidents, iom_last_run


def set_last_run_per_type(last_run: list, index: LastRunIndex, data: dict, is_fetch_events=False) -> None:
    """
    Safely set the specific sub last_run dictionary in the main last_run list.

    Args:
        last_run: The last_run list of dictionaries, where each index represents a last_run object for different fetch type.
        fetch_type: The fetch type index enum
        data: The data to set for the specified fetch type
        is_fetch_events: Flag to determine whether it's for fetch-events command (XSIAM) or fetch-incidents command (XSOAR).

    Returns:
        The updated last_run list.
    """
    demisto.debug(f"CrowdStrikeFalconMsg: set_last_run_per_type with {index=}")
    if not isinstance(data, dict):
        return_error(f"Invalid data type : last_run is a list of dictionary, expected dictionary, got {type(data).__name__}")
    last_run_length = TOTAL_FETCH_TYPE_XSIAM if is_fetch_events else TOTAL_FETCH_TYPE_XSOAR
    if index >= last_run_length:
        return_error(f"Invalid last_run index {index}, cannot exceed {last_run_length - 1}")
    if index < 0:
        return_error(f"Invalid last_run index {index}, index cannot be negative")
    # Extend the list if necessary to accommodate the fetch_type index
    while len(last_run) <= index:
        last_run.append({})
    last_run[index] = data
    demisto.debug(f"CrowdStrikeFalconMsg: {last_run}")


def get_last_run_per_type(last_run: list, fetch_type: LastRunIndex) -> dict:
    """
    Safely get the specific sub last_run dictionary from the main last_run list.

    Args:
        last_run: The last_run list of dictionaries, where each index represents a last_run object for different fetch type.
        fetch_type: The fetch type index enum
        default: Default value if not present (defaults to empty dict)

    Returns:
        The last_run dict from the list for the requested type.
    """

    if len(last_run) <= fetch_type:
        return {}

    return last_run[fetch_type]


def fetch_items(command="fetch-incidents"):
    """
    Fetch incidents or events from CrowdStrike Falcon based on configuration.

    Args:
        command (str): Either 'fetch-incidents' or 'fetch-events'

    Returns:
        tuple: (last_run, items) where last_run is the updated state and items are the fetched incidents/events
    """

    is_fetch_events = command == "fetch-events"
    items = []
    params = demisto.params()

    # Initialize and migrate last_run
    last_run = demisto.getLastRun()
    last_run_length = TOTAL_FETCH_TYPE_XSIAM if is_fetch_events else TOTAL_FETCH_TYPE_XSOAR
    if not last_run:
        last_run = [{} for _ in range(last_run_length)]
    last_run = migrate_last_run(last_run, is_fetch_events)

    # last_run objects - common for fetch_incident and fetch_events
    detections_last_run: dict = get_last_run_per_type(last_run, LastRunIndex.DETECTIONS)
    idp_detections_last_run: dict = get_last_run_per_type(last_run, LastRunIndex.IDP_DETECTIONS)
    mobile_detections_last_run: dict = get_last_run_per_type(last_run, LastRunIndex.MOBILE_DETECTIONS)
    on_demand_detections_last_run: dict = get_last_run_per_type(last_run, LastRunIndex.ON_DEMAND_DETECTIONS)
    ofp_detection_last_run: dict = get_last_run_per_type(last_run, LastRunIndex.OFP_DETECTION)

    # last_run objects - fetch types only for fetch-incidents
    iom_last_run: dict[str, Any] = {}
    ioa_last_run: dict[str, Any] = {}
    third_party_detection_last_run: dict[str, Any] = {}
    ngsiem_detection_last_run: dict[str, Any] = {}
    recon_last_run: dict[str, Any] = {}
    ngsiem_incident_last_run: dict[str, Any] = {}
    ngsiem_automated_lead_last_run: dict[str, Any] = {}
    ngsiem_case_last_run: dict[str, Any] = {}

    if is_fetch_events:
        fetch_incidents_or_detections = params.get("fetch_events_or_detections", "")
        look_back = int(params.get("look_back_xsiam") or 2)
    else:
        fetch_incidents_or_detections = params.get("fetch_incidents_or_detections", "")
        look_back = int(params.get("look_back") or 2)

        # last_run object - Only for fetch_incident
        iom_last_run = get_last_run_per_type(last_run, LastRunIndex.IOM)
        ioa_last_run = get_last_run_per_type(last_run, LastRunIndex.IOA)
        third_party_detection_last_run = get_last_run_per_type(last_run, LastRunIndex.THIRD_PARTY_DETECTIONS)
        ngsiem_detection_last_run = get_last_run_per_type(last_run, LastRunIndex.NGSIEM_DETECTIONS)
        recon_last_run = get_last_run_per_type(last_run, LastRunIndex.RECON_NOTIFICATIONS)
        ngsiem_incident_last_run = get_last_run_per_type(last_run, LastRunIndex.NGSIEM_INCIDENTS)
        ngsiem_automated_lead_last_run = get_last_run_per_type(last_run, LastRunIndex.NGSIEM_AUTOMATED_LEADS)
        ngsiem_case_last_run = get_last_run_per_type(last_run, LastRunIndex.NGSIEM_CASES)

    demisto.debug(f"CrowdstrikeFalconMsg: Selected fetch types: {fetch_incidents_or_detections}")

    # Fetch Endpoint Detections
    if is_detection_fetch_type_selected(selected_types=fetch_incidents_or_detections):
        # if "Detections" in fetch_incidents_or_detections or "Endpoint Detection" in fetch_incidents_or_detections:
        demisto.debug("CrowdStrikeFalconMsg: Start fetch Detections")
        demisto.debug(f"CrowdStrikeFalconMsg: Current detections_last_run object: {detections_last_run}")

        fetched_detections, detections_last_run = fetch_endpoint_detections(detections_last_run, look_back, is_fetch_events)
        items.extend(fetched_detections)

    # Fetch IDP Detections
    if IDP_DETECTION_FETCH_TYPE in fetch_incidents_or_detections:
        demisto.debug("CrowdStrikeFalconMsg: Start fetch IDP Detection")
        demisto.debug(f"CrowdStrikeFalconMsg: Current IDP Detection last_run object: {idp_detections_last_run}")

        fetched_idp_detections, idp_detections_last_run = fetch_detections_by_product_type(
            idp_detections_last_run,
            look_back=look_back,
            fetch_query=params.get("idp_detections_fetch_query", ""),
            detections_type=IDP_DETECTION,
            product_type="idp",
            detection_name_prefix=IDP_DETECTION_FETCH_TYPE,
            start_time_key="created_timestamp",
            is_fetch_events=is_fetch_events,
        )
        items.extend(fetched_idp_detections)

    # Fetch Mobile Detections
    if MOBILE_DETECTION_FETCH_TYPE in fetch_incidents_or_detections:
        demisto.debug("CrowdStrikeFalconMsg: Start fetch Mobile Detection")
        demisto.debug(f"CrowdStrikeFalconMsg: Current Mobile Detection last_run object: {mobile_detections_last_run}")

        fetched_mobile_detections, mobile_detections_last_run = fetch_detections_by_product_type(
            mobile_detections_last_run,
            look_back=look_back,
            fetch_query=params.get("mobile_detections_fetch_query", ""),
            detections_type=MOBILE_DETECTION,
            product_type="mobile",
            detection_name_prefix=MOBILE_DETECTION_FETCH_TYPE,
            start_time_key="timestamp",
            is_fetch_events=is_fetch_events,
        )
        items.extend(fetched_mobile_detections)

    # Fetch On-Demand Scan Detections
    if ON_DEMAND_SCANS_DETECTION_TYPE in fetch_incidents_or_detections:
        demisto.debug("CrowdStrikeFalconMsg: Start fetch ODS Detection")
        demisto.debug(f"CrowdStrikeFalconMsg: Current ODS Detection last_run object: {on_demand_detections_last_run}")

        fetched_on_demand_detections, on_demand_detections_last_run = fetch_detections_by_product_type(
            on_demand_detections_last_run,
            look_back=look_back,
            fetch_query=params.get("on_demand_fetch_query", ""),
            detections_type=ON_DEMAND_SCANS_DETECTION,
            product_type="ods",
            detection_name_prefix=ON_DEMAND_SCANS_DETECTION_TYPE,
            start_time_key="created_timestamp",
            is_fetch_events=is_fetch_events,
        )
        items.extend(fetched_on_demand_detections)

    # Fetch OFP Detections
    if OFP_DETECTION_TYPE in fetch_incidents_or_detections:
        demisto.debug("CrowdStrikeFalconMsg: Start fetch OFP Detection")
        demisto.debug(f"CrowdStrikeFalconMsg: Current OFP Detection last_run object: {ofp_detection_last_run}")

        fetched_ofp_detections, ofp_detection_last_run = fetch_detections_by_product_type(
            ofp_detection_last_run,
            look_back=look_back,
            fetch_query=params.get("ofp_detection_fetch_query", ""),
            detections_type=OFP_DETECTION,
            product_type="ofp",
            detection_name_prefix=OFP_DETECTION_TYPE,
            start_time_key="created_timestamp",
            is_fetch_events=is_fetch_events,
        )
        items.extend(fetched_ofp_detections)

    if NGSIEM_INCIDENT_FETCH_TYPE in fetch_incidents_or_detections:
        demisto.debug("CrowdstrikeFalconMsg: Start fetch NGSIEM Incident Detection")
        demisto.debug(f"CrowdStrikeFalconMsg: Current NGSIEM Incident last_run_object: {ngsiem_incident_last_run}")

        fetched_ngsiem_incidents, ngsiem_incident_last_run = fetch_detections_by_product_type(
            ngsiem_incident_last_run,
            look_back=look_back,
            fetch_query=params.get("ngsiem_incidents_fetch_query", ""),
            detections_type=NGSIEM_INCIDENT,
            product_type="xdr",
            detection_name_prefix=NGSIEM_INCIDENT_FETCH_TYPE,
            start_time_key="created_timestamp",
            is_fetch_events=False,
        )
        items.extend(fetched_ngsiem_incidents)

    if NGSIEM_AUTOMATED_LEADS_FETCH_TYPE in fetch_incidents_or_detections:
        demisto.debug("CrowdstrikeFalconMsg: Start fetch NGSIEM Automated Lead")
        demisto.debug(f"CrowdStrikeFalconMsg: Current NGSIEM Automated Lead last_run_object: {ngsiem_automated_lead_last_run}")

        fetched_ngsiem_automated_leads, ngsiem_automated_lead_last_run = fetch_detections_by_product_type(
            ngsiem_automated_lead_last_run,
            look_back=look_back,
            fetch_query=params.get("automated_leads_fetch_query", ""),
            detections_type=NGSIEM_AUTOMATED_LEAD,
            product_type="automated-lead",
            detection_name_prefix=NGSIEM_AUTOMATED_LEADS_FETCH_TYPE,
            start_time_key="created_timestamp",
            is_fetch_events=False,
        )
        demisto.debug(f"Extending items with Automated Leads: {fetched_ngsiem_automated_leads}")
        items.extend(fetched_ngsiem_automated_leads)

    if NGSIEM_CASES_FETCH_TYPE in fetch_incidents_or_detections:
        demisto.debug("CrowdstrikeFalconMsg: Start fetch NGSIEM Cases")
        demisto.debug(f"CrowdStrikeFalconMsg: Current NGSIEM Cases last_run_object: {ngsiem_case_last_run}")

        fetched_ngsiem_cases, ngsiem_case_last_run = fetch_ngsiem_cases(
            ngsiem_case_last_run, look_back, params.get("ngsiem_cases_fetch_query", "")
        )
        items.extend(fetched_ngsiem_cases)

    # Fetch Indicators of Misconfiguration (IOM) - supported for fetch-incidents command only.
    if not is_fetch_events and IOM_FETCH_TYPE in fetch_incidents_or_detections:
        demisto.debug("CrowdStrikeFalconMsg: Start fetch IOM")
        demisto.debug(f"CrowdStrikeFalconMsg: Current IOM last_run object: {iom_last_run}")

        fetched_iom_incidents, iom_last_run = fetch_iom_incidents(iom_last_run)
        items.extend(fetched_iom_incidents)

    # Fetch Indicators of Attack (IOA) - supported for fetch-incidents command only.
    if not is_fetch_events and IOA_FETCH_TYPE in fetch_incidents_or_detections:
        demisto.debug("CrowdStrikeFalconMsg: Start fetch IOA")
        demisto.debug(f"CrowdStrikeFalconMsg: Current IOA last_run object: {ioa_last_run}")

        fetched_ioa_incidents, ioa_last_run = fetch_detections_by_product_type(
            ioa_last_run,
            look_back=look_back,
            fetch_query=params.get("ioa_fetch_query", ""),
            detections_type=IOA_DETECTION,
            product_type=IncidentType.IOA_TYPE_TAG.value,
            detection_name_prefix=IOA_FETCH_TYPE,
            start_time_key="created_timestamp",
            is_fetch_events=False,
        )
        items.extend(fetched_ioa_incidents)

    if not is_fetch_events and NGSIEM_DETECTION_FETCH_TYPE in fetch_incidents_or_detections:
        demisto.debug("CrowdStrikeFalconMsg: Start fetch NGSIEM Detection")
        demisto.debug(f"CrowdStrikeFalconMsg: Current NGSIEM Detection last_run object: {ngsiem_detection_last_run}")

        fetched_ngsiem_detections, ngsiem_detection_last_run = fetch_detections_by_product_type(
            ngsiem_detection_last_run,
            look_back=look_back,
            fetch_query=params.get("ngsiem_detection_fetch_query", ""),
            detections_type=NGSIEM_DETECTION,
            product_type="ngsiem",
            detection_name_prefix=NGSIEM_DETECTION_FETCH_TYPE,
            start_time_key="created_timestamp",
            is_fetch_events=is_fetch_events,
        )
        items.extend(fetched_ngsiem_detections)

    if not is_fetch_events and THIRD_PARTY_DETECTION_FETCH_TYPE in fetch_incidents_or_detections:
        demisto.debug("CrowdStrikeFalconMsg: Start fetch THIRD PARTY Detection")
        demisto.debug(f"CrowdStrikeFalconMsg: Current THIRD PARTY Detection last_run object: {third_party_detection_last_run}")

        fetched_third_party_detections, third_party_detection_last_run = fetch_detections_by_product_type(
            third_party_detection_last_run,
            look_back=look_back,
            fetch_query=params.get("third_party_detection_fetch_query", ""),
            detections_type=THIRD_PARTY_DETECTION,
            product_type="thirdparty",
            detection_name_prefix=THIRD_PARTY_DETECTION_FETCH_TYPE,
            start_time_key="created_timestamp",
            is_fetch_events=is_fetch_events,
        )
        items.extend(fetched_third_party_detections)

    if not is_fetch_events and RECON_FETCH_TYPE in fetch_incidents_or_detections:
        demisto.debug("Recon-Log CrowdStrikeFalconMsg: Start fetch Recon Notifications")
        demisto.debug(f"Recon-Log CrowdStrikeFalconMsg: Current Recon Notifications last_run object: {recon_last_run}")

        fetched_recon_notifications, recon_last_run = fetch_recon_incidents(recon_last_run)
        demisto.debug(f"Recon-Log Recon updated_run: {recon_last_run}")
        items.extend(fetched_recon_notifications)

    # Assign each sub last_run info per type at its proper index
    set_last_run_per_type(last_run, index=LastRunIndex.DETECTIONS, data=detections_last_run, is_fetch_events=is_fetch_events)
    set_last_run_per_type(
        last_run, index=LastRunIndex.IDP_DETECTIONS, data=idp_detections_last_run, is_fetch_events=is_fetch_events
    )
    set_last_run_per_type(
        last_run, index=LastRunIndex.MOBILE_DETECTIONS, data=mobile_detections_last_run, is_fetch_events=is_fetch_events
    )
    set_last_run_per_type(
        last_run, index=LastRunIndex.ON_DEMAND_DETECTIONS, data=on_demand_detections_last_run, is_fetch_events=is_fetch_events
    )
    set_last_run_per_type(
        last_run, index=LastRunIndex.OFP_DETECTION, data=ofp_detection_last_run, is_fetch_events=is_fetch_events
    )

    if not is_fetch_events:
        set_last_run_per_type(last_run, index=LastRunIndex.IOM, data=iom_last_run, is_fetch_events=is_fetch_events)
        set_last_run_per_type(last_run, index=LastRunIndex.IOA, data=ioa_last_run, is_fetch_events=is_fetch_events)
        set_last_run_per_type(
            last_run,
            index=LastRunIndex.THIRD_PARTY_DETECTIONS,
            data=third_party_detection_last_run,
            is_fetch_events=is_fetch_events,
        )
        set_last_run_per_type(
            last_run, index=LastRunIndex.NGSIEM_DETECTIONS, data=ngsiem_detection_last_run, is_fetch_events=is_fetch_events
        )
        set_last_run_per_type(
            last_run, index=LastRunIndex.RECON_NOTIFICATIONS, data=recon_last_run, is_fetch_events=is_fetch_events
        )
        set_last_run_per_type(last_run, index=LastRunIndex.NGSIEM_INCIDENTS, data=ngsiem_incident_last_run, is_fetch_events=False)
        set_last_run_per_type(
            last_run, index=LastRunIndex.NGSIEM_AUTOMATED_LEADS, data=ngsiem_automated_lead_last_run, is_fetch_events=False
        )
        set_last_run_per_type(last_run, index=LastRunIndex.NGSIEM_CASES, data=ngsiem_case_last_run, is_fetch_events=False)
        demisto.setLastRun(last_run)

    demisto.debug(f"CrowdStrikeFalconMsg: Updated last_run object after fetch: {last_run}")
    return last_run, items


def list_cnapp_alerts_command(args: dict[str, Any]) -> CommandResults:
    filter = args.get("filter", "")

    response = preform_get_cnapp_alerts_request(filter=filter)

    alerts = response.get("resources", [])
    return CommandResults(
        outputs_prefix="CrowdStrike.CnappAlert",
        outputs_key_field="detection_name",
        outputs=alerts,
        readable_output=tableToMarkdown(
            name="CrowdStrike CNAPP alerts",
            t=alerts,
            sort_headers=False,
        ),
        raw_response=alerts,
    )


def preform_get_cnapp_alerts_request(offset=0, filter=""):
    """Preforms request to get CNAPP alerts

    Args:
        offset (int, optional): The offset for pagination
        filter (str, optional): A filter to use if given.

    Returns:
       the response.
    """
    limit = 100
    endpoint = "/container-security/combined/container-alerts/v1"
    params = {"offset": offset, "limit": limit}
    if filter:
        params["filter"] = filter
    demisto.info(f"Preforming a reuest to get cnapp alerts. Calling {endpoint=} with {params=}")
    return http_request("GET", endpoint, params)


def get_cnapp_assets():
    last_run = demisto.getAssetsLastRun()
    demisto.debug(f"Starting a new cnapp fetch assets execution with {last_run=}")
    snapshot_id = last_run.get("snapshot_id", str(round(time.time() * 1000)))
    offset = int(last_run.get("offset", 0))
    total_fetched_until_now = int(last_run.get("total_fetched_until_now", 0))
    new_last_run = {}

    response = preform_get_cnapp_alerts_request(offset=offset)

    cnapp_alerts = response.get("resources", [])
    total_detections = demisto.get(response, "meta.pagination.total")
    total_fetched_until_now += len(cnapp_alerts)
    demisto.debug(f"Fetched {len(cnapp_alerts)} CNAPP assets, reulsting a toal of {total_fetched_until_now}.")

    if total_detections > total_fetched_until_now:  # type: ignore
        demisto.debug(
            f"Fetch {total_fetched_until_now} assets out of expected {total_detections} so far, setting NextTrigger to 0."
        )
        offset += len(cnapp_alerts)
        items_count = 1
        new_last_run = {
            "offset": offset,
            "total_fetched_until_now": total_fetched_until_now,
            "snapshot_id": snapshot_id,
            "nextTrigger": "0",
            "type": 1,
        }
    else:
        demisto.debug(f"Fetched all expected assets ({total_detections}), closing the snapshot.")
        offset = 0
        items_count = total_fetched_until_now
        new_last_run = {"offset": offset, "total_fetched_until_now": 0}

    return new_last_run, cnapp_alerts, items_count, snapshot_id


def save_spotlight_state(context_store: ContentClientContextStore, spotlight_state: ContentClientState) -> None:
    """
    Save Spotlight state to integration context without breaking other keys.

    Args:
        context_store: Context store for writing integration context
        spotlight_state: Spotlight state object to save
    """
    # Update only the spotlight_assets key, preserving all other context
    integration_context = context_store.read()
    integration_context["spotlight_assets"] = spotlight_state.to_dict()
    context_store.write(integration_context)
    spotlight_data = integration_context.get("spotlight_assets", {})
    log_falcon_assets(f"Saved Spotlight state: metadata={spotlight_data.get('metadata', {})}")


class AssetsDeviceHandler:
    """
    Handler for enriching and ingesting device assets asynchronously.

    Buffers unique AIDs from vulnerability batches, enriches them via the Devices API,
    and sends enriched data to XSIAM using the async fire-and-forget pattern.

    Maintains separate batch tracking from vulnerability chain to prevent out-of-order context saves.
    """

    def __init__(
        self,
        client: ContentClient,
        context_store: ContentClientContextStore,
        spotlight_state: ContentClientState,
        snapshot_id: str,
        processed_aids: set,
        batch_limit: int = MAX_FETCH_SPOTLIGHT_ASSETS,
    ):
        """
        Initialize the AssetsDeviceHandler.

        Args:
            client: ContentClient instance for API calls
            context_store: Context store for thread-safe state persistence
            spotlight_state: Spotlight state object for metadata updates
            snapshot_id: Snapshot ID for asset collection tracking
            processed_aids: Set of already processed AIDs (for deduplication)
            batch_limit: Number of AIDs to accumulate before triggering enrichment
        """
        self.client = client
        self.context_store = context_store
        self.spotlight_state = spotlight_state
        self.snapshot_id = snapshot_id
        self.processed_aids = processed_aids
        self.pending_buffer: set[str] = set()
        self.batch_limit = batch_limit

        # SEPARATE batch tracking for assets chain (independent from vulnerability chain)
        self.asset_batch_counter = 0
        self.asset_last_saved_batch_number = 0

        self.running_tasks: set[asyncio.Task] = set()

    async def receive_new_aids(self, new_aids: set[str]) -> None:
        """
        Receive new AIDs and trigger enrichment when buffer reaches batch_limit.
        Keeps at least 1 item in the buffer to ensure we can send the final count with the last batch.

        Args:
            new_aids: Set of AIDs extracted from vulnerability batch
        """
        # Deduplicate against already processed AIDs
        unique_new = new_aids - self.processed_aids
        self.pending_buffer.update(unique_new)

        log_falcon_assets(f"AssetsDeviceHandler: Received {len(unique_new)} new AIDs, buffer size: {len(self.pending_buffer)}")

        # Trigger enrichment for full batches, but keep at least 1 item for the final flush
        # Threshold is batch_limit + 1 to ensure we always have leftovers for flush_remaining
        threshold = self.batch_limit + 1

        while len(self.pending_buffer) >= threshold:
            full_list = list(self.pending_buffer)
            batch = full_list[: self.batch_limit]
            self.pending_buffer = set(full_list[self.batch_limit :])

            log_falcon_assets(f"AssetsDeviceHandler: Buffer full, triggering enrichment for {len(batch)} AIDs")

            # Create async enrichment task
            task = asyncio.create_task(self.enrich_and_ingest_batch(batch))
            self.running_tasks.add(task)
            task.add_done_callback(self.running_tasks.discard)

    async def enrich_and_ingest_batch(self, aid_batch: list[str], final_items_count: int = 1) -> None:
        """
        Enrich a batch of AIDs via Devices API and send to XSIAM.

        Args:
            aid_batch: List of AIDs to enrich
            final_items_count: Total items count to send to XSIAM (1 for intermediate batches, actual total for final batch)
        """
        # Increment ASSET batch counter (separate from vulnerability chain)
        self.asset_batch_counter += 1
        current_batch_number = self.asset_batch_counter

        log_falcon_assets(f"AssetsDeviceHandler: [Batch {current_batch_number}] Enriching {len(aid_batch)} AIDs")

        try:
            # 1. Enrich the AID batch via ContentClient.
            # /devices/entities/devices/v2 returns HTTP 400 on partial success (valid devices in
            # "resources", rejected IDs in "errors"). Accept 400 (ok_codes) to ingest the resolved
            # devices instead of discarding the whole batch and raising on the full response body.
            response = await self.client._request(
                method="POST",
                url_suffix="/devices/entities/devices/v2",
                json_data={"ids": aid_batch},
                ok_codes=(200, 400),
            )
            log_falcon_assets(
                f"AssetsDeviceHandler: [Batch {current_batch_number}] CrowdStrike response status={response.status_code}"
            )

            # Parse response
            response_data = response.json()
            devices = response_data.get("resources", [])

            # Log any invalid device IDs returned in the partial-success "errors" array.
            errors = response_data.get("errors") or []
            if errors:
                log_falcon_assets(
                    f"AssetsDeviceHandler: [Batch {current_batch_number}] CrowdStrike returned "
                    f"{len(errors)} invalid device ID(s); skipping them. First error: {errors[0].get('message')}",
                    "warning",
                )

            # Mark the entire batch processed (including invalid IDs) regardless of whether any
            # devices resolved, so permanently-invalid IDs are not retried indefinitely on every fetch.
            self.processed_aids.update(aid_batch)
            self.spotlight_state.metadata["processed_aids_count"] = len(self.processed_aids)

            if not devices:
                log_falcon_assets(f"AssetsDeviceHandler: [Batch {current_batch_number}] No devices returned from API")
                return

            log_falcon_assets(f"AssetsDeviceHandler: [Batch {current_batch_number}] Enriched {len(devices)} devices")

            devices = self._filter_asset_fields(devices)

            # 2. Send to XSIAM using existing generic function (fire-and-forget)
            send_task = create_task_send_batch_to_xsiam_and_save_context(
                data=devices,
                product=SPOTLIGHT_ASSETS_PRODUCT,
                snapshot_id=self.snapshot_id,
                items_count=final_items_count,
                batch_number=current_batch_number,
                last_saved_batch_number=self.asset_last_saved_batch_number,
                context_store=self.context_store,
                state=self.spotlight_state,
                save_state_callback=save_spotlight_state,
                data_type="assets",
            )

            # Track task with callback to update last_saved_batch_number
            def update_last_saved(future):
                # 'self' is accessible from enclosing method scope - no nonlocal needed
                try:
                    saved_batch_num = future.result()
                    if saved_batch_num > self.asset_last_saved_batch_number:
                        self.asset_last_saved_batch_number = saved_batch_num
                        log_falcon_assets(f"AssetsDeviceHandler: Updated asset_last_saved_batch_number to {saved_batch_num}")
                except asyncio.CancelledError:
                    log_falcon_assets(
                        f"AssetsDeviceHandler: [Batch {current_batch_number}] Send task was cancelled (script exiting)."
                    )
                except Exception as e:
                    log_falcon_assets(f"AssetsDeviceHandler: Enrichment task failed: {e}", "error")
                finally:
                    self.running_tasks.discard(future)

            # Track the send task
            self.running_tasks.add(send_task)
            send_task.add_done_callback(update_last_saved)
            log_falcon_assets(f"AssetsDeviceHandler: [Batch {current_batch_number}] Created send task")

        except Exception as e:
            log_falcon_assets(f"AssetsDeviceHandler: [Batch {current_batch_number}] Error enriching assets: {e}", "error")
            raise

    async def flush_remaining(self, total_items_count: int) -> None:
        """
        Flush remaining AIDs in buffer and wait for all enrichment tasks.
        This is the FINAL batch, so we send the actual total_items_count.

        Args:
            total_items_count: The final count of unique assets to report to XSIAM.
        """
        # Handle leftover AIDs that didn't reach batch_limit
        if self.pending_buffer:
            log_falcon_assets(
                f"AssetsDeviceHandler: Flushing {len(self.pending_buffer)} remaining AIDs with final count {total_items_count}",
                "info",
            )
            # Create task for remaining batch (fire-and-forget)
            task = asyncio.create_task(
                self.enrich_and_ingest_batch(list(self.pending_buffer), final_items_count=total_items_count)
            )
            self.running_tasks.add(task)
            task.add_done_callback(self.running_tasks.discard)
            self.pending_buffer.clear()

        # Wait for all enrichment and send tasks to complete
        while self.running_tasks:
            log_falcon_assets("AssetsDeviceHandler: Starting flush of remaining assets.", "info")
            # Create a snapshot of the current tasks
            current_batch = list(self.running_tasks)
            if not current_batch:
                break
            count = len(current_batch)
            log_falcon_assets(f"AssetsDeviceHandler: Waiting for {count} background tasks to complete...", "info")
            # Wait for this specific batch.
            await asyncio.gather(*current_batch, return_exceptions=True)
            self.running_tasks.difference_update(current_batch)
        log_falcon_assets("AssetsDeviceHandler: All enrichment/send tasks completed successfully", "info")

    @staticmethod
    def _filter_asset_fields(assets: list[Dict]) -> list[Dict]:
        """
        Filters a list of asset dictionaries to retain only specific keys.
        """
        # Filtering assets key according to UVEM request
        allowed_keys = {
            "device_id",
            "cid",
            "external_ip",
            "mac_address",
            "hostname",
            "first_seen",
            "last_login_timestamp",
            "last_seen",
            "local_ip",
            "machine_domain",
            "os_version",
            "os_build",
            "serial_number",
            "status",
            "os_product_name",
            "connection_mac_address",
            "tags",
        }

        return [{k: asset.get(k, [] if k == "tags" else "") for k in allowed_keys} for asset in assets]


async def xsiam_api_call_async(
    xsiam_url: str, zipped_data: bytes, headers: dict, num_of_attempts: int, data_type: str = "assets"
) -> aiohttp.ClientResponse | None:
    """
    Send data to XSIAM asynchronously with retry logic.
    Generic function for sending any type of data to XSIAM.

    Args:
        xsiam_url: XSIAM API endpoint URL (e.g., "https://api-{domain}")
        zipped_data: Gzip-compressed data bytes to send
        headers: HTTP headers including authorization token, format, vendor, product, etc.
        num_of_attempts: Maximum number of retry attempts for failed requests
        data_type: Type of data being sent (e.g., "assets", "events"). Used for logging. Defaults to "assets"

    Returns:
        aiohttp.ClientResponse: The HTTP response object from the XSIAM API

    Raises:
        DemistoException: If all retry attempts fail or non-retryable error occurs
    """
    status_code = None
    attempt_num = 1
    response = None

    while status_code != 200 and attempt_num < num_of_attempts + 1:
        log_falcon_assets(f"Sending {data_type} to XSIAM, attempt {attempt_num}/{num_of_attempts}")
        ok_codes = (200, 429) if attempt_num < num_of_attempts else None

        async with aiohttp.ClientSession() as session:  # noqa: SIM117
            async with session.post(urljoin(xsiam_url, "/logs/v1/xsiam"), data=zipped_data, headers=headers) as response:
                try:
                    response.raise_for_status()
                    status_code = response.status

                except aiohttp.ClientResponseError as e:
                    if ok_codes and e.status in ok_codes:
                        status_code = e.status
                        if e.status == 429:
                            await asyncio.sleep(1)
                            attempt_num += 1
                        continue
                    else:
                        header_msg = f"Error sending {data_type} to XSIAM: {e.message}"
                        log_falcon_assets(header_msg, "error")
                        demisto.updateModuleHealth(header_msg + e.message, is_error=True)

        log_falcon_assets(f"received status code: {status_code}")
        if status_code == 429:
            await asyncio.sleep(1)
        attempt_num += 1
    return response


def send_data_to_xsiam_async(
    data: Union[str, list],
    vendor: str,
    product: str,
    data_format: str = "json",
    url_key: str = "url",
    num_of_attempts: int = 3,
    chunk_size: int = XSIAM_EVENT_CHUNK_SIZE,
    data_type: str = "assets",
    snapshot_id: str = "",
    items_count: int = 1,
) -> list:
    """
    Send data to XSIAM asynchronously by creating async tasks for each data chunk.
    Generic function for sending any type of data (assets, events, etc.) to XSIAM.
    Adapted from Rapid7_Nexpose.py lines 7631-7672.

    Args:
        data: List of data objects to send (e.g., vulnerabilities, alerts, events)
        vendor: Vendor name for XSIAM headers (e.g., "CrowdStrike")
        product: Product name for XSIAM headers
        data_format: Format of the data being sent. Defaults to "json"
        url_key: Parameter key to retrieve the final reporting device URL from params. Defaults to "url"
        num_of_attempts: Maximum retry attempts for failed requests. Defaults to 3
        chunk_size: Maximum size in bytes for each data chunk. Defaults to 1 MiB (2**20)
        data_type: Type of data being sent for XSIAM collector-type header. Defaults to "assets"
        snapshot_id: Snapshot ID for asset collection tracking. Required for assets, empty for events
        items_count: Total items count - final count when complete, 1 when in-progress. Defaults to 1

    Returns:
        list: List of asyncio.Task objects for each data chunk being sent

    Note:
        - Data is automatically converted to newline-separated JSON strings
        - Data is compressed with gzip before sending
        - Data is split into chunks based on chunk_size parameter
        - Each chunk is sent as a separate async task
    """
    params = demisto.params()
    calling_context = demisto.callingContext.get("context", {})
    instance_name = calling_context.get("IntegrationInstance", "")
    collector_name = calling_context.get("IntegrationBrand", "")

    # We only return early if data is empty AND it's NOT an asset snapshot update.
    # If it is assets, we might be sending the "Final Seal" (empty data + count header).
    if not data and data_type != "assets":
        log_falcon_assets(f"No {data_type} to send to XSIAM")
        return []

    # Convert data to a newline-separated JSON string
    data_str = _normalize_data_to_str(data, data_type)
    if data_str is None:
        return []

    # Get XSIAM credentials
    xsiam_api_token = demisto.getLicenseCustomField("Http_Connector.token")
    xsiam_domain = demisto.getLicenseCustomField("Http_Connector.url")
    xsiam_url = f"https://api-{xsiam_domain}"

    # Build headers
    headers = remove_empty_elements(
        {
            "authorization": xsiam_api_token,
            "format": data_format,
            "product": product,
            "vendor": vendor,
            "content-encoding": "gzip",
            "collector-name": collector_name,
            "instance-name": instance_name,
            "final-reporting-device": params.get(url_key, ""),
            "collector-type": "assets" if data_type == "assets" else "events",
        }
    )

    # Adapt headers to asset data
    if data_type == "assets":
        if not snapshot_id:
            snapshot_id = str(round(time.time() * 1000))
        headers["snapshot-id"] = snapshot_id + instance_name + product
        headers["total-items-count"] = str(items_count)

    # If data_str is empty (the seal), we force a list with one empty string [""] to ensure the task is created
    if not data_str and data_type == "assets":
        data_chunks = [""]
        log_falcon_assets("Preparing empty 'Seal' batch to close snapshot.")
    else:
        data_chunks = list(split_data_to_chunks(data_str, chunk_size))

    # Free the intermediate JSON string — chunks now hold the only references
    del data_str

    # Compress chunks synchronously to free raw string data before creating async tasks.
    # This reduces memory fragmentation by allowing Python to reuse arenas for the next batch.
    compressed_chunks: list[tuple[bytes, int]] = []
    total_raw_bytes = 0
    total_compressed_bytes = 0
    for chunk in data_chunks:
        chunk_size_val = len(chunk) if isinstance(chunk, list) else 1
        chunk_str = "\n".join(chunk) if isinstance(chunk, list) else chunk
        raw_bytes = chunk_str.encode("utf-8")
        total_raw_bytes += len(raw_bytes)
        zipped_data = gzip.compress(raw_bytes)
        total_compressed_bytes += len(zipped_data)
        del raw_bytes  # Free the encoded string immediately
        compressed_chunks.append((zipped_data, chunk_size_val))

    # Free the uncompressed chunks — only compressed bytes remain
    del data_chunks

    if total_raw_bytes > 0:
        ratio = total_compressed_bytes / total_raw_bytes * 100
        log_falcon_assets(
            f"Compressed {len(compressed_chunks)} chunks: "
            f"{total_raw_bytes / 1024:.1f} KB → {total_compressed_bytes / 1024:.1f} KB ({ratio:.1f}%)"
        )

    async def send_compressed_async(zipped_data: bytes, chunk_size_val: int) -> int:
        await xsiam_api_call_async(
            xsiam_url=xsiam_url, zipped_data=zipped_data, headers=headers, num_of_attempts=num_of_attempts, data_type=data_type
        )
        return chunk_size_val

    tasks = [asyncio.create_task(send_compressed_async(zipped, size)) for zipped, size in compressed_chunks]
    return tasks


async def send_batch_to_xsiam_and_save_context(
    data: list,
    vendor: str,
    product: str,
    snapshot_id: str,
    items_count: int,
    batch_number: int,
    last_saved_batch_number: int,
    context_store: ContentClientContextStore,
    state: ContentClientState,
    save_state_callback: Callable[[ContentClientContextStore, ContentClientState], None],
    data_type: str = "assets",
) -> int:
    """
    Send batch to XSIAM asynchronously, then save context ONLY if send succeeds AND this is the latest batch.

    Generic function implementing the async fire-and-forget pattern for sending any type of data
    to XSIAM while managing state persistence. Prevents out-of-order context saves by only saving
    when batch_number > last_saved_batch_number.

    Args:
        data: List of data objects to send (e.g., vulnerabilities, alerts, events)
        vendor: Vendor name for XSIAM headers (e.g., "CrowdStrike")
        product: Product name for XSIAM headers (e.g., "Falcon_Spotlight", "Falcon_CNAPP")
        snapshot_id: Snapshot ID for asset collection tracking
        items_count: Total items count - use final count when complete, 1 when in-progress
        batch_number: Current batch number being processed
        last_saved_batch_number: Highest batch number that has successfully saved context
        context_store: ContentClientContextStore instance for thread-safe context operations
        state: ContentClientState object containing cursor and metadata
        save_state_callback: Callback function to save state with signature:
                            (ContentClientContextStore, dict, ContentClientState) -> None
                            Example: save_spotlight_state, save_cnapp_state, etc.
        data_type: Type of data being sent for XSIAM collector-type header. Defaults to "assets"

    Returns:
        int: batch_number if context was saved, else last_saved_batch_number
    """
    log_falcon_assets(f"[Batch {batch_number}] Sending {len(data)} {data_type} to XSIAM")

    try:
        # 1. Send to XSIAM (compresses data synchronously, returns async tasks for HTTP only)
        tasks = send_data_to_xsiam_async(
            data=data,
            vendor=vendor,
            product=product,
            data_format="json",
            url_key="url",
            num_of_attempts=3,
            chunk_size=XSIAM_EVENT_CHUNK_SIZE,
            data_type=data_type,
            snapshot_id=snapshot_id,
            items_count=items_count,
        )
        # Release raw data — compression already done synchronously, async tasks hold only compressed bytes
        del data

        # 2. Wait for all chunks to complete
        await asyncio.gather(*tasks)
        log_falcon_assets(f"[Batch {batch_number}] for {product=} Successfully sent to XSIAM")

        # 3. Save context ONLY if this is the latest batch using the provided callback
        if batch_number > last_saved_batch_number:
            save_state_callback(context_store, state)
            log_falcon_assets(f"[Batch {batch_number}] Context saved")
            return batch_number
        else:
            log_falcon_assets(
                f"[Batch {batch_number}] for {product=} Skipped save (batch {last_saved_batch_number} already saved)"
            )
            return last_saved_batch_number

    except Exception as e:
        log_falcon_assets(f"[Batch {batch_number}] Failed: {str(e)}", "error")
        raise


def create_task_send_batch_to_xsiam_and_save_context(
    data,
    product,
    snapshot_id,
    items_count,
    batch_number,
    last_saved_batch_number,
    context_store,
    state,
    save_state_callback,
    data_type,
):
    """
    Create an async task to send vulnerability batch to XSIAM and save context.
    Parameters now match the order and names of the internal async function.

    Args:
        data: List of data items to send
        product: The product name
        snapshot_id: Snapshot ID for tracking
        items_count: Total items count - use final count when complete, 1 when in-progress
        batch_number: Current batch number being processed
        last_saved_batch_number: Highest batch number that has successfully saved context
        context_store: ContentClientContextStore instance for thread-safe context operations
        state: ContentClientState object containing cursor and metadata
        save_state_callback: Callback function to save state with signature:
                            (ContentClientContextStore, dict, ContentClientState) -> None
                            Example: save_spotlight_state, save_cnapp_state, etc.
        data_type: Type of data being sent for XSIAM collector-type header. Defaults to "assets"
    Returns:
        asyncio.Task: The created async task
    """
    task = asyncio.create_task(
        send_batch_to_xsiam_and_save_context(
            data=data,
            vendor=VENDOR,
            product=product,
            snapshot_id=snapshot_id,
            items_count=items_count,
            batch_number=batch_number,
            last_saved_batch_number=last_saved_batch_number,
            context_store=context_store,
            state=state,
            save_state_callback=save_state_callback,
            data_type=data_type,
        )
    )
    return task


def create_spotlight_client(context_store: ContentClientContextStore) -> ContentClient:
    """
    Create and configure ContentClient for Spotlight API with OAuth2 authentication.

    Args:
        context_store: Context store for token and state persistence

    Returns:
        Configured ContentClient instance
    """
    return ContentClient(
        base_url=SERVER,
        verify=USE_SSL,
        proxy=PROXY,
        # OAuth2 authentication with token persistence
        auth_handler=OAuth2ClientCredentialsHandler(
            token_url=f"{SERVER}/oauth2/token", client_id=CLIENT_ID, client_secret=SECRET, context_store=context_store
        ),
        # diagnostic_mode retains a history of every request/response (full parsed response bodies),
        # which on large tenants grows memory linearly with the number of vulnerabilities and leads
        # to an out-of-memory failure. Keep it disabled so memory stays bounded.
        diagnostic_mode=False,
        client_name="FalconSpotlightAssetCollector",
    )


def extract_unique_aids(vulnerabilities: list, existing_unique_aids: set) -> None:
    """
    Extract unique AIDs (Host IDs) from vulnerabilities and merge with existing set.
    Equivalent to JavaScript: const u_aid = [...new Set(aids)]
    Update the set of unique AIDs in place.

    Args:
        vulnerabilities: List of vulnerability objects
        existing_unique_aids: Existing set of unique AIDs
    """
    # Extract AIDs from this batch
    batch_aids = {vuln.get("aid") for vuln in vulnerabilities if vuln.get("aid")}

    # Merge with existing
    existing_unique_aids.update(batch_aids)

    log_falcon_assets(f"Batch AIDs: {len(batch_aids)}, Total unique AIDs: {len(existing_unique_aids)}")


def load_spotlight_state(
    context_store: ContentClientContextStore,
) -> tuple[ContentClientState, str, int, set, set, list[str], list[dict]]:
    """
    Load Spotlight state from integration context.

    Args:
        context_store: Context store for reading integration context

    Returns:
        Tuple of (state_object, snapshot_id, total_fetched, unique_aids, processed_aids,
        completed_severities, withheld_records).
    """
    # Read entire integration context (preserves all existing keys)
    integration_context = context_store.read()
    log_falcon_assets(f"Loaded integration context with keys: {list(integration_context.keys())}")

    # Get Spotlight-specific state
    spotlight_state_dict = integration_context.get("spotlight_assets", {})
    spotlight_state = ContentClientState.from_dict(spotlight_state_dict)

    # Extract state variables
    snapshot_id = spotlight_state.metadata.get("snapshot_id") or str(round(time.time() * 1000))
    total_fetched = spotlight_state.metadata.get("total_fetched_until_now", 0)
    # AIDs are no longer stored in context (only counts) to reduce memory/serialization overhead.
    # Backward compat: read old "unique_aids"/"processed_aids" lists if present, otherwise use counts.
    unique_aids_count = spotlight_state.metadata.get("unique_aids_count", len(spotlight_state.metadata.get("unique_aids", [])))
    processed_aids_count = spotlight_state.metadata.get(
        "processed_aids_count", len(spotlight_state.metadata.get("processed_aids", []))
    )
    completed_severities = spotlight_state.metadata.get("completed_severities", [])
    # Records withheld for the seal in previous cycles, persisted across resume cycles.
    withheld_records = spotlight_state.metadata.get("withheld_records", [])

    log_falcon_assets(
        f"Loaded Spotlight state: {snapshot_id=}, {total_fetched=}, "
        f"{unique_aids_count=}, {processed_aids_count=}, "
        f"completed_severities={completed_severities}, "
        f"withheld_records_count={len(withheld_records)}, "
        f"after_token={spotlight_state.cursor}"
    )

    return (
        spotlight_state,
        snapshot_id,
        total_fetched,
        unique_aids_count,
        processed_aids_count,
        completed_severities,
        withheld_records,
    )


def update_spotlight_state_and_metadata(
    spotlight_state: ContentClientState,
    cursor: str | None,
    snapshot_id: str,
    total_fetched: int,
    unique_aids: set,
    processed_aids: set,
    completed_severities: list[str] | None = None,
    withheld_records: list[dict] | None = None,
) -> None:
    """
    Update Spotlight state with cursor and metadata.
    Centralizes the repetitive state update logic.

    Args:
        spotlight_state: State object to update
        cursor: Pagination cursor/token
        snapshot_id: Snapshot ID for tracking
        total_fetched: Total vulnerabilities fetched
        unique_aids: Set of unique AIDs
        processed_aids: Set of processed AIDs
        completed_severities: List of severities that have completed successfully (optional)
        withheld_records: Records withheld for the final sealing batch, persisted across
            resume cycles. At most one record per severity (optional)
    """
    spotlight_state.cursor = cursor

    # Preserve existing completed_severities if not explicitly provided
    if completed_severities is None and isinstance(spotlight_state.metadata, dict):
        completed_severities = spotlight_state.metadata.get("completed_severities", [])
    elif completed_severities is None:
        completed_severities = []

    # Preserve existing withheld_records if not explicitly provided
    if withheld_records is None and isinstance(spotlight_state.metadata, dict):
        withheld_records = spotlight_state.metadata.get("withheld_records", [])
    elif withheld_records is None:
        withheld_records = []

    spotlight_state.metadata = {
        "snapshot_id": snapshot_id,
        "total_fetched_until_now": total_fetched,
        "unique_aids_count": len(unique_aids),
        "processed_aids_count": len(processed_aids),
        "completed_severities": completed_severities,
        "withheld_records": withheld_records,
    }


async def fetch_spotlight_vulnerabilities_page(
    client: ContentClient, after_token: str | None, filter_query: str
) -> tuple[list, dict]:
    """
    Fetch a single page of Spotlight vulnerabilities with custom filter.

    Args:
        client: ContentClient instance
        after_token: Pagination token (None for first request)
        filter_query: FQL filter query (e.g., "status:['open','reopen']" or "status:['open','reopen']+cve.severity:['CRITICAL']")

    Returns:
        Tuple of (vulnerabilities_list, response_data)
    """
    # Build request parameters
    params = {"limit": MAX_FETCH_SPOTLIGHT_ASSETS, "filter": filter_query, "facet": ["host_info", "cve"]}

    # Add pagination token if provided
    if after_token:
        params["after"] = after_token

    log_falcon_assets(
        f"Fetching Spotlight page with limit={MAX_FETCH_SPOTLIGHT_ASSETS}, after_token={'present' if after_token else 'none'}"
    )

    # Make ASYNC API request
    response = await client._request(method="GET", url_suffix="/spotlight/combined/vulnerabilities/v1", params=params)

    # Parse JSON response
    response_data = response.json()
    vulnerabilities = response_data.get("resources", [])

    log_falcon_assets(f"Fetched {len(vulnerabilities)} vulnerabilities in this page")

    return vulnerabilities, response_data


async def wait_for_background_tasks(pending_tasks: set[asyncio.Task], task_description: str = "background") -> None:
    """Wait for all pending async tasks to complete and raise on first failure.

    Args:
        pending_tasks: Set of asyncio.Task objects to await.
        task_description: Human-readable label for log messages (e.g. "vulnerability send").

    Raises:
        Exception: Re-raises the first exception encountered from a failed task.
    """
    if not pending_tasks:
        return

    log_falcon_assets(f"Waiting for {len(pending_tasks)} {task_description} tasks to complete", "info")
    results = await asyncio.gather(*pending_tasks, return_exceptions=True)

    for res in results:
        if isinstance(res, Exception):
            log_falcon_assets(f"Background {task_description} task failed: {res}", "error")
            raise res

    log_falcon_assets(f"All {task_description} tasks completed successfully", "info")


async def fetch_vulnerabilities_by_severity(
    client: ContentClient,
    severity: str,
    context_store: ContentClientContextStore,
    spotlight_state: ContentClientState,
    snapshot_id: str,
    asset_handler: AssetsDeviceHandler,
) -> tuple[int, set, set[asyncio.Task], list[dict]]:
    """Fetch all vulnerabilities for a single severity level with pagination.

    This function handles continuous pagination for one severity, avoiding cursor
    expiration by fetching all pages sequentially without delays.

    Args:
        client: ContentClient instance for API calls
        severity: Severity level to filter (CRITICAL, HIGH, MEDIUM, LOW, NONE, UNKNOWN)
        context_store: Context store for state persistence
        spotlight_state: Current Spotlight state object
        snapshot_id: Snapshot ID for asset collection tracking
        asset_handler: AssetsDeviceHandler for AID enrichment

    Returns:
        Tuple of (total_vulnerabilities_fetched, unique_aids, pending_tasks, withheld_records)
    """
    log_falcon_assets(f"[{severity}] Starting vulnerability fetch for severity: {severity}", "info")

    total_fetched = 0
    unique_aids: set = set()
    pending_tasks: set[asyncio.Task] = set()
    after_token: str | None = None
    batch_counter = 0
    last_saved_batch_number = 0
    # The first fetched record is withheld from the data batches to be sent in the seal.
    withheld_records: list[dict] = []

    try:
        while True:
            # BACKPRESSURE: before fetching next page, wait if too many send tasks are pending.
            # This prevents unbounded memory growth from fire-and-forget vulnerability batches.
            while len(pending_tasks) >= MAX_PENDING_TASKS_PER_SEVERITY:
                log_falcon_assets(
                    f"[{severity}] Backpressure: {len(pending_tasks)} pending tasks >= limit {MAX_PENDING_TASKS_PER_SEVERITY}, "
                    f"waiting for at least one to complete (RSS: {_get_process_memory_mb()})"
                )
                done, pending_tasks_updated = await asyncio.wait(pending_tasks, return_when=asyncio.FIRST_COMPLETED)
                pending_tasks = pending_tasks_updated
                # Process completed tasks to update last_saved_batch_number
                for completed_task in done:
                    try:
                        result = completed_task.result()
                        if isinstance(result, int) and result > last_saved_batch_number:
                            last_saved_batch_number = result
                    except Exception as e:
                        log_falcon_assets(f"[{severity}] Background send task failed: {e}", "error")
                log_falcon_assets(
                    f"[{severity}] Backpressure released: {len(done)} tasks completed, " f"{len(pending_tasks)} still pending"
                )

            # Build filter query with severity and a lookback window.
            # Only fetch vulnerabilities updated within the last SPOTLIGHT_LOOKBACK_DAYS days to bound
            # the dataset size for very large tenants. Uses FQL relative time syntax.
            filter_query = (
                f"status:['open','reopen']+cve.severity:['{severity}']" f"+updated_timestamp:>'now-{SPOTLIGHT_LOOKBACK_DAYS}d'"
            )

            log_falcon_assets(
                f"[{severity}] Fetching batch {batch_counter + 1} with limit={MAX_FETCH_SPOTLIGHT_ASSETS}, "
                f"after_token={'present' if after_token else 'none'}"
            )

            vulnerabilities, response_data = await fetch_spotlight_vulnerabilities_page(
                client=client, after_token=after_token, filter_query=filter_query
            )

            log_falcon_assets(f"[{severity}] Fetched {len(vulnerabilities)} vulnerabilities in batch {batch_counter + 1}")

            # Extract unique AIDs from this batch (covers the withheld record too).
            extract_unique_aids(vulnerabilities, unique_aids)

            # Send AIDs to asset handler for enrichment (async fire-and-forget)
            batch_aids = {vuln.get("aid") for vuln in vulnerabilities if vuln.get("aid")}
            await asset_handler.receive_new_aids(batch_aids)

            # Count every fetched record, including the withheld one, so the count stays exact.
            total_fetched += len(vulnerabilities)
            batch_counter += 1

            # Withhold the first record of this severity from the data batches; it is sent later
            # in the sealing batch. It is already counted and AID-enriched above, so it is still
            # sent exactly once.
            records_to_send = vulnerabilities
            if not withheld_records and vulnerabilities:
                withheld_records.append(vulnerabilities[0])
                records_to_send = vulnerabilities[1:]
                log_falcon_assets(
                    f"[{severity}] Withholding first record for the sealing batch "
                    f"(id={vulnerabilities[0].get('id')}); sending {len(records_to_send)} records in this batch.",
                    "info",
                )

            # Get next pagination token
            new_after_token = response_data.get("meta", {}).get("pagination", {}).get("after")

            # Determine if this is the last batch for this severity
            is_last_batch = not new_after_token

            # For severity-based fetching, we use items_count=1 for all batches
            # The final sealing happens in the orchestrator after all severities complete
            items_count = 1

            # Create task to send batch to XSIAM (without the withheld first record)
            task = create_task_send_batch_to_xsiam_and_save_context(
                data=records_to_send,
                product=SPOTLIGHT_VULN_PRODUCT,
                snapshot_id=snapshot_id,
                items_count=items_count,
                batch_number=batch_counter,
                last_saved_batch_number=last_saved_batch_number,
                context_store=context_store,
                state=spotlight_state,
                save_state_callback=save_spotlight_state,
                data_type="assets",
            )

            # Track task and update last_saved_batch_number when task completes
            def update_last_saved(future, _pending=pending_tasks):
                nonlocal last_saved_batch_number
                try:
                    last_saved_batch_number = future.result()
                except Exception as e:
                    log_falcon_assets(f"[{severity}] Background vulnerability task failed: {e}", "error")
                finally:
                    _pending.discard(future)

            pending_tasks.add(task)
            task.add_done_callback(update_last_saved)
            log_falcon_assets(
                f"[{severity}] Created send task for batch {batch_counter} "
                f"(pending: {len(pending_tasks)}/{MAX_PENDING_TASKS_PER_SEVERITY})"
            )

            # Log memory stats every 10 batches
            if batch_counter % 10 == 0:
                log_falcon_assets(
                    f"[{severity}] Memory checkpoint: batch={batch_counter}, total_fetched={total_fetched}, "
                    f"unique_aids={len(unique_aids)}, pending_tasks={len(pending_tasks)}, "
                    f"RSS: {_get_process_memory_mb()}",
                    "info",
                )

            # Check if more pages exist
            if is_last_batch:
                log_falcon_assets(
                    f"[{severity}] Completed fetching vulnerabilities. Total: {total_fetched}, Unique hosts: {len(unique_aids)}, "
                    f"pending_tasks: {len(pending_tasks)}, RSS: {_get_process_memory_mb()}",
                    "info",
                )
                break

            # More pages exist - continue to next batch
            log_falcon_assets(f"[{severity}] More pages available. Fetched so far: {total_fetched}")
            after_token = new_after_token

    except ContentClientError as e:
        # Check if this is an authentication error (HTTP 401)
        # Authentication errors are not transient and should fail immediately
        if e.response and e.response.status_code == 401:
            error_msg = (
                f"Authentication failed (HTTP 401) while fetching {severity} severity vulnerabilities. "
                f"Invalid or expired credentials. Please verify the API credentials and try again. "
                f"Error: {e}"
            )
            log_falcon_assets(f"[{severity}] {error_msg}", "error")
            raise ContentClientError(error_msg) from e

        # Check for "Unauthorized" in error message as fallback
        error_str = str(e)
        if "Unauthorized" in error_str or "401" in error_str:
            error_msg = (
                f"Authentication failed while fetching {severity} severity vulnerabilities. "
                f"Invalid or expired credentials. Please verify the API credentials and try again. "
                f"Error: {e}"
            )
            log_falcon_assets(f"[{severity}] {error_msg}", "error")
            raise ContentClientError(error_msg) from e

        # Check if this is an expired cursor error
        if "Search context expired" in error_str or ('"code": 404' in error_str and "after" in error_str):
            log_falcon_assets(
                f"[{severity}] Pagination cursor expired. This should not happen with continuous fetching. "
                f"Progress ({total_fetched} vulnerabilities) will be lost.",
                "error",
            )
        log_falcon_assets(f"[{severity}] Error during fetch: {e}", "error")
        raise
    except Exception as e:
        log_falcon_assets(f"[{severity}] Unexpected error during fetch: {e}", "error")
        raise

    return total_fetched, unique_aids, pending_tasks, withheld_records


async def await_and_aggregate_severity_results(
    severity_tasks: list[tuple[str, asyncio.Task]],
    current_completed_severities: list[str],
    context_store: ContentClientContextStore,
    spotlight_state: ContentClientState,
    snapshot_id: str,
    prior_withheld_records: list[dict] | None = None,
) -> tuple[int, set, set[asyncio.Task], list[str], list[dict]]:
    """Wait for all severity tasks and aggregate their results.

    Args:
        severity_tasks: List of (severity, task) tuples to await
        current_completed_severities: List of severities already completed
        context_store: Context store for state persistence
        spotlight_state: Current Spotlight state object
        snapshot_id: Snapshot ID for asset collection tracking
        prior_withheld_records: Records withheld by severities completed in previous cycles.
            New per-severity withheld records are appended so the seal covers all severities.

    Returns:
        Tuple of (total_vulnerabilities, all_unique_aids, all_pending_tasks,
        updated_completed_severities, withheld_records). ``withheld_records`` holds the
        records withheld across all completed severities (this cycle + prior cycles).
    """
    total_vulnerabilities = 0
    all_unique_aids: set = set()
    all_pending_tasks: set[asyncio.Task] = set()
    # Seed with records withheld in previous cycles so the seal isn't missing earlier severities.
    all_withheld_records: list[dict] = list(prior_withheld_records or [])

    for severity, task in severity_tasks:
        try:
            log_falcon_assets(f"Waiting for {severity} severity task to complete...", "info")
            severity_total, severity_aids, severity_tasks_result, severity_withheld = await task
            total_vulnerabilities += severity_total
            all_unique_aids.update(severity_aids)
            all_pending_tasks.update(severity_tasks_result)
            all_withheld_records.extend(severity_withheld)
            log_falcon_assets(
                f"[{severity}] Completed: {severity_total} vulnerabilities, {len(severity_aids)} unique hosts", "info"
            )

            # Mark this severity as completed
            if severity not in current_completed_severities:
                current_completed_severities.append(severity)
                log_falcon_assets(f"[{severity}] Marked as completed. Total completed: {current_completed_severities}", "info")

                # Persist completed severities and the accumulated withheld records after each
                # severity completes, so a resumed run does not lose earlier severities' records.
                update_spotlight_state_and_metadata(
                    spotlight_state=spotlight_state,
                    cursor=None,  # No cursor needed for severity-based fetching
                    snapshot_id=snapshot_id,
                    total_fetched=0,  # Reset for next cycle
                    unique_aids=set(),  # Reset for next cycle
                    processed_aids=set(),  # Reset for next cycle
                    completed_severities=current_completed_severities,
                    withheld_records=all_withheld_records,
                )
                save_spotlight_state(context_store, spotlight_state)
                log_falcon_assets(
                    f"[{severity}] Saved completion state to context (withheld_records so far: {len(all_withheld_records)})",
                    "info",
                )

        except Exception as e:
            log_falcon_assets(f"[{severity}] Failed with error: {e}", "error")
            # Don't mark as completed if it failed - will retry next cycle
            continue

    log_falcon_assets(
        f"All severity queries completed. Total vulnerabilities: {total_vulnerabilities}, "
        f"Total unique hosts: {len(all_unique_aids)}",
        "info",
    )

    return total_vulnerabilities, all_unique_aids, all_pending_tasks, current_completed_severities, all_withheld_records


async def finalize_severity_fetch(
    all_pending_tasks: set[asyncio.Task],
    current_completed_severities: list[str],
    total_vulnerabilities: int,
    all_unique_aids: set,
    asset_handler: AssetsDeviceHandler,
    context_store: ContentClientContextStore,
    spotlight_state: ContentClientState,
    snapshot_id: str,
    withheld_records: list[dict] | None = None,
) -> None:
    """Finalize the severity fetch by waiting for background tasks and sealing snapshot if complete.

    Args:
        all_pending_tasks: Set of background tasks to wait for
        current_completed_severities: List of severities completed in this cycle
        total_vulnerabilities: Total number of vulnerabilities fetched
        all_unique_aids: Set of all unique asset IDs
        asset_handler: Asset handler for enrichment
        context_store: Context store for state persistence
        spotlight_state: Current Spotlight state object
        snapshot_id: Snapshot ID for asset collection tracking
        withheld_records: Records withheld during fetching to send as the sealing batch.
            Each record is sent exactly once (only here), so the count stays exact.
    """
    withheld_records = withheld_records or []

    # Wait for all background vulnerability send tasks to complete
    log_falcon_assets(f"Waiting for {len(all_pending_tasks)} background vulnerability send tasks...", "info")
    await wait_for_background_tasks(all_pending_tasks, "vulnerability send")

    # Check if ALL severities have completed (including previously completed ones)
    all_severities_completed = set(current_completed_severities) == set(SPOTLIGHT_SEVERITIES)

    if all_severities_completed:
        if not withheld_records:
            # Grand total is zero: there is no real record to seal with. Emitting an empty
            # request would not create a BQ row anyway (the original bug), so skip sealing.
            # This is a legitimately empty snapshot.
            log_falcon_assets("All severities completed but no records were fetched. Skipping seal (empty snapshot).", "info")
        else:
            # Send the final sealing batch with the withheld records and the actual total count.
            log_falcon_assets(
                f"All severities completed successfully. Sending final sealing batch for "
                f"snapshot_id={snapshot_id} with {len(withheld_records)} withheld record(s) and "
                f"total-items-count={total_vulnerabilities}",
                "info",
            )
            final_task = create_task_send_batch_to_xsiam_and_save_context(
                data=withheld_records,  # Real data rows so the count lands in BigQuery
                product=SPOTLIGHT_VULN_PRODUCT,
                snapshot_id=snapshot_id,
                items_count=total_vulnerabilities,  # Final total count
                batch_number=999999,  # High number to ensure it's processed last
                last_saved_batch_number=0,
                context_store=context_store,
                state=spotlight_state,
                save_state_callback=save_spotlight_state,
                data_type="assets",
            )
            await final_task
            log_falcon_assets(
                f"Final sealing batch sent successfully for snapshot_id={snapshot_id} "
                f"(total-items-count={total_vulnerabilities})",
                "info",
            )

        # Flush remaining AIDs and wait for all asset enrichment tasks
        total_assets_count = len(all_unique_aids)
        log_falcon_assets(
            f"Flushing remaining AIDs and waiting for asset enrichment tasks. Total assets: {total_assets_count}", "info"
        )
        await asset_handler.flush_remaining(total_items_count=total_assets_count)

        log_falcon_assets(
            f"Parallel severity fetch completed. Total vulnerabilities: {total_vulnerabilities}, "
            f"Total unique hosts: {len(all_unique_aids)}, Enriched assets: {len(asset_handler.processed_aids)}",
            "info",
        )

        # State will be cleared by fetch_spotlight_assets() after this function returns
        log_falcon_assets("All severities completed successfully.", "info")
    else:
        log_falcon_assets(
            f"Not all severities completed yet. Snapshot NOT sealed. Completed: {current_completed_severities}, "
            f"Remaining: {[s for s in SPOTLIGHT_SEVERITIES if s not in current_completed_severities]}",
            "warning",
        )
        log_falcon_assets(
            f"Partial fetch completed. Total vulnerabilities in this cycle: {total_vulnerabilities}, "
            f"Total unique hosts: {len(all_unique_aids)}. Will retry incomplete severities in next fetch.",
            "info",
        )


async def fetch_spotlight_by_severity_parallel(
    client: ContentClient,
    context_store: ContentClientContextStore,
    spotlight_state: ContentClientState,
    snapshot_id: str,
    completed_severities: list[str],
    prior_withheld_records: list[dict] | None = None,
) -> tuple[int, set]:
    """Orchestrate parallel vulnerability fetching across all severity levels.

    Runs 6 parallel queries (one per severity) to avoid cursor expiration issues.
    Each severity query maintains its own cursor and fetches continuously.
    Skips severities that have already completed in previous fetch cycles.

    Args:
        client: ContentClient instance for API calls
        context_store: Context store for state persistence
        spotlight_state: Current Spotlight state object
        snapshot_id: Snapshot ID for asset collection tracking
        completed_severities: List of severities already completed in previous cycles
        prior_withheld_records: Records withheld for the seal by severities completed in
            previous cycles, carried forward so the seal includes them.

    Returns:
        Tuple of (total_vulnerabilities, unique_aids)
    """
    log_falcon_assets("Starting parallel vulnerability fetch by severity", "info")
    log_falcon_assets(f"All severities: {SPOTLIGHT_SEVERITIES}", "info")
    log_falcon_assets(f"Previously completed severities: {completed_severities}", "info")

    # Filter out already completed severities
    severities_to_fetch = [s for s in SPOTLIGHT_SEVERITIES if s not in completed_severities]

    if not severities_to_fetch:
        log_falcon_assets("All severities already completed. Nothing to fetch.", "info")
        return 0, set()

    log_falcon_assets(f"Severities to fetch in this cycle: {severities_to_fetch}", "info")

    # Track completed severities in this cycle (start with previously completed)
    current_completed_severities = completed_severities.copy()

    # Create asset handler for enrichment
    asset_handler = AssetsDeviceHandler(
        client=client,
        context_store=context_store,
        spotlight_state=spotlight_state,
        snapshot_id=snapshot_id,
        processed_aids=set(),  # Start fresh for this fetch
        batch_limit=MAX_FETCH_SPOTLIGHT_ASSETS,
    )

    # Create parallel tasks for each severity that needs fetching
    severity_tasks = []
    for severity in severities_to_fetch:
        task = asyncio.create_task(
            fetch_vulnerabilities_by_severity(
                client=client,
                severity=severity,
                context_store=context_store,
                spotlight_state=spotlight_state,
                snapshot_id=snapshot_id,
                asset_handler=asset_handler,
            )
        )
        severity_tasks.append((severity, task))

    log_falcon_assets(f"Created {len(severity_tasks)} parallel severity fetch tasks", "info")

    # Wait for all severity tasks and aggregate results
    (
        total_vulnerabilities,
        all_unique_aids,
        all_pending_tasks,
        current_completed_severities,
        withheld_records,
    ) = await await_and_aggregate_severity_results(
        severity_tasks=severity_tasks,
        current_completed_severities=current_completed_severities,
        context_store=context_store,
        spotlight_state=spotlight_state,
        snapshot_id=snapshot_id,
        prior_withheld_records=prior_withheld_records,
    )

    await finalize_severity_fetch(
        all_pending_tasks=all_pending_tasks,
        current_completed_severities=current_completed_severities,
        total_vulnerabilities=total_vulnerabilities,
        all_unique_aids=all_unique_aids,
        asset_handler=asset_handler,
        context_store=context_store,
        spotlight_state=spotlight_state,
        snapshot_id=snapshot_id,
        withheld_records=withheld_records,
    )

    return total_vulnerabilities, all_unique_aids


async def fetch_spotlight_assets():
    """Fetch Spotlight vulnerabilities using severity-based parallel approach.

    IMPLEMENTATION (Severity-Based Parallel):
    1. Split vulnerability fetching by severity: CRITICAL, HIGH, MEDIUM, LOW, NONE, UNKNOWN
    2. Run 6 parallel queries, each with independent cursor
    3. Each query fetches continuously (no cursor expiration within query)
    4. Aggregate results from all severities
    5. Extract unique AIDs and enrich assets
    6. Send vulnerabilities and assets to XSIAM with proper snapshot sealing

    This approach solves the pagination cursor TTL issue for customers with 6M+ vulnerabilities
    by parallelizing across severity levels. Largest query (LOW, ~2.4M vulns) completes in ~136 minutes.
    Total time = max(all queries) = ~2.3 hours. No cursor expiration, no duplication.
    """
    log_falcon_assets("Starting Spotlight assets fetch execution (severity-based parallel approach).", "info")
    fetch_start_time = time.monotonic()

    # Start tracemalloc to track Python allocations vs OS RSS (quantifies arena fragmentation)
    import tracemalloc

    if not tracemalloc.is_tracing():
        tracemalloc.start()
        log_falcon_assets("tracemalloc started for memory diagnostics")

    context_store = ContentClientContextStore(namespace="SpotlightAssets")
    (
        spotlight_state,
        snapshot_id,
        _total_fetched,
        _unique_aids,
        _processed_aids,
        completed_severities,
        prior_withheld_records,
    ) = load_spotlight_state(context_store)
    # Note: total_fetched, unique_aids, processed_aids not used in severity-based approach
    # Each severity starts fresh. Only completed_severities and prior_withheld_records are used.

    client = create_spotlight_client(context_store)

    try:
        # Fetch vulnerabilities in parallel by severity
        total_vulnerabilities, all_unique_aids = await fetch_spotlight_by_severity_parallel(
            client=client,
            context_store=context_store,
            spotlight_state=spotlight_state,
            snapshot_id=snapshot_id,
            completed_severities=completed_severities,
            prior_withheld_records=prior_withheld_records,
        )

        # Reset state after successful fetch (completed_severities already cleared in parallel function if all done).
        # Also clear the persisted withheld_records so they do not leak into the next snapshot.
        log_falcon_assets("Resetting Spotlight state after successful complete fetch")
        update_spotlight_state_and_metadata(
            spotlight_state=spotlight_state,
            cursor=None,
            snapshot_id="",
            total_fetched=0,
            unique_aids=set(),
            processed_aids=set(),
            completed_severities=[],  # Ensure it's cleared
            withheld_records=[],  # Clear persisted seal records for the next snapshot
        )
        save_spotlight_state(context_store, spotlight_state)

        fetch_elapsed = time.monotonic() - fetch_start_time
        fetch_minutes = fetch_elapsed / 60
        log_falcon_assets(
            f"Finished Spotlight assets fetch in {fetch_minutes:.1f} minutes ({fetch_elapsed:.0f}s). "
            f"Total vulnerabilities: {total_vulnerabilities}, "
            f"Total unique hosts: {len(all_unique_aids)}, "
            f"RSS: {_get_process_memory_mb()}",
            "info",
        )

    except (ContentClientError, Exception) as e:
        log_falcon_assets(f"Error during Spotlight fetch: {e}", "error")

        if isinstance(e, ContentClientError):
            diagnosis = client.diagnose_error(e)
            log_falcon_assets(f"Issue: {diagnosis['issue']}, Solution: {diagnosis['solution']}", "error")

        raise

    finally:
        await client.aclose()


def fetch_cnapp_assets():
    log_falcon_assets("Starting fetch assets execution.", "info", asset="CNAPP Alerts")
    new_last_run, detections, items_count, snapshot_id = get_cnapp_assets()

    log_falcon_assets(
        f"Sending a batch of {len(detections)} assets to xsiam with {snapshot_id=}", log_type="debug", asset="CNAPP Alerts"
    )
    send_data_to_xsiam(
        data=detections,
        vendor=VENDOR,
        product=CNAPP_PRODUCT,
        data_type="assets",
        snapshot_id=snapshot_id,
        items_count=items_count,
        should_update_health_module=False,
    )
    log_falcon_assets("Finished sending a batch of assets.", log_type="debug", asset="CNAPP Alerts")

    log_falcon_assets(f"Preparing to save assets last run with {new_last_run=}.", log_type="debug", asset="CNAPP Alerts")
    demisto.setAssetsLastRun(new_last_run)
    log_falcon_assets("Assets last run was saved succesfuly.", log_type="debug", asset="CNAPP Alerts")

    demisto.updateModuleHealth({"assetsPulled": len(detections)})

    log_falcon_assets("Finished fetch assets exeuction.", log_type="info", asset="CNAPP Alerts")


def fetch_assets_command():
    log_falcon_assets("Starting fetch assets execution.", "info", asset="")
    params = demisto.params()
    fetch_assets_types = params.get("fetch_assets_type", "")

    if "CNAPP Alerts" in fetch_assets_types:
        fetch_cnapp_assets()

    if "Spotlight" in fetch_assets_types:
        asyncio.run(fetch_spotlight_assets())


def fetch_detections_by_product_type(
    current_fetch_info: dict,
    look_back: int,
    product_type: str,
    fetch_query: str,
    detections_type: str,
    detection_name_prefix: str,
    start_time_key: str,
    is_fetch_events: bool = False,
) -> tuple[List, dict]:
    """The fetch logic for idp, ods and mobile detections.

    Args:
        current_fetch_info (dict): The last run object.
        look_back (int): The number of minutes to lookback.
        product_type (str): The product_type, used for debug & query.
        fetch_query (str): The user's query param.
        detections_type (str): The detection type, used for debugging and context save.
        detection_name_prefix (str): The name prefix for the fetched incidents.
        start_time_key (str): The key to save as the incident occurred time.

    Returns:
        tuple[List, dict]: The list of the fetched incidents and the updated last object.
    """
    detections: List = []
    # The configured per-run limit (10000 for XSIAM, "Max incidents per fetch" for XSOAR).
    base_fetch_limit = MAX_FETCH_DETECTION_PER_API_CALL if is_fetch_events else INCIDENTS_PER_FETCH
    offset: int = current_fetch_info.get("offset") or 0
    start_fetch_time, end_fetch_time = get_fetch_run_time_range(
        last_run=current_fetch_info, first_fetch=FETCH_TIME, look_back=look_back, date_format=DETECTION_DATE_FORMAT
    )

    fetch_limit = current_fetch_info.get("limit") or base_fetch_limit

    # Build the base product/type filter clauses.
    # Most product types (e.g. idp, mobile, ngsiem, xdr, automated-lead, thirdparty) map to a single
    # `product:'<value>'` clause. ON_DEMAND ('ods') and OFP ('ofp') need `type:'<value>'` instead.
    # IOA needs a compound `product:'fcs'+type:'cloud-ioa'` selector.
    product_type_to_clauses: dict[str, tuple[str | None, str | None]] = {
        IncidentType.ON_DEMAND.value: (None, IncidentType.ON_DEMAND.value),
        IncidentType.OFP.value: (None, IncidentType.OFP.value),
        IncidentType.IOA_TYPE_TAG.value: ("fcs", IncidentType.IOA_TYPE_TAG.value),
    }
    product_clause_value, type_clause_value = product_type_to_clauses.get(product_type, (product_type, None))
    filter_clauses: list[str] = []
    if product_clause_value:
        filter_clauses.append(f"product:'{product_clause_value}'")
    if type_clause_value:
        filter_clauses.append(f"type:'{type_clause_value}'")
    filter_clauses.append(f"created_timestamp:>'{start_fetch_time}'")
    filter = "+".join(filter_clauses)

    if fetch_query:
        filter = f"({filter})+({fetch_query})"
    # The API rejects requests where offset + limit exceeds MAX_FETCH_SIZE. With look_back, fetch_limit can grow
    # past that bound, so cap the value sent to the API while keeping fetch_limit for dedup and last_run bookkeeping.
    api_limit = min(fetch_limit, MAX_FETCH_SIZE - offset)
    response = get_detections_ids(filter_arg=filter, limit=api_limit, offset=offset, product_type=product_type)
    detections_ids: list[dict] = demisto.get(response, "resources", [])
    demisto.debug(f"CrowdStrikeFalconMsg: Total fetched detections: {len(detections_ids)}")
    total_detections = demisto.get(response, "meta.pagination.total")
    offset = calculate_new_offset(offset, len(detections_ids), total_detections)
    if offset:
        if offset + fetch_limit > MAX_FETCH_SIZE:
            demisto.debug(
                f"CrowdStrikeFalconMsg: The new offset: {offset} + limit: {fetch_limit} reached "
                f"{MAX_FETCH_SIZE}, resetting the offset to 0"
            )
            offset = 0
        demisto.debug(f"CrowdStrikeFalconMsg: The new {detections_type} offset is {offset}")

    if detections_ids:
        raw_res = get_detection_entities(detections_ids)
        if "resources" in raw_res:
            full_detections = demisto.get(raw_res, "resources")
            for detection in full_detections:
                detection["incident_type"] = detections_type
                detection_to_context = detection_to_incident_context(
                    detection, detection_name_prefix, start_time_key, is_fetch_events=is_fetch_events
                )
                detections.append(detection_to_context)
        detections = (
            truncate_long_time_str(detections, "occurred")
            if product_type
            in {IncidentType.ON_DEMAND.value, IncidentType.OFP.value, IncidentType.NGSIEM_DETECTION, IncidentType.THIRD_PARTY}
            else detections
        )
        detections = filter_incidents_by_duplicates_and_limit(
            incidents_res=detections, last_run=current_fetch_info, fetch_limit=fetch_limit, id_field="name"
        )

    demisto.debug(f"CrowdstrikeFalconMsg: last_run before update: {current_fetch_info}")
    current_fetch_info = update_last_run_object(
        last_run=current_fetch_info,
        incidents=detections,
        fetch_limit=base_fetch_limit,
        start_fetch_time=start_fetch_time,
        end_fetch_time=end_fetch_time,
        look_back=look_back,
        created_time_field="occurred",
        id_field="name",
        date_format=DETECTION_DATE_FORMAT,
        new_offset=offset,
    )
    demisto.debug(f"CrowdstrikeFalconMsg: last_run after update: {current_fetch_info}")
    demisto.debug(f"CrowdstrikeFalconMsg: Ending fetch {detections_type}. Fetched {len(detections)}")
    return detections, current_fetch_info


def fetch_ngsiem_cases(last_run: dict, look_back: int, fetch_query: str):
    """
    Fetches NGSIEM cases from CrowdStrikeFalcon
    :param last_run: The last run object
    :param look_back: The look back time in minutes
    :param fetch_query: The fetch query
    :return: A tuple containing a list of cases and the updated last run object
    """
    cases = []
    offset = last_run.get("offset", 0)
    fetch_limit = last_run.get("limit", INCIDENTS_PER_FETCH)
    start_fetch_time, end_fetch_time = get_fetch_run_time_range(
        last_run=last_run, first_fetch=FETCH_TIME, look_back=look_back, date_format=DETECTION_DATE_FORMAT
    )

    # build query and fetch cases data
    filter = f"created_timestamp:>'{start_fetch_time}'"
    if fetch_query:
        filter += f"+{fetch_query}"
    demisto.debug(f"CrowdStrikeFalconMsg: fetching NGSIEM case ids with: {filter=}, {fetch_limit=}, {offset=}")
    total_cases, ids = get_cases_data(filter, fetch_limit, offset)
    demisto.debug(f"CrowdStrikeFalconMsg: fetched a total of {len(ids)} NGSIEM case ids")

    # calculate new offset
    offset = calculate_new_offset(offset, len(ids), total_cases)
    if offset and offset + fetch_limit > MAX_FETCH_SIZE:
        demisto.debug(
            f"CrowdStrikeFalconMsg: The new offset: {offset} + limit: {fetch_limit} reached "
            f"{MAX_FETCH_SIZE}, resetting the offset to 0"
        )
        offset = 0
    demisto.debug(f"CrowdStrikeFalconMsg: The new ngsiem cases offset is {offset}")

    # fetch cases details if ids exist
    if ids:
        cases_details = get_cases_details(ids)
        # add incident type and append to list
        demisto.debug(f"CrowdStrikeFalconMsg: fetched cases details: {json.dumps(cases_details)=}")
        for case in cases_details:
            add_mirroring_fields(case)
            case["incident_type"] = NGSIEM_CASE
            fix_time_field(case, "created_timestamp")
            case_context = {
                "name": f"{NGSIEM_CASE} ID: {case.get('id')}",
                "occurred": case.get("created_timestamp"),
                "severity": case.get("severity"),
                "rawJSON": json.dumps(case),
            }
            cases.append(case_context)
        demisto.debug(f"cases before filter: {cases=}")
        cases = filter_incidents_by_duplicates_and_limit(
            incidents_res=cases, last_run=last_run, fetch_limit=fetch_limit, id_field="name"
        )
    demisto.debug(f"CrowdstrikeFalconMsg: cases last_run before update: {last_run}")
    last_run = update_last_run_object(
        last_run=last_run,
        incidents=cases,
        fetch_limit=fetch_limit,
        start_fetch_time=start_fetch_time,
        end_fetch_time=end_fetch_time,
        look_back=look_back,
        created_time_field="occurred",
        id_field="name",
        date_format=DETECTION_DATE_FORMAT,
        new_offset=offset,
    )
    demisto.debug(f"CrowdstrikeFalconMsg: cases last_run after update: {last_run}")
    demisto.debug(f"CrowdstrikeFalconMsg: Ending NGSIEM Cases fetch. Fetched {len(cases)}")
    demisto.debug(f"CrowdstrikeFalconMsg: Ending NGSIEM Cases fetch. {cases=}")
    return cases, last_run


def parse_ioa_iom_incidents(
    fetched_data: list[dict[str, Any]],
    last_date: str,
    last_fetched_ids: list[str],
    date_key: str,
    id_key: str,
    date_format: str,
    is_paginating: bool,
    to_incident_context: Callable[[dict[str, Any], str], dict[str, Any]],
    incident_type: str,
) -> tuple[list[dict[str, Any]], list[str], str]:
    """This function is in charge of parsing IOA, and IOM data from their respective API,
    to create incidents from them.

    Args:
        fetched_data (list[dict[str, Any]]): The fetched data.
        last_date (str): The last date saved in the last run object.
        last_fetched_ids (list[str]): The last fetched IDs.
        date_key (str): The key of the value that holds the date in the API.
        id_key (str): The key of the value that holds the ID in the API.
        date_format (str): The date format.
        is_paginating (bool): Whether we are doing pagination or not. When false, the previously fetched IDs
        will NOT be considered for duplicates removal.
        new_next_token (str | None): The next token that will be used in the next run.
        next_token (str | None): The next token that was used in the current round.
        to_incident_context (Callable[[dict[str, Any], str], dict[str, Any]]): The function that is used to convert
        data from the API to an incident.
        incident_type (str): The incident type.

    Returns:
        tuple[list[dict[str, Any]], list[str], str]: The fetched incidents, the fetched ids, the largest date
        found withing the fetched incidents.
    """
    incidents: list[dict[str, Any]] = []
    fetched_ids: list[str] = []
    # Hold the date_time_since of all fetched incidents, to acquire the largest date
    fetched_dates: list[datetime] = [safe_strptime(last_date, date_format)]
    for data in fetched_data:
        data_id = data.get(id_key, "")
        if data_id not in last_fetched_ids:
            demisto.debug(f"Creating an incident for CrowdStrike CSPM ID: {data_id}")
            fetched_ids.append(data_id)
            incident_context = to_incident_context(data, incident_type)
            incidents.append(incident_context)
            event_created = reformat_timestamp(demisto.get(data, date_key, ""), date_format)  # type: ignore
            fetched_dates.append(safe_strptime(event_created, date_format))
        else:
            demisto.debug(f"Ignoring CSPM incident with {data_id=} - was already fetched in the previous run")
    new_last_date = max(fetched_dates).strftime(date_format)
    if is_paginating:
        demisto.debug(f"Current run did pagination, or next one will, keeping {len(last_fetched_ids)} IDs from last fetch")
        # If the next run will do pagination, or the current run did pagination, we should keep the ids from the last fetch
        # until progress is made, so we exclude them in the next fetch.
        fetched_ids.extend(last_fetched_ids)
    return incidents, fetched_ids, new_last_date


def get_recon_notification_ids_for_fetch(
    filter: str, recon_offset: Optional[int], limit: int = INCIDENTS_PER_FETCH, sort: str = "created_date|asc", query: str = ""
) -> tuple[list[str], int, int]:
    """
    Get the Recon notification IDs for fetch.

    :param filter: The filter to use.
    :param recon_offset: The offset to start from.
    :param limit: The limit of the results.
    :param sort: The sort order.
    :param query: The query to use.
    :return: A tuple containing the IDs, the offset, and the total number of results.
    """
    params = assign_params(filter=filter, limit=limit, offset=recon_offset, sort=sort, q=query)
    demisto.debug(f"Recon-Log Recon notifications query params: {params=}")
    # The API limit of this request(limit + offset) is 10K
    raw = http_request("GET", "/recon/queries/notifications/v1", params=params)

    ids = raw.get("resources", [])
    pagination = dict_safe_get(raw, ["meta", "pagination"]) or {}

    total = pagination.get("total", 0)
    offset = pagination.get("offset", 0)

    demisto.debug(f"Recon-Log Recon notifications pagination object: {pagination=}")

    return ids, offset, total


def get_recon_notifications_detailed(notification_ids: list[str]) -> list[dict[str, Any]]:
    """
    Get the Recon notification entities with pagination support.

    Args:
        notification_ids (list[str]): The Recon notification IDs.

    Returns:
        list[dict[str, Any]]: A list of the Recon notification entities.
    """
    if not notification_ids:
        return []
    demisto.debug(f"Recon-Log get_recon_notifications_detailed: {notification_ids=}")
    all_resources: list[dict[str, Any]] = []
    offset = 0
    total = offset + 1
    while offset < total:
        query_params = {"ids": notification_ids, "offset": offset}
        demisto.debug(f"Recon-Log Recon notifications detailed request params: {query_params=}")
        raw = http_request(method="GET", url_suffix="/recon/entities/notifications-detailed/v1", params=query_params)

        all_resources.extend(raw.get("resources", []))

        pagination = dict_safe_get(raw, ["meta", "pagination"], {})
        total = pagination.get("total", 0)
        limit = pagination.get("limit", 1)
        offset = pagination.get("offset", 0) + limit

        demisto.debug(f"Recon-Log pagination info: {offset=}, {total=}, {limit=} for detailed notifications")
    return all_resources


def recon_notifications_pagination(
    filter: str,
    recon_offset: Optional[int],
    api_limit: int = MAX_FETCH_SIZE,
    fetch_limit: int = INCIDENTS_PER_FETCH,
    is_fetch: bool = True,
) -> tuple[list[str], list[dict[str, Any]], int | None]:
    """
    Paginates through Recon notifications based on a filter and fetch limits.

    It first fetches notification IDs using pagination, and optionally retrieves
    the detailed notification data for incident creation.

    Args:
        filter: The query filter string to apply to the notifications API.
        api_limit: The maximum number of items to request per single API call.
        recon_offset: The offset (page token) for the current pagination request.
        fetch_limit: The maximum number of total incidents to collect in this run.
                     Defaults to INCIDENTS_PER_FETCH.
        is_fetch: If True, detailed notification data is fetched. If False, only IDs are collected.
                  Defaults to True (used during incident fetching).

    Returns:
        A tuple containing:
        1. collected_ids: A list of all fetched notification IDs (str).
        2. collected_notifications_detailed: A list of dictionaries containing detailed
           notification information (only if is_fetch is True).
        3. next_offset: The offset for the next pagination request (int) or None if
           all results were fetched in this iteration or if is_fetch is False.
    """

    fetch_query = demisto.params().get("recon_fetch_query", "")
    demisto.debug(f"Recon-Log Doing Recon pagination with: {filter=}, {recon_offset=}, {api_limit=}, {fetch_limit=}")

    ids, offset, remote_total = get_recon_notification_ids_for_fetch(
        filter=filter, recon_offset=recon_offset, limit=min(api_limit, fetch_limit), query=fetch_query
    )
    demisto.debug(f"Recon-Log Pagination results: {len(ids)=}, {offset=}")
    full_notifications_deta = []
    if is_fetch:
        full_notifications_deta = get_recon_notifications_detailed(notification_ids=ids)

    next_offset = offset + len(ids) if offset + len(ids) < remote_total else 0

    if not is_fetch:
        return ids, [], None
    return ids, full_notifications_deta, next_offset


def recon_notification_to_incident(recon_notification: dict[str, Any], incident_type: str) -> dict[str, Any]:
    """Create an incident from a Recon notification entity.

    Args:
        recon_notification (dict[str, Any]): A Recon notification entity.
        incident_type (str): The incident type.

    Returns:
        dict[str, Any]: An incident from a Recon notification entity.
    """
    incident_metadata = assign_params(
        mirror_direction=MIRROR_DIRECTION, mirror_instance=INTEGRATION_INSTANCE, incident_type=incident_type
    )
    severity_map = {
        "low": IncidentSeverity.LOW,
        "medium": IncidentSeverity.MEDIUM,
        "high": IncidentSeverity.HIGH,
        "critical": IncidentSeverity.CRITICAL,
        "unknown": IncidentSeverity.UNKNOWN,
    }
    raw_severity = dict_safe_get(recon_notification, ["notification", "rule_priority"], "unknown")

    incident_context = {
        "name": recon_notification.get("id"),
        "occurred": dict_safe_get(recon_notification, ["notification", "created_date"], ""),
        "severity": severity_map.get(str(raw_severity).lower()),
        "rawJSON": json.dumps(recon_notification | incident_metadata),
    }
    return incident_context


def create_recon_filter(is_paginating: bool, last_fetch_filter: str, last_created_date: str, first_fetch_timestamp: str) -> str:
    """Retrieve the Recon filter that will be used in the current fetch round.
    Args:
        is_paginating (bool): Whether we are doing pagination or not.
        last_fetch_filter (str): The last fetch filter that was used in the previous round.
        last_created_date (str): The last created timestamp.
        first_fetch_timestamp (str): The first fetch timestamp.

    Raises:
        DemistoException: If paginating and last filter is an empty string.

    Returns:
        str: The Recon filter that will be used in the current fetch.
    """
    filter = "created_date:"
    if is_paginating:
        if not last_fetch_filter:
            raise DemistoException("Last fetch filter must not be empty when doing pagination")
        # Doing pagination, we need to use the same fetch query as the previous round
        filter = last_fetch_filter
        demisto.debug(f"Recon-Log Doing pagination, using the same query as the previous round. Filter is {filter}")
    else:
        if last_created_date == first_fetch_timestamp:
            # First fetch,
            filter = f"{filter}>='{last_created_date}'"
            demisto.debug(f"Recon-Log First fetch, looking for created_date >= {last_created_date=}. Filter is {filter}")
        else:
            # Not first fetch,
            filter = f"{filter}>'{last_created_date}'"
            demisto.debug(f"Recon-Log Not first fetch, looking for created_date > {last_created_date=}. Filter is {filter}")
    return filter


def fetch_recon_incidents(recon_last_run: Dict[str, Any]) -> tuple[List[Dict], Dict[str, Any]]:
    """
    Fetches Recon notifications and converts them into XSOAR incidents.

    Args:
        recon_last_run: A dictionary containing the last run object for Recon,
                        including offset, last fetched timestamp, and IDs.

    Returns:
        A tuple containing:
        1. A list of incident dictionaries to be created in XSOAR.
        2. A dictionary representing the updated last run object for the next fetch.
    """
    demisto.debug(f"Recon-Log {recon_last_run=}")

    last_ids, recon_offset, last_created, first_fetch_ts = get_current_fetch_data(
        last_run_object=recon_last_run,
        date_format=DATE_FORMAT,
        last_date_key="last_created_date",
        next_token_key="recon_offset",
        last_fetched_ids_key="last_resource_ids",
    )
    demisto.debug(f"Recon-Log Recon fetch current last run: {last_ids=},{recon_offset=},{last_created=},{first_fetch_ts=}")

    # Validate if offset + limit exceeds the 10,000 record limit
    offset_int = arg_to_number(recon_offset) or 0
    if offset_int + min(MAX_FETCH_SIZE, INCIDENTS_PER_FETCH) > 10000:
        demisto.debug(f"Recon-Log: Offset {offset_int} exceeds limit. Resetting offset.")
        return [], {
            "recon_offset": 0,
            "last_created_date": last_created,
            "last_resource_ids": last_ids,
        }

    filter = create_recon_filter(
        is_paginating=bool(recon_offset),
        last_fetch_filter=recon_last_run.get("last_fetch_filter", ""),
        last_created_date=last_created,
        first_fetch_timestamp=first_fetch_ts,
    )
    demisto.debug(f"Recon-Log Recon fetch filter: {filter=}")

    ids, notifications_detailed, new_offset = recon_notifications_pagination(
        filter=filter, recon_offset=arg_to_number(recon_offset)
    )
    demisto.debug(f"Recon-Log Fetched the following Recon notification IDs: [{', '.join(ids)}]")

    recon_incidents, fetched_ids, new_created_ts = parse_ioa_iom_incidents(
        fetched_data=notifications_detailed,
        last_date=last_created,
        last_fetched_ids=last_ids,
        date_key="notification.created_date",
        id_key="id",
        date_format=RECON_DATE_FORMAT,
        is_paginating=bool(new_offset),
        to_incident_context=recon_notification_to_incident,
        incident_type=RECON_NOTIFICATION,
    )

    updated_run = {
        "recon_offset": new_offset,
        "last_created_date": new_created_ts,
        "last_fetch_filter": filter,
        "last_resource_ids": fetched_ids or last_ids,
    }
    return recon_incidents, updated_run


def get_current_fetch_data(
    last_run_object: dict[str, Any],
    date_format: str,
    last_date_key: str,
    next_token_key: str,
    last_fetched_ids_key: str,
) -> tuple[list[str], str | None, str, str]:
    """Returns the last fetched ids, next token that will be used in current round, last date
    found in the last run object, and the first fetch timestamp.

    Args:
        last_run_object (dict[str, Any]): The last run object.
        date_format (str): The date format.
        last_date_key (str): The key of the value that holds the date in the last run object.
        next_token_key (str): The key of the value that holds the next token in the last run object.
        last_fetched_ids_key (str): The key of the value that holds the last fetched ids in the
        last run object.

    Returns:
        tuple[list[str], str | None | int, str, str]: The last fetched IDs, the next token/offset that will be used
        in the current fetch round, the last date saved in the last run object, and the first
        fetch timestamp.
    """
    first_fetch_timestamp = reformat_timestamp(
        time=FETCH_TIME, date_format=date_format, dateparser_settings={"TIMEZONE": "UTC", "RETURN_AS_TIMEZONE_AWARE": True}
    )
    last_date = last_run_object.get(last_date_key, first_fetch_timestamp)
    # The next token is used when not all the results have been returned from the API, therefore,
    # we would need to do pagination using the next token query parameter
    next_token = last_run_object.get(next_token_key)
    # In order to deal with duplicates, we retrieve the last resource ids of the last run, so we can
    # compare them with the newly fetched ids, and ignore any duplicates
    last_fetched_ids: list[str] = last_run_object.get(last_fetched_ids_key, [])
    return last_fetched_ids, next_token, last_date, first_fetch_timestamp


def create_iom_filter(
    is_paginating: bool, last_fetch_filter: str, last_scan_time: str, first_fetch_timestamp: str, configured_fetch_query: str
) -> str:
    """Retrieve the IOM filter that will be used in the current fetch round.

    Args:
        is_paginating (bool): Whether we are doing pagination or not.
        last_fetch_filter (str): The last fetch filter that was used in the previous round.
        last_scan_time (str): The last scan time.
        first_fetch_timestamp (str): The first fetch timestamp.
        configured_fetch_query (str): The fetched query configured by the user.

    Raises:
        DemistoException: If paginating and last filter is an empty string.

    Returns:
        str: The IOM filter that will be used in the current fetch.
    """
    filter = "scan_time:"
    if is_paginating:
        if not last_fetch_filter:
            raise DemistoException("Last fetch filter must not be empty when doing pagination")
        # Doing pagination, we need to use the same fetch query as the previous round
        filter = last_fetch_filter
        demisto.debug(f"Doing pagination, using the same query as the previous round. Filter is {filter}")
    else:
        # If entered here, that means we aren't doing pagination
        if last_scan_time == first_fetch_timestamp:
            # First fetch, we want to include resources with a scan time
            # EQUAL or GREATER than the first fetch timestamp
            filter = f"{filter} >='{last_scan_time}'"
            demisto.debug(f"First fetch, looking for scan time >= {last_scan_time=}. Filter is {filter}")
        else:
            # Not first fetch, we only want to include resources with a scan time
            # GREATER than the last configured scan time, to prevent duplicates.
            filter = f"{filter} >'{last_scan_time}'"
            demisto.debug(f"Not first fetch, only looking for scan time > {last_scan_time=}. Filter is {filter}")
    if configured_fetch_query and not is_paginating:
        # If the user entered a fetch query, then append it to the filter
        demisto.debug("User entered fetch query, appending to filter")
        filter = f"{filter}+{configured_fetch_query}"
    return filter


def validate_iom_fetch_query(iom_fetch_query: str) -> None:
    if "scan_time" in iom_fetch_query:
        raise DemistoException("scan_time is not allowed as part of the IOM fetch query.")


def add_seconds_to_date(date: str, seconds_to_add: int, date_format: str) -> str:
    """Takes in a date in string format, and adds seconds to it according to seconds_to_add.

    Args:
        date (str): The date we want to add seconds to it.
        seconds_to_add (int): The amount of seconds to add to the date.
        date_format (str): The date format.

    Returns:
        str: The date with an increase in seconds.
    """
    added_datetime = safe_strptime(date, date_format) + timedelta(seconds=seconds_to_add)
    return added_datetime.strftime(date_format)


def reformat_timestamp(time: str, date_format: str, dateparser_settings: Any | None = None) -> str:
    """Format the given time according to the supplied date format.

    Args:
        time (str): The time to format.
        date_format (str): The date format.

    Raises:
        DemistoException: If the time is not a proper date string.

    Returns:
        str: The time in the supplied format.
    """
    if parsed_scan_time := dateparser.parse(time, settings=dateparser_settings):
        return parsed_scan_time.strftime(date_format)
    else:
        raise DemistoException(f"{time=} is not a proper date string")


def iom_resource_to_incident(iom_resource: dict[str, Any], incident_type: str) -> dict[str, Any]:
    """Create an incident from an IOM entity.

    Args:
        iom_resource (dict[str, Any]): An IOM entity.
        incident_type (str): The incident type.

    Returns:
        dict[str, Any]: An incident from an IOM entity.
    """
    incident_metadata = assign_params(
        mirror_direction=MIRROR_DIRECTION, mirror_instance=INTEGRATION_INSTANCE, incident_type=incident_type
    )

    incident_context = {
        "name": f'IOM Event ID: {iom_resource.get("id")}',
        "rawJSON": json.dumps(iom_resource | incident_metadata),
    }
    return incident_context


def iom_ids_pagination(
    filter: str, api_limit: int, iom_next_token: str | None, fetch_limit: int = INCIDENTS_PER_FETCH
) -> tuple[list[str], str | None]:
    """This is in charge of doing the pagination process in a single fetch run, since the fetch limit can be greater than
    the api limit, in such a case, we do multiple API calls until we reach the fetch limit, or no more results are found by the
    API.

    Args:
        filter (str): The IOM filter query parameter.
        api_limit (int): The API limit
        iom_next_token (str | None): The IOM next token to start the pagination from.
        fetch_limit (int, optional): The fetch limit. Defaults to INCIDENTS_PER_FETCH.

    Returns:
        tuple[list[dict[str, Any]], str | None]: A tuple where the first element is the fetched resources, and the second is the
        next token that will be used in the next fetch run.
    """
    total_incidents_count = 0
    iom_new_next_token = iom_next_token
    fetched_iom_events: list[str] = []
    continue_pagination = True
    while continue_pagination:
        demisto.debug(f"Doing IOM pagination with the arguments: {filter=}, {api_limit=}, {iom_new_next_token=},{fetch_limit=}")
        iom_resource_ids, iom_new_next_token = get_iom_ids_for_fetch(
            filter=filter, iom_next_token=iom_new_next_token, limit=min(api_limit, fetch_limit - total_incidents_count)
        )
        fetched_iom_events.extend(iom_resource_ids)
        total_incidents_count += len(iom_resource_ids)
        demisto.debug(f"Results of IOM pagination: {total_incidents_count=}, {iom_new_next_token=}")
        if total_incidents_count >= fetch_limit or iom_new_next_token is None:
            # If the number of fetched incidents reaches the fetching limit, or there are no more results to be fetched
            # (by checking the next token variable), then we should stop the pagination process
            continue_pagination = False
    return fetched_iom_events, iom_new_next_token


def get_iom_ids_for_fetch(
    filter: str, iom_next_token: str | None = None, limit: int = INCIDENTS_PER_FETCH
) -> tuple[list[str], str | None]:
    """Do a single API call to receive IOM resource ids.

    Args:
        filter (str | None): The filter to use when fetching IOM events.
        iom_next_token (int | None): The next token to be used as part of the pagination process.
        limit (int, optional): The maximum amount to fetch IOA events. Defaults to INCIDENTS_PER_FETCH.

    Returns:
        tuple[list[dict[str, Any]], str | None]: A tuple where the first element is the returned events, and the second is the
        next token that will be used in the next API call.
    """
    query_params = assign_params(filter=filter, limit=limit, next_token=iom_next_token)
    demisto.debug(f"IOM {query_params=}")
    raw_response = http_request(method="GET", url_suffix="/detects/queries/iom/v2", params=query_params)
    resource_ids = raw_response.get("resources", [])
    pagination_obj = demisto.get(raw_response, "meta.pagination", {})
    demisto.debug(f"{pagination_obj=}")
    next_token = pagination_obj.get("next_token")
    if next_token:
        # If next_token has a value, that means more pagination is needed, and the next run should use it
        return resource_ids, next_token
    else:
        # If it is None, that means no more pagination is required, therefore,
        # the next token for the next run should be None
        return resource_ids, None


def get_iom_resources(iom_resource_ids: list[str]) -> list[dict[str, Any]]:
    """Get the IOM entities/details that were fetched.

    Args:
        iom_resource_ids (list[str]): The IOM resource IDs.

    Returns:
        list[dict[str, Any]]: A list of the IOM entities.
    """
    if iom_resource_ids:
        query_params = "&".join(f"ids={resource_id}" for resource_id in iom_resource_ids)
        raw_response = http_request("GET", "/detects/entities/iom/v2", params=query_params)
        return raw_response.get("resources", [])
    else:
        return []


def upload_ioc_command(
    ioc_type=None, value=None, policy=None, expiration_days=None, share_level=None, description=None, source=None
):
    """
    :param ioc_type: The type of the indicator:
    :param policy :The policy to enact when the value is detected on a host.
    :param share_level: The level at which the indicator will be shared.
    :param expiration_days: This represents the days the indicator should be valid for.
    :param source: The source where this indicator originated.
    :param description: A meaningful description of the indicator.
    :param value: The string representation of the indicator.
    """
    raw_res = upload_ioc(ioc_type, value, policy, expiration_days, share_level, description, source)
    handle_response_errors(raw_res)
    iocs = search_iocs(ids=f"{ioc_type}:{value}").get("resources")
    if not iocs:
        raise DemistoException("Failed to create IOC. Please try again.")
    ec = [get_trasnformed_dict(iocs[0], IOC_KEY_MAP)]
    enrich_ioc_dict_with_ids(ec)
    return create_entry_object(
        contents=raw_res,
        ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec},
        hr=tableToMarkdown("Custom IOC was created successfully", ec),
    )


def update_ioc_command(
    ioc_type=None, value=None, policy=None, expiration_days=None, share_level=None, description=None, source=None
):
    """
    :param ioc_type: The type of the indicator:
    :param policy :The policy to enact when the value is detected on a host.
    :param share_level: The level at which the indicator will be shared.
    :param expiration_days: This represents the days the indicator should be valid for.
    :param source: The source where this indicator originated.
    :param description: A meaningful description of the indicator.
    :param value: The string representation of the indicator.
    """
    raw_res = update_ioc(ioc_type, value, policy, expiration_days, share_level, description, source)
    handle_response_errors(raw_res)
    iocs = search_iocs(ids=f"{ioc_type}:{value}").get("resources")
    ec = [get_trasnformed_dict(iocs[0], IOC_KEY_MAP)]
    enrich_ioc_dict_with_ids(ec)
    return create_entry_object(
        contents=raw_res,
        ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec},
        hr=tableToMarkdown("Custom IOC was created successfully", ec),
    )


def search_iocs_command(
    types=None,
    values=None,
    policies=None,
    sources=None,
    from_expiration_date=None,
    to_expiration_date=None,
    share_levels=None,
    limit=None,
    sort=None,
    offset=None,
):
    """
    :param types: A list of indicator types. Separate multiple types by comma.
    :param values: Comma-separated list of indicator values
    :param policies: Comma-separated list of indicator policies
    :param sources: Comma-separated list of IOC sources
    :param from_expiration_date: Start of date range to search (YYYY-MM-DD format).
    :param to_expiration_date: End of date range to search (YYYY-MM-DD format).
    :param share_levels: A list of share levels. Only red is supported.
    :param limit: The maximum number of records to return. The minimum is 1 and the maximum is 500. Default is 100.
    :param sort: The order of the results. Format
    :param offset: The offset to begin the list from
    """
    raw_res = search_iocs(
        types=types,
        values=values,
        policies=policies,
        sources=sources,
        sort=sort,
        offset=offset,
        expiration_from=from_expiration_date,
        expiration_to=to_expiration_date,
        share_levels=share_levels,
        limit=limit,
    )
    if not raw_res:
        return create_entry_object(hr="Could not find any Indicators of Compromise.")
    handle_response_errors(raw_res)
    iocs = raw_res.get("resources")
    ec = [get_trasnformed_dict(ioc, IOC_KEY_MAP) for ioc in iocs]
    enrich_ioc_dict_with_ids(ec)
    return create_entry_object(
        contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec}, hr=tableToMarkdown("Indicators of Compromise", ec)
    )


def get_ioc_command(ioc_type: str, value: str):
    """
    :param ioc_type: The type of the indicator
    :param value: The IOC value to retrieve
    """
    raw_res = search_iocs(ids=f"{ioc_type}:{value}")
    handle_response_errors(raw_res, "Could not find any Indicators of Compromise.")
    iocs = raw_res.get("resources")
    ec = [get_trasnformed_dict(ioc, IOC_KEY_MAP) for ioc in iocs]
    enrich_ioc_dict_with_ids(ec)
    return create_entry_object(
        contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec}, hr=tableToMarkdown("Indicator of Compromise", ec)
    )


def delete_ioc_command(ioc_type, value):
    """
    :param ioc_type: The type of the indicator
    :param value: The IOC value to delete
    """
    raw_res = delete_ioc(ioc_type, value)
    handle_response_errors(raw_res, "The server has not confirmed deletion, please manually confirm deletion.")
    ids = f"{ioc_type}:{value}"
    return create_entry_object(contents=raw_res, hr=f"Custom IOC {ids} was successfully deleted.")


def search_custom_iocs_command(
    types: list | str | None = None,
    values: list | str | None = None,
    sources: list | str | None = None,
    expiration: str | None = None,
    limit: str = "50",
    sort: str | None = None,
    offset: str | None = None,
    next_page_token: str | None = None,
) -> list[dict]:
    """
    :param types: A list of indicator types. Separate multiple types by comma.
    :param values: Comma-separated list of indicator values
    :param sources: Comma-separated list of IOC sources
    :param expiration: The date on which the indicator will become inactive. (YYYY-MM-DD format).
    :param limit: The maximum number of records to return. The minimum is 1 and the maximum is 500. Default is 100.
    :param sort: The order of the results. Format
    :param offset: The offset to begin the list from
    :param next_page_token: A pagination token used with the limit parameter to manage pagination of results.
                  On your first request, don't provide an 'after' token. On subsequent requests, provide
                  the 'after' token from the previous response to continue from that place in the results.
                  To access more than 10k indicators, use the 'after' parameter instead of 'offset'.
    """
    raw_res = search_custom_iocs(
        types=argToList(types),
        values=argToList(values),
        sources=argToList(sources),
        sort=sort,
        offset=offset,
        expiration=expiration,
        limit=limit,
        after=next_page_token,
    )
    iocs = raw_res.get("resources")
    meta = raw_res.get("meta")
    pagination_token = meta["pagination"].get("after") if meta else None
    if not iocs:
        return create_entry_object(hr="Could not find any Indicators of Compromise.")
    handle_response_errors(raw_res)
    entry_objects_list = []
    ec = [get_trasnformed_dict(ioc, IOC_KEY_MAP) for ioc in iocs]
    entry_objects_list.append(
        create_entry_object(
            contents=raw_res,
            ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec},
            hr=tableToMarkdown("Indicators of Compromise", ec, headers=IOC_HEADERS),
        )
    )
    entry_objects_list.append(
        create_entry_object(
            contents=raw_res,
            ec={"CrowdStrike.NextPageToken": pagination_token},
            hr=tableToMarkdown("Pagination Info", pagination_token, headers=["Next Page Token"]),
        )
    )
    return entry_objects_list


def get_custom_ioc_command(
    ioc_type: str | None = None,
    value: str | None = None,
    ioc_id: str | None = None,
) -> dict:
    """
    :param ioc_type: IOC type
    :param value: IOC value
    :param ioc_id: IOC ID
    """

    if not ioc_id and not (ioc_type and value):
        raise ValueError("Either ioc_id or ioc_type and value must be provided.")

    raw_res = get_custom_ioc(ioc_id) if ioc_id else search_custom_iocs(types=argToList(ioc_type), values=argToList(value))

    iocs = raw_res.get("resources")
    handle_response_errors(raw_res)
    if not iocs:
        return create_entry_object(hr="Could not find any Indicators of Compromise.")
    ec = [get_trasnformed_dict(ioc, IOC_KEY_MAP) for ioc in iocs]
    return create_entry_object(
        contents=raw_res,
        ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec},
        hr=tableToMarkdown("Indicator of Compromise", ec, headers=IOC_HEADERS),
    )


def upload_custom_ioc_command(
    ioc_type: str,
    value: str,
    action: str,
    platforms: str,
    severity: str | None = None,
    source: str | None = None,
    description: str | None = None,
    expiration: str | None = None,
    applied_globally: bool | None = None,
    host_groups: list[str] | None = None,
    tags: list[str] | None = None,
    file_name: str | None = None,
    mobile_action: str | None = None,
) -> list[dict]:
    """
    :param ioc_type: The type of the indicator.
    :param value: The string representation of the indicator.
    :param action: Action to take when a host observes the custom IOC.
    :param platforms: The platforms that the indicator applies to.
    :param severity: The severity level to apply to this indicator.
    :param source: The source where this indicator originated.
    :param description: A meaningful description of the indicator.
    :param expiration: The date on which the indicator will become inactive.
    :param applied_globally: Whether the indicator is applied globally.
    :param host_groups: List of host group IDs that the indicator applies to.
    :param tags: List of tags to apply to the indicator.
    :param mobile_action: Action to take on mobile when a host observes the custom IOC.

    """
    if action in {"prevent", "detect"} and not severity:
        raise ValueError(f"Severity is required for action {action}.")
    values: list[str] = argToList(value)
    applied_globally = argToBoolean(applied_globally) if applied_globally else None
    host_groups: list[str] = argToList(host_groups)
    tags = argToList(tags)
    platforms_list = argToList(platforms)
    if mobile_action and ("android" not in platforms_list and "ios" not in platforms_list):
        raise ValueError("mobile_action requires a mobile platform (android or ios) in the platforms argument.")

    iocs_json_batch = create_json_iocs_list(
        ioc_type,
        values,
        action,
        platforms_list,
        severity,
        source,
        description,
        expiration,
        applied_globally,
        host_groups,
        tags,
        file_name,
        mobile_action,
    )
    raw_res = upload_batch_custom_ioc(ioc_batch=iocs_json_batch)
    handle_response_errors(raw_res)
    iocs = raw_res.get("resources", [])

    entry_objects_list = []
    for ioc in iocs:
        ec = [get_trasnformed_dict(ioc, IOC_KEY_MAP)]
        ec[0]["Filename"] = ioc.get("metadata", {}).get("filename")
        entry_objects_list.append(
            create_entry_object(
                contents=raw_res,
                ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec},
                hr=tableToMarkdown(f"Custom IOC {ioc['value']} was created successfully", ec),
            )
        )
    return entry_objects_list


def update_custom_ioc_command(
    ioc_id: str,
    action: str | None = None,
    platforms: str | None = None,
    severity: str | None = None,
    source: str | None = None,
    description: str | None = None,
    expiration: str | None = None,
    file_name: str | None = None,
    mobile_action: str | None = None,
) -> dict:
    """
    :param ioc_id: The ID of the indicator to update.
    :param action: Action to take when a host observes the custom IOC.
    :param platforms: The platforms that the indicator applies to.
    :param severity: The severity level to apply to this indicator.
    :param source: The source where this indicator originated.
    :param description: A meaningful description of the indicator.
    :param expiration: The date on which the indicator will become inactive.
    :param file_name: The file name associated with the indicator.
    :param mobile_action: Action to take on mobile when a host observes the custom IOC.
    """

    raw_res = update_custom_ioc(
        ioc_id,
        action,
        argToList(platforms),
        severity,
        source,
        description,
        expiration,
        file_name,
        mobile_action,
    )
    handle_response_errors(raw_res)
    iocs = raw_res.get("resources", [])
    ec = [get_trasnformed_dict(iocs[0], IOC_KEY_MAP)]
    ec[0]["Filename"] = iocs[0].get("metadata", {}).get("filename")
    return create_entry_object(
        contents=raw_res,
        ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec},
        hr=tableToMarkdown("Custom IOC was updated successfully", ec),
    )


def delete_custom_ioc_command(ioc_id: str) -> dict:
    """
    :param ioc_id: The ID of indicator to delete.
    """
    raw_res = delete_custom_ioc(ioc_id)
    handle_response_errors(raw_res, "The server has not confirmed deletion, please manually confirm deletion.")
    return create_entry_object(contents=raw_res, hr=f"Custom IOC {ioc_id} was successfully deleted.")


def get_ioc_device_count_command(ioc_type: str, value: str):
    """
    :param ioc_type: The type of the indicator
    :param value: The IOC value
    """
    raw_res = get_ioc_device_count(ioc_type, value)
    if "No results found for" in raw_res:
        return raw_res
    else:
        handle_response_errors(raw_res)
        device_count_res = raw_res.get("resources")
        ioc_id = f"{ioc_type}:{value}"
        if not device_count_res:
            return create_entry_object(raw_res, hr=f"Could not find any devices the IOC **{ioc_id}** was detected in.")

        device_count = device_count_res[0].get("device_count")
        if argToBoolean(device_count_res[0].get("limit_exceeded", False)):
            demisto.debug(f"limit exceeded for {ioc_id}, trying to count by run_indicator_device_id_request")
            # rate limit exceeded, so we will get the count by running the run_indicator_device_id_request function
            # see https://falcon.crowdstrike.com/documentation/page/ed1b4a95/detection-and-prevention-policy-apis

            device_count = 0
            params = assign_params(type=ioc_type, value=value)

            while True:
                device_ids_raw = run_indicator_device_id_request(params)
                device_count += len(device_ids_raw.get("resources", []))
                offset = demisto.get(device_ids_raw, "meta.pagination.offset")
                if not offset:
                    break
                params["offset"] = offset

            device_count_res[0]["device_count"] = device_count

        context = [get_trasnformed_dict(device_count, IOC_DEVICE_COUNT_MAP) for device_count in device_count_res]
        hr = f"Indicator of Compromise **{ioc_id}** device count: **{device_count}**"
        return create_entry_object(contents=raw_res, ec={"CrowdStrike.IOC(val.ID === obj.ID)": context}, hr=hr)


def get_process_details_command(ids: str):
    """
    :param ids: proccess ids
    """
    ids = argToList(ids)
    raw_res = get_process_details(ids)
    handle_response_errors(raw_res)
    proc = raw_res.get("resources")
    if not proc:
        return create_entry_object(raw_res, hr="Could not find any searched processes.")
    proc_hr_ids = str(ids)[1:-1].replace("'", "")
    title = f"Details for process{'es' if len(ids) > 1 else ''}: {proc_hr_ids}."
    return create_entry_object(
        contents=raw_res, hr=tableToMarkdown(title, proc), ec={"CrowdStrike.Process(val.process_id === obj.process_id)": proc}
    )


def get_proccesses_ran_on_command(ioc_type, value, device_id):
    """
    :param device_id: Device id the IOC ran on
    :param ioc_type: The type of the indicator
    :param value: The IOC value
    """
    raw_res = get_proccesses_ran_on(ioc_type, value, device_id)
    handle_response_errors(raw_res)
    proc_ids = raw_res.get("resources")
    ioc_id = f"{ioc_type}:{value}"
    if not proc_ids:
        return create_entry_object(raw_res, hr=f"Could not find any processes associated with the IOC **{ioc_id}**.")
    context = {"ID": ioc_id, "Type": ioc_type, "Value": value, "Process": {"DeviceID": device_id, "ID": proc_ids}}
    hr = tableToMarkdown(f"Processes with custom IOC {ioc_id} on device {device_id}.", proc_ids, headers="Process ID")
    return create_entry_object(contents=raw_res, hr=hr, ec={"CrowdStrike.IOC(val.ID === obj.ID)": context})


def search_device_command():
    """
    Searches for a device
    :return: EntryObject of search device command
    """
    raw_res = search_device()
    device_ids = []
    if not raw_res:
        return create_entry_object(hr="Could not find any devices.")
    devices = raw_res.get("resources")
    extended_data = argToBoolean(demisto.args().get("extended_data", False))
    for device in devices:
        device_id = device.get("device_id")
        device_ids.append(device_id)
    state_data = get_status(device_ids)
    command_results = []
    for single_device in devices:
        endpoint = generate_endpoint_by_contex_standard(single_device, state_data)
        if not extended_data:
            entry = get_trasnformed_dict(single_device, SEARCH_DEVICE_KEY_MAP)
            headers = ["ID", "Hostname", "OS", "MacAddress", "LocalIP", "ExternalIP", "FirstSeen", "LastSeen", "Status"]
        else:
            if device_groups := single_device.get("groups"):
                single_device.update({"group_names": list(enrich_groups(device_groups).values())})
            entry = get_trasnformed_dict(single_device, SEARCH_DEVICE_VERBOSE_KEY_MAP)
            headers = list(SEARCH_DEVICE_VERBOSE_KEY_MAP.values())
        command_results.append(
            CommandResults(
                outputs_prefix="CrowdStrike.Device",
                outputs_key_field="ID",
                outputs=entry,
                readable_output=tableToMarkdown("Devices", entry, headers=headers, headerTransform=pascalToSpace),
                raw_response=raw_res,
                indicator=endpoint,
            )
        )
    return command_results


def search_device_by_ip(raw_res, ip_address):
    devices = raw_res.get("resources")
    filtered_devices = []
    for single_device in devices:
        if single_device.get("local_ip") == ip_address:
            filtered_devices.append(single_device)

    if filtered_devices:
        raw_res["resources"] = filtered_devices
    else:
        raw_res = None
    return raw_res


def enrich_groups(all_group_ids) -> dict[str, Any]:
    """
    Receives a list of group_ids
    Returns a dict {group_id: group_name}
    """
    result = {}
    params = {"ids": all_group_ids}
    response_json = http_request("GET", "/devices/entities/host-groups/v1", params, status_code=404)
    for resource in response_json["resources"] or []:
        try:
            result[resource["id"]] = resource["name"]
        except KeyError:
            demisto.debug(f"Could not retrieve group name for {resource=}")
    return result


def get_status(device_ids):
    """
    Get the online status for one or more hosts by specifying each host’s unique ID (up to 100 max).
    The status can be online, offline, or unknown.
    Args:
        device_ids: list of device ids.

    Returns: dictionary contains the id:state

    """
    state_data = {}
    batch_size = 100
    for i in range(0, len(device_ids), batch_size):
        batch = device_ids[i : i + batch_size]
        raw_res = http_request("GET", "/devices/entities/online-state/v1", params={"ids": batch})
        for res in raw_res.get("resources"):
            state = res.get("state", "")
            device_id = res.get("id", "")
            if state == "unknown":
                demisto.debug(
                    f"Device with id: {device_id} returned an unknown state, which indicates that the host has not"
                    f" been seen recently and we are not confident about its current state"
                )
            state_data[device_id] = HOST_STATUS_DICT[state]
    return state_data


def get_isolation_status(endpoint_status):
    is_isolated = ""

    if endpoint_status == "containment_pending":
        is_isolated = "Pending isolation"
    elif endpoint_status == "contained":
        is_isolated = "Yes"
    elif endpoint_status == "lift_containment_pending":
        is_isolated = "Pending unisolation"
    elif endpoint_status.lower() != "normal":
        raise DemistoException(f"Error: Unknown endpoint status was given: {endpoint_status}")
    return is_isolated


def generate_endpoint_by_contex_standard(single_device, state_data):
    device_id = single_device.get("device_id")
    endpoint = Common.Endpoint(
        id=device_id,
        hostname=single_device.get("hostname"),
        ip_address=single_device.get("local_ip"),
        os=single_device.get("platform_name"),
        os_version=single_device.get("os_version"),
        status=state_data.get(device_id),
        is_isolated=get_isolation_status(single_device.get("status")),
        mac_address=single_device.get("mac_address"),
        vendor=INTEGRATION_NAME,
    )
    return endpoint


def get_endpoint_command():
    args = demisto.args()
    if "id" in args:
        args["ids"] = args.get("id", "")

    if not args.get("ip") and not args.get("id") and not args.get("hostname"):
        # in order not to return all the devices
        return create_entry_object(hr="Please add a filter argument - ip, hostname or id.")

    # use OR operator between filters (https://github.com/demisto/etc/issues/46353)
    raw_res = search_device(filter_operator="OR")

    if not raw_res:
        return create_entry_object(hr="Could not find any devices.")
    devices = raw_res.get("resources")
    device_ids = []
    for device in devices:
        device_id = device.get("device_id")
        device_ids.append(device_id)
    state_data = get_status(device_ids)

    # filter hostnames that will match the exact hostnames including case-sensitive
    if hostnames := argToList(args.get("hostname")):
        lowercase_hostnames = {hostname.lower() for hostname in hostnames}
        devices = [device for device in devices if (device.get("hostname") or "").lower() in lowercase_hostnames]

    standard_endpoints = []
    for single_device in devices:
        standard_endpoints.append(generate_endpoint_by_contex_standard(single_device, state_data))

    command_results = []
    for endpoint in standard_endpoints:
        endpoint_context = endpoint.to_context().get(Common.Endpoint.CONTEXT_PATH)
        hr = tableToMarkdown("CrowdStrike Falcon Endpoint", endpoint_context)

        command_results.append(CommandResults(readable_output=hr, raw_response=raw_res, indicator=endpoint))
    return command_results


def get_behavior_command():
    """
    Gets a behavior by ID
    :return: EntryObject of get behavior command
    """
    behavior_id = demisto.args().get("behavior_id")
    detections_ids = demisto.get(get_detections(behavior_id=behavior_id), "resources")
    raw_res = get_detections_entities(detections_ids)
    entries = []
    if "resources" in raw_res:
        for resource in demisto.get(raw_res, "resources"):
            for behavior in demisto.get(resource, "behaviors"):
                entries.append(behavior_to_entry_context(behavior))
    hr = tableToMarkdown(f"Behavior ID: {behavior_id}", entries, headerTransform=pascalToSpace)
    # no dt since behavior vary by more than their ID
    ec = {"CrowdStrike.Behavior": entries}
    return create_entry_object(contents=raw_res, ec=ec, hr=hr)


def search_detections_command():
    """
    Searches for a detection
    :return: EntryObject of search detections command
    """
    d_args = demisto.args()
    detections_ids = argToList(d_args.get("ids"))
    extended_data = argToBoolean(d_args.get("extended_data", False))
    if not detections_ids:
        # This value is similar to the default value of the filter argument in the YAML file
        filter_arg = d_args.get("filter", "product:'epp'+type:'ldt'")
        detections_ids = get_detections(filter_arg=filter_arg).get("resources")
    raw_res = get_detections_entities(detections_ids)
    entries = []
    headers = ["ID", "Status", "System", "ProcessStartTime", "CustomerID", "MaxSeverity"]
    if "resources" in raw_res:
        for detection in demisto.get(raw_res, "resources"):
            detection_entry = {}

            detection = modify_detection_outputs(detection)

            for path, new_key in DETECTIONS_BASE_KEY_MAP.items():
                detection_entry[new_key] = demisto.get(detection, path)
            behaviors = []

            for behavior in demisto.get(detection, "behaviors"):
                behaviors.append(behavior_to_entry_context(behavior))
            detection_entry["Behavior"] = behaviors

            if extended_data:
                detection_entry["Device"] = demisto.get(detection, "device")

            entries.append(detection_entry)

    hr = tableToMarkdown("Detections Found:", entries, headers=headers, removeNull=True, headerTransform=pascalToSpace)

    return CommandResults(
        readable_output=hr, outputs=entries, outputs_key_field="ID", outputs_prefix="CrowdStrike.Detection", raw_response=raw_res
    )


def resolve_detection_command():
    """
    Resolves single or multiple detections
    :return: EntryObject of resolve detection command
    """
    args = demisto.args()
    ids = argToList(args.get("ids"))
    username = args.get("username")
    assigned_to_uuid = args.get("assigned_to_uuid")
    comment = args.get("comment")
    if username and assigned_to_uuid:
        raise ValueError("Only one of the arguments assigned_to_uuid or username should be provided, not both.")

    status = args.get("status")
    if status in ["true_positive", "false_positive", "ignored"]:
        raise ValueError(
            f"The status chosen: {status} is deprecated due to the deprecation of the Legacy API. Choose a different one from the available options."  # noqa: E501
        )  # noqa: E501
    tag = args.get("tag")
    show_in_ui = args.get("show_in_ui")
    if not (username or assigned_to_uuid or comment or status or show_in_ui or tag):
        raise DemistoException("Please provide at least one argument to resolve the detection with.")
    raw_res = resolve_detection(ids, status, assigned_to_uuid, username, show_in_ui, comment, tag)
    args.pop("ids")
    hr = f"Detection {str(ids)[1:-1]} updated\n"
    hr += "With the following values:\n"
    for k, arg in args.items():
        hr += f"\t{k}:{arg}\n"
    return create_entry_object(contents=raw_res, hr=hr)


def contain_host_command():
    """
    Contains hosts with user arg ids
    :return: EntryObject of contain host command
    """
    ids = argToList(demisto.args().get("ids"))
    raw_res = contain_host(ids)
    hr = f"Host {str(ids)[1:-1]} contained"
    return create_entry_object(contents=raw_res, hr=hr)


def lift_host_containment_command():
    """
    Lifts containment off a host
    :return: EntryObject of lift host containment
    """
    ids = argToList(demisto.args().get("ids"))
    lift_filesystem = argToBoolean(demisto.args().get("lift_filesystem_containment_all", "false"))
    action_name = "lift_filesystem_containment_all" if lift_filesystem else "lift_containment"
    raw_res = lift_host_containment(ids, action_name=action_name)
    if lift_filesystem:
        hr = f"Filesystem containment has been lifted off host {str(ids)[1:-1]}"
    else:
        hr = f"Containment has been lifted off host {str(ids)[1:-1]}"
    return create_entry_object(contents=raw_res, hr=hr)


def run_command():
    args = demisto.args()
    host_ids = argToList(args.get("host_ids"))
    command_type = args.get("command_type")
    full_command = args.get("full_command")
    scope = args.get("scope", "read")
    target = args.get("target", "batch")
    timeout = int(args.get("timeout", 180))

    offline = argToBoolean(args.get("queue_offline", False))

    output = []

    if target == "batch":
        batch_id = args.get("batch_id", None) if args.get("batch_id", None) else init_rtr_batch_session(host_ids, offline)
        demisto.debug(f"{args.get('batch_id', None)=} , {batch_id=}")
        timer = Timer(300, batch_refresh_session, kwargs={"batch_id": batch_id})
        timer.start()
        try:
            if scope == "read":
                response = run_batch_read_cmd(batch_id, command_type, full_command, timeout=timeout)
            elif scope == "write":
                response = run_batch_write_cmd(batch_id, command_type, full_command, timeout=timeout)
            else:  # scope = admin
                response = run_batch_admin_cmd(batch_id, command_type, full_command, timeout=timeout)
        finally:
            timer.cancel()

        resources: dict = response.get("combined", {}).get("resources", {})

        for _, resource in resources.items():
            errors = resource.get("errors", [])
            if errors:
                error_message = errors[0].get("message", "")
                if not error_message:
                    error_message = f"Could not run command\n{errors}"
                return_error(error_message)
            output.append(
                {
                    "HostID": resource.get("aid"),
                    "SessionID": resource.get("session_id"),
                    "Stdout": resource.get("stdout"),
                    "Stderr": resource.get("stderr"),
                    "BaseCommand": resource.get("base_command"),
                    "Command": full_command,
                    "BatchID": batch_id,
                }
            )

        human_readable = tableToMarkdown(f"Command {full_command} results", output, removeNull=True)
        entry_context_batch = {"CrowdStrike": {"Command": output}}
        return create_entry_object(contents=response, ec=entry_context_batch, hr=human_readable)
    else:  # target = 'single'
        responses = []
        for host_id in host_ids:
            if scope == "read":
                response1 = run_single_read_cmd(host_id, command_type, full_command, offline, timeout=timeout)
            elif scope == "write":
                response1 = run_single_write_cmd(host_id, command_type, full_command, offline, timeout=timeout)
            else:  # scope = admin
                response1 = run_single_admin_cmd(host_id, command_type, full_command, offline, timeout=timeout)
            responses.append(response1)

            for resource in response1.get("resources", []):
                errors = resource.get("errors", [])
                if errors:
                    error_message = errors[0].get("message", "")
                    if not error_message:
                        error_message = f"Could not run command\n{errors}"
                    return_error(error_message)
                output.append(
                    {
                        "HostID": host_id,
                        "TaskID": resource.get("cloud_request_id"),
                        "SessionID": resource.get("session_id"),
                        "BaseCommand": command_type,
                        "Command": full_command,
                        "Complete": False,
                        "NextSequenceID": 0,
                    }
                )

        human_readable = tableToMarkdown(f"Command {full_command} results", output, removeNull=True)
        entry_context_single = {"CrowdStrike.Command(val.TaskID === obj.TaskID)": output}
        return create_entry_object(contents=responses, ec=entry_context_single, hr=human_readable)


def upload_script_command():
    args = demisto.args()
    name = args.get("name")
    permission_type = args.get("permission_type", "private")
    content = args.get("content")
    entry_id = args.get("entry_id")

    if content and entry_id:
        raise ValueError("Only one of the arguments entry_id or content should be provided, not both.")
    elif not content and not entry_id:
        raise ValueError("One of the arguments entry_id or content must be provided, none given.")

    response = upload_script(name, permission_type, content, entry_id)

    return create_entry_object(contents=response, hr="The script was uploaded successfully")


def get_script_command():
    script_id = argToList(demisto.args().get("script_id"))

    response = get_script(script_id)

    resources: list = response.get("resources", [])
    if resources and isinstance(resources, list):
        resource = resources[0]
        script = {
            "ID": resource.get("id"),
            "CreatedBy": resource.get("created_by"),
            "CreatedTime": resource.get("created_timestamp"),
            "Description": resource.get("description"),
            "ModifiedBy": resource.get("modified_by"),
            "ModifiedTime": resource.get("modified_timestamp"),
            "Name": resource.get("name"),
            "Permission": resource.get("permission_type"),
            "SHA256": resource.get("sha256"),
            "RunAttemptCount": resource.get("run_attempt_count"),
            "RunSuccessCount": resource.get("run_success_count"),
            "WriteAccess": resource.get("write_access"),
        }

        human_readable = tableToMarkdown(f"CrowdStrike Falcon script {script_id}", script)

        entry_context = {"CrowdStrike.Script(val.ID === obj.ID)": script}

        script_content = resource.get("content")
        if script_content:
            demisto.results(fileResult(f"{resource.get('name', 'script')}.ps1", script_content))

        return create_entry_object(contents=response, ec=entry_context, hr=human_readable)
    else:
        return "No script found."


def delete_script_command():
    script_id = demisto.args().get("script_id")

    response = delete_script(script_id)

    return create_entry_object(contents=response, hr=f"Script {script_id} was deleted successfully")


def list_scripts_command():
    response = list_scripts()

    resources: list = response.get("resources", [])

    scripts = []

    for resource in resources:
        scripts.append(
            {
                "ID": resource.get("id"),
                "CreatedBy": resource.get("created_by"),
                "CreatedTime": resource.get("created_timestamp"),
                "Description": resource.get("description"),
                "ModifiedBy": resource.get("modified_by"),
                "ModifiedTime": resource.get("modified_timestamp"),
                "Name": resource.get("name"),
                "Permission": resource.get("permission_type"),
                "SHA256": resource.get("sha256"),
                "RunAttemptCount": resource.get("run_attempt_count"),
                "RunSuccessCount": resource.get("run_success_count"),
                "Platform": resource.get("platform"),
                "WriteAccess": resource.get("write_access"),
            }
        )

    human_readable = tableToMarkdown("CrowdStrike Falcon scripts", scripts)

    entry_context = {"CrowdStrike.Script(val.ID === obj.ID)": scripts}

    return create_entry_object(contents=response, ec=entry_context, hr=human_readable)


def upload_file_command():
    entry_id = demisto.args().get("entry_id")
    description = demisto.args().get("description", "File uploaded from Demisto")

    response, file_name = upload_file(entry_id, description)

    return create_entry_object(contents=response, hr="File was uploaded successfully")


def delete_file_command():
    """
    This command deletes a file by either file_id or file_name. If file_name is provided
    without file_id, it will first list all files to find the corresponding file_id.
    Args:
        file_id (str, optional): The ID of the file to delete
        file_name (str, optional): The name of the file to delete
    Returns:
        dict: Entry object with deletion confirmation message
    Raises:
        ValueError: If neither file_name nor file_id is provided, or if file with given name is not found
    """
    file_id = demisto.args().get("file_id")
    file_name = demisto.args().get("file_name")

    if not file_name and not file_id:
        raise ValueError("Either file_name or file_id must be provided.")

    if not file_id:
        file_id = get_file_id_by_name(file_name)

        if not file_id:
            raise ValueError(f"File with name '{file_name}' not found.")

    response = delete_file(file_id)

    return create_entry_object(contents=response, hr=f"File {file_id} was deleted successfully")


def get_file_command():
    file_id = argToList(demisto.args().get("file_id"))

    response = get_file(file_id)

    resources: list = response.get("resources", [])
    if resources and isinstance(resources, list):
        # will always be a list of one resource
        resource = resources[0]
        file_ = {
            "ID": resource.get("id"),
            "CreatedBy": resource.get("created_by"),
            "CreatedTime": resource.get("created_timestamp"),
            "Description": resource.get("description"),
            "Type": resource.get("file_type"),
            "ModifiedBy": resource.get("modified_by"),
            "ModifiedTime": resource.get("modified_timestamp"),
            "Name": resource.get("name"),
            "Permission": resource.get("permission_type"),
            "SHA256": resource.get("sha256"),
        }
        file_standard_context = {
            "Type": resource.get("file_type"),
            "Name": resource.get("name"),
            "SHA256": resource.get("sha256"),
            "Size": resource.get("size"),
        }

        human_readable = tableToMarkdown(f"CrowdStrike Falcon file {file_id}", file_)

        entry_context = {"CrowdStrike.File(val.ID === obj.ID)": file_, outputPaths["file"]: file_standard_context}

        file_content = resource.get("content")
        if file_content:
            demisto.results(fileResult(resource.get("name"), file_content))

        return create_entry_object(contents=response, ec=entry_context, hr=human_readable)
    else:
        return "No file found."


def list_files_command():
    response = list_files()

    resources: list = response.get("resources", [])

    files_output = []
    file_standard_context = []

    for resource in resources:
        files_output.append(
            {
                "ID": resource.get("id"),
                "CreatedBy": resource.get("created_by"),
                "CreatedTime": resource.get("created_timestamp"),
                "Description": resource.get("description"),
                "Type": resource.get("file_type"),
                "ModifiedBy": resource.get("modified_by"),
                "ModifiedTime": resource.get("modified_timestamp"),
                "Name": resource.get("name"),
                "Permission": resource.get("permission_type"),
                "SHA256": resource.get("sha256"),
            }
        )
        file_standard_context.append(
            {
                "Type": resource.get("file_type"),
                "Name": resource.get("name"),
                "SHA256": resource.get("sha256"),
                "Size": resource.get("size"),
            }
        )

    human_readable = tableToMarkdown("CrowdStrike Falcon files", files_output)

    entry_context = {"CrowdStrike.File(val.ID === obj.ID)": files_output, outputPaths["file"]: file_standard_context}

    return create_entry_object(contents=response, ec=entry_context, hr=human_readable)


def run_script_command():
    args = demisto.args()
    script_name = args.get("script_name")
    raw = args.get("raw")
    host_ids = argToList(args.get("host_ids"))
    offline = argToBoolean(args.get("queue_offline", False))
    full_command = ""
    try:
        timeout = int(args.get("timeout", 30))
    except ValueError as e:
        demisto.error(str(e))
        raise ValueError("Timeout argument should be an integer, for example: 30")

    full_command = ""  # initialized variable here to avoid pylint errors
    if script_name and raw:
        raise ValueError("Only one of the arguments script_name or raw should be provided, not both.")
    elif not script_name and not raw:
        raise ValueError("One of the arguments script_name or raw must be provided, none given.")
    elif script_name:
        full_command = f"runscript -CloudFile={script_name}"
    elif raw:
        full_command = f"runscript -Raw=```{raw}```"
    full_command += f" -Timeout={timeout}"

    command_type = "runscript"

    batch_id = init_rtr_batch_session(host_ids, offline)
    timer = Timer(300, batch_refresh_session, kwargs={"batch_id": batch_id})
    timer.start()
    try:
        response = run_batch_admin_cmd(batch_id, command_type, full_command, timeout)
    finally:
        timer.cancel()

    resources: dict = response.get("combined", {}).get("resources", {})

    output = []

    for _, resource in resources.items():
        errors = resource.get("errors", [])
        if errors:
            error_message = errors[0].get("message", "")
            if not error_message:
                error_message = f"Could not run command\n{errors}"
            return_error(error_message)
        full_command = full_command.replace("`", "")
        stderr = resource.get("stderr")
        output.append(
            {
                "HostID": resource.get("aid"),
                "SessionID": resource.get("session_id"),
                "Stdout": resource.get("stdout"),
                "Stderr": stderr,
                "BaseCommand": resource.get("base_command"),
                "Command": full_command,
            }
        )
        if stderr:
            raise DemistoException(f"cs-falcon-run-script command failed with the following error: {stderr}")

    human_readable = tableToMarkdown(f"Command {full_command} results", output)
    entry_context = {"CrowdStrike": {"Command": output}}

    return create_entry_object(contents=response, ec=entry_context, hr=human_readable)


def run_get_command(is_polling=False, offline=False):
    request_ids_for_polling = []
    args = demisto.args()
    host_ids = argToList(args.get("host_ids"))
    file_path = args.get("file_path")
    optional_hosts = argToList(args.get("optional_hosts"))
    timeout = args.get("timeout")
    timeout_duration = args.get("timeout_duration")

    timeout = timeout and int(timeout)
    response = run_batch_get_cmd(host_ids, file_path, optional_hosts, timeout, timeout_duration, offline)

    resources: dict = response.get("combined", {}).get("resources", {})

    output = []

    for _, resource in resources.items():
        errors = resource.get("errors", [])
        if errors:
            error_message = errors[0].get("message", "")
            if not error_message:
                error_message = f"Could not get command\n{errors}"
            return_error(error_message)
        output.append(
            {
                "HostID": resource.get("aid"),
                "Stdout": resource.get("stdout"),
                "Stderr": resource.get("stderr"),
                "BaseCommand": resource.get("base_command"),
                "TaskID": resource.get("task_id"),
                "GetRequestID": response.get("batch_get_cmd_req_id"),
                "Complete": resource.get("complete") or False,
                "FilePath": file_path,
            }
        )
        request_ids_for_polling.append(
            {
                "RequestID": response.get("batch_get_cmd_req_id"),
                "HostID": resource.get("aid"),
            }
        )

    if is_polling:
        return request_ids_for_polling

    human_readable = tableToMarkdown(f"Get command has requested for a file {file_path}", output)
    entry_context = {"CrowdStrike.Command(val.TaskID === obj.TaskID)": output}

    return create_entry_object(contents=response, ec=entry_context, hr=human_readable)


def status_get_command(args, is_polling=False):
    request_ids_for_polling = {}
    request_ids = argToList(args.get("request_ids"))
    timeout = args.get("timeout")
    timeout_duration = args.get("timeout_duration")

    timeout = timeout and int(timeout)

    responses = []
    files_output = []
    file_standard_context = []

    sha256 = ""  # Used for the polling. When this isn't empty it indicates that the status is "ready".
    for request_id in request_ids:
        response = status_get_cmd(request_id, timeout, timeout_duration)
        responses.append(response)

        resources: dict = response.get("resources", {})

        for host_id, resource in resources.items():
            errors = resource.get("errors", [])
            if errors:
                error_message = errors[0].get("message", "")
                if not error_message:
                    error_message = f"Could not get command\n{errors}"
                return_error(error_message)
            files_output.append(
                {
                    "ID": resource.get("id"),
                    "TaskID": resource.get("cloud_request_id"),
                    "CreatedAt": resource.get("created_at"),
                    "DeletedAt": resource.get("deleted_at"),
                    "UpdatedAt": resource.get("updated_at"),
                    "Name": resource.get("name"),
                    "Size": resource.get("size"),
                    "SHA256": resource.get("sha256"),
                }
            )
            file_standard_context.append(
                {
                    "Name": resource.get("name"),
                    "SHA256": resource.get("sha256"),
                    "Size": resource.get("size"),
                }
            )
            sha256 = resource.get("sha256", "")
            request_ids_for_polling[host_id] = {"SHA256": sha256}

    if is_polling:
        args["SHA256"] = sha256
        return request_ids_for_polling, args

    human_readable = tableToMarkdown("CrowdStrike Falcon files", files_output)
    entry_context = {
        "CrowdStrike.File(val.ID === obj.ID || val.TaskID === obj.TaskID)": files_output,
        outputPaths["file"]: file_standard_context,
    }
    if len(responses) == 1:
        return create_entry_object(contents=responses[0], ec=entry_context, hr=human_readable)
    else:
        return create_entry_object(contents=response, ec=entry_context, hr=human_readable)


def status_command():
    args = demisto.args()
    request_id = args.get("request_id")
    sequence_id = args.get("sequence_id")
    scope = args.get("scope", "read")

    sequence_id = None if sequence_id is None else int(sequence_id)

    if scope == "read":
        response = status_read_cmd(request_id, sequence_id)
    elif scope == "write":
        response = status_write_cmd(request_id, sequence_id)
    else:  # scope = admin
        response = status_admin_cmd(request_id, sequence_id)

    resources: list = response.get("resources", [])

    output = []

    for resource in resources:
        errors = resource.get("errors", [])
        if errors:
            error_message = errors[0].get("message", "")
            if not error_message:
                error_message = f"Could not run command\n{errors}"
            return_error(error_message)

        sequence_id = int(resource.get("sequence_id", 0))
        output.append(
            {
                "Complete": resource.get("complete") or False,
                "Stdout": resource.get("stdout"),
                "Stderr": resource.get("stderr"),
                "BaseCommand": resource.get("base_command"),
                "TaskID": resource.get("task_id"),
                "SequenceID": sequence_id,
                "NextSequenceID": sequence_id + 1,
            }
        )

    human_readable = tableToMarkdown("Command status results", output, removeNull=True)
    entry_context = {"CrowdStrike.Command(val.TaskID === obj.TaskID)": output}

    return create_entry_object(contents=response, ec=entry_context, hr=human_readable)


def get_extracted_file_command(args):
    host_id = args.get("host_id")
    sha256 = args.get("sha256")
    filename = args.get("filename")

    response = get_extracted_file(host_id, sha256, filename)

    # save an extracted file
    content_type = response.headers.get("Content-Type", "").lower()
    if content_type == "application/x-7z-compressed":
        content_disposition = response.headers.get("Content-Disposition", "").lower()
        if content_disposition:
            filename = email.message_from_string(f"Content-Disposition: {content_disposition}\n\n").get_filename()

        if not filename:
            sha256 = sha256 or hashlib.sha256(response.content).hexdigest()
            filename = sha256.lower() + ".7z"

        return fileResult(filename, response.content)

    return_error("An extracted file is missing in the response")
    return None


def list_host_files_command():
    args = demisto.args()
    host_id = args.get("host_id")
    session_id = args.get("session_id")

    response = list_host_files(host_id, session_id)
    resources: list = response.get("resources", [])

    files_output = []
    file_standard_context = []
    command_output = []

    for resource in resources:
        errors = resource.get("errors", [])
        if errors:
            error_message = errors[0].get("message", "")
            if not error_message:
                error_message = f"Could not run command\n{errors}"
            return_error(error_message)
        command_output.append(
            {"HostID": host_id, "TaskID": resource.get("cloud_request_id"), "SessionID": resource.get("session_id")}
        )
        files_output.append(
            {
                "ID": resource.get("id"),
                "CreatedAt": resource.get("created_at"),
                "DeletedAt": resource.get("deleted_at"),
                "UpdatedAt": resource.get("updated_at"),
                "Name": resource.get("name"),
                "SHA256": resource.get("sha256"),
                "Size": resource.get("size"),
                "Stdout": resource.get("stdout"),
                "Stderr": resource.get("stderr"),
            }
        )
        file_standard_context.append(
            {
                "Name": resource.get("name"),
                "SHA256": resource.get("sha256"),
                "Size": resource.get("size"),
            }
        )

    human_readable = tableToMarkdown("CrowdStrike Falcon files", files_output) if files_output else "No result found"

    entry_context = {
        "CrowdStrike.Command(val.TaskID === obj.TaskID)": command_output,
        "CrowdStrike.File(val.ID === obj.ID)": files_output,
        outputPaths["file"]: file_standard_context,
    }

    return create_entry_object(contents=response, ec=entry_context, hr=human_readable)


def refresh_session_command():
    args = demisto.args()
    host_id = args.get("host_id")

    response = refresh_session(host_id)
    resources: list = response.get("resources", [])

    session_id = None
    for resource in resources:
        errors = resource.get("errors", [])
        if errors:
            error_message = errors[0].get("message", "")
            if not error_message:
                error_message = f"Could not run command\n{errors}"
            return_error(error_message)
        session_id = resource.get("session_id")

    return create_entry_object(contents=response, hr=f"CrowdStrike Session Refreshed: {session_id}")


def build_error_message(raw_res):
    if raw_res.get("errors"):
        error_data = raw_res.get("errors")[0]
    else:
        error_data = {"code": "None", "message": "something got wrong, please try again"}
    error_code = error_data.get("code")
    error_message = error_data.get("message")
    return f"Error: error code: {error_code}, error_message: {error_message}."


def validate_response(raw_res):
    return "resources" in raw_res


def run_indicator_device_id_request(params):
    return http_request("GET", "/indicators/queries/devices/v1", params=params, status_code=404)


def get_indicator_device_id():
    args = demisto.args()
    ioc_type = args.get("type")
    ioc_value = args.get("value")
    params = assign_params(type=ioc_type, value=ioc_value)
    raw_res = run_indicator_device_id_request(params=params)
    errors = raw_res.get("errors", [])
    for error in errors:
        if error.get("code") == 404:
            return f"No results found for {ioc_type} - {ioc_value}"
    devices_response = []
    if validate_response(raw_res):
        devices_response = raw_res.get("resources")
    else:
        error_message = build_error_message(raw_res)
        return_error(error_message)
    ioc_id = f"{ioc_type}:{ioc_value}"
    readable_output = tableToMarkdown(f"Devices that encountered the IOC {ioc_id}", devices_response, headers="Device ID")
    outputs = {
        "DeviceID": devices_response,
        "DeviceIOC": {
            "Type": ioc_type,
            "Value": ioc_value,
            "ID": ioc_id,
            "DeviceID": devices_response,
        },
    }
    return CommandResults(
        readable_output=readable_output,
        outputs_prefix="CrowdStrike",
        outputs_key_field="DeviceIOC.ID",
        outputs=outputs,
        raw_response=raw_res,
    )


def detections_to_human_readable(detections):
    detections_readable_outputs = []
    for detection in detections:
        readable_output = assign_params(
            status=detection.get("status"),
            max_severity=detection.get("severity_name"),
            detection_id=detection.get("detection_id"),
            created_time=detection.get("created_timestamp"),
        )
        detections_readable_outputs.append(readable_output)
    headers = ["detection_id", "created_time", "status", "max_severity"]
    human_readable = tableToMarkdown("CrowdStrike Detections", detections_readable_outputs, headers, removeNull=True)
    return human_readable


def list_detection_summaries_command():
    args = demisto.args()
    fetch_query = args.get("fetch_query")

    args_ids = args.get("ids")
    if args_ids:
        detections_ids = argToList(args_ids)
    elif fetch_query:
        fetch_query = f"{fetch_query}"
        detections_ids = demisto.get(get_fetch_detections(filter_arg=fetch_query), "resources")
    else:
        detections_ids = demisto.get(get_fetch_detections(), "resources")
    detections_response_data = get_detections_entities(detections_ids)
    detections = list(detections_response_data.get("resources")) if detections_response_data else []
    # modify the new version (raptor) outputs to match the old format for backward compatibility
    detections = [modify_detection_summaries_outputs(detection) for detection in detections]
    detections_human_readable = detections_to_human_readable(detections)

    return CommandResults(
        readable_output=detections_human_readable,
        outputs_prefix="CrowdStrike.Detections",
        outputs_key_field="detection_id",
        outputs=detections,
    )


def cases_to_human_readable(cases):
    """
    Converts a list of cases to a human-readable format.
    Args:
        cases: A list of cases.
    Returns:
        str: The human-readable string.
    """
    cases_readable_outputs = []
    for case in cases:
        readable_output = assign_params(
            case_id=case.get("id"),
            name=case.get("name"),
            created_time=case.get("created_timestamp"),
            status=case.get("status"),
            version=case.get("version"),
            description=case.get("description"),
            severity=case.get("severity"),
            assigned_to=case.get("assigned_to"),
            tags=case.get("tags"),
        )
        demisto.debug(f"appending {readable_output=} to cases_readable_outputs")
        cases_readable_outputs.append(readable_output)
    headers = ["case_id", "name", "created_timestamp", "status", "version", "description", "severity", "assigned_to", "tags"]
    return tableToMarkdown(
        "CrowdStrike Cases", cases_readable_outputs, headers, removeNull=True, headerTransform=string_to_table_header
    )


def list_case_summaries_command():
    """
    Lists case summaries.
    """
    args = demisto.args()
    ids = argToList(args.get("ids"))
    if not ids:
        _, ids = get_cases_data()

    demisto.debug(f"About to call get_cases_entities with {ids=}")
    cases = get_cases_entities(ids)
    demisto.debug(f"got {cases=}")
    cases_human_readable = cases_to_human_readable(cases)
    return CommandResults(
        readable_output=cases_human_readable,
        outputs_prefix="CrowdStrike.Case",
        outputs_key_field="id",
        outputs=cases,
    )


def get_evidence_for_case_command(args: dict[str, Any]) -> CommandResults:
    """
    Get evidence for a specific case.
    Args:
        args: The arguments of the command.
    Returns:
        CommandResults: The command results object.
    """
    case_id = args.get("id")
    if not case_id:
        raise ValueError("The 'id' argument is required.")

    cases = get_cases_entities([case_id])
    if not cases:
        return CommandResults(readable_output=f"No case found with id {case_id}")

    case = cases[0]
    evidence = case.get("evidence", {})

    # Prepare Human Readable output
    alerts = [record.get("selector", {}).get("id") for record in evidence.get("alerts", {}).get("records", [])]
    events = [record.get("selector", {}).get("id") for record in evidence.get("events", {}).get("records", [])]
    leads = [record.get("selector", {}).get("id") for record in evidence.get("leads", {}).get("records", [])]

    readable_output = [{"Case Id": case.get("id"), "Case Alerts": alerts, "Case Events": events, "Case Leads": leads}]
    markdown_output = tableToMarkdown(
        "Case Evidence", readable_output, headers=["Case Id", "Case Alerts", "Case Events", "Case Leads"], removeNull=True
    )
    return CommandResults(
        outputs_prefix="CrowdStrike.CaseEvidence", outputs=evidence, readable_output=markdown_output, raw_response=case
    )


def add_case_tags_command(args: dict[str, Any]) -> CommandResults:
    """
    Add tags to a case.
    Args:
        args: The arguments of the command.
    Returns:
        CommandResults: The command results object.
    """
    case_id = args.get("id")
    tags = argToList(args.get("tags"))

    if not case_id:
        raise ValueError("The 'id' argument is required.")
    if not tags:
        raise ValueError("The 'tags' argument is required.")

    add_case_tags(case_id, tags)
    return CommandResults(readable_output="Tags were added successfully.")


def delete_case_tags_command(args: dict[str, Any]) -> CommandResults:
    """
    Delete a tag from a case.
    Args:
        args: The arguments of the command.
    Returns:
        CommandResults: The command results object.
    """
    case_id = args.get("id")
    tag = args.get("tag")

    if not case_id:
        raise ValueError("The 'id' argument is required.")
    if not tag:
        raise ValueError("The 'tag' argument is required.")

    delete_case_tags(case_id, tag)
    return CommandResults(readable_output="Tags were deleted successfully.")


def patch_remote_entity(
    case_id: str,
    status: str | None = None,
    name: str | None = None,
    assigned_to_uuid: str | None = None,
    description: str | None = None,
    remove_user_assignment: bool | None = None,
    severity: int | None = None,
    template_id: str | None = None,
    is_recon_type: bool | None = None,
) -> dict:
    """
    Updates an incident (Case or Recon Notification) in the remote system via PATCH request.

    Args:
        case_id (str): The unique identifier of the incident/case.
        status (str | None): The status to set for the incident/case.
        assigned_to_uuid (str | None): A UUID of a user to assign the incident/case to.
        description (str | None): A new description for the incident/case.
        remove_user_assignment (bool): Whether to remove incident/case assignment from current user.
        severity (int | None): The new incident/case severity rating (10-100).
        template_id (str | None): The unique ID of the template to apply to the incident/case.
        is_recon_type (bool | None): Whether the incident is a recon type.

    Returns:
        dict: The response from the API.
    """
    # Build fields dict with API field names, filtering out None values
    fields = {
        "status": status,
        "name": name,
        "assigned_to_user_uuid": assigned_to_uuid,
        "description": description,
        "severity": severity,
        "template": {"id": template_id} if template_id else None,
        "remove_user_assignment": remove_user_assignment if remove_user_assignment else None,
    }
    remove_nulls_from_dictionary(fields)

    if is_recon_type:
        url_suffix = "/recon/entities/notifications/v1"
        payload: dict | list = [{"id": case_id, **fields}]
    else:
        payload = {"id": case_id, "fields": fields}
        url_suffix = "/cases/entities/cases/v2"
    demisto.debug(f"Sending PATCH request to {url_suffix} for ID: {case_id}. Payload: {payload}")
    return http_request("PATCH", url_suffix, json=payload)


def resolve_case_command(args: dict[str, Any]) -> CommandResults:
    """
    Command function for cs-falcon-resolve-case.
    """
    case_id = args.get("id")

    if not case_id:
        raise ValueError("The 'id' argument is required.")

    severity = arg_to_number(args.get("severity"))
    if severity is not None and not (10 <= severity <= 100):
        raise ValueError("Severity must be an integer between 10 and 100.")

    # We take care of that value seperatly so that it won't appear in the HR unless passed by the user
    remove_user_assignment_str_value = args.get("remove_user_assignment")

    # Collect changed fields for both API call and display
    changed_fields: dict[str, Any] = {
        "status": args.get("status"),
        "name": args.get("name"),
        "assigned_to_uuid": args.get("assigned_to_uuid"),
        "description": args.get("description"),
        "severity": severity,
        "template_id": args.get("template_id"),
    }

    patch_remote_entity(
        case_id=case_id, remove_user_assignment=argToBoolean(remove_user_assignment_str_value or False), **changed_fields
    )

    readable_output = f"Case {case_id} was changed successfully"
    display_data = {"id": case_id, "remove_user_assignment": remove_user_assignment_str_value, **changed_fields}

    if argToBoolean(remove_user_assignment_str_value or False):
        display_data["assigned_to_uuid"] = "Unassigned"

    table = tableToMarkdown(
        "Edited Case",
        display_data,
        headers=list(changed_fields.keys()),
        headerTransform=string_to_table_header,
        removeNull=True,
    )

    return CommandResults(readable_output=f"{readable_output}\n{table}")


def create_host_group_command(
    name: str, group_type: str | None = None, description: str | None = None, assignment_rule: str | None = None
) -> CommandResults:
    response = change_host_group(
        is_post=True, name=name, group_type=group_type, description=description, assignment_rule=assignment_rule
    )
    host_groups = response.get("resources")
    return CommandResults(
        outputs_prefix="CrowdStrike.HostGroup",
        outputs_key_field="id",
        outputs=host_groups,
        readable_output=tableToMarkdown("Host Groups", host_groups, headers=HOST_GROUP_HEADERS),
        raw_response=response,
    )


def update_host_group_command(
    host_group_id: str, name: str | None = None, description: str | None = None, assignment_rule: str | None = None
) -> CommandResults:
    response = change_host_group(
        is_post=False, host_group_id=host_group_id, name=name, description=description, assignment_rule=assignment_rule
    )
    host_groups = response.get("resources")
    return CommandResults(
        outputs_prefix="CrowdStrike.HostGroup",
        outputs_key_field="id",
        outputs=host_groups,
        readable_output=tableToMarkdown("Host Groups", host_groups, headers=HOST_GROUP_HEADERS),
        raw_response=response,
    )


def list_host_group_members_command(
    host_group_id: str | None = None,
    filter: str | None = None,
    offset: str | None = None,
    limit: str | None = None,
    sort: str | None = None,
) -> CommandResults:
    response = host_group_members(filter, host_group_id, limit, offset, sort)
    devices = response.get("resources")
    if not devices:
        return CommandResults(readable_output="No hosts are found", raw_response=response)
    headers = list(SEARCH_DEVICE_KEY_MAP.values())
    outputs = [get_trasnformed_dict(single_device, SEARCH_DEVICE_KEY_MAP) for single_device in devices]
    return CommandResults(
        outputs_prefix="CrowdStrike.Device",
        outputs_key_field="ID",
        outputs=outputs,
        readable_output=tableToMarkdown("Devices", outputs, headers=headers, headerTransform=pascalToSpace),
        raw_response=response,
    )


def add_host_group_members_command(host_group_id: str, host_ids: list[str]) -> CommandResults:
    response = change_host_group_members(action_name="add-hosts", host_group_id=host_group_id, host_ids=host_ids)
    host_groups = response.get("resources")
    return CommandResults(
        outputs_prefix="CrowdStrike.HostGroup",
        outputs_key_field="id",
        outputs=host_groups,
        readable_output=tableToMarkdown("Host Groups", host_groups, headers=HOST_GROUP_HEADERS),
        raw_response=response,
    )


def remove_host_group_members_command(host_group_id: str, host_ids: list[str]) -> CommandResults:
    response = change_host_group_members(action_name="remove-hosts", host_group_id=host_group_id, host_ids=host_ids)
    host_groups = response.get("resources")
    return CommandResults(
        outputs_prefix="CrowdStrike.HostGroup",
        outputs_key_field="id",
        outputs=host_groups,
        readable_output=tableToMarkdown("Host Groups", host_groups, headers=HOST_GROUP_HEADERS),
        raw_response=response,
    )


def list_host_groups_command(filter: str | None = None, offset: str | None = None, limit: str | None = None) -> CommandResults:
    response = list_host_groups(filter, limit, offset)
    host_groups = response.get("resources")
    return CommandResults(
        outputs_prefix="CrowdStrike.HostGroup",
        outputs_key_field="id",
        outputs=host_groups,
        readable_output=tableToMarkdown("Host Groups", host_groups, headers=HOST_GROUP_HEADERS),
        raw_response=response,
    )


def delete_host_groups_command(host_group_ids: list[str]) -> CommandResults:
    response = delete_host_groups(host_group_ids)
    deleted_ids = response.get("resources")
    readable = (
        "\n".join([f"Host groups {host_group_id} deleted successfully" for host_group_id in deleted_ids])
        if deleted_ids
        else f"Host groups {host_group_ids} are not deleted"
    )
    return CommandResults(readable_output=readable, raw_response=response)


def upload_batch_custom_ioc_command(
    multiple_indicators_json: str | None = None,
    timeout: str = "180",
) -> list[dict]:
    """
    :param multiple_indicators_json: A JSON object with list of CS Falcon indicators to upload.

    """
    batch_json = safe_load_json(multiple_indicators_json)
    raw_res = upload_batch_custom_ioc(batch_json, timeout=float(timeout))
    handle_response_errors(raw_res)
    iocs = raw_res.get("resources", [])
    entry_objects_list = []
    for ioc in iocs:
        ec = [get_trasnformed_dict(ioc, IOC_KEY_MAP)]
        entry_objects_list.append(
            create_entry_object(
                contents=raw_res,
                ec={"CrowdStrike.IOC(val.ID === obj.ID)": ec},
                hr=tableToMarkdown(f"Custom IOC {ioc['value']} was created successfully", ec),
            )
        )
    return entry_objects_list


# ============== NGSIEM Search Events Functions ==============
def initiate_ngsiem_search_request(repository: str, body: dict) -> dict:
    """
    Initiate an NGSIEM search query job.

    Args:
        repository: The repository to search (e.g., 'search-all').
        body: The request body containing query parameters.

    Returns:
        dict: Response containing the job ID.
    """
    demisto.debug(f"Initiating NGSIEM search with {repository=}, {body=}")

    return http_request(
        method="POST",
        url_suffix=f"/humio/api/v1/repositories/{repository}/queryjobs",
        json=body,
    )


def get_ngsiem_search_results_request(repository: str, job_id: str) -> dict:
    """
    Get the results of an NGSIEM search query job.

    Args:
        repository: The repository that was searched.
        job_id: The job ID from the initiate search request.

    Returns:
        dict: Response containing the search results and status.
    """
    demisto.debug(f"Getting NGSIEM search results for {repository=}, {job_id=}")

    return http_request(
        method="GET",
        url_suffix=f"/humio/api/v1/repositories/{repository}/queryjobs/{job_id}",
    )


def clean_ngsiem_rawstring_field(events: list[dict]) -> list[dict]:
    """
    Clean the @rawstring field by replacing escaped '\\&' sequences with '&'.

    The NGSIEM API may return @rawstring values containing '\\&' (literal backslash + ampersand).
    This replacement ensures the string is clean before the whole event is later serialized with json.dumps.
    """
    for event in events:
        raw = event.get("@rawstring")
        if isinstance(raw, str) and "\\&" in raw:
            event["@rawstring"] = raw.replace("\\&", "&")
    return events


def build_ngsiem_query_with_limit(query: str, limit: int) -> str:
    """
    Add tail() function to query if not already present to limit results.

    The default number of events returned for each API call is 200,
    unless the 'tail' function is used.

    Args:
        query: The original query string.
        limit: Maximum number of events to return.

    Returns:
        str: Query with tail() function appended if needed.
    """
    if "tail(" not in query.lower():
        return f"{query} | tail({limit})"
    return query


def arg_to_timestamp(val: Any) -> Optional[int]:
    """Converts a value to an epoch-milliseconds timestamp using ``arg_to_datetime``.

    Returns ``None`` for empty/None values, otherwise an ``int`` (epoch ms).
    """
    if not val:
        return None
    dt = arg_to_datetime(val)
    return int(dt.timestamp() * 1000) if dt else None


def build_ngsiem_search_body(args: dict) -> dict:
    """
    Build the request body for NGSIEM search.

    Args:
        args: Command arguments.

    Returns:
        dict: The request body.
    """
    query = args.get("query", "")
    around_config = assign_params(
        eventId=args.get("around_event_id"),
        numberOfEventsBefore=arg_to_number(args.get("around_number_events_before")),
        numberOfEventsAfter=arg_to_number(args.get("around_number_events_after")),
        timestamp=arg_to_timestamp(args.get("around_timestamp")),
    )

    if not around_config.get("numberOfEventsBefore") and not around_config.get("numberOfEventsAfter"):
        # If an "around" is used (around_number_events_before/after), adding `limit` would override/ignore the config,
        # so we only set `limit` when "around" is not used.
        limit = arg_to_number(args.get("limit")) or 50
        query = build_ngsiem_query_with_limit(query, limit)

    body = assign_params(
        queryString=query,
        start=arg_to_timestamp(args.get("start")),
        end=arg_to_timestamp(args.get("end")),
        ingestStart=arg_to_timestamp(args.get("ingest_start")),
        ingestEnd=arg_to_timestamp(args.get("ingest_end")),
        useIngestTime=argToBoolean(args.get("use_ingest_time")) if args.get("use_ingest_time") else None,
        around=around_config,
    )
    return body


def build_ngsiem_hr_rows(events: list[dict], hr_keys: list[str]) -> list[dict]:
    """
    Build human-readable table rows from NGSIEM events.

    For each desired key, resolves the value by trying the bare key first,
    then falling back to the '@' and '#' prefixed variants.
    Converts epoch-ms timestamp values to ISO 8601 date strings.

    Args:
        events: The raw event dicts (not modified).
        hr_keys: The unprefixed keys to extract for display.

    Returns:
        list[dict]: A list of dicts ready for tableToMarkdown (HR only).
    """
    hr_rows: list[dict] = []
    for event in events:
        row: dict[str, Any] = {}
        for key in hr_keys:
            val = event.get(key) or event.get(f"@{key}") or event.get(f"#{key}")
            if key == "timestamp" and val is not None:
                try:
                    if isinstance(val, (int | float)):
                        val = timestamp_to_datestring(val)
                    elif isinstance(val, str) and val.isdigit():
                        val = timestamp_to_datestring(int(val))
                except Exception:
                    demisto.debug(f"Failed to convert timestamp {val} to date string")
            row[key] = val
        hr_rows.append(row)
    return hr_rows


def process_ngsiem_search_completion(response: dict, args: dict) -> PollResult:
    """
    Process the completion of an NGSIEM search job.

    Args:
        response: The response from the search job.
        args: Command arguments.

    Returns:
        PollResult: The result of the polling.
    """
    args["wait_for_result"] = False
    events = response.get("events", [])
    warnings = response.get("warnings", [])
    if warnings:
        demisto.debug(f"NGSIEM search completed with warnings: {warnings}")

    if events:
        events = clean_ngsiem_rawstring_field(events)
        demisto.debug(f"Returned {len(events)} results from NGSIEM search")

        hr_keys = ["id", "event_simpleName", "user.name", "host.hostname", "timestamp"]

        def header_transform(header: str) -> str:
            return header.replace("_", " ").replace(".", " ").title()

        hr = tableToMarkdown(
            name=f"NGSIEM Events (Total: {len(events)})",
            t=build_ngsiem_hr_rows(events, hr_keys),
            headerTransform=header_transform,
            headers=hr_keys,
            removeNull=True,
        )
    else:
        demisto.debug("No events found matching the query.")
        hr = "No events found matching the query."
    command_results = CommandResults(
        outputs_prefix="CrowdStrike.NGSiemEvent",
        outputs=events,
        readable_output=hr,
        raw_response=response,
    )
    return PollResult(response=command_results, continue_to_poll=False)


@polling_function(
    "cs-falcon-search-ngsiem-events",
    poll_message="Searching NGSIEM events:",
    polling_arg_name="wait_for_result",
    interval=arg_to_number(demisto.args().get("interval_in_seconds", DEFAULT_INTERVAL)),
    timeout=arg_to_number(demisto.args().get("timeout_in_seconds", DEFAULT_TIMEOUT_NGSIEM_SEARCH)),
)
def cs_falcon_search_ngsiem_events_command(args: dict) -> PollResult:
    """
    Search NGSIEM historical events using polling.

    This command initiates a search query job and polls for results until complete.
    Query jobs must continue to be polled until complete. If a query job is still
    in progress, the response will show done as false.

    Args:
        args: Command arguments including query, repository, time range, etc.

    Returns:
        PollResult: Contains the search results or indicates to continue polling.
    """
    job_id = args.get("job_id")
    repository = args.get("repository", "search-all")

    if not job_id:
        # First call - initiate the search job
        body = build_ngsiem_search_body(args)
        response = initiate_ngsiem_search_request(repository=repository, body=body)

        job_id = response.get("id")
        if not job_id:
            raise DemistoException(f"Failed to initiate NGSIEM search. Response: {response}")

        demisto.debug(f"NGSIEM search job initiated with {job_id=}")
        args["job_id"] = job_id

    # Poll for results
    response = get_ngsiem_search_results_request(repository, job_id)

    is_done = response.get("done", False)
    is_cancelled = response.get("cancelled", False)
    demisto.debug(f"NGSIEM search job status: {is_done=}, {is_cancelled=}")
    if is_cancelled:
        raise DemistoException(f"NGSIEM search job {job_id} was cancelled.")

    if is_done:
        return process_ngsiem_search_completion(response, args)

    demisto.info(f"NGSIEM search job {job_id} still in progress, continuing to poll...")

    return PollResult(
        response=CommandResults(readable_output=f"NGSIEM search job {job_id} still in progress, continuing to poll..."),
        continue_to_poll=True,
        args_for_next_run=args,
    )


def module_test():
    try:
        get_token(new_token=True)
    except (ValueError, DemistoException, requests.exceptions.RequestException) as e:
        demisto.debug(f"test-module failed to obtain a token: {e}\n{traceback.format_exc()}")
        return (
            "Connection Error: Failed to reach the CrowdStrike Falcon server. Verify that the Server URL parameter is"
            " correct, that the API credentials are valid, and that the server is reachable from your host"
            " (check network connectivity, DNS, and proxy settings)."
        )
    if demisto.params().get("isFetch"):
        try:
            fetch_items(command="fetch-incidents")
        except ValueError:
            return "Error: Something is wrong with the filters you entered for the fetch incident, please try again."
    return "ok"


def rtr_kill_process_command(args: dict) -> CommandResults:
    host_id = args.get("host_id")
    process_ids = remove_duplicates_from_list_arg(args, "process_ids")
    command_type = "kill"
    raw_response = []
    host_ids = [host_id]
    offline = argToBoolean(args.get("queue_offline", False))
    batch_id = init_rtr_batch_session(host_ids, offline)
    timeout = arg_to_number(args.get("timeout"))
    outputs = []

    for process_id in process_ids:
        full_command = f"{command_type} {process_id}"
        response = execute_run_batch_write_cmd_with_timer(batch_id, command_type, full_command, timeout=timeout)
        outputs.extend(parse_rtr_command_response(response, host_ids, process_id=process_id))
        raw_response.append(response)

    human_readable = tableToMarkdown(
        f"{INTEGRATION_NAME} {command_type} command on host {host_id}:", outputs, headers=["ProcessID", "Error"]
    )
    human_readable += get_human_readable_for_failed_command(outputs, process_ids, "ProcessID")
    return CommandResults(
        raw_response=raw_response,
        readable_output=human_readable,
        outputs=outputs,
        outputs_prefix="CrowdStrike.Command.kill",
        outputs_key_field="ProcessID",
    )


def get_human_readable_for_failed_command(outputs, required_elements, element_id):
    failed_elements = {}
    for output in outputs:
        if output.get("Error") != "Success":
            failed_elements[output.get(element_id)] = output.get("Error")
    return add_error_message(failed_hosts=failed_elements, all_requested_hosts=required_elements)


def parse_rtr_command_response(response, host_ids, process_id=None) -> list:
    outputs = []
    resources: dict = response.get("combined", {}).get("resources", {})

    for host_id, host_data in resources.items():
        current_error = ""
        errors = host_data.get("errors")  # API errors
        stderr = host_data.get("stderr")  # host command error (as path does not exist and more)
        command_failed_with_error = errors or stderr  # API errors are "stronger" that host stderr
        if command_failed_with_error:
            if errors:
                current_error = errors[0].get("message", "")
            elif stderr:
                current_error = stderr
        outputs_data = {
            "HostID": host_id,
            "Error": current_error if current_error else "Success",
        }
        if process_id:
            outputs_data.update({"ProcessID": process_id})

        outputs.append(outputs_data)

    found_host_ids = {host.get("HostID") for host in outputs}
    not_found_host_ids = set(host_ids) - found_host_ids

    for not_found_host in not_found_host_ids:
        outputs.append(
            {
                "HostID": not_found_host,
                "Error": "The host ID was not found.",
            }
        )
    return outputs


def match_remove_command_for_os(operating_system, file_path):
    if operating_system == "Windows":
        return f"rm '{file_path}' --force"
    elif operating_system == "Linux" or operating_system == "Mac":
        return f"rm '{file_path}' -r -d"
    else:
        return ""


def rtr_remove_file_command(args: dict) -> CommandResults:
    file_path = args.get("file_path")
    host_ids = remove_duplicates_from_list_arg(args, "host_ids")
    offline = argToBoolean(args.get("queue_offline", False))
    operating_system = args.get("os")
    timeout = arg_to_number(args.get("timeout"))
    full_command = match_remove_command_for_os(operating_system, file_path)
    command_type = "rm"

    batch_id = init_rtr_batch_session(host_ids, offline)
    response = execute_run_batch_write_cmd_with_timer(batch_id, command_type, full_command, host_ids, timeout)
    outputs = parse_rtr_command_response(response, host_ids)
    human_readable = tableToMarkdown(
        f"{INTEGRATION_NAME} {command_type} over the file: {file_path}", outputs, headers=["HostID", "Error"]
    )
    human_readable += get_human_readable_for_failed_command(outputs, host_ids, "HostID")
    return CommandResults(
        raw_response=response,
        readable_output=human_readable,
        outputs=outputs,
        outputs_prefix="CrowdStrike.Command.rm",
        outputs_key_field="HostID",
    )


def execute_run_batch_write_cmd_with_timer(batch_id, command_type, full_command, host_ids=None, timeout=None):
    """
    Executes a timer for keeping the session refreshed
    """
    timer = Timer(300, batch_refresh_session, kwargs={"batch_id": batch_id})
    timer.start()
    try:
        response = run_batch_write_cmd(
            batch_id, command_type=command_type, full_command=full_command, optional_hosts=host_ids, timeout=timeout
        )
    finally:
        timer.cancel()
    return response


def execute_run_batch_admin_cmd_with_timer(batch_id, command_type, full_command, host_ids=None, timeout=None):
    timer = Timer(300, batch_refresh_session, kwargs={"batch_id": batch_id})
    timer.start()
    try:
        response = run_batch_admin_cmd(
            batch_id, command_type=command_type, full_command=full_command, optional_hosts=host_ids, timeout=timeout
        )
    finally:
        timer.cancel()
    return response


def rtr_general_command_on_hosts(
    host_ids: list,
    command: str,
    full_command: str,
    get_session_function: Callable,
    write_to_context=True,
    offline=False,
    timeout=None,
) -> list[CommandResults | dict]:  # type:ignore
    """
    General function to run RTR commands depending on the given command.
    """
    batch_id = init_rtr_batch_session(host_ids, offline)
    response = get_session_function(batch_id, command_type=command, full_command=full_command, host_ids=host_ids, timeout=timeout)  # type:ignore
    output, file, not_found_hosts = parse_rtr_stdout_response(host_ids, response, command)

    human_readable = tableToMarkdown(f"{INTEGRATION_NAME} {command} command on host {host_ids[0]}:", output, headers="Stdout")
    human_readable += add_error_message(not_found_hosts, host_ids)

    if write_to_context:
        outputs = {"Filename": file[0].get("File")}
        return [
            CommandResults(
                raw_response=response,
                readable_output=human_readable,
                outputs=outputs,
                outputs_prefix=f"CrowdStrike.Command.{command}",
                outputs_key_field="Filename",
            ),
            file,
        ]

    return [CommandResults(raw_response=response, readable_output=human_readable), file]


def parse_rtr_stdout_response(host_ids, response, command, file_name_suffix=""):
    resources: dict = response.get("combined", {}).get("resources", {})
    outputs = []
    files = []

    for host_id, resource in resources.items():
        current_error = ""
        errors = resource.get("errors")
        stderr = resource.get("stderr")
        command_failed_with_error = errors or stderr
        if command_failed_with_error:
            if errors:
                current_error = errors[0].get("message", "")
            elif stderr:
                current_error = stderr
            return_error(current_error)
        stdout = resource.get("stdout", "")
        file_name = f"{command}-{host_id}{file_name_suffix}"
        outputs.append({"Stdout": stdout, "FileName": file_name})
        files.append(fileResult(file_name, stdout))

    not_found_hosts = set(host_ids) - resources.keys()
    return outputs, files, not_found_hosts


def rtr_read_registry_keys_command(args: dict):
    host_ids = remove_duplicates_from_list_arg(args, "host_ids")
    offline = argToBoolean(args.get("queue_offline", False))
    registry_keys = remove_duplicates_from_list_arg(args, "registry_keys")
    timeout = arg_to_number(args.get("timeout"))
    command_type = "reg"
    raw_response = []
    batch_id = init_rtr_batch_session(host_ids, offline)
    outputs = []
    files = []
    not_found_hosts = set()

    for registry_key in registry_keys:
        full_command = f"{command_type} query {registry_key}"
        response = execute_run_batch_write_cmd_with_timer(
            batch_id, command_type, full_command, host_ids=host_ids, timeout=timeout
        )
        output, file, not_found_host = parse_rtr_stdout_response(host_ids, response, command_type, file_name_suffix=registry_key)
        not_found_hosts.update(not_found_host)
        outputs.extend(output)
        files.append(file)
        raw_response.append(response)

    human_readable = tableToMarkdown(f"{INTEGRATION_NAME} {command_type} command on hosts {host_ids}:", outputs)
    human_readable += add_error_message(not_found_hosts, host_ids)
    return [CommandResults(raw_response=raw_response, readable_output=human_readable), files]


def add_error_message(failed_hosts, all_requested_hosts):
    human_readable = ""
    if failed_hosts:
        if len(all_requested_hosts) == len(failed_hosts):
            raise DemistoException(f"{INTEGRATION_NAME} The command was failed with the errors: {failed_hosts}")
        human_readable = "Note: you don't see the following IDs in the results as the request was failed for them. \n"
        for host_id in failed_hosts:
            human_readable += f"ID {host_id} failed as it was not found. \n"
    return human_readable


def rtr_polling_retrieve_file_command(args: dict):
    """
    This function is generically handling the polling flow.
    In this case, the polling flow is:
    1. run the "cs-falcon-run-get-command" command to get the request id.
    2. run the "cs-falcon-status-get-command" command to get the status of the first "get" command by the request id.
    2.1 start polling - wait for the 2nd step to be finished (when we get at least sha256 one time).
    3. run the "cs-falcon-get-extracted-file" command to get the extracted file.
    Args:
        args: the arguments required to the command being called, under cmd
    Returns:
        The return value is:
        1. All the extracted files.
        2. A list of dictionaries. Each dict includes a host id and a file name.
    """
    cmd = "cs-falcon-rtr-retrieve-file"
    ScheduledCommand.raise_error_if_not_supported()
    interval_in_secs = int(args.get("interval_in_seconds", 60))

    if "hosts_and_requests_ids" not in args:
        # this is the very first time we call the polling function. We don't wont to call this function more that
        # one time, so we store that arg between the different runs
        offline = argToBoolean(args.get("queue_offline", False))
        # run the first command to retrieve file
        args["hosts_and_requests_ids"] = run_get_command(is_polling=True, offline=offline)

    # we are here after we ran the cs-falcon-run-get-command command at the current run or in previous
    if not args.get("SHA256"):
        # this means that we don't have status yet (i.e we didn't get sha256)
        hosts_and_requests_ids = args.pop("hosts_and_requests_ids")
        args["request_ids"] = [res.get("RequestID") for res in hosts_and_requests_ids]
        get_status_response, args = status_get_command(args, is_polling=True)

        if args.get("SHA256"):
            # the status is ready, we can get the extracted files
            args.pop("SHA256")
            return rtr_get_extracted_file(get_status_response, args.get("filename"))  # type:ignore

        else:
            # we should call the polling on status, cause the status is not ready
            args["hosts_and_requests_ids"] = hosts_and_requests_ids
            args.pop("request_ids")
            args.pop("SHA256")
            polling_timeout = arg_to_number(args.get("polling_timeout", 600))
            scheduled_command = ScheduledCommand(
                command=cmd, next_run_in_seconds=interval_in_secs, args=args, timeout_in_seconds=polling_timeout
            )
            command_results = CommandResults(
                scheduled_command=scheduled_command, readable_output="Waiting for the polling execution"
            )
            return command_results
    return None


def rtr_get_extracted_file(args_to_get_files: dict, file_name: str):
    files = []
    outputs_data = []

    for host_id, values in args_to_get_files.items():
        arg = {"host_id": host_id, "sha256": values.get("SHA256"), "filename": file_name}
        file = get_extracted_file_command(arg)
        files.append(file)
        outputs_data.append({"HostID": arg.get("host_id"), "FileName": file.get("File")})
    return [
        CommandResults(readable_output="CrowdStrike Falcon files", outputs=outputs_data, outputs_prefix="CrowdStrike.File"),
        files,
    ]


def build_url_filter(values: list[str] | str | None):
    return "cve.id:['" + "','".join(argToList(values)) + "']"


def cs_falcon_spotlight_search_vulnerability_request(
    aid: list[str] | None,
    cve_id: list[str] | None,
    cve_severity: list[str] | None,
    tags: list[str] | None,
    status: list[str] | None,
    platform_name: str | None,
    host_group: list[str] | None,
    host_type: list[str] | None,
    last_seen_within: str | None,
    is_suppressed: str | None,
    filter_: str,
    remediation: bool | None,
    evaluation_logic: bool | None,
    host_info: bool | None,
    limit: str | None,
    next_token: str | None = None,
) -> dict:
    input_arg_dict = {
        "aid": aid,
        "cve.id": cve_id,
        "host_info.tags": tags,
        "status": status,
        "host_info.groups": host_group,
        "last_seen_within": last_seen_within,
        "suppression_info.is_suppressed": is_suppressed,
    }
    input_arg_dict["cve.severity"] = [severity.upper() for severity in cve_severity] if cve_severity else None
    input_arg_dict["host_info.platform_name"] = platform_name.capitalize() if platform_name else None
    input_arg_dict["host_info.product_type_desc"] = [host_type_.capitalize() for host_type_ in host_type] if host_type else None
    remove_nulls_from_dictionary(input_arg_dict)
    # In Falcon Query Language, '+' (after decode '%2B) stands for AND and ',' for OR
    # (https://falcon.crowdstrike.com/documentation/45/falcon-query-language-fql)
    url_filter = filter_.replace("+", "%2B")
    if not any((input_arg_dict, url_filter)):
        raise DemistoException("Please add a at least one filter argument")
    for key, arg in input_arg_dict.items():
        if url_filter:
            url_filter += "%2B"
        if isinstance(arg, list):
            url_filter += f"{key}:['" + "','".join(arg) + "']"
        else:
            url_filter += f"{key}:'{arg}'"  # All args should be a list. this is a fallback
    url_facet = "&facet=cve"
    for argument, url_value in (
        ("remediation", remediation),
        ("evaluation_logic", evaluation_logic),
        ("host_info", host_info),
    ):
        if argToBoolean(url_value):
            url_facet += f"&facet={argument}"
    # The url is hardcoded since facet is a parameter that can have serval values, therefore we can't use a dict
    suffix_url = f"/spotlight/combined/vulnerabilities/v1?filter={url_filter}{url_facet}&limit={limit}"
    if next_token:
        suffix_url += f"&after={urllib.parse.quote(next_token, safe='')}"
    return http_request("GET", suffix_url)


def cs_falcon_spotlight_list_host_by_vulnerability_request(cve_ids: list[str] | None, limit: str) -> dict:
    url_filter = build_url_filter(cve_ids)
    params = {"filter": url_filter, "facet": "host_info", "limit": limit}
    return http_request("GET", "/spotlight/combined/vulnerabilities/v1", params=params)


def cve_request(cve_id: list[str] | None) -> dict:
    url_filter = build_url_filter(cve_id)
    return http_request("GET", "/spotlight/combined/vulnerabilities/v1", params={"filter": url_filter, "facet": "cve"})


def cs_falcon_spotlight_search_vulnerability_command(args: dict) -> list[CommandResults]:
    """Search Spotlight vulnerabilities with cursor-based pagination via ``next_token``.

    The pagination cursor returned by CrowdStrike (``meta.pagination.after``) is always
    emitted to the ``CrowdStrike.VulnerabilityNextToken`` context output when present,
    and never rendered in the human-readable war-room output.

    Args:
        args: Command arguments (filter, limit, next_token, etc.).

    Returns:
        list[CommandResults]: Bulk-data entry, followed by a cursor entry when the
        API returned a non-empty ``after`` cursor.
    """
    next_token = args.get("next_token")

    limit = arg_to_number(args.get("limit", 50))
    if limit is not None and limit > MAX_SPOTLIGHT_VULNERABILITY_PAGE_SIZE:
        limit = MAX_SPOTLIGHT_VULNERABILITY_PAGE_SIZE

    try:
        vulnerability_response = cs_falcon_spotlight_search_vulnerability_request(
            argToList(args.get("aid")),
            argToList(args.get("cve_id")),
            argToList(args.get("cve_severity")),
            argToList(args.get("tags")),
            argToList(args.get("status")),
            args.get("platform_name"),
            argToList(args.get("host_group")),
            argToList(args.get("host_type")),
            args.get("last_seen_within"),
            args.get("is_suppressed"),
            args.get("filter", ""),
            args.get("display_remediation_info"),
            args.get("display_evaluation_logic_info"),
            args.get("display_host_info"),
            str(limit),
            next_token,
        )
    except DemistoException as exc:
        # Narrow intercept: only the expired-cursor case (HTTP 404).
        # The generic 400 "Invalid pagination token" is already self-explanatory
        # and is intentionally NOT caught here.
        if "Search context expired" in str(exc):
            return_error(
                "CrowdStrike Spotlight pagination cursor has expired "
                "(these cursors are short-lived, typically a few minutes). "
                "Please rerun the command without the next_token argument to start a fresh pagination session."
            )
        raise
    headers = ["ID", "Severity", "Status", "Base Score", "Published Date", "Impact Score", "Exploitability Score", "Vector"]
    outputs = []
    for vulnerability in vulnerability_response.get("resources", {}):
        outputs.append(
            {
                "ID": vulnerability.get("cve", {}).get("id"),
                "Severity": vulnerability.get("cve", {}).get("severity"),
                "Status": vulnerability.get("status"),
                "Base Score": vulnerability.get("cve", {}).get("base_score"),
                "Published Date": vulnerability.get("cve", {}).get("published_date"),
                "Impact Score": vulnerability.get("cve", {}).get("impact_score"),
                "Exploitability Score": vulnerability.get("cve", {}).get("exploitability_score"),
                "Vector": vulnerability.get("cve", {}).get("vector"),
            }
        )
    human_readable = tableToMarkdown("List Vulnerabilities", outputs, removeNull=True, headers=headers)

    raw_after = vulnerability_response.get("meta", {}).get("pagination", {}).get("after")

    results: list[CommandResults] = [
        CommandResults(
            outputs_prefix="CrowdStrike.Vulnerability",
            outputs_key_field="id",
            outputs=vulnerability_response.get("resources"),
            readable_output=human_readable,
            raw_response=vulnerability_response,
        )
    ]

    if raw_after:
        results.append(
            CommandResults(
                outputs_prefix="CrowdStrike.VulnerabilityNextToken",
                outputs=raw_after,
                readable_output="Token for next page was generated and can be found under CrowdStrike.VulnerabilityNextToken",
                replace_existing=True,
            )
        )

    return results


def cs_falcon_spotlight_list_host_by_vulnerability_command(args: dict) -> CommandResults:
    """
    Get a list of vulnerability by spotlight
    : args: filter which include params or filter param.
    : return: a list of vulnerabilities according to the user.
    """
    cve_ids = args.get("cve_ids")
    limit = args.get("limit", "50")
    vulnerability_response = cs_falcon_spotlight_list_host_by_vulnerability_request(cve_ids, limit)
    headers = [
        "CVE ID",
        "hostname",
        "os Version",
        "Product Type Desc",
        "Local IP",
        "ou",
        "Machine Domain",
        "Site Name",
        "CVE Exploitability Score",
        "CVE Vector",
    ]
    outputs = []
    for vulnerability in vulnerability_response.get("resources", {}):
        outputs.append(
            {
                "CVE ID": vulnerability.get("cve", {}).get("id"),
                "hostname": vulnerability.get("host_info", {}).get("hostname"),
                "os Version": vulnerability.get("host_info", {}).get("os_version"),
                "Product Type Desc": vulnerability.get("host_info", {}).get("product_type_desc"),
                "Local IP": vulnerability.get("host_info", {}).get("local_ip"),
                "ou": vulnerability.get("host_info", {}).get("ou"),
                "Machine Domain": vulnerability.get("host_info", {}).get("machine_domain"),
                "Site Name": vulnerability.get("host_info", {}).get("site_name"),
            }
        )
    human_readable = tableToMarkdown("List Vulnerabilities For Host", outputs, removeNull=True, headers=headers)
    return CommandResults(
        raw_response=vulnerability_response,
        readable_output=human_readable,
        outputs=vulnerability_response.get("resources"),
        outputs_prefix="CrowdStrike.VulnerabilityHost",
        outputs_key_field="id",
    )


def get_cve_command(args: dict) -> list[dict[str, Any]]:
    """
    Get a list of vulnerabilities by spotlight
    : args: filter which include params or filter param.
    : return: a list of cve indicators according to the user.
    """
    cve = args.get("cve") or args.get("cve_id")
    if not cve:
        raise DemistoException('Please add a filter argument "cve".')
    command_results_list = []
    http_response = cve_request(cve)
    raw_cve = [res_element.get("cve") for res_element in http_response.get("resources", [])]
    if not raw_cve:
        command_results_list = [(CommandResults(readable_output="No matching results found.")).to_context()]
    else:
        for cve in raw_cve:
            relationships_list = create_relationships(cve)
            cve_dbot_score = create_dbot_Score(cve=cve, reliability=args.get("Reliability", "A+ - 3rd party enrichment"))
            cve_indicator = Common.CVE(
                id=cve.get("id"),
                cvss="",
                published=cve.get("published_date"),
                modified="",
                description=cve.get("description"),
                cvss_score=cve.get("base_score"),
                cvss_vector=cve.get("vector"),
                dbot_score=cve_dbot_score,
                publications=create_publications(cve),
                relationships=relationships_list,
            )
            cve_human_readable = {
                "ID": cve.get("id"),
                "Description": cve.get("description"),
                "Published Date": cve.get("published_date"),
                "Base Score": cve.get("base_score"),
            }
            human_readable = tableToMarkdown(
                "CrowdStrike Falcon CVE", cve_human_readable, headers=["ID", "Description", "Published Date", "Base Score"]
            )
            command_results = CommandResults(
                raw_response=cve, readable_output=human_readable, relationships=relationships_list, indicator=cve_indicator
            ).to_context()
            if command_results not in command_results_list:
                command_results_list.append(command_results)
    return command_results_list


def create_ml_exclusion_command(args: dict) -> CommandResults:
    """Creates a machine learning exclusion.

    Args:
        args: Arguments to create the exclusion from.

    Returns:
        The created exclusion meta data.

    """
    create_args = assign_params(
        value=args.get("value"),
        excluded_from=argToList(args.get("excluded_from")),
        comment=args.get("comment"),
        groups=argToList(args.get("groups", "all")),
    )

    exclusion = create_exclusion("ml", create_args).get("resources")
    human_readable = tableToMarkdown(
        "CrowdStrike Falcon machine learning exclusion",
        exclusion,
        sort_headers=False,
        headerTransform=underscoreToCamelCase,
        is_auto_json_transform=True,
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix="CrowdStrike.MLExclusion",
        outputs_key_field="id",
        outputs=exclusion,
        readable_output=human_readable,
    )


def update_ml_exclusion_command(args: dict) -> CommandResults:
    """Updates a machine learning exclusion by providing an ID.

    Args:
        args: Arguments for updating the exclusion.

    Returns:
        The updated exclusion meta data.

    """
    update_args = assign_params(value=args.get("value"), comment=args.get("comment"), groups=argToList(args.get("groups")))
    if not update_args:
        raise Exception("At least one argument (besides the id argument) should be provided to update the exclusion.")
    update_args.update({"id": args.get("id")})

    exclusion = update_exclusion("ml", update_args).get("resources")
    human_readable = tableToMarkdown(
        "CrowdStrike Falcon machine learning exclusion",
        exclusion,
        sort_headers=False,
        headerTransform=underscoreToCamelCase,
        is_auto_json_transform=True,
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix="CrowdStrike.MLExclusion",
        outputs_key_field="id",
        outputs=exclusion,
        readable_output=human_readable,
    )


def delete_ml_exclusion_command(args: dict) -> CommandResults:
    """Delete a machine learning exclusion by providing an ID.

    Args:
        args: Arguments for deleting the exclusion (in particular only the id is needed).

    Returns:
        A message that the exclusion has been deleted.

    """
    ids = argToList(args.get("ids"))

    delete_exclusion("ml", ids)

    return CommandResults(readable_output=f'The machine learning exclusions with IDs {" ".join(ids)} was successfully deleted.')


def search_ml_exclusion_command(args: dict) -> CommandResults:
    """Searches machine learning exclusions by providing an ID / value / cusotm-filter.

    Args:
        args: Arguments for searching the exclusions.

    Returns:
        The exclusions meta data.

    """
    if not (ids := argToList(args.get("ids"))):
        search_args = assign_params(
            sort=args.get("sort"),
            limit=args.get("limit"),
            offset=args.get("offset"),
        )
        if value := args.get("value"):
            ids = get_exclusions("ml", f"value:'{value}'", search_args).get("resources")
        else:
            ids = get_exclusions("ml", args.get("filter"), search_args).get("resources")

    if not ids:
        return CommandResults(readable_output="The arguments/filters you provided did not match any exclusion.")

    exclusions = get_exclusion_entities("ml", ids).get("resources")
    human_readable = tableToMarkdown(
        "CrowdStrike Falcon machine learning exclusions",
        exclusions,
        sort_headers=False,
        headerTransform=underscoreToCamelCase,
        is_auto_json_transform=True,
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix="CrowdStrike.MLExclusion",
        outputs_key_field="id",
        outputs=exclusions,
        readable_output=human_readable,
    )


def create_ioa_exclusion_command(args: dict) -> CommandResults:
    """Creates an IOA exclusion.

    Args:
        args: Arguments to create the exclusion from.

    Returns:
        The created exclusion meta data.

    """
    create_args = assign_params(
        name=args.get("exclusion_name"),
        pattern_id=args.get("pattern_id"),
        pattern_name=args.get("pattern_name"),
        cl_regex=args.get("cl_regex"),
        ifn_regex=args.get("ifn_regex"),
        comment=args.get("comment"),
        description=args.get("description"),
        groups=argToList(args.get("groups", "all")),
        detection_json=args.get("detection_json"),
    )

    exclusion = create_exclusion("ioa", create_args).get("resources")
    human_readable = tableToMarkdown(
        "CrowdStrike Falcon IOA exclusion",
        exclusion,
        is_auto_json_transform=True,
        headerTransform=underscoreToCamelCase,
        sort_headers=False,
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix="CrowdStrike.IOAExclusion",
        outputs_key_field="id",
        outputs=exclusion,
        readable_output=human_readable,
    )


def update_ioa_exclusion_command(args: dict) -> CommandResults:
    """Updates an IOA exclusion by providing an ID.

    Args:
        args: Arguments for updating the exclusion.

    Returns:
        The updated exclusion meta data.

    """
    update_args = assign_params(
        name=args.get("exclusion_name"),
        pattern_id=args.get("pattern_id"),
        pattern_name=args.get("pattern_name"),
        cl_regex=args.get("cl_regex"),
        ifn_regex=args.get("ifn_regex"),
        comment=args.get("comment"),
        description=args.get("description"),
        groups=argToList(args.get("groups")),
        detection_json=args.get("detection_json"),
    )
    if not update_args:
        raise Exception("At least one argument (besides the id argument) should be provided to update the exclusion.")
    update_args.update({"id": args.get("id")})

    exclusion = update_exclusion("ioa", update_args).get("resources")
    human_readable = tableToMarkdown(
        "CrowdStrike Falcon IOA exclusion",
        exclusion,
        is_auto_json_transform=True,
        headerTransform=underscoreToCamelCase,
        removeNull=True,
        sort_headers=False,
    )

    return CommandResults(
        outputs_prefix="CrowdStrike.IOAExclusion",
        outputs_key_field="id",
        outputs=exclusion,
        readable_output=human_readable,
    )


def delete_ioa_exclusion_command(args: dict) -> CommandResults:
    """Delete an IOA exclusion by providing an ID.

    Args:
        args: Arguments for deleting the exclusion (in particular only the id is needed).

    Returns:
        A message that the exclusion has been deleted.

    """
    ids = argToList(args.get("ids"))

    delete_exclusion("ioa", ids)

    return CommandResults(readable_output=f'The IOA exclusions with IDs {" ".join(ids)} was successfully deleted.')


def search_ioa_exclusion_command(args: dict) -> CommandResults:
    """Searches IOA exclusions by providing an ID / name / cusotm-filter.

    Args:
        args: Arguments for searching the exclusions.

    Returns:
        The exclusions meta data.

    """
    exclusion_name = args.get("name")
    if not (ids := argToList(args.get("ids"))):
        search_args = assign_params(limit=args.get("limit"), offset=args.get("offset"))
        if exclusion_name:
            ids = get_exclusions("ioa", f"name:~'{exclusion_name}'", search_args).get("resources")
        else:
            ids = get_exclusions("ioa", args.get("filter"), search_args).get("resources")

    if not ids:
        return CommandResults(readable_output="The arguments/filters you provided did not match any exclusion.")

    exclusions = get_exclusion_entities("ioa", ids).get("resources", [])
    if exclusion_name and exclusions:
        exclusions = list(filter(lambda x: x.get("name") == exclusion_name, exclusions))
    human_readable = tableToMarkdown(
        "CrowdStrike Falcon IOA exclusions",
        exclusions,
        is_auto_json_transform=True,
        headerTransform=underscoreToCamelCase,
        removeNull=True,
        sort_headers=False,
    )

    return CommandResults(
        outputs_prefix="CrowdStrike.IOAExclusion",
        outputs_key_field="id",
        outputs=exclusions,
        readable_output=human_readable,
    )


def list_quarantined_file_command(args: dict) -> CommandResults:
    """Get quarantine file metadata by specified IDs / custom-filter.

    Args:
        args: Arguments for searching the quarantine files.

    Returns:
        The quarantine files meta data.

    """
    if not (ids := argToList(args.get("ids"))):
        pagination_args = assign_params(limit=args.get("limit", "50"), offset=args.get("offset"))
        search_args = assign_params(
            state=args.get("state"),
            sha256=argToList(args.get("sha256")),
            filename=argToList(args.get("filename")),
            hostname=argToList(args.get("hostname")),
            username=argToList(args.get("username")),
        )

        ids = list_quarantined_files_id(args.get("filter"), search_args, pagination_args).get("resources")

    if not ids:
        return CommandResults(readable_output=NO_QUARANTINED_FILES_MSG)

    files = list_quarantined_files(ids).get("resources")
    if isinstance(files, list):
        for file in files:
            if isinstance(file, dict) and "composite_ids" in file:
                file["detect_ids"] = file.pop("composite_ids")

    human_readable = tableToMarkdown(
        "CrowdStrike Falcon Quarantined File",
        t=files,
        headers=QUARANTINE_FILES_OUTPUT_HEADERS,
        is_auto_json_transform=True,
        headerTransform=underscoreToCamelCase,
        sort_headers=False,
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix="CrowdStrike.QuarantinedFile",
        outputs_key_field="id",
        outputs=files,
        readable_output=human_readable,
    )


def apply_quarantine_file_action_command(args: dict) -> CommandResults:
    """Apply action to quarantine file.

    Args:
        args: Arguments for searching and applying action to the quarantine files.

    Returns:
        The applied quarantined files meta data.

    """
    if not (ids := argToList(args.get("ids"))):
        pagination_args = assign_params(limit=args.get("limit", "50"), offset=args.get("offset"))
        search_args = assign_params(
            state=args.get("state"),
            sha256=argToList(args.get("sha256")),
            filename=argToList(args.get("filename")),
            hostname=argToList(args.get("hostname")),
            username=argToList(args.get("username")),
        )
        if not search_args:
            raise Exception(
                "At least one search argument (filename, hostname, sha256, state, username, ids, or filter)"
                " is required to update the quarantine file."
            )

        ids = list_quarantined_files_id(args.get("filter"), search_args, pagination_args).get("resources")

    if not ids:
        # No matching quarantined files were found for the given search arguments/filter.
        # Returning a friendly message instead of letting the PATCH go out without ids,
        # which CrowdStrike rejects with HTTP 400 Validation error.
        return CommandResults(readable_output=NO_QUARANTINED_FILES_MSG)

    update_args = assign_params(
        ids=ids,
        action=args.get("action"),
        comment=args.get("comment"),
    )
    if not update_args:
        raise Exception("At least one update argument (action, comment) should be provided to update the quarantine file.")

    apply_quarantined_files_action(update_args).get("resources")

    return CommandResults(
        readable_output=f"The Quarantined File with IDs {ids} was successfully updated.",
    )


def build_cs_falcon_filter(custom_filter: str | None = None, **filter_args) -> str:
    """Creates an FQL syntax filter from a dictionary and a custom built filter

    :custom_filter: custom filter from user (will take priority if conflicts with dictionary), defaults to None
    :filter_args: args to translate to FQL format.

    :return: FQL syntax filter.
    """

    custom_filter_list = custom_filter.split("+") if custom_filter else []
    arguments = [f"{key}:{argToList(value)}" for key, value in filter_args.items() if value]
    # custom_filter takes priority because it is first
    return "%2B".join(custom_filter_list + arguments)


def ODS_query_scans_request(**query_params) -> dict:
    remove_nulls_from_dictionary(query_params)
    # http_request messes up the params, so they were put directly in the url:
    url_params = "&".join(f"{k}={v}" for k, v in query_params.items())
    return http_request("GET", f"/ods/queries/scans/v1?{url_params}")


def ODS_get_scans_by_id_request(ids: list[str]) -> dict:
    url_params = "&".join(f"ids={query_id}" for query_id in ids)
    return http_request("GET", f"/ods/entities/scans/v1?{url_params}")


def map_scan_resource_to_UI(resource: dict) -> dict:
    output = {
        "ID": resource.get("id"),
        "Status": resource.get("status"),
        "Severity": resource.get("severity"),
        # Every host in resource.metadata has a "filecount" which is a dictionary
        # that counts the files traversed, skipped, found to be malicious and the like.
        "File Count": "\n-\n".join(
            "\n".join(f"{k}: {v}" for k, v in filecount.items())
            for host in resource.get("metadata", [])
            if (filecount := host.get("filecount", {}))
        ),
        "Description": resource.get("description"),
        "Hosts/Host groups": resource.get("hosts") or resource.get("host_groups"),
        "Start time": resource.get("scan_started_on"),
        "End time": resource.get("scan_completed_on"),
        "Run by": resource.get("created_by"),
    }
    return output


def ODS_get_scan_resources_to_human_readable(resources: list[dict]) -> str:
    human_readable = tableToMarkdown(
        "CrowdStrike Falcon ODS Scans",
        [map_scan_resource_to_UI(resource) for resource in resources],
        headers=[
            "ID",
            "Status",
            "Severity",
            "File Count",
            "Description",
            "Hosts/Host groups",
            "End time",
            "Start time",
            "Run by",
        ],
    )

    return human_readable


def get_ODS_scan_ids(args: dict) -> list[str] | None:
    demisto.debug("Fetching IDs from query api")

    query_filter = build_cs_falcon_filter(
        custom_filter=args.get("filter"),
        initiated_from=args.get("initiated_from"),
        status=args.get("status"),
        severity=args.get("severity"),
        scan_started_on=args.get("scan_started_on"),
        scan_completed_on=args.get("scan_completed_on"),
    )

    raw_response = ODS_query_scans_request(
        filter=query_filter,
        offset=args.get("offset"),
        limit=args.get("limit"),
    )

    return raw_response.get("resources")


@polling_function(
    "cs-falcon-ods-query-scan",
    poll_message="Retrieving scan results:",
    polling_arg_name="wait_for_result",
    interval=arg_to_number(dict_safe_get(demisto.args(), ["interval_in_seconds"], 0, (int, str))),
    timeout=arg_to_number(dict_safe_get(demisto.args(), ["timeout_in_seconds"], 0, (int, str))),
)
def cs_falcon_ODS_query_scans_command(args: dict) -> PollResult:
    # call the query api if no ids given
    ids = argToList(args.get("ids")) or get_ODS_scan_ids(args)

    if not ids:
        command_results = CommandResults(readable_output="No scans match the arguments/filter.")
        scan_in_progress = False

    else:
        response = ODS_get_scans_by_id_request(ids)
        resources = response.get("resources", [])

        scan_in_progress = len(resources) == 1 and dict_safe_get(resources, [0, "status"]) in ("pending", "running")

        human_readable = ODS_get_scan_resources_to_human_readable(resources)
        command_results = CommandResults(
            raw_response=response,
            outputs_prefix="CrowdStrike.ODSScan",
            outputs_key_field="id",
            outputs=resources,
            readable_output=human_readable,
        )

    return PollResult(response=command_results, continue_to_poll=scan_in_progress, args_for_next_run=args)


def ODS_query_scheduled_scans_request(**query_params) -> dict:
    remove_nulls_from_dictionary(query_params)
    # http_request messes up the params, so they were put directly in the url:
    url_params = "&".join(f"{k}={v}" for k, v in query_params.items())
    return http_request("GET", f"/ods/queries/scheduled-scans/v1?{url_params}")


def ODS_get_scheduled_scans_by_id_request(ids: list[str]) -> dict:
    url_params = "&".join(f"ids={query_id}" for query_id in ids)
    return http_request("GET", f"/ods/entities/scheduled-scans/v1?{url_params}")


def map_scheduled_scan_resource_to_UI(resource: dict) -> dict:
    output = {
        "ID": resource.get("id"),
        "Hosts targeted": len(resource.get("metadata", [])),
        "Description": resource.get("description"),
        "Host groups": resource.get("host_groups"),
        "Start time": resource.get("schedule", {}).get("start_timestamp"),
        "Created by": resource.get("created_by"),
    }
    return output


def ODS_get_scheduled_scan_resources_to_human_readable(resources: list[dict]) -> str:
    human_readable = tableToMarkdown(
        "CrowdStrike Falcon ODS Scheduled Scans",
        [map_scheduled_scan_resource_to_UI(resource) for resource in resources],
        headers=["ID", "Hosts targeted", "Description", "Host groups", "Start time", "Created by"],
    )

    return human_readable


def get_ODS_scheduled_scan_ids(args: dict) -> list[str] | None:
    demisto.debug("Fetching IDs from query api")

    query_filter = build_cs_falcon_filter(
        **{
            "custom_filter": args.get("filter"),
            "initiated_from": args.get("initiated_from"),
            "status": args.get("status"),
            "created_on": args.get("created_on"),
            "created_by": args.get("created_by"),
            "schedule.start_timestamp": args.get("start_timestamp"),
            "deleted": args.get("deleted"),
        }
    )

    raw_response = ODS_query_scheduled_scans_request(
        filter=query_filter,
        offset=args.get("offset"),
        limit=args.get("limit"),
    )

    return raw_response.get("resources")


def cs_falcon_ODS_query_scheduled_scan_command(args: dict) -> CommandResults:
    # call the query api if no ids given
    ids = argToList(args.get("ids")) or get_ODS_scheduled_scan_ids(args)

    if not ids:
        return CommandResults(readable_output="No scheduled scans match the arguments/filter.")

    response = ODS_get_scheduled_scans_by_id_request(ids)
    resources = response.get("resources", [])
    human_readable = ODS_get_scheduled_scan_resources_to_human_readable(resources)

    command_results = CommandResults(
        raw_response=response,
        outputs_prefix="CrowdStrike.ODSScheduledScan",
        outputs_key_field="id",
        outputs=resources,
        readable_output=human_readable,
    )

    return command_results


def ODS_query_scan_hosts_request(**query_params) -> dict:
    remove_nulls_from_dictionary(query_params)
    # http_request messes up the params, so they were put directly in the url:
    url_params = "&".join(f"{k}={v}" for k, v in query_params.items())
    return http_request("GET", f"/ods/queries/scan-hosts/v1?{url_params}")


def ODS_get_scan_hosts_by_id_request(ids: list[str]) -> dict:
    url_params = "&".join(f"ids={query_id}" for query_id in ids)
    return http_request("GET", f"/ods/entities/scan-hosts/v1?{url_params}")


def get_ODS_scan_host_ids(args: dict) -> list[str]:
    query_filter = build_cs_falcon_filter(
        custom_filter=args.get("filter"),
        host_id=args.get("host_ids"),
        scan_id=args.get("scan_ids"),
        status=args.get("status"),
        started_on=args.get("started_on"),
        completed_on=args.get("completed_on"),
    )

    raw_response = ODS_query_scan_hosts_request(
        filter=query_filter,
        offset=args.get("offset"),
        limit=args.get("limit"),
    )

    return raw_response.get("resources", [])


def map_scan_host_resource_to_UI(resource: dict) -> dict:
    output = {
        "ID": resource.get("id"),
        "Scan ID": resource.get("scan_id"),
        "Host ID": resource.get("host_id"),
        "Filecount": resource.get("filecount"),
        "Status": resource.get("status"),
        "Severity": resource.get("severity"),
        "Started on": resource.get("started_on"),
    }
    return output


def ODS_get_scan_hosts_resources_to_human_readable(resources: list[dict]) -> str:
    human_readable = tableToMarkdown(
        "CrowdStrike Falcon ODS Scan Hosts",
        [map_scan_host_resource_to_UI(resource) for resource in resources],
        headers=["ID", "Scan ID", "Host ID", "Filecount", "Status", "Severity", "Started on"],
    )

    return human_readable


def cs_falcon_ods_query_scan_host_command(args: dict) -> CommandResults:
    ids = get_ODS_scan_host_ids(args)

    if not ids:
        return CommandResults(readable_output="No hosts to display.")

    response = ODS_get_scan_hosts_by_id_request(ids)
    resources = response.get("resources", [])
    human_readable = ODS_get_scan_hosts_resources_to_human_readable(resources)

    command_results = CommandResults(
        raw_response=response,
        outputs_prefix="CrowdStrike.ODSScanHost",
        outputs_key_field="id",
        outputs=resources,
        readable_output=human_readable,
    )

    return command_results


def ODS_query_malicious_files_request(**query_params) -> dict:
    remove_nulls_from_dictionary(query_params)
    # http_request messes up the params, so they were put directly in the url:
    url_params = "&".join(f"{k}={v}" for k, v in query_params.items())
    return http_request("GET", f"/ods/queries/malicious-files/v1?{url_params}")


def ODS_get_malicious_files_by_id_request(ids: list[str]) -> dict:
    url_params = "&".join(f"ids={query_id}" for query_id in ids)
    return http_request("GET", f"/ods/entities/malicious-files/v1?{url_params}")


def map_malicious_file_resource_to_UI(resource: dict) -> dict:
    output = {
        "ID": resource.get("id"),
        "Scan id": resource.get("scan_id"),
        "Filename": resource.get("filename"),
        "Hash": resource.get("hash"),
        "Severity": resource.get("severity"),
        "Last updated": resource.get("last_updated"),
    }
    return output


def ODS_get_malicious_files_resources_to_human_readable(resources: list[dict]) -> str:
    human_readable = tableToMarkdown(
        "CrowdStrike Falcon ODS Malicious Files",
        [map_malicious_file_resource_to_UI(resource) for resource in resources],
        headers=["ID", "Scan id", "Filename", "Hash", "Severity", "Last updated"],
    )

    return human_readable


def get_ODS_malicious_files_ids(args: dict) -> list[str] | None:
    demisto.debug("Fetching IDs from query api")

    query_filter = build_cs_falcon_filter(
        custom_filter=args.get("filter"),
        host_id=args.get("host_ids"),
        scan_id=args.get("scan_ids"),
        filepath=args.get("file_paths"),
        filename=args.get("file_names"),
        hash=args.get("hash"),
    )

    raw_response = ODS_query_malicious_files_request(
        filter=query_filter,
        offset=args.get("offset"),
        limit=args.get("limit"),
    )

    return raw_response.get("resources")


def cs_falcon_ODS_query_malicious_files_command(args: dict) -> CommandResults:
    # call the query api if no file_ids given
    ids = argToList(args.get("file_ids")) or get_ODS_malicious_files_ids(args)

    if not ids:
        return CommandResults(readable_output="No malicious files match the arguments/filter.")

    response = ODS_get_malicious_files_by_id_request(ids)
    resources = response.get("resources", [])
    human_readable = ODS_get_malicious_files_resources_to_human_readable(resources)

    command_results = CommandResults(
        raw_response=response,
        outputs_prefix="CrowdStrike.ODSMaliciousFile",
        outputs_key_field="id",
        outputs=resources,
        readable_output=human_readable,
    )

    return command_results


def make_create_scan_request_body(args: dict, is_scheduled: bool) -> dict:
    result = {
        "host_groups": argToList(args.get("host_groups")),
        "file_paths": argToList(args.get("file_paths")),
        "scan_exclusions": argToList(args.get("scan_exclusions")),
        "scan_inclusions": argToList(args.get("scan_inclusions")),
        "initiated_from": args.get("initiated_from"),
        "cpu_priority": CPU_UTILITY_STR_TO_INT_KEY_MAP.get(args.get("cpu_priority")),  # type: ignore[arg-type]
        "description": args.get("description"),
        "quarantine": argToBoolean(args.get("quarantine")) if args.get("quarantine") is not None else None,
        "pause_duration": arg_to_number(args.get("pause_duration")),
        "sensor_ml_level_detection": arg_to_number(args.get("sensor_ml_level_detection")),
        "sensor_ml_level_prevention": arg_to_number(args.get("sensor_ml_level_prevention")),
        "cloud_ml_level_detection": arg_to_number(args.get("cloud_ml_level_detection")),
        "cloud_ml_level_prevention": arg_to_number(args.get("cloud_ml_level_prevention")),
        "cloud_pup_adware_level_detection": arg_to_number(args.get("cloud_pup_adware_level_detection")),
        "cloud_pup_adware_level_prevention": arg_to_number(args.get("cloud_pup_adware_level_prevention")),
        "max_duration": arg_to_number(args.get("max_duration")),
    }

    if is_scheduled:
        result["schedule"] = {
            "interval": SCHEDULE_INTERVAL_STR_TO_INT.get(args["schedule_interval"].lower()),
            "start_timestamp": (
                dateparser.parse(args["schedule_start_timestamp"]) or return_error("Invalid start_timestamp.")
            ).strftime("%Y-%m-%dT%H:%M"),
        }

    else:
        result["hosts"] = argToList(args.get("hosts"))

    return result


def ODS_create_scan_request(args: dict, is_scheduled: bool) -> dict:
    body = make_create_scan_request_body(args, is_scheduled)
    remove_nulls_from_dictionary(body)
    return http_request("POST", f'/ods/entities/{"scheduled-" * is_scheduled}scans/v1', json=body)


def ODS_verify_create_scan_command(args: dict) -> None:
    if not (args.get("hosts") or args.get("host_groups")):
        raise DemistoException("MUST set either hosts OR host_groups.")

    if not (args.get("file_paths") or args.get("scan_inclusions")):
        raise DemistoException("MUST set either file_paths OR scan_inclusions.")


def ods_create_scan(args: dict, is_scheduled: bool) -> dict:
    ODS_verify_create_scan_command(args)

    response = ODS_create_scan_request(args, is_scheduled)
    resource = dict_safe_get(response, ("resources", 0), return_type=dict, raise_return_type=False)

    if not (resource and resource.get("id")):
        raise DemistoException("Unexpected response from CrowdStrike Falcon")

    return resource


def cs_falcon_ods_create_scan_command(args: dict) -> CommandResults:
    resource = ods_create_scan(args, is_scheduled=False)
    scan_id = resource.get("id")

    polling = argToBoolean(args.get("polling", True))

    if not polling:
        human_readable = f"Successfully created scan with ID: {scan_id}"
        return CommandResults(
            raw_response=resource,
            outputs_prefix="CrowdStrike.ODSScan",
            outputs_key_field="id",
            outputs=resource,
            readable_output=human_readable,
        )

    query_scan_args = {
        "ids": scan_id,
        "wait_for_result": True,
        "interval_in_seconds": args.get("interval_in_seconds"),
        "timeout_in_seconds": args.get("timeout_in_seconds"),
    }

    return cs_falcon_ODS_query_scans_command(query_scan_args)


def cs_falcon_ods_create_scheduled_scan_command(args: dict) -> CommandResults:
    resource = ods_create_scan(args, is_scheduled=True)

    human_readable = f'Successfully created scheduled scan with ID: {resource.get("id")}'

    command_results = CommandResults(
        raw_response=resource,
        outputs_prefix="CrowdStrike.ODSScheduledScan",
        outputs_key_field="id",
        outputs=resource,
        readable_output=human_readable,
    )

    return command_results


def ODS_delete_scheduled_scans_request(ids: list[str], scan_filter: str | None = None) -> dict:
    ids_params = [f"ids={scan_id}" for scan_id in ids]
    filter_param = [f'filter={scan_filter.replace("+", "%2B")}'] if scan_filter else []
    url_params = "&".join(ids_params + filter_param)
    return http_request("DELETE", f"/ods/entities/scheduled-scans/v1?{url_params}", status_code=500)


def cs_falcon_ods_delete_scheduled_scan_command(args: dict) -> CommandResults:
    ids, scan_filter = argToList(args.get("ids")), args.get("filter")
    response = ODS_delete_scheduled_scans_request(ids, scan_filter)

    if dict_safe_get(response, ["errors", 0, "code"]) == 500:
        raise DemistoException(
            'CS Falcon returned an error.\n'
            'Code: 500\n'
            f'Message: {dict_safe_get(response, ["errors", 0, "message"])}\n'
            'Perhaps there are no scans to delete?'
        )

    human_readable = tableToMarkdown("Deleted Scans:", response.get("resources", []), headers=["Scan ID"])

    command_results = CommandResults(
        raw_response=response,
        readable_output=human_readable,
    )

    return command_results


def list_identity_entities_command(args: dict) -> CommandResults:
    """List identity entities
    Args:
        args: The demisto.args() dict object.
    Returns:
        The command result object.
    """
    client = create_gql_client()
    args_keys_ls = ["sort_key", "sort_order", "max_risk_score_severity", "min_risk_score_severity"]
    ls_args_keys_ls = ["type", "entity_id", "primary_display_name", "secondary_display_name", "email"]
    variables = {}
    for key in args_keys_ls:
        if key in args:
            variables[key] = args.get(key)
    for key in ls_args_keys_ls:
        if key in args:
            variables[key] = args.get(key, "").split(",")
    if "enabled" in args:
        variables["enabled"] = argToBoolean(args.get("enabled"))
    idp_query = gql("""
    query ($sort_key: EntitySortKey, $type: [EntityType!], $sort_order: SortOrder, $entity_id: [UUID!],
           $primary_display_name: [String!], $secondary_display_name: [String!], $max_risk_score_severity: ScoreSeverity,
           $min_risk_score_severity: ScoreSeverity, $enabled: Boolean, $email: [String!], $first: Int, $after: Cursor) {
        entities(types: $type, sortKey: $sort_key, sortOrder: $sort_order, entityIds: $entity_id, enabled: $enabled,
                 primaryDisplayNames: $primary_display_name, secondaryDisplayNames: $secondary_display_name,
                 maxRiskScoreSeverity: $max_risk_score_severity, minRiskScoreSeverity: $min_risk_score_severity,
                 emailAddresses: $email, first: $first, after: $after) {
            pageInfo{
                hasNextPage
                endCursor
            }
            nodes{
                primaryDisplayName
                secondaryDisplayName
                isHuman:hasRole(type: HumanUserAccountRole)
                isProgrammatic:hasRole(type: ProgrammaticUserAccountRole)
                ...
                on
                UserEntity{
                    emailAddresses
                }
                riskScore
                riskScoreSeverity
                riskFactors{
                    type
                    severity
                }
            }
        }
    }
""")
    identity_entities_ls = []
    next_token = args.get("next_token", "")
    limit = arg_to_number(args.get("limit", "50")) or 50
    page = arg_to_number(args.get("page", "0"))
    page_size = arg_to_number(args.get("page_size", "50"))
    res_ls = []
    has_next_page = True
    if page:
        variables["first"] = page_size
        while has_next_page and page:
            if next_token:
                variables["after"] = next_token
            res = client.execute(idp_query, variable_values=variables)
            res_ls.append(res)
            page -= 1
            pageInfo = res.get("entities", {}).get("pageInfo", {})
            has_next_page = pageInfo.get("hasNextPage", False)
            if page == 0:
                identity_entities_ls.extend(res.get("entities", {}).get("nodes", []))
            if has_next_page:
                next_token = pageInfo.get("endCursor", "")
    else:
        while has_next_page and limit > 0:
            variables["first"] = min(1000, limit)
            if next_token:
                variables["after"] = next_token
            res = client.execute(idp_query, variable_values=variables)
            res_ls.append(res)
            pageInfo = res.get("entities", {}).get("pageInfo", {})
            has_next_page = pageInfo.get("hasNextPage", False)
            identity_entities_ls.extend(res.get("entities", {}).get("nodes", []))
            if has_next_page:
                next_token = pageInfo.get("endCursor", "")
            limit -= 1000
    headers = [
        "primaryDisplayName",
        "secondaryDisplayName",
        "isHuman",
        "isProgrammatic",
        "isAdmin",
        "emailAddresses",
        "riskScore",
        "riskScoreSeverity",
        "riskFactors",
    ]

    return CommandResults(
        outputs_prefix="CrowdStrike.IDPEntity",
        outputs=createContext(response_to_context(identity_entities_ls), removeNull=True),
        readable_output=tableToMarkdown(
            "Identity entities", identity_entities_ls, headers=headers, removeNull=True, headerTransform=pascalToSpace
        ),
        raw_response=res_ls,
    )


def create_gql_client(url_suffix="identity-protection/combined/graphql/v1"):
    """
        Creates a gql client to handle the gql requests.
    Args:
        url_suffix: The url suffix for the request.
    Returns:
        The created client.
    """
    url_suffix = url_suffix["url"][1:] if url_suffix.startswith("/") else url_suffix
    kwargs = {
        "url": f"{SERVER}/{url_suffix}",
        "verify": USE_SSL,
        "retries": 10,
        "headers": {"Authorization": f"Bearer {get_token()}", "Accept": "application/json", "Content-Type": "application/json"},
    }
    transport = RequestsHTTPTransport(**kwargs)  # type: ignore[arg-type]
    client = Client(
        transport=transport,
        fetch_schema_from_transport=False,
    )
    return client


def cspm_list_policy_details_request(policy_ids: list[str]) -> dict[str, Any]:
    """Do an API call to retrieve policy details.

    Args:
        policy_ids (list[str]): The policy ids.

    Returns:
        dict[str, Any]: The raw response of the API.
    """
    query_params = "&".join(f"ids={policy_id}" for policy_id in policy_ids)
    # Status codes of 500 and 400 are sometimes returned when the policy IDs given do not exist, therefore we want
    # to catch this case so we can return a proper message to the user
    # Status code of 207 is returned when the API returns data about the policy IDs that were found,
    # and an error for the policy IDs that were not found, in the same response
    return http_request(
        method="GET", url_suffix="/settings/entities/policy-details/v1", params=query_params, status_code=[500, 400, 207]
    )


def cs_falcon_cspm_list_policy_details_command(args: dict[str, Any]) -> CommandResults:
    """Command to list policy details.

    Args:
        args (dict[str, Any]): The arguments of the command

    Raises:
        DemistoException: If a status code of 500 is returned.

    Returns:
        CommandResults: The command results object.
    """
    policy_ids = argToList(args.get("policy_ids"))
    raw_response = cspm_list_policy_details_request(policy_ids=policy_ids)
    # The API returns errors in the form of a list, under the key 'errors'
    if errors := raw_response.get("errors", []):
        if errors[0].get("code", "") == 500:
            raise DemistoException(
                'CS Falcon CSPM returned an error.\n'
                'Code: 500\n'
                f'Message: {dict_safe_get(raw_response, ["errors", 0, "message"])}\n'
                'Perhaps the policy IDs are invalid?'
            )
        for error in errors:
            if error.get("code") == 400:
                return_warning(
                    f'CS Falcon CSPM returned an error.\nCode:  {error.get("code")}\nMessage: {error.get("message")}\n'
                )

    if resources := raw_response.get("resources", []):
        human_readable = tableToMarkdown(
            "CSPM Policy Details:",
            resources,
            headers=[
                "ID",
                "description",
                "policy_statement",
                "policy_remediation",
                "cloud_service_subtype",
                "cloud_platform_type",
                "cloud_service_type",
                "default_severity",
                "policy_type",
                "tactic",
                "technique",
            ],
            headerTransform=string_to_table_header,
        )
        return CommandResults(
            readable_output=human_readable,
            outputs=resources,
            outputs_key_field="ID",
            outputs_prefix="CrowdStrike.CSPMPolicy",
            raw_response=raw_response,
        )
    return CommandResults(readable_output="No policy details were found for the given policy IDs.")


def cspm_list_service_policy_settings_request(policy_id: str, cloud_platform: str, service: str) -> dict[str, Any]:
    """Do an API call to retrieve the policy settings.

    Args:
        policy_id (str): The policy ID.
        cloud_platform (str): The cloud platform to filter by.
        service (str): The service type to filter by.

    Returns:
        dict[str, Any]: The raw response of the API.
    """
    query_params: dict[str, Any] = assign_params(service=service)
    if policy_id:
        query_params["policy-id"] = policy_id
    if cloud_platform:
        query_params["cloud-platform"] = cloud_platform
    return http_request(method="GET", url_suffix="/settings/entities/policy/v1", params=query_params, status_code=[207])


def cs_falcon_cspm_list_service_policy_settings_command(args: dict[str, Any]) -> CommandResults:
    """Command to list service policy settings.

    Args:
        args (dict[str, Any]): The arguments of the command.

    Returns:
        CommandResults: The command results object.
    """
    policy_id = args.get("policy_id", "")
    cloud_platform = args.get("cloud_platform", "")
    service = args.get("service", "")
    limit = arg_to_number(args.get("limit")) or 50

    raw_response = cspm_list_service_policy_settings_request(policy_id=policy_id, cloud_platform=cloud_platform, service=service)
    if resources := raw_response.get("resources", []):
        # The API does not support pagination, therefore we have to do it manually
        paginated_resources = resources[:limit]
        human_readable = tableToMarkdown(
            "CSPM Policy Settings:",
            paginated_resources,
            headers=[
                "policy_id",
                "is_remediable",
                "remediation_summary",
                "name",
                "policy_type",
                "cloud_service_subtype",
                "cloud_service",
                "default_severity",
            ],
            headerTransform=string_to_table_header,
        )
        return CommandResults(
            readable_output=human_readable,
            outputs=paginated_resources,
            outputs_key_field="policy_id",
            outputs_prefix="CrowdStrike.CSPMPolicySetting",
            raw_response=raw_response,
        )
    return CommandResults(readable_output="No policy settings were found for the given arguments.")


def cspm_update_policy_settings_request(
    account_id: str, enabled: bool, policy_id: int, regions: list[str], severity: str, tag_excluded: bool | None
) -> dict[str, Any]:
    """Do an API call to update the policy settings.

    Args:
        account_id (str): The account ID.
        enabled (bool): Whether to enable the policy or not.
        policy_id (int): The policy ID.
        regions (list[str]): The regions of the policy.
        severity (str): The severity of the policy.
        tag_excluded (bool | None): Whether to exclude tag or not.

    Returns:
        dict[str, Any]: The raw response of the API.
    """
    # https://assets.falcon.crowdstrike.com/support/api/swagger.html#/cspm-registration/UpdateCSPMPolicySettings
    # You have to be logged into https://falcon.crowdstrike.com/
    resources_body: dict[str, Any] = {
        "resources": [
            assign_params(
                account_id=account_id,
                enabled=enabled,
                policy_id=policy_id,
                regions=regions,
                severity=severity,
                tag_excluded=tag_excluded,
            )
        ]
    }
    return http_request(method="PATCH", url_suffix="/settings/entities/policy/v1", json=resources_body, status_code=500)


def cs_falcon_cspm_update_policy_settings_command(args: dict[str, Any]) -> CommandResults:
    """Command to update policy settings.

    Args:
        args (dict[str, Any]): The arguments of the command.

    Raises:
        DemistoException: If the policy ID is not an integer.
        DemistoException: If a status code 500 is returned.

    Returns:
        CommandResults: The command results object.
    """
    account_id = args.get("account_id", "")
    enabled = argToBoolean(args.get("enabled", "true"))
    policy_id = arg_to_number(args.get("policy_id"))
    if policy_id is None:
        raise DemistoException("policy_id must be an integer")
    regions = argToList(args.get("regions", []))
    severity = args.get("severity", "")
    tag_excluded = args.get("tag_excluded")
    tag_excluded = argToBoolean(tag_excluded) if tag_excluded else tag_excluded
    raw_response = cspm_update_policy_settings_request(
        account_id=account_id, enabled=enabled, policy_id=policy_id, regions=regions, severity=severity, tag_excluded=tag_excluded
    )
    if (errors := raw_response.get("errors", [])) and errors[0].get("code", "") == 500:
        raise DemistoException(
            'CS Falcon CSPM returned an error.\n'
            'Code: 500\n'
            f'Message: {dict_safe_get(raw_response, ["errors", 0, "message"])}\n'
            'Perhaps the policy ID or account ID are invalid?'
        )
    return CommandResults(readable_output=f"Policy {policy_id} was updated successfully")


def resolve_detections_prepare_body_request(ids: list[str], action_params_values: dict[str, Any]) -> dict[str, Any]:
    """Create the body of the request to resolve detections.

    Args:
        ids (list[str]): The IDs of the detections.
        action_params_values (dict[str, Any]): A dictionary that holds key-value pairs corresponding
        to the action_parameters object of the API request.

    Returns:
        dict[str, Any]: The body of the request.
    """
    # Values need to be in the form {'name': name_of_key, 'value': value_of_key}, as can be seen here
    # https://assets.falcon.crowdstrike.com/support/api/swagger.html#/Alerts/PatchEntitiesAlertsV2

    # Implemented the same as:
    # https://github.com/CrowdStrike/falconpy/blob/main/src/falconpy/_payload/_alerts.py#L40
    action_params = []
    for key, value in action_params_values.items():
        if value:
            param = {"name": key, "value": value}
            action_params.append(param)
    ids_request_key = "composite_ids"
    return {"action_parameters": action_params, ids_request_key: ids}


def resolve_detections_request(ids: list[str], **kwargs) -> dict[str, Any]:
    """Do an API call to resolve detections.

    Args:
        ids (list[str]): The IDs of the detections.

    Returns:
        dict[str, Any]: The raw response of the API.
    """
    url_suffix = "/alerts/entities/alerts/v3"
    body_payload = resolve_detections_prepare_body_request(ids=ids, action_params_values=kwargs)
    demisto.debug(f"In resolve_detections: {url_suffix=}, {body_payload=} ")
    return http_request(method="PATCH", url_suffix=url_suffix, json=body_payload)


def cs_falcon_resolve_identity_detection(args: dict[str, Any]) -> CommandResults:
    """Command to resolve identity detections.

    Args:
        args (dict[str, Any]): The arguments of the command.

    Returns:
        CommandResults: The command results object.
    """
    return handle_resolve_detections(args, "IDP Detection(s) {} were successfully updated")


def cs_falcon_resolve_mobile_detection(args: dict[str, Any]) -> CommandResults:
    """Command to resolve mobile detections.

    Args:
        args (dict[str, Any]): The arguments of the command.

    Returns:
        CommandResults: The command results object.
    """
    return handle_resolve_detections(args, "Mobile Detection(s) {} were successfully updated")


def handle_resolve_detections(args: dict[str, Any], hr_template: str) -> CommandResults:
    """Handle the mobile & identity detections resolve commands.

    Args:
        args (dict[str, Any]): The arguments of the command.

    Returns:
        CommandResults: The command results object.
    """
    ids = argToList(args.get("ids", "")) or []
    update_status = args.get("update_status", "")
    assign_to_name = args.get("assign_to_name", "")
    assign_to_uuid = args.get("assign_to_uuid", "")
    assign_to_user_id = args.get("assign_to_user_id", "")

    # This argument is sent to the API in the form of a string, having the values 'true' or 'false'
    unassign = args.get("unassign", "")

    append_comment = args.get("append_comment", "")
    add_tag = args.get("add_tag", "")
    remove_tag = args.get("remove_tag", "")

    # This argument is sent to the API in the form of a string, having the values 'true' or 'false'
    show_in_ui = args.get("show_in_ui", "")
    # We pass the arguments in the form of **kwargs, since we also need the arguments' names for the API,
    # and it easier to achieve that using **kwargs

    if sum(map(bool, [assign_to_uuid, assign_to_name, assign_to_user_id])) > 1:
        raise ValueError("Only one of the arguments assign_to_uuid, assign_to_name, assign_to_user_id should be provided.")

    resolve_detections_request(
        ids=ids,
        update_status=update_status,
        assign_to_name=assign_to_name,
        assign_to_uuid=assign_to_uuid,
        assign_to_user_id=assign_to_user_id,
        unassign=unassign,
        append_comment=append_comment,
        add_tag=add_tag,
        remove_tag=remove_tag,
        show_in_ui=show_in_ui,
    )
    return CommandResults(readable_output=hr_template.format(", ".join(ids)))


def cs_falcon_list_users_command(args: dict[str, Any]) -> CommandResults:
    users_ids = argToList(args.get("id"))
    offset = arg_to_number(args.get("offset")) or 0
    limit = arg_to_number(args.get("limit")) or 50
    query_filter = args.get("filter")

    if not users_ids:
        users_api_response = get_users(offset=offset, limit=limit, query_filter=query_filter)
        users_ids = users_api_response.get("resources", [])

        if not users_ids:
            return CommandResults(readable_output="No matching results found.")

    users_data_api_response = get_users_data(user_ids=users_ids)
    users_data = users_data_api_response.get("resources", [])

    def table_headers_transformer(header: str) -> str:
        mapping = {
            "uuid": "UUID",
            "first_name": "First Name",
            "last_name": "Last Name",
            "uid": "E-Mail (UID)",
            "last_login_at": "Last Login",
        }

        return mapping.get(header, header)

    return CommandResults(
        outputs_prefix="CrowdStrike.Users",
        outputs_key_field="uuid",
        outputs=users_data,
        readable_output=tableToMarkdown(
            name="CrowdStrike Users",
            t=users_data,
            headers=["uuid", "first_name", "last_name", "uid", "last_login_at"],
            headerTransform=table_headers_transformer,
            sort_headers=False,
        ),
        raw_response=users_data_api_response,
    )


def get_ioarules_command(args: dict) -> CommandResults:
    rule_ids = argToList(args["rule_ids"])
    ioarules_response_data = get_ioarules(rule_ids)

    ioarules = ioarules_response_data.get("resources", [])

    return CommandResults(
        outputs_prefix="CrowdStrike.IOARules",
        outputs_key_field="instance_id",
        outputs=ioarules,
        readable_output=tableToMarkdown(
            name="CrowdStrike IOA Rules",
            t=ioarules,
            headers=["instance_id", "description", "enabled", "name", "pattern_id"],
            headerTransform=string_to_table_header,
            removeNull=True,
            sort_headers=False,
        ),
        raw_response=ioarules_response_data,
    )


def list_workflow_definitions_command(args: dict[str, Any]) -> CommandResults:
    """
    Lists workflow definitions from CrowdStrike Falcon.
    Builds an FQL filter from convenience arguments and calls the API.
    """
    if raw_filter := args.get("filter"):
        # If explicit filter is provided, use it exclusively (ignore convenience args)
        filter_query = raw_filter
    else:
        # Build FQL filter from convenience arguments
        filter_parts: list[str] = []
        if definition_id := args.get("definition_id"):
            filter_parts.append(f"id:'{definition_id}'")
        if activity_id := args.get("activity_id"):
            filter_parts.append(f"activity_id:'{activity_id}'")
        if name := args.get("name"):
            filter_parts.append(f"name:~'{name}'")
        if description := args.get("description"):
            filter_parts.append(f"description:~'{description}'")
        filter_query = "+".join(filter_parts)
    demisto.debug(f"[Workflow] list_workflow_definitions_command: built {filter_query=}")
    offset = args.get("offset", "0")
    limit = arg_to_number(args.get("limit", 50)) or 50
    sort = args.get("sort", "")

    response = list_workflow_definitions(filter_query=filter_query, offset=offset, limit=limit, sort=sort)
    definitions = response.get("resources", [])
    demisto.debug(f"[Workflow] list_workflow_definitions_command: found {len(definitions)} definitions")

    # Build human-readable table
    hr_data = []
    for definition in definitions:
        trigger = definition.get("trigger", {})
        hr_data.append(
            {
                "Definition ID": definition.get("id"),
                "Name": definition.get("name"),
                "Description": definition.get("description"),
                "Trigger Event": trigger.get("event"),
                "Trigger Name": trigger.get("name"),
                "Trigger Schedule": trigger.get("schedule"),
                "Trigger Type": trigger.get("type"),
            }
        )

    readable_output = tableToMarkdown(
        name="Workflow Definitions",
        t=hr_data,
        headers=["Definition ID", "Name", "Description", "Trigger Event", "Trigger Name", "Trigger Schedule", "Trigger Type"],
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix="CrowdStrike.WorkflowDefinition",
        outputs_key_field="id",
        outputs=definitions,
        readable_output=readable_output,
        raw_response=response,
    )


def workflow_execute_command(args: dict[str, Any]) -> CommandResults:
    """
    Executes an on-demand workflow in CrowdStrike Falcon.
    Either definition_id or name must be provided.
    """
    definition_id = argToList(args.get("definition_id"))
    name = args.get("name")

    if not definition_id and not name:
        raise DemistoException("Either 'definition_id' or 'name' must be provided.")

    execution_cid = argToList(args.get("execution_cid"))
    key = args.get("key")
    source_event_url = args.get("source_event_url")
    # body is mendatory in the http request - if not provided, we set to empty dict
    body = args.get("body", "{}")

    response = execute_workflow(
        definition_id=definition_id or None,
        name=name,
        execution_cid=execution_cid or None,
        key=key,
        source_event_url=source_event_url,
        body=body,
    )

    resources = response.get("resources", [])
    demisto.debug(f"[Workflow] workflow_execute_command: got {len(resources)} resources")

    # Build human-readable output
    # The API returns resources as a list of execution ID strings, not dicts.
    hr_data = [{"Execution ID": resource} for resource in resources]

    readable_output = tableToMarkdown(
        name="Workflow Execution",
        t=hr_data,
        headers=["Execution ID"],
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix="CrowdStrike.Workflow",
        outputs=resources,
        readable_output=readable_output,
        raw_response=response,
    )


def list_workflow_executions_command(args: dict[str, Any]) -> CommandResults:
    """
    Lists workflow executions from CrowdStrike Falcon.
    Builds an FQL filter from convenience arguments and calls the API.
    """
    if raw_filter := args.get("filter"):
        # If explicit filter is provided, use it exclusively (ignore convenience args)
        filter_query = raw_filter
    else:
        # Build FQL filter from convenience arguments
        filter_parts: list[str] = []
        if definition_id := args.get("definition_id"):
            filter_parts.append(f"definition_id:'{definition_id}'")
        if definition_name := args.get("definition_name"):
            filter_parts.append(f"definition_name:~'{definition_name}'")
        if execution_id := args.get("execution_id"):
            filter_parts.append(f"id:'{execution_id}'")
        filter_query = "+".join(filter_parts)
    demisto.debug(f"[Workflow] list_workflow_executions_command: built {filter_query=}")
    offset = args.get("offset", "0")
    limit = arg_to_number(args.get("limit", 50)) or 50
    sort = args.get("sort", "")

    response = list_workflow_executions(filter_query=filter_query, offset=offset, limit=limit, sort=sort)
    executions = response.get("resources", [])
    demisto.debug(f"[Workflow] list_workflow_executions_command: found {len(executions)} executions")

    # Build human-readable table from activities
    hr_data = []
    for execution in executions:
        execution_id_val = execution.get("execution_id", execution.get("id"))
        activities = execution.get("activities", [])
        if activities:
            for activity in activities:
                hr_data.append(
                    {
                        "Execution ID": execution_id_val,
                        "Activity Node ID": activity.get("node_id"),
                        "Activity Start Timestamp": activity.get("start_timestamp"),
                        "Activity End Timestamp": activity.get("end_timestamp"),
                        "Activity Status": activity.get("status"),
                        "Activity Name": activity.get("name"),
                        "Activity Type": activity.get("type"),
                    }
                )
        else:
            hr_data.append({"Execution ID": execution_id_val})

    readable_output = tableToMarkdown(
        name="Workflow Executions",
        t=hr_data,
        headers=[
            "Execution ID",
            "Activity Node ID",
            "Activity Start Timestamp",
            "Activity End Timestamp",
            "Activity Status",
            "Activity Name",
            "Activity Type",
        ],
        removeNull=True,
    )

    return CommandResults(
        outputs_prefix="CrowdStrike.Workflows.Execution",
        outputs_key_field="execution_id",
        outputs=executions,
        readable_output=readable_output,
        raw_response=response,
    )


def list_workflow_execution_results_command(args: dict[str, Any]) -> CommandResults:
    """
    Gets detailed results for specific workflow executions.
    """
    ids = argToList(args.get("ids"))

    response = get_workflow_execution_results(ids=ids)
    results = response.get("resources", [])
    demisto.debug(f"[Workflow] list_workflow_execution_results_command: got {len(results)} results")

    # Build human-readable table from nested activities
    hr_data = []
    for result in results:
        execution_id = result.get("execution_id")
        activities = result.get("activities", [])
        for activity in activities:
            hr_data.append(
                {
                    "Execution ID": execution_id,
                    "Activity Node ID": activity.get("node_id"),
                    "Activity Start Timestamp": activity.get("start_timestamp"),
                    "Activity End Timestamp": activity.get("end_timestamp"),
                    "Activity Status": activity.get("status"),
                    "Activity ID": activity.get("id"),
                    "Activity Name": activity.get("name"),
                    "Activity Type": activity.get("type"),
                }
            )

    readable_output = tableToMarkdown(
        name="Workflow Execution Results",
        t=hr_data,
        headers=[
            "Execution ID",
            "Activity Node ID",
            "Activity Start Timestamp",
            "Activity End Timestamp",
            "Activity Status",
            "Activity ID",
            "Activity Name",
            "Activity Type",
        ],
        removeNull=True,
    )

    # Append errors if any (e.g., some IDs not found)
    errors = response.get("errors", [])
    if errors:
        error_lines = ["\n**Errors:**"]
        for error in errors:
            error_code = error.get("code", "Unknown")
            error_message = error.get("message", "Unknown error")
            error_lines.append(f"- Code: {error_code}, Message: {error_message}")
        readable_output += "\n".join(error_lines)

    return CommandResults(
        outputs_prefix="CrowdStrike.Workflows.ExecutionResult",
        outputs_key_field="execution_id",
        outputs=results,
        readable_output=readable_output,
        raw_response=response,
    )


def workflow_execution_action_command(args: dict[str, Any]) -> CommandResults:
    """
    Performs an action (cancel or resume) on one or more workflow executions.
    Handles partial success where some IDs are affected and others return errors.
    """
    ids = argToList(args.get("ids"))
    action_name = args.get("action_name", "")

    if action_name not in ("cancel", "resume"):
        raise DemistoException(f"Invalid action_name '{action_name}'. Must be 'cancel' or 'resume'.")

    response = perform_workflow_execution_action(ids=ids, action_name=action_name)

    action_past_tense = "cancelled" if action_name == "cancel" else "resumed"

    # Extract resources_affected from meta.writes
    resources_affected = response.get("meta", {}).get("writes", {}).get("resources_affected", 0)

    # Extract errors if any
    errors = response.get("errors", [])

    # Calculate succeeded IDs
    error_ids = {error.get("id") for error in errors if error.get("id")}
    succeeded_ids = [id_ for id_ in ids if id_ not in error_ids]

    # Build readable output
    hr_parts: list[str] = []
    if succeeded_ids:
        hr_parts.append(f"{len(succeeded_ids)} workflow execution(s) {action_past_tense}: {', '.join(succeeded_ids)}")
    else:
        hr_parts.append(f"0 workflow execution(s) {action_past_tense}.")

    if errors:
        hr_parts.append("\n**Errors:**")
        for error in errors:
            error_id = error.get("id", "Unknown")
            error_code = error.get("code", "Unknown")
            error_message = error.get("message", "Unknown error")
            hr_parts.append(f"- ID: {error_id} — Code: {error_code}, Message: {error_message}")

    readable_output = "\n".join(hr_parts)

    demisto.debug(
        f"[Workflow] workflow_execution_action_command: {action_name} completed, "
        f"{resources_affected} affected, {len(errors)} errors for {len(ids)} ids"
    )

    return CommandResults(
        readable_output=readable_output,
        raw_response=response,
    )


def main():  # pragma: no cover
    command = demisto.command()
    args = demisto.args()
    demisto.debug(f"Command being called is {command}")

    try:
        if command == "test-module":
            result = module_test()
            return_results(result)
        elif command == "fetch-incidents":
            last_run, incidents = fetch_items(command=command)
            demisto.incidents(incidents)
        elif command == "fetch-events":
            last_run, events = fetch_items(command=command)
            send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT)
            demisto.setLastRun(last_run)
        # Mirroring commands
        elif command == "get-remote-data":
            disable_for_xsiam()
            return_results(get_remote_data_command(args))
        elif command == "get-modified-remote-data":
            disable_for_xsiam()
            return_results(get_modified_remote_data_command(args))
        elif command == "update-remote-system":
            disable_for_xsiam()
            return_results(update_remote_system_command(args))
        elif command == "get-mapping-fields":
            disable_for_xsiam()
            return_results(get_mapping_fields_command())
        # ------- End Mirroring commands ----------

        elif command in ("cs-device-ran-on", "cs-falcon-device-ran-on"):
            return_results(get_indicator_device_id())
        elif demisto.command() == "cs-falcon-search-device":
            return_results(search_device_command())
        elif command == "cs-falcon-get-behavior":
            demisto.results(get_behavior_command())
        elif command == "cs-falcon-search-detection":
            return_results(search_detections_command())
        elif command == "cs-falcon-resolve-detection":
            demisto.results(resolve_detection_command())
        elif command == "cs-falcon-contain-host":
            demisto.results(contain_host_command())
        elif command == "cs-falcon-lift-host-containment":
            demisto.results(lift_host_containment_command())
        elif command == "cs-falcon-run-command":
            demisto.results(run_command())
        elif command == "cs-falcon-upload-script":
            demisto.results(upload_script_command())
        elif command == "cs-falcon-get-script":
            demisto.results(get_script_command())
        elif command == "cs-falcon-delete-script":
            demisto.results(delete_script_command())
        elif command == "cs-falcon-list-scripts":
            demisto.results(list_scripts_command())
        elif command == "cs-falcon-upload-file":
            demisto.results(upload_file_command())
        elif command == "cs-falcon-delete-file":
            demisto.results(delete_file_command())
        elif command == "cs-falcon-get-file":
            demisto.results(get_file_command())
        elif command == "cs-falcon-list-files":
            demisto.results(list_files_command())
        elif command == "cs-falcon-run-script":
            demisto.results(run_script_command())
        elif command == "cs-falcon-run-get-command":
            demisto.results(run_get_command())
        elif command == "cs-falcon-status-get-command":
            demisto.results(status_get_command(args))
        elif command == "cs-falcon-status-command":
            demisto.results(status_command())
        elif command == "cs-falcon-get-extracted-file":
            demisto.results(get_extracted_file_command(args))
        elif command == "cs-falcon-list-host-files":
            demisto.results(list_host_files_command())
        elif command == "cs-falcon-refresh-session":
            demisto.results(refresh_session_command())
        elif command == "cs-falcon-list-detection-summaries":
            return_results(list_detection_summaries_command())
        elif command == "cs-falcon-list-case-summaries":
            return_results(list_case_summaries_command())
        elif command == "cs-falcon-get-evidence-for-case":
            return_results(get_evidence_for_case_command(args))
        elif command == "cs-falcon-search-iocs":
            return_results(search_iocs_command(**args))
        elif command == "cs-falcon-get-ioc":
            return_results(get_ioc_command(ioc_type=args.get("type"), value=args.get("value")))
        elif command == "cs-falcon-upload-ioc":
            return_results(upload_ioc_command(**args))
        elif command == "cs-falcon-update-ioc":
            return_results(update_ioc_command(**args))
        elif command == "cs-falcon-delete-ioc":
            return_results(delete_ioc_command(ioc_type=args.get("type"), value=args.get("value")))
        elif command == "cs-falcon-search-custom-iocs":
            return_results(search_custom_iocs_command(**args))
        elif command == "cs-falcon-get-custom-ioc":
            return_results(get_custom_ioc_command(ioc_type=args.get("type"), value=args.get("value"), ioc_id=args.get("ioc_id")))
        elif command == "cs-falcon-upload-custom-ioc":
            return_results(upload_custom_ioc_command(**args))
        elif command == "cs-falcon-update-custom-ioc":
            return_results(update_custom_ioc_command(**args))
        elif command == "cs-falcon-delete-custom-ioc":
            return_results(delete_custom_ioc_command(ioc_id=args.get("ioc_id")))
        elif command == "cs-falcon-device-count-ioc":
            return_results(get_ioc_device_count_command(ioc_type=args.get("type"), value=args.get("value")))
        elif command == "cs-falcon-process-details":
            return_results(get_process_details_command(**args))
        elif command == "cs-falcon-processes-ran-on":
            return_results(
                get_proccesses_ran_on_command(ioc_type=args.get("type"), value=args.get("value"), device_id=args.get("device_id"))
            )
        elif command == "endpoint":
            return_results(get_endpoint_command())
        elif command == "cs-falcon-create-host-group":
            return_results(create_host_group_command(**args))
        elif command == "cs-falcon-update-host-group":
            return_results(update_host_group_command(**args))
        elif command == "cs-falcon-list-host-groups":
            return_results(list_host_groups_command(**args))
        elif command == "cs-falcon-delete-host-groups":
            return_results(delete_host_groups_command(host_group_ids=argToList(args.get("host_group_id"))))
        elif command == "cs-falcon-list-host-group-members":
            return_results(list_host_group_members_command(**args))
        elif command == "cs-falcon-add-host-group-members":
            return_results(
                add_host_group_members_command(host_group_id=args.get("host_group_id"), host_ids=argToList(args.get("host_ids")))
            )
        elif command == "cs-falcon-remove-host-group-members":
            return_results(
                remove_host_group_members_command(
                    host_group_id=args.get("host_group_id"), host_ids=argToList(args.get("host_ids"))
                )
            )
        elif command == "cs-falcon-batch-upload-custom-ioc":
            return_results(upload_batch_custom_ioc_command(**args))

        elif command == "cs-falcon-rtr-kill-process":
            return_results(rtr_kill_process_command(args))

        elif command == "cs-falcon-rtr-remove-file":
            return_results(rtr_remove_file_command(args))

        elif command == "cs-falcon-rtr-list-processes":
            host_id = args.get("host_id")
            offline = argToBoolean(args.get("queue_offline", False))
            timeout = arg_to_number(args.get("timeout"))
            return_results(
                rtr_general_command_on_hosts(
                    [host_id], "ps", "ps", execute_run_batch_write_cmd_with_timer, True, offline, timeout=timeout
                )
            )

        elif command == "cs-falcon-rtr-list-network-stats":
            host_id = args.get("host_id")

            offline = argToBoolean(args.get("queue_offline", False))
            timeout = arg_to_number(args.get("timeout"))
            return_results(
                rtr_general_command_on_hosts(
                    [host_id], "netstat", "netstat", execute_run_batch_write_cmd_with_timer, True, offline, timeout=timeout
                )
            )

        elif command == "cs-falcon-rtr-read-registry":
            return_results(rtr_read_registry_keys_command(args))

        elif command == "cs-falcon-rtr-list-scheduled-tasks":
            full_command = f"runscript -Raw=```schtasks /query /fo LIST /v```"  # noqa: F541
            host_ids = argToList(args.get("host_ids"))
            offline = argToBoolean(args.get("queue_offline", False))
            timeout = arg_to_number(args.get("timeout"))
            return_results(
                rtr_general_command_on_hosts(
                    host_ids, "runscript", full_command, execute_run_batch_admin_cmd_with_timer, offline, timeout=timeout
                )
            )
        elif command == "cs-falcon-rtr-retrieve-file":
            return_results(rtr_polling_retrieve_file_command(args))
        elif command == "cs-falcon-spotlight-search-vulnerability":
            return_results(cs_falcon_spotlight_search_vulnerability_command(args))
        elif command == "cs-falcon-spotlight-list-host-by-vulnerability":
            return_results(cs_falcon_spotlight_list_host_by_vulnerability_command(args))
        elif command == "cve":
            return_results(get_cve_command(args))
        elif command == "cs-falcon-create-ml-exclusion":
            return_results(create_ml_exclusion_command(args))
        elif command == "cs-falcon-update-ml-exclusion":
            return_results(update_ml_exclusion_command(args))
        elif command == "cs-falcon-delete-ml-exclusion":
            return_results(delete_ml_exclusion_command(args))
        elif command == "cs-falcon-search-ml-exclusion":
            return_results(search_ml_exclusion_command(args))
        elif command == "cs-falcon-create-ioa-exclusion":
            return_results(create_ioa_exclusion_command(args))
        elif command == "cs-falcon-update-ioa-exclusion":
            return_results(update_ioa_exclusion_command(args))
        elif command == "cs-falcon-delete-ioa-exclusion":
            return_results(delete_ioa_exclusion_command(args))
        elif command == "cs-falcon-search-ioa-exclusion":
            return_results(search_ioa_exclusion_command(args))
        elif command == "cs-falcon-list-quarantined-file":
            return_results(list_quarantined_file_command(args))
        elif command == "cs-falcon-apply-quarantine-file-action":
            return_results(apply_quarantine_file_action_command(args))
        elif command == "cs-falcon-ods-query-scan":
            return_results(cs_falcon_ODS_query_scans_command(args))
        elif command == "cs-falcon-ods-query-scheduled-scan":
            return_results(cs_falcon_ODS_query_scheduled_scan_command(args))
        elif command == "cs-falcon-ods-query-scan-host":
            return_results(cs_falcon_ods_query_scan_host_command(args))
        elif command == "cs-falcon-ods-query-malicious-files":
            return_results(cs_falcon_ODS_query_malicious_files_command(args))
        elif command == "cs-falcon-ods-create-scan":
            return_results(cs_falcon_ods_create_scan_command(args))
        elif command == "cs-falcon-ods-create-scheduled-scan":
            return_results(cs_falcon_ods_create_scheduled_scan_command(args))
        elif command == "cs-falcon-ods-delete-scheduled-scan":
            return_results(cs_falcon_ods_delete_scheduled_scan_command(args))
        elif command == "cs-falcon-list-identity-entities":
            return_results(list_identity_entities_command(args))
        # New commands
        elif command == "cs-falcon-cspm-list-policy-details":
            return_results(cs_falcon_cspm_list_policy_details_command(args=args))
        elif command == "cs-falcon-cspm-list-service-policy-settings":
            return_results(cs_falcon_cspm_list_service_policy_settings_command(args=args))
        elif command == "cs-falcon-cspm-update-policy_settings":
            return_results(cs_falcon_cspm_update_policy_settings_command(args=args))
        elif command == "cs-falcon-resolve-identity-detection":
            return_results(cs_falcon_resolve_identity_detection(args=args))
        elif command == "cs-falcon-resolve-mobile-detection":
            return_results(cs_falcon_resolve_mobile_detection(args=args))
        elif command == "cs-falcon-list-users":
            return_results(cs_falcon_list_users_command(args=args))
        elif command == "cs-falcon-get-ioarules":
            return_results(get_ioarules_command(args=args))
        elif command == "fetch-assets":
            fetch_assets_command()
        elif command == "cs-falcon-list-cnapp-alerts":
            return_results(list_cnapp_alerts_command(args=args))
        elif command == "cs-falcon-add-case-tag":
            return_results(add_case_tags_command(args))
        elif command == "cs-falcon-delete-case-tag":
            return_results(delete_case_tags_command(args))
        elif command == "cs-falcon-resolve-case":
            return_results(resolve_case_command(args))
        elif command == "cs-falcon-search-ngsiem-events":
            return_results(cs_falcon_search_ngsiem_events_command(args))
        elif command == "cs-falcon-list-workflow-definitions":
            return_results(list_workflow_definitions_command(args))
        elif command == "cs-falcon-workflow-execute":
            return_results(workflow_execute_command(args))
        elif command == "cs-falcon-list-workflow-executions":
            return_results(list_workflow_executions_command(args))
        elif command == "cs-falcon-list-workflow-execution-results":
            return_results(list_workflow_execution_results_command(args))
        elif command == "cs-falcon-workflow-execution-action":
            return_results(workflow_execution_action_command(args))
        else:
            raise NotImplementedError(f"CrowdStrike Falcon error: command {command} is not implemented")
    except Exception as e:
        return_error(f"Failed to execute {command!r} command.\nError:\n{e!s}")


if __name__ in ("__main__", "builtin", "builtins"):
    main()