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 json
import os
import asyncio
from unittest.mock import ANY
from urllib.parse import unquote

import demistomock as demisto
import pytest
from CommonServerPython import (
    CommandResults,
    DemistoException,
    IncidentStatus,
    ScheduledCommand,
    entryTypes,
    outputPaths,
    requests,
)
from freezegun import freeze_time
from pytest_mock import MockerFixture
from test_data import input_data

RETURN_ERROR_TARGET = "CrowdStrikeFalcon.return_error"
SERVER_URL = "https://4.4.4.4"


def create_empty_last_run(length: int) -> list[dict]:
    """
    Creates an empty last_run data structure for fetch operations.

    The last_run object is defined as a list of dictionaries.
    Each index in the list represents a different fetch type.

    The length parameter varies depending on the platform:
    - XSOAR length constant - TOTAL_FETCH_TYPE_XSOAR
    - XSIAM length constant - TOTAL_FETCH_TYPE_XSIAM
    Args:
        length (int): Number of fetch types to initialize

    Returns:
        list[dict]: A list of empty dictionaries representing the last_run state
    """
    return [{} for _ in range(length)]


def load_json(file: str):
    with open(file) as f:
        return json.load(f)


@pytest.fixture(autouse=True)
def get_access_token(requests_mock, mocker):
    mocker.patch.object(
        demisto,
        "params",
        return_value={
            "url": SERVER_URL,
            "proxy": True,
            "incidents_per_fetch": 2,
            "fetch_incidents_or_detections": ["Detections"],
            "fetch_time": "3 days",
        },
    )
    requests_mock.post(f"{SERVER_URL}/oauth2/token", json={"access_token": "token"}, status_code=200)


IOCS_JSON_LIST = [
    {
        "type": "ipv4",
        "value": "4.4.4.4",
        "source": "cortex xsoar",
        "action": "no_action",
        "severity": "informational",
        "description": "lala",
        "platforms": ["linux"],
        "tags": ["test"],
        "expiration": "2022-02-15T15:55:09Z",
        "applied_globally": True,
    },
    {
        "type": "ipv4",
        "value": "5.5.5.5",
        "source": "cortex xsoar",
        "action": "no_action",
        "severity": "informational",
        "description": "lala",
        "platforms": ["linux"],
        "tags": ["test"],
        "expiration": "2022-02-15T15:55:09Z",
        "applied_globally": True,
    },
]


def test_detection_to_incident_context():
    from CrowdStrikeFalcon import detection_to_incident_context

    res = detection_to_incident_context(input_data.response_idp_detection.copy(), "IDP Detection", "created_timestamp")
    assert res == input_data.context_idp_detection


def test_create_json_iocs_list():
    from CrowdStrikeFalcon import create_json_iocs_list

    res = create_json_iocs_list(
        ioc_type="ipv4",
        iocs_value=["4.4.4.4", "5.5.5.5"],
        action="no_action",
        platforms=["linux"],
        severity="informational",
        source="cortex xsoar",
        description="lala",
        expiration="2022-02-15T15:55:09Z",
        applied_globally=True,
        host_groups=[],
        tags=["test"],
    )
    assert res == IOCS_JSON_LIST


def test_timestamp_length_equalization():
    from CrowdStrikeFalcon import timestamp_length_equalization

    timestamp_in_millisecond = 1574585006000
    timestamp_in_seconds = 1574585015

    timestamp_in_millisecond_after, timestamp_in_seconds_after = timestamp_length_equalization(
        timestamp_in_millisecond, timestamp_in_seconds
    )

    assert timestamp_in_millisecond_after == 1574585006000
    assert timestamp_in_seconds_after == 1574585015000

    timestamp_in_seconds_after, timestamp_in_millisecond_after = timestamp_length_equalization(
        timestamp_in_seconds, timestamp_in_millisecond
    )

    assert timestamp_in_millisecond_after == 1574585006000
    assert timestamp_in_seconds_after == 1574585015000


def test_run_command_failure_sensor_offline(requests_mock, mocker):
    from CrowdStrikeFalcon import run_command

    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "host_ids": "284771ee197e422d5176d6634a62b934",
            "command_type": "ls",
            "full_command": "cd C:\\some_directory",
        },
    )
    error_object = {
        "meta": {"query_time": 0.505762223, "powered_by": "empower-api", "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
        "batch_id": "",
        "resources": {
            "284771ee197e422d5176d6634a62b934": {
                "session_id": "",
                "complete": False,
                "stdout": "",
                "stderr": "",
                "aid": "284771ee197e422d5176d6634a62b934",
                "errors": [{"code": 40407, "message": "Sensor appears to be offline"}],
                "query_time": 0,
            }
        },
        "errors": [{"code": 404, "message": "no successful hosts initialized on RTR"}],
    }
    requests_mock.post(
        f"{SERVER_URL}/real-time-response/combined/batch-init-session/v1", json={"batch_id": "batch_id"}, status_code=201
    )
    requests_mock.post(
        f"{SERVER_URL}/real-time-response/combined/batch-command/v1", json=error_object, status_code=404, reason="Not found"
    )
    with pytest.raises(DemistoException) as error_info:
        run_command()
    assert (
        str(error_info.value) == "Error in API call to CrowdStrike Falcon: code: 404 - "
        "reason: Not found\nHost ID 284771ee197e422d5176d6634a62b934 - "
        "Sensor appears to be offline"
    )


def test_run_command_read_scope(requests_mock, mocker):
    from CrowdStrikeFalcon import run_command

    response = {
        "meta": {"query_time": 1.178901572, "powered_by": "empower-api", "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
        "combined": {
            "resources": {
                "284771ee197e422d5176d6634a62b934": {
                    "session_id": "1113b475-2c28-4486-8617-d000b8f3bc8d",
                    "task_id": "e0149c46-4ba0-48c9-9e98-49b806a0033f",
                    "complete": True,
                    "stdout": "Directory listing for C:\\ -\n\n"
                    "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": "",
                    "base_command": "ls",
                    "aid": "284771ee197e422d5176d6634a62b934",
                    "errors": None,
                    "query_time": 1.1783866060000001,
                }
            }
        },
        "errors": [],
    }
    mocker.patch.object(
        demisto,
        "args",
        return_value={"host_ids": "284771ee197e422d5176d6634a62b934", "command_type": "ls", "full_command": "ls C:\\"},
    )
    requests_mock.post(
        f"{SERVER_URL}/real-time-response/combined/batch-init-session/v1", json={"batch_id": "batch_id"}, status_code=201
    )
    requests_mock.post(f"{SERVER_URL}/real-time-response/combined/batch-command/v1", json=response, status_code=201)
    results = run_command()
    expected_results = {
        "CrowdStrike": {
            "Command": [
                {
                    "HostID": "284771ee197e422d5176d6634a62b934",
                    "SessionID": "1113b475-2c28-4486-8617-d000b8f3bc8d",
                    "BatchID": "batch_id",
                    "Stdout": "Directory listing for C:\\ -\n\n"
                    "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:\\",
                }
            ]
        }
    }
    assert results["EntryContext"] == expected_results


def test_run_command_write_scope(requests_mock, mocker):
    from CrowdStrikeFalcon import run_command

    response = {
        "combined": {
            "resources": {
                "284771ee197e422d5176d6634a62b934": {
                    "aid": "284771ee197e422d5176d6634a62b934",
                    "base_command": "mkdir",
                    "complete": True,
                    "errors": None,
                    "query_time": 0.478191482,
                    "session_id": "ed0743e0-b156-4f98-8bbb-7a720a4192cf",
                    "stderr": "",
                    "stdout": "C:\\demistotest1",
                    "task_id": "e579eee6-ce7a-487c-8fef-439ebc9c3bc0",
                }
            }
        },
        "errors": [],
        "meta": {"powered_by": "empower-api", "query_time": 0.478696373, "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
    }
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "host_ids": "284771ee197e422d5176d6634a62b934",
            "command_type": "mkdir",
            "full_command": "mkdir C:\\demistotest1",
            "scope": "write",
        },
    )
    requests_mock.post(
        f"{SERVER_URL}/real-time-response/combined/batch-init-session/v1", json={"batch_id": "batch_id"}, status_code=201
    )
    requests_mock.post(
        f"{SERVER_URL}/real-time-response/combined/batch-active-responder-command/v1", json=response, status_code=201
    )
    results = run_command()
    expected_results = {
        "CrowdStrike": {
            "Command": [
                {
                    "HostID": "284771ee197e422d5176d6634a62b934",
                    "BatchID": "batch_id",
                    "SessionID": "ed0743e0-b156-4f98-8bbb-7a720a4192cf",
                    "Stdout": "C:\\demistotest1",
                    "Stderr": "",
                    "BaseCommand": "mkdir",
                    "Command": "mkdir C:\\demistotest1",
                }
            ]
        }
    }
    assert results["EntryContext"] == expected_results


def test_run_command_with_stderr(requests_mock, mocker):
    from CrowdStrikeFalcon import run_command

    response = {
        "combined": {
            "resources": {
                "284771ee197e422d5176d6634a62b934": {
                    "aid": "284771ee197e422d5176d6634a62b934",
                    "base_command": "runscript",
                    "complete": True,
                    "errors": None,
                    "query_time": 4.111527091,
                    "session_id": "4d41588e-8455-4f0f-a3ee-0515922a8d94",
                    "stderr": "The term 'somepowershellscript' is not recognized as the name of a cmdlet, function,"
                    " script file, or operable program. Check the spelling of the name, or if a path was "
                    "included, verify that the path is correct and try again.",
                    "stdout": "",
                    "task_id": "6d78e0ab-ec8a-4a5b-a948-1dca6381a9d1",
                }
            }
        },
        "errors": [],
        "meta": {"powered_by": "empower-api", "query_time": 4.112103195, "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
    }
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "host_ids": "284771ee197e422d5176d6634a62b934",
            "command_type": "runscript",
            "full_command": "runscript -CloudFile=InvalidPowerShellScript",
            "scope": "admin",
        },
    )
    requests_mock.post(
        f"{SERVER_URL}/real-time-response/combined/batch-init-session/v1", json={"batch_id": "batch_id"}, status_code=201
    )
    requests_mock.post(f"{SERVER_URL}/real-time-response/combined/batch-admin-command/v1", json=response, status_code=201)
    results = run_command()
    expected_results = {
        "CrowdStrike": {
            "Command": [
                {
                    "HostID": "284771ee197e422d5176d6634a62b934",
                    "BatchID": "batch_id",
                    "SessionID": "4d41588e-8455-4f0f-a3ee-0515922a8d94",
                    "Stdout": "",
                    "Stderr": "The term 'somepowershellscript' is not recognized as the name of a cmdlet, function,"
                    " script file, or operable program. Check the spelling of the name, or if a path was "
                    "included, verify that the path is correct and try again.",
                    "BaseCommand": "runscript",
                    "Command": "runscript -CloudFile=InvalidPowerShellScript",
                }
            ]
        }
    }
    assert results["EntryContext"] == expected_results


def test_run_script(requests_mock, mocker):
    from CrowdStrikeFalcon import run_script_command

    response = {
        "combined": {
            "resources": {
                "284771ee197e422d5176d6634a62b934": {
                    "aid": "284771ee197e422d5176d6634a62b934",
                    "base_command": "runscript",
                    "complete": True,
                    "errors": None,
                    "query_time": 4.111527091,
                    "session_id": "4d41588e-8455-4f0f-a3ee-0515922a8d94",
                    "stderr": "",
                    "stdout": "Hello, World!",
                    "task_id": "6d78e0ab-ec8a-4a5b-a948-1dca6381a9d1",
                }
            }
        },
        "errors": [],
        "meta": {"powered_by": "empower-api", "query_time": 4.112103195, "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
    }
    mocker.patch.object(
        demisto, "args", return_value={"host_id": "284771ee197e422d5176d6634a62b934", "raw": "Write-Output 'Hello, World!"}
    )
    requests_mock.post(
        f"{SERVER_URL}/real-time-response/combined/batch-init-session/v1", json={"batch_id": "batch_id"}, status_code=201
    )
    requests_mock.post(f"{SERVER_URL}/real-time-response/combined/batch-admin-command/v1", json=response, status_code=201)
    results = run_script_command()
    expected_results = {
        "CrowdStrike": {
            "Command": [
                {
                    "HostID": "284771ee197e422d5176d6634a62b934",
                    "SessionID": "4d41588e-8455-4f0f-a3ee-0515922a8d94",
                    "Stdout": "Hello, World!",
                    "Stderr": "",
                    "BaseCommand": "runscript",
                    "Command": "runscript -Raw=Write-Output 'Hello, World! -Timeout=30",
                }
            ]
        }
    }
    assert results["EntryContext"] == expected_results


def test_run_script_failure_bad_inputs(mocker):
    from CrowdStrikeFalcon import run_script_command

    # test failure given both script_name and raw arguments
    mocker.patch.object(demisto, "args", return_value={"script_name": "iloveny", "raw": "RAWR"})
    with pytest.raises(ValueError) as e:
        run_script_command()
    assert str(e.value) == "Only one of the arguments script_name or raw should be provided, not both."

    # test failure none of the arguments script_name and raw given
    mocker.patch.object(demisto, "args", return_value={})
    with pytest.raises(ValueError) as e:
        run_script_command()
    assert str(e.value) == "One of the arguments script_name or raw must be provided, none given."


def test_upload_script_given_content(requests_mock, mocker):
    from CrowdStrikeFalcon import upload_script_command

    response = {
        "meta": {
            "query_time": 0.782968846,
            "writes": {"resources_affected": 1},
            "powered_by": "empower",
            "trace_id": "07kk11c3-496g-42df-9157-834e499e279d",
        }
    }
    requests_mock.post(f"{SERVER_URL}/real-time-response/entities/scripts/v1", json=response, status_code=200)
    mocker.patch.object(demisto, "args", return_value={"name": "iloveny", "content": "Write-Output 'Hello, World!'"})
    results = upload_script_command()
    assert results["HumanReadable"] == "The script was uploaded successfully"
    assert results["Contents"] == response


def test_upload_script_given_file(requests_mock, mocker):
    from CrowdStrikeFalcon import upload_script_command

    response = {
        "meta": {
            "query_time": 0.782968846,
            "writes": {"resources_affected": 1},
            "powered_by": "empower",
            "trace_id": "07kk11c3-496g-42df-9157-834e499e279d",
        }
    }
    requests_mock.post(f"{SERVER_URL}/real-time-response/entities/scripts/v1", json=response, status_code=200)
    mocker.patch.object(demisto, "args", return_value={"name": "iloveny", "entry_id": "23@32"})
    mocker.patch.object(demisto, "getFilePath", return_value={"path": "test_data/HelloWorld.ps1", "name": "HelloWorld.ps1"})
    mocker.patch.object(demisto, "results")
    results = upload_script_command()
    assert results["HumanReadable"] == "The script was uploaded successfully"
    assert results["Contents"] == response


def test_upload_script_failure_already_exists(requests_mock, mocker):
    from CrowdStrikeFalcon import upload_script_command

    response = {
        "meta": {"query_time": 0.01543348, "powered_by": "empower", "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
        "errors": [{"code": 409, "message": "file with given name already exists"}],
    }
    requests_mock.post(f"{SERVER_URL}/real-time-response/entities/scripts/v1", json=response, status_code=409, reason="Conflict")
    mocker.patch.object(demisto, "args", return_value={"name": "iloveny", "content": "Write-Output 'Hello, World!'"})
    with pytest.raises(DemistoException) as error_info:
        upload_script_command()
    assert (
        str(error_info.value) == "Error in API call to CrowdStrike Falcon: code: 409 - "
        "reason: Conflict\nfile with given name already exists"
    )


def test_upload_script_failure_bad_inputs(requests_mock, mocker):
    from CrowdStrikeFalcon import upload_script_command

    # test failure given both content and entry_id arguments
    mocker.patch.object(
        demisto, "args", return_value={"name": "iloveny", "content": "Write-Output 'Hello, World!'", "entry_id": "23@32"}
    )
    with pytest.raises(ValueError) as e:
        upload_script_command()
    assert str(e.value) == "Only one of the arguments entry_id or content should be provided, not both."

    # test failure none of the arguments content and entry_id given
    mocker.patch.object(demisto, "args", return_value={"name": "iloveny"})
    with pytest.raises(ValueError) as e:
        upload_script_command()
    assert str(e.value) == "One of the arguments entry_id or content must be provided, none given."


def test_get_script_without_content(requests_mock, mocker):
    from CrowdStrikeFalcon import get_script_command

    script_id = "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a"
    response = {
        "meta": {"powered_by": "empower-api", "query_time": 0.082774607, "trace_id": "0f047130-1ea2-44cb-a178-e5a85b2ad55a"},
        "resources": [
            {
                "created_by": "spongobob@demisto.com",
                "created_by_uuid": "94cc8c66-5447-41ft-a1d8-2bd1faabfb9q",
                "created_timestamp": "2019-10-17T13:41:48.487520845Z",
                "description": "Demisto",
                "file_type": "script",
                "id": script_id,
                "modified_by": "spongobob@demisto.com",
                "modified_timestamp": "2019-10-17T13:41:48.487521161Z",
                "name": "Demisto",
                "permission_type": "private",
                "run_attempt_count": 0,
                "run_success_count": 0,
                "sha256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
                "size": 4444,
                "write_access": True,
            }
        ],
    }
    mocker.patch.object(demisto, "results")
    mocker.patch.object(demisto, "args", return_value={"script_id": script_id})
    requests_mock.get(f"{SERVER_URL}/real-time-response/entities/scripts/v2?ids={script_id}", json=response, status_code=200)
    results = get_script_command()
    expected_results = {
        "CrowdStrike.Script(val.ID === obj.ID)": {
            "CreatedBy": "spongobob@demisto.com",
            "CreatedTime": "2019-10-17T13:41:48.487520845Z",
            "Description": "Demisto",
            "ID": "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a",
            "ModifiedBy": "spongobob@demisto.com",
            "ModifiedTime": "2019-10-17T13:41:48.487521161Z",
            "Name": "Demisto",
            "Permission": "private",
            "SHA256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
            "RunAttemptCount": 0,
            "RunSuccessCount": 0,
            "WriteAccess": True,
        }
    }
    assert results["EntryContext"] == expected_results
    # verify there was no file returned as there no file content was returned
    assert demisto.results.call_count == 0


def test_get_script_with_content(requests_mock, mocker, request):
    from CrowdStrikeFalcon import get_script_command

    script_id = "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a"
    script_content = "function Demisto {}"
    response = {
        "meta": {"powered_by": "empower-api", "query_time": 0.082774607, "trace_id": "0f047130-1ea2-44cb-a178-e5a85b2ad55a"},
        "resources": [
            {
                "content": script_content,
                "created_by": "spongobob@demisto.com",
                "created_by_uuid": "94cc8c66-5447-41ft-a1d8-2bd1faabfb9q",
                "created_timestamp": "2019-10-17T13:41:48.487520845Z",
                "description": "Demisto",
                "file_type": "script",
                "id": script_id,
                "modified_by": "spongobob@demisto.com",
                "modified_timestamp": "2019-10-17T13:41:48.487521161Z",
                "name": "Demisto",
                "permission_type": "private",
                "run_attempt_count": 0,
                "run_success_count": 0,
                "sha256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
                "size": 4444,
                "write_access": True,
            }
        ],
    }
    file_name = "1_test_file_result"

    def cleanup():
        try:
            os.remove(file_name)
        except OSError:
            pass

    request.addfinalizer(cleanup)
    mocker.patch.object(demisto, "uniqueFile", return_value="test_file_result")
    mocker.patch.object(demisto, "investigation", return_value={"id": "1"})
    mocker.patch.object(demisto, "results")
    mocker.patch.object(demisto, "args", return_value={"script_id": script_id})
    requests_mock.get(f"{SERVER_URL}/real-time-response/entities/scripts/v2?ids={script_id}", json=response, status_code=200)
    results = get_script_command()
    expected_results = {
        "CrowdStrike.Script(val.ID === obj.ID)": {
            "CreatedBy": "spongobob@demisto.com",
            "CreatedTime": "2019-10-17T13:41:48.487520845Z",
            "Description": "Demisto",
            "ID": "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a",
            "ModifiedBy": "spongobob@demisto.com",
            "ModifiedTime": "2019-10-17T13:41:48.487521161Z",
            "Name": "Demisto",
            "Permission": "private",
            "SHA256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
            "RunAttemptCount": 0,
            "RunSuccessCount": 0,
            "WriteAccess": True,
        }
    }
    assert results["EntryContext"] == expected_results
    # verify there was file returned
    assert demisto.results.call_count == 1
    results = demisto.results.call_args[0]
    assert len(results) == 1
    assert results[0]["Type"] == entryTypes["file"]
    assert results[0]["File"] == "Demisto.ps1"
    with open(file_name, "rb") as f:
        assert f.read().decode() == script_content


def test_get_script_does_not_exist(requests_mock, mocker):
    from CrowdStrikeFalcon import get_script_command

    script_id = "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a"
    response = {
        "meta": {"powered_by": "empower-api", "query_time": 0.082774607, "trace_id": "0f047130-1ea2-44cb-a178-e5a85b2ad55a"},
        "resources": [],
    }
    mocker.patch.object(demisto, "args", return_value={"script_id": script_id})
    requests_mock.get(f"{SERVER_URL}/real-time-response/entities/scripts/v2?ids={script_id}", json=response, status_code=200)

    assert get_script_command() == "No script found."


def test_delete_script(requests_mock, mocker):
    from CrowdStrikeFalcon import delete_script_command

    script_id = "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a"
    response = {
        "meta": {
            "query_time": 0.535416674,
            "writes": {"resources_affected": 1},
            "powered_by": "empower",
            "trace_id": "b48fc444-8e80-48bf-akbf-281fb9471e5g",
        }
    }
    mocker.patch.object(demisto, "args", return_value={"script_id": script_id})
    requests_mock.delete(f"{SERVER_URL}/real-time-response/entities/scripts/v1?ids={script_id}", json=response, status_code=200)

    assert delete_script_command()["HumanReadable"] == f"Script {script_id} was deleted successfully"


def test_delete_script_failure_insufficient_permissions(requests_mock, mocker):
    from CrowdStrikeFalcon import delete_script_command

    script_id = "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a"
    response = {
        "meta": {
            "query_time": 0.001585675,
            "powered_by": "crowdstrike-api-gateway",
            "trace_id": "01fcdbc6-6319-42e4-8ab1-b3edca76aa2c",
        },
        "errors": [{"code": 403, "message": "access denied, authorization failed"}],
    }
    mocker.patch.object(demisto, "args", return_value={"script_id": script_id})
    requests_mock.delete(
        f"{SERVER_URL}/real-time-response/entities/scripts/v1?ids={script_id}", json=response, status_code=403, reason="Forbidden"
    )

    with pytest.raises(DemistoException) as error_info:
        delete_script_command()
    assert (
        str(error_info.value) == "Error in API call to CrowdStrike Falcon: code: 403 - "
        "reason: Forbidden\naccess denied, authorization failed"
    )


def test_delete_script_failure_not_found(requests_mock, mocker):
    from CrowdStrikeFalcon import delete_script_command

    script_id = "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a"
    response = {
        "meta": {"query_time": 0.001585675, "powered_by": "empower", "trace_id": "01fcdbc6-6319-42e4-8ab1-b3edca76aa2c"},
        "errors": [{"code": 404, "message": "Could not find file for deletion"}],
    }
    mocker.patch.object(demisto, "args", return_value={"script_id": script_id})
    requests_mock.delete(
        f"{SERVER_URL}/real-time-response/entities/scripts/v1?ids={script_id}", json=response, status_code=404, reason="Not Found"
    )
    with pytest.raises(DemistoException) as error_info:
        delete_script_command()
    assert (
        str(error_info.value) == "Error in API call to CrowdStrike Falcon: code: 404 - "
        "reason: Not Found\nCould not find file for deletion"
    )


def test_list_scripts(requests_mock):
    from CrowdStrikeFalcon import list_scripts_command

    response = {
        "meta": {"powered_by": "empower-api", "query_time": 0.031727879, "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
        "resources": [
            {
                "created_by": "spongobob@demisto.com",
                "created_by_uuid": "94cc8c66-5447-41ft-a1d8-2bd1faabfb9q",
                "created_timestamp": "2019-10-17T13:41:48.487520845Z",
                "description": "Demisto",
                "file_type": "script",
                "id": "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a",
                "modified_by": "spongobob@demisto.com",
                "modified_timestamp": "2019-10-17T13:41:48.487521161Z",
                "name": "Demisto",
                "permission_type": "private",
                "run_attempt_count": 0,
                "run_success_count": 0,
                "sha256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
                "size": 4444,
                "platform": ["windows"],
                "write_access": True,
            }
        ],
    }
    requests_mock.get(f"{SERVER_URL}/real-time-response/entities/scripts/v2", json=response)
    results = list_scripts_command()
    expected_results = {
        "CrowdStrike.Script(val.ID === obj.ID)": [
            {
                "CreatedBy": "spongobob@demisto.com",
                "CreatedTime": "2019-10-17T13:41:48.487520845Z",
                "Description": "Demisto",
                "ID": "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a",
                "ModifiedBy": "spongobob@demisto.com",
                "ModifiedTime": "2019-10-17T13:41:48.487521161Z",
                "Name": "Demisto",
                "Permission": "private",
                "SHA256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
                "RunAttemptCount": 0,
                "RunSuccessCount": 0,
                "Platform": ["windows"],
                "WriteAccess": True,
            }
        ]
    }
    assert results["EntryContext"] == expected_results


def test_upload_file(requests_mock, mocker):
    from CrowdStrikeFalcon import upload_file_command

    response = {
        "meta": {
            "query_time": 0.782968846,
            "writes": {"resources_affected": 1},
            "powered_by": "empower",
            "trace_id": "07kk11c3-496g-42df-9157-834e499e279d",
        }
    }
    requests_mock.post(f"{SERVER_URL}/real-time-response/entities/put-files/v1", json=response, status_code=200)
    mocker.patch.object(demisto, "args", return_value={"name": "iloveny", "entry_id": "23@32"})
    mocker.patch.object(demisto, "getFilePath", return_value={"path": "test_data/HelloWorld.ps1", "name": "HelloWorld.ps1"})
    results = upload_file_command()
    assert results["HumanReadable"] == "File was uploaded successfully"
    assert results["Contents"] == response


def test_upload_file_failure_already_exists(requests_mock, mocker):
    from CrowdStrikeFalcon import upload_file_command

    response = {
        "meta": {"query_time": 0.01543348, "powered_by": "empower", "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
        "errors": [{"code": 409, "message": "file with given name already exists"}],
    }
    requests_mock.post(
        f"{SERVER_URL}/real-time-response/entities/put-files/v1", json=response, status_code=409, reason="Conflict"
    )
    mocker.patch.object(demisto, "args", return_value={"name": "iloveny", "entry_id": "23@32"})
    mocker.patch.object(demisto, "getFilePath", return_value={"path": "test_data/HelloWorld.ps1", "name": "HelloWorld.ps1"})
    with pytest.raises(DemistoException) as error_info:
        upload_file_command()
    assert (
        str(error_info.value) == "Error in API call to CrowdStrike Falcon: code: 409 - "
        "reason: Conflict\nfile with given name already exists"
    )


def test_get_file_without_content(requests_mock, mocker):
    from CrowdStrikeFalcon import get_file_command

    file_id = "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a"
    response = {
        "meta": {"powered_by": "empower-api", "query_time": 0.082774607, "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
        "resources": [
            {
                "created_by": "spongobob@demisto.com",
                "created_by_uuid": "94cc8c66-5447-41ft-a1d8-2bd1faabfb9q",
                "created_timestamp": "2019-10-17T13:41:48.487520845Z",
                "description": "Demisto",
                "file_type": "script",
                "id": file_id,
                "modified_by": "spongobob@demisto.com",
                "modified_timestamp": "2019-10-17T13:41:48.487521161Z",
                "name": "Demisto",
                "permission_type": "private",
                "run_attempt_count": 0,
                "run_success_count": 0,
                "sha256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
                "size": 4444,
                "write_access": True,
            }
        ],
    }
    mocker.patch.object(demisto, "results")
    mocker.patch.object(demisto, "args", return_value={"file_id": file_id})
    requests_mock.get(f"{SERVER_URL}/real-time-response/entities/put-files/v2?ids={file_id}", json=response, status_code=200)
    results = get_file_command()
    expected_results = {
        "CrowdStrike.File(val.ID === obj.ID)": {
            "CreatedBy": "spongobob@demisto.com",
            "CreatedTime": "2019-10-17T13:41:48.487520845Z",
            "Description": "Demisto",
            "ID": "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a",
            "ModifiedBy": "spongobob@demisto.com",
            "ModifiedTime": "2019-10-17T13:41:48.487521161Z",
            "Name": "Demisto",
            "Permission": "private",
            "SHA256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
            "Type": "script",
        },
        outputPaths["file"]: {
            "Name": "Demisto",
            "Size": 4444,
            "Type": "script",
            "SHA256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
        },
    }
    assert results["EntryContext"] == expected_results
    # verify there was no file returned as there no file content was returned
    assert demisto.results.call_count == 0


def test_get_file_with_content(requests_mock, mocker, request):
    from CrowdStrikeFalcon import get_file_command

    file_id = "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a"
    file_content = "function Demisto {}"
    response = {
        "meta": {"powered_by": "empower-api", "query_time": 0.082774607, "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
        "resources": [
            {
                "content": file_content,
                "created_by": "spongobob@demisto.com",
                "created_by_uuid": "94cc8c66-5447-41ft-a1d8-2bd1faabfb9q",
                "created_timestamp": "2019-10-17T13:41:48.487520845Z",
                "description": "Demisto",
                "file_type": "script",
                "id": file_id,
                "modified_by": "spongobob@demisto.com",
                "modified_timestamp": "2019-10-17T13:41:48.487521161Z",
                "name": "Demisto",
                "permission_type": "private",
                "sha256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
                "size": 4444,
            }
        ],
    }
    file_name = "1_test_file_result"

    def cleanup():
        try:
            os.remove(file_name)
        except OSError:
            pass

    request.addfinalizer(cleanup)
    mocker.patch.object(demisto, "uniqueFile", return_value="test_file_result")
    mocker.patch.object(demisto, "investigation", return_value={"id": "1"})
    mocker.patch.object(demisto, "results")
    mocker.patch.object(demisto, "args", return_value={"file_id": file_id})
    requests_mock.get(f"{SERVER_URL}/real-time-response/entities/put-files/v2?ids={file_id}", json=response, status_code=200)
    results = get_file_command()
    expected_results = {
        "CrowdStrike.File(val.ID === obj.ID)": {
            "CreatedBy": "spongobob@demisto.com",
            "CreatedTime": "2019-10-17T13:41:48.487520845Z",
            "Description": "Demisto",
            "ID": "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a",
            "ModifiedBy": "spongobob@demisto.com",
            "ModifiedTime": "2019-10-17T13:41:48.487521161Z",
            "Name": "Demisto",
            "Permission": "private",
            "SHA256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
            "Type": "script",
        },
        outputPaths["file"]: {
            "Name": "Demisto",
            "Size": 4444,
            "Type": "script",
            "SHA256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
        },
    }
    assert results["EntryContext"] == expected_results
    # verify there was file returned
    assert demisto.results.call_count == 1
    results = demisto.results.call_args[0]
    assert len(results) == 1
    assert results[0]["Type"] == entryTypes["file"]
    assert results[0]["File"] == "Demisto"
    with open(file_name, "rb") as f:
        assert f.read().decode() == file_content


def test_get_file_does_not_exist(requests_mock, mocker):
    from CrowdStrikeFalcon import get_file_command

    file_id = "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a"
    response = {
        "meta": {"powered_by": "empower-api", "query_time": 0.082774607, "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
        "resources": [],
    }
    mocker.patch.object(demisto, "args", return_value={"file_id": file_id})
    requests_mock.get(f"{SERVER_URL}/real-time-response/entities/put-files/v2?ids={file_id}", json=response, status_code=200)

    assert get_file_command() == "No file found."


def test_delete_file(requests_mock, mocker):
    from CrowdStrikeFalcon import delete_file_command

    file_id = "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a"
    response = {
        "meta": {
            "query_time": 0.535416674,
            "writes": {"resources_affected": 1},
            "powered_by": "empower",
            "trace_id": "07kk11c3-496g-42df-9157-834e499e279d",
        }
    }
    mocker.patch.object(demisto, "args", return_value={"file_id": file_id})
    requests_mock.delete(f"{SERVER_URL}/real-time-response/entities/put-files/v1?ids={file_id}", json=response, status_code=200)

    assert delete_file_command()["HumanReadable"] == f"File {file_id} was deleted successfully"


def test_delete_file_failure_insufficient_permissions(requests_mock, mocker):
    from CrowdStrikeFalcon import delete_file_command

    file_id = "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a"
    response = {
        "meta": {
            "query_time": 0.001585675,
            "powered_by": "crowdstrike-api-gateway",
            "trace_id": "07kk11c3-496g-42df-9157-834e499e279d",
        },
        "errors": [{"code": 403, "message": "access denied, authorization failed"}],
    }
    mocker.patch.object(demisto, "args", return_value={"file_id": file_id})
    requests_mock.delete(
        f"{SERVER_URL}/real-time-response/entities/put-files/v1?ids={file_id}", json=response, status_code=403, reason="Forbidden"
    )
    with pytest.raises(DemistoException) as error_info:
        delete_file_command()
    assert (
        str(error_info.value) == "Error in API call to CrowdStrike Falcon: code: 403 - "
        "reason: Forbidden\naccess denied, authorization failed"
    )


def test_delete_file_failure_not_found(requests_mock, mocker):
    from CrowdStrikeFalcon import delete_file_command

    file_id = "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a"
    response = {
        "meta": {"query_time": 0.001585675, "powered_by": "empower", "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
        "errors": [{"code": 404, "message": "Could not find file for deletion"}],
    }
    mocker.patch.object(demisto, "args", return_value={"file_id": file_id})
    requests_mock.delete(
        f"{SERVER_URL}/real-time-response/entities/put-files/v1?ids={file_id}", json=response, status_code=404, reason="Not Found"
    )
    with pytest.raises(DemistoException) as error_info:
        delete_file_command()
    assert (
        str(error_info.value) == "Error in API call to CrowdStrike Falcon: code: 404 - "
        "reason: Not Found\nCould not find file for deletion"
    )


def test_list_files(requests_mock):
    from CrowdStrikeFalcon import list_files_command

    response = {
        "meta": {"powered_by": "empower-api", "query_time": 0.082774607, "trace_id": "07kk11c3-496g-42df-9157-834e499e279d"},
        "resources": [
            {
                "content": "function Demisto {}",
                "created_by": "spongobob@demisto.com",
                "created_by_uuid": "94cc8c66-5447-41ft-a1d8-2bd1faabfb9q",
                "created_timestamp": "2019-10-17T13:41:48.487520845Z",
                "description": "Demisto",
                "file_type": "script",
                "id": "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a",
                "modified_by": "spongobob@demisto.com",
                "modified_timestamp": "2019-10-17T13:41:48.487521161Z",
                "name": "Demisto",
                "permission_type": "private",
                "run_attempt_count": 0,
                "run_success_count": 0,
                "sha256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
                "size": 4444,
            }
        ],
    }
    requests_mock.get(f"{SERVER_URL}/real-time-response/entities/put-files/v2", json=response)
    results = list_files_command()
    expected_results = {
        "CrowdStrike.File(val.ID === obj.ID)": [
            {
                "CreatedBy": "spongobob@demisto.com",
                "CreatedTime": "2019-10-17T13:41:48.487520845Z",
                "Description": "Demisto",
                "ID": "le10098bf0e311e989190662caec3daa_94cc8c55556741faa1d82bd1faabfb4a",
                "ModifiedBy": "spongobob@demisto.com",
                "ModifiedTime": "2019-10-17T13:41:48.487521161Z",
                "Name": "Demisto",
                "Permission": "private",
                "SHA256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
                "Type": "script",
            }
        ],
        outputPaths["file"]: [
            {
                "Name": "Demisto",
                "Size": 4444,
                "Type": "script",
                "SHA256": "5a4440f2b9ce60b070e98c304370050446a2efa4b3850550a99e4d7b8f447fcc",
            }
        ],
    }
    assert results["EntryContext"] == expected_results


def test_run_get(requests_mock, mocker):
    from CrowdStrikeFalcon import run_get_command

    response = {
        "batch_get_cmd_req_id": "84ee4d50-f499-482e-bac6-b0e296149bbf",
        "combined": {
            "resources": {
                "edfd6a04ad134c4344f8fb119a3ad88e": {
                    "aid": "edfd6a04ad134c4344f8fb119a3ad88e",
                    "base_command": "get",
                    "complete": True,
                    "errors": [],
                    "query_time": 1.6280021580000001,
                    "session_id": "7f861cda-f19a-4df3-8599-e2a4f6761359",
                    "stderr": "",
                    "stdout": "C:\\Windows\\notepad.exe",
                    "task_id": "b5c8f140-280b-43fd-8501-9900f837510b",
                }
            }
        },
        "errors": [],
        "meta": {"powered_by": "empower-api", "query_time": 1.630543865, "trace_id": "8637f34a-7202-445a-818d-816715c5b368"},
    }
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "host_ids": "edfd6a04ad134c4344f8fb119a3ad88e",
            "file_path": "C:\\Windows\\notepad.exe",
            "raw": "Write-Output 'Hello, World!",
        },
    )
    requests_mock.post(
        f"{SERVER_URL}/real-time-response/combined/batch-init-session/v1", json={"batch_id": "batch_id"}, status_code=201
    )
    requests_mock.post(f"{SERVER_URL}/real-time-response/combined/batch-get-command/v1", json=response, status_code=201)
    results = run_get_command()
    expected_results = {
        "CrowdStrike.Command(val.TaskID === obj.TaskID)": [
            {
                "HostID": "edfd6a04ad134c4344f8fb119a3ad88e",
                "Stdout": "C:\\Windows\\notepad.exe",
                "Stderr": "",
                "BaseCommand": "get",
                "TaskID": "b5c8f140-280b-43fd-8501-9900f837510b",
                "GetRequestID": "84ee4d50-f499-482e-bac6-b0e296149bbf",
                "Complete": True,
                "FilePath": "C:\\Windows\\notepad.exe",
            }
        ]
    }
    assert results["EntryContext"] == expected_results


def test_status_get(requests_mock, mocker):
    from CrowdStrikeFalcon import status_get_command

    response = {
        "errors": [],
        "meta": {"powered_by": "empower-api", "query_time": 0.00252648, "trace_id": "7cd74ed7-4695-403a-a1f5-f7402b7b9409"},
        "resources": {
            "edfd6a04ad134c4344f8fb119a3ad88e": {
                "cloud_request_id": "b5c8f140-280b-43fd-8501-9900f837510b",
                "created_at": "2020-05-01T16:09:00Z",
                "deleted_at": None,
                "id": 185596,
                "name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
                "session_id": "7f861cda-f19a-4df3-8599-e2a4f6761359",
                "sha256": "f1d62648ef915d85cb4fc140359e925395d315c70f3566b63bb3e21151cb2ce3",
                "size": 0,
                "updated_at": "2020-05-01T16:09:00Z",
            }
        },
    }
    mocker.patch.object(
        demisto,
        "args",
        return_value={"request_ids": ["84ee4d50-f499-482e-bac6-b0e296149bbf"], "raw": "Write-Output 'Hello, World!"},
    )
    requests_mock.get(f"{SERVER_URL}/real-time-response/combined/batch-get-command/v1", json=response, status_code=201)
    results = status_get_command(demisto.args())
    expected_results = {
        "CrowdStrike.File(val.ID === obj.ID || val.TaskID === obj.TaskID)": [
            {
                "CreatedAt": "2020-05-01T16:09:00Z",
                "DeletedAt": None,
                "ID": 185596,
                "Name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
                "SHA256": "f1d62648ef915d85cb4fc140359e925395d315c70f3566b63bb3e21151cb2ce3",
                "Size": 0,
                "TaskID": "b5c8f140-280b-43fd-8501-9900f837510b",
                "UpdatedAt": "2020-05-01T16:09:00Z",
            }
        ],
        "File(val.MD5 \u0026\u0026 val.MD5 == obj.MD5 || val.SHA1 \u0026\u0026 val.SHA1 == obj.SHA1 || val.SHA256 "
        "\u0026\u0026 val.SHA256 == obj.SHA256 || val.SHA512 \u0026\u0026 val.SHA512 == obj.SHA512 || val.CRC32 "
        "\u0026\u0026 val.CRC32 == obj.CRC32 || val.CTPH \u0026\u0026 val.CTPH == obj.CTPH || val.SSDeep \u0026\u0026 "
        "val.SSDeep == obj.SSDeep)": [
            {
                "Name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
                "SHA256": "f1d62648ef915d85cb4fc140359e925395d315c70f3566b63bb3e21151cb2ce3",
                "Size": 0,
            }
        ],
    }
    assert results["EntryContext"] == expected_results


def test_status(requests_mock, mocker):
    from CrowdStrikeFalcon import status_command

    response = {
        "errors": [],
        "meta": {"powered_by": "empower-api", "query_time": 0.00635876, "trace_id": "083a0a94-87f2-4e66-8621-32eb75b4f205"},
        "resources": [
            {
                "base_command": "ls",
                "complete": True,
                "session_id": "ea68c338-84c9-4870-a3c9-b10e405622c1",
                "stderr": "",
                "stdout": "Directory listing for C:\\ ....",
                "task_id": "ae323961-5aa8-442e-8461-8d05c4541d7d",
            }
        ],
    }
    mocker.patch.object(
        demisto, "args", return_value={"request_id": "ae323961-5aa8-442e-8461-8d05c4541d7d", "raw": "Write-Output 'Hello, World!"}
    )
    requests_mock.get(f"{SERVER_URL}/real-time-response/entities/command/v1", json=response, status_code=201)
    results = status_command()
    expected_results = {
        "CrowdStrike.Command(val.TaskID === obj.TaskID)": [
            {
                "BaseCommand": "ls",
                "Complete": True,
                "NextSequenceID": 1,
                "SequenceID": 0,
                "Stderr": "",
                "Stdout": "Directory listing for C:\\ ....",
                "TaskID": "ae323961-5aa8-442e-8461-8d05c4541d7d",
            }
        ]
    }
    assert results["EntryContext"] == expected_results


def test_get_extracted_file(requests_mock, mocker):
    from CrowdStrikeFalcon import get_extracted_file_command

    response_content = b"file-data"

    session_id = "fdd6408f-6688-441b-8659-41bcad25441c"
    response_session = {
        "errors": [],
        "meta": {"powered_by": "empower-api", "query_time": 0.025573986, "trace_id": "291d3fda-9684-4ed7-ae88-bcc3940a2104"},
        "resources": [
            {
                "created_at": "2020-05-01T17:52:16.781771496Z",
                "existing_aid_sessions": 1,
                "scripts": [],
                "session_id": f"{session_id}",
            }
        ],
    }
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "host_id": "edfd6a04ad134c4344f8fb119a3ad88e",
            "sha256": "f1d62648ef915d85cb4fc140359e925395d315c70f3566b63bb3e21151cb2ce3",
            "raw": "Write-Output 'Hello, World!",
        },
    )
    requests_mock.post(f"{SERVER_URL}/real-time-response/entities/sessions/v1", json=response_session, status_code=201)
    requests_mock.get(
        f"{SERVER_URL}/real-time-response/entities/extracted-file-contents/v1",
        headers={"Content-Type": "application/x-7z-compressed", "Content-Disposition": "test.7z"},
        content=response_content,
        status_code=201,
    )
    mocker.patch.object(demisto, "debug", return_value=None)
    results = get_extracted_file_command(demisto.args())

    fpath = demisto.investigation()["id"] + "_" + results["FileID"]
    with open(fpath, "rb") as f:
        assert f.read() == response_content
    os.remove(fpath)


def test_list_host_files(requests_mock, mocker):
    from CrowdStrikeFalcon import list_host_files_command

    response = {
        "errors": [],
        "meta": {"powered_by": "empower-api", "query_time": 0.002667573, "trace_id": "fe95bfec-54bd-4236-9652-81aa9f6ca66d"},
        "resources": [
            {
                "cloud_request_id": "1269ad9e-c11f-4e38-8aba-1a0275304f9c",
                "created_at": "2020-05-01T17:57:42Z",
                "deleted_at": None,
                "id": 186811,
                "name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
                "session_id": "fdd6408f-6688-441b-8659-41bcad25441c",
                "sha256": "f1d62648ef915d85cb4fc140359e925395d315c70f3566b63bb3e21151cb2ce3",
                "size": 0,
                "updated_at": "2020-05-01T17:57:42Z",
            }
        ],
    }

    session_id = "fdd6408f-6688-441b-8659-41bcad25441c"
    response_session = {
        "errors": [],
        "meta": {"powered_by": "empower-api", "query_time": 0.025573986, "trace_id": "291d3fda-9684-4ed7-ae88-bcc3940a2104"},
        "resources": [
            {
                "created_at": "2020-05-01T17:52:16.781771496Z",
                "existing_aid_sessions": 1,
                "scripts": [],
                "session_id": f"{session_id}",
            }
        ],
    }
    mocker.patch.object(
        demisto, "args", return_value={"host_id": "edfd6a04ad134c4344f8fb119a3ad88e", "raw": "Write-Output 'Hello, World!"}
    )
    requests_mock.post(f"{SERVER_URL}/real-time-response/entities/sessions/v1", json=response_session, status_code=201)
    requests_mock.get(f"{SERVER_URL}/real-time-response/entities/file/v2", json=response, status_code=201)
    results = list_host_files_command()
    expected_results = {
        "CrowdStrike.Command(val.TaskID === obj.TaskID)": [
            {
                "HostID": "edfd6a04ad134c4344f8fb119a3ad88e",
                "SessionID": "fdd6408f-6688-441b-8659-41bcad25441c",
                "TaskID": "1269ad9e-c11f-4e38-8aba-1a0275304f9c",
            }
        ],
        "CrowdStrike.File(val.ID === obj.ID)": [
            {
                "CreatedAt": "2020-05-01T17:57:42Z",
                "DeletedAt": None,
                "ID": 186811,
                "Name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
                "SHA256": "f1d62648ef915d85cb4fc140359e925395d315c70f3566b63bb3e21151cb2ce3",
                "Size": 0,
                "Stderr": None,
                "Stdout": None,
                "UpdatedAt": "2020-05-01T17:57:42Z",
            }
        ],
        "File(val.MD5 \u0026\u0026 val.MD5 == obj.MD5 || val.SHA1 \u0026\u0026 val.SHA1 == obj.SHA1 || val.SHA256 "
        "\u0026\u0026 val.SHA256 == obj.SHA256 || val.SHA512 \u0026\u0026 val.SHA512 == obj.SHA512 || val.CRC32 "
        "\u0026\u0026 val.CRC32 == obj.CRC32 || val.CTPH \u0026\u0026 val.CTPH == obj.CTPH || val.SSDeep \u0026\u0026 "
        "val.SSDeep == obj.SSDeep)": [
            {
                "Name": "\\Device\\HarddiskVolume2\\Windows\\notepad.exe",
                "SHA256": "f1d62648ef915d85cb4fc140359e925395d315c70f3566b63bb3e21151cb2ce3",
                "Size": 0,
            }
        ],
    }
    assert results["EntryContext"] == expected_results


def test_list_host_files_with_given_session_id(mocker):
    """
    Given:
        - session_id to use when getting host files
    When:
        - run list_host_files command
    Then:
        - validate the givven session_id was used
    """
    # prepare
    import CrowdStrikeFalcon

    mocker.patch.object(demisto, "args", return_value={"host_id": "test_host_id", "session_id": "test_session_id"})
    mocker.patch.object(CrowdStrikeFalcon, "list_host_files", return_value={})

    # call
    CrowdStrikeFalcon.list_host_files_command()

    # assert
    CrowdStrikeFalcon.list_host_files.assert_called_with("test_host_id", "test_session_id")


def test_refresh_session(requests_mock, mocker):
    from CrowdStrikeFalcon import refresh_session_command

    session_id = "fdd6408f-6688-441b-8659-41bcad25441c"
    response = {
        "errors": [],
        "meta": {"powered_by": "empower-api", "query_time": 0.025573986, "trace_id": "291d3fda-9684-4ed7-ae88-bcc3940a2104"},
        "resources": [
            {
                "created_at": "2020-05-01T17:52:16.781771496Z",
                "existing_aid_sessions": 1,
                "scripts": [
                    {
                        "args": [
                            {
                                "arg_name": "Path",
                                "arg_type": "arg",
                                "command_level": "non-destructive",
                                "created_at": "2019-06-25T23:48:59Z",
                                "data_type": "string",
                                "default_value": "",
                                "description": "File to concatenate",
                                "encoding": "",
                                "id": 7,
                                "options": None,
                                "required": True,
                                "requires_value": False,
                                "script_id": 6,
                                "sequence": 1,
                                "updated_at": "2019-06-25T23:48:59Z",
                            },
                            {
                                "arg_name": "Count",
                                "arg_type": "arg",
                                "command_level": "non-destructive",
                                "created_at": "2019-06-25T23:48:59Z",
                                "data_type": "string",
                                "default_value": "",
                                "description": "Number of bytes to read (max=32768)",
                                "encoding": "",
                                "id": 51,
                                "options": None,
                                "required": False,
                                "requires_value": False,
                                "script_id": 6,
                                "sequence": 2,
                                "updated_at": "2019-06-25T23:48:59Z",
                            },
                            {
                                "arg_name": "Offset",
                                "arg_type": "arg",
                                "command_level": "non-destructive",
                                "created_at": "2019-06-25T23:48:59Z",
                                "data_type": "string",
                                "default_value": "",
                                "description": "Offset (in byte value) to start reading from",
                                "encoding": "",
                                "id": 52,
                                "options": None,
                                "required": False,
                                "requires_value": False,
                                "script_id": 6,
                                "sequence": 3,
                                "updated_at": "2019-06-25T23:48:59Z",
                            },
                            {
                                "arg_name": "ShowHex",
                                "arg_type": "flag",
                                "command_level": "non-destructive",
                                "created_at": "2019-06-25T23:48:59Z",
                                "data_type": "string",
                                "default_value": "",
                                "description": "Show the results in hexadecimal format instead of ASCII",
                                "encoding": "",
                                "id": 53,
                                "options": None,
                                "required": False,
                                "requires_value": False,
                                "script_id": 6,
                                "sequence": 4,
                                "updated_at": "2019-06-25T23:48:59Z",
                            },
                        ],
                        "command": "cat",
                        "description": "Read a file from disk and display as ASCII or hex",
                        "examples": "    C:\\\u003e cat c:\\mytextfile.txt",
                        "internal_only": False,
                        "runnable": True,
                        "sub_commands": [],
                    },
                    {
                        "args": [
                            {
                                "arg_name": "Path",
                                "arg_type": "arg",
                                "command_level": "non-destructive",
                                "created_at": "2018-11-08T18:27:18Z",
                                "data_type": "string",
                                "default_value": "",
                                "description": "Relative or absolute directory",
                                "encoding": "",
                                "id": 8,
                                "options": None,
                                "required": True,
                                "requires_value": False,
                                "script_id": 8,
                                "sequence": 1,
                                "updated_at": "2018-11-08T18:27:18Z",
                            }
                        ],
                        "command": "cd",
                        "description": "Change the current working directory",
                        "examples": "    C:\\\u003e cd C:\\Users\\Administrator\r\n",
                        "internal_only": False,
                        "runnable": True,
                        "sub_commands": [],
                    },
                    {
                        "args": [],
                        "command": "env",
                        "description": "Get environment variables for all scopes (Machine / User / Process)",
                        "examples": "",
                        "internal_only": False,
                        "runnable": True,
                        "sub_commands": [],
                    },
                    {
                        "args": [],
                        "command": "eventlog",
                        "description": "Inspect event logs.",
                        "examples": "",
                        "internal_only": False,
                        "runnable": False,
                        "sub_commands": [
                            {
                                "args": [
                                    {
                                        "arg_name": "Name",
                                        "arg_type": "arg",
                                        "command_level": "non-destructive",
                                        "created_at": "2018-05-01T19:38:30Z",
                                        "data_type": "string",
                                        "default_value": "",
                                        "description": 'Name of the event log, for example "Application", "System"',
                                        "encoding": "",
                                        "id": 35,
                                        "options": None,
                                        "required": True,
                                        "requires_value": False,
                                        "script_id": 25,
                                        "sequence": 1,
                                        "updated_at": "2018-05-01T19:38:30Z",
                                    },
                                    {
                                        "arg_name": "Count",
                                        "arg_type": "arg",
                                        "command_level": "non-destructive",
                                        "created_at": "2018-05-01T19:38:30Z",
                                        "data_type": "string",
                                        "default_value": "",
                                        "description": "Optional number of entries to return. Default:100 Max=500",
                                        "encoding": "",
                                        "id": 36,
                                        "options": None,
                                        "required": False,
                                        "requires_value": False,
                                        "script_id": 25,
                                        "sequence": 2,
                                        "updated_at": "2018-05-01T19:38:30Z",
                                    },
                                    {
                                        "arg_name": "SourceName",
                                        "arg_type": "arg",
                                        "command_level": "non-destructive",
                                        "created_at": "2018-05-01T19:38:30Z",
                                        "data_type": "string",
                                        "default_value": "",
                                        "description": 'Optional name of the event source, e.x. "WinLogon"',
                                        "encoding": "",
                                        "id": 37,
                                        "options": None,
                                        "required": False,
                                        "requires_value": False,
                                        "script_id": 25,
                                        "sequence": 3,
                                        "updated_at": "2018-05-01T19:38:30Z",
                                    },
                                ],
                                "command": "view",
                                "description": "View most recent N events in a given event log",
                                "examples": "    C:\\\u003e eventlog view Application",
                                "internal_only": False,
                                "runnable": True,
                                "sub_commands": [],
                            },
                            {
                                "args": [
                                    {
                                        "arg_name": "Name",
                                        "arg_type": "arg",
                                        "command_level": "non-destructive",
                                        "created_at": "2020-03-17T18:11:22Z",
                                        "data_type": "string",
                                        "default_value": "",
                                        "description": 'Name of the event log, for example "Application", "System"',
                                        "encoding": "",
                                        "id": 38,
                                        "options": None,
                                        "required": True,
                                        "requires_value": False,
                                        "script_id": 26,
                                        "sequence": 1,
                                        "updated_at": "2020-03-17T18:11:22Z",
                                    },
                                    {
                                        "arg_name": "Filename",
                                        "arg_type": "arg",
                                        "command_level": "non-destructive",
                                        "created_at": "2020-03-17T18:11:22Z",
                                        "data_type": "string",
                                        "default_value": "",
                                        "description": "Target file on disk",
                                        "encoding": "",
                                        "id": 39,
                                        "options": None,
                                        "required": True,
                                        "requires_value": False,
                                        "script_id": 26,
                                        "sequence": 2,
                                        "updated_at": "2020-03-17T18:11:22Z",
                                    },
                                ],
                                "command": "export",
                                "description": "Export the specified event log to a file (.csv) on disk",
                                "examples": "    C:\\\u003eeventlog export System",
                                "internal_only": False,
                                "runnable": True,
                                "sub_commands": [],
                            },
                            {
                                "args": [],
                                "command": "list",
                                "description": "Event log list: show available event log sources",
                                "examples": "    C:\\\u003e eventlog list",
                                "internal_only": False,
                                "runnable": True,
                                "sub_commands": [],
                            },
                            {
                                "args": [
                                    {
                                        "arg_name": "Name",
                                        "arg_type": "arg",
                                        "command_level": "non-destructive",
                                        "created_at": "2019-05-09T23:55:03Z",
                                        "data_type": "string",
                                        "default_value": "",
                                        "description": 'Name of the event log, for example "Application", "System"',
                                        "encoding": "",
                                        "id": 519,
                                        "options": None,
                                        "required": True,
                                        "requires_value": False,
                                        "script_id": 470,
                                        "sequence": 1,
                                        "updated_at": "2019-05-09T23:55:03Z",
                                    },
                                    {
                                        "arg_name": "Filename",
                                        "arg_type": "arg",
                                        "command_level": "non-destructive",
                                        "created_at": "2019-05-09T23:55:03Z",
                                        "data_type": "string",
                                        "default_value": "",
                                        "description": "Target file on disk",
                                        "encoding": "",
                                        "id": 520,
                                        "options": None,
                                        "required": True,
                                        "requires_value": False,
                                        "script_id": 470,
                                        "sequence": 2,
                                        "updated_at": "2019-05-09T23:55:03Z",
                                    },
                                ],
                                "command": "backup",
                                "description": "Back up the specified event log to a file (.evtx) on disk",
                                "examples": "    C:\\\u003eeventlog backup System",
                                "internal_only": False,
                                "runnable": True,
                                "sub_commands": [],
                            },
                        ],
                    },
                    {
                        "args": [
                            {
                                "arg_name": "Path",
                                "arg_type": "arg",
                                "command_level": "non-destructive",
                                "created_at": "2020-03-17T18:10:50Z",
                                "data_type": "string",
                                "default_value": "",
                                "description": "File to hash",
                                "encoding": "",
                                "id": 72,
                                "options": None,
                                "required": True,
                                "requires_value": False,
                                "script_id": 45,
                                "sequence": 1,
                                "updated_at": "2020-03-17T18:10:50Z",
                            }
                        ],
                        "command": "filehash",
                        "description": "Generate the MD5, SHA1, and SHA256 hashes of a file",
                        "examples": "C:\\\u003e filehash C:\\Windows\\System32\\cmd.exe",
                        "internal_only": False,
                        "runnable": True,
                        "sub_commands": [],
                    },
                    {
                        "args": [
                            {
                                "arg_name": "UserName",
                                "arg_type": "arg",
                                "command_level": "non-destructive",
                                "created_at": "2018-05-10T16:22:42Z",
                                "data_type": "string",
                                "default_value": "",
                                "description": "Partial or full username to filter results",
                                "encoding": "",
                                "id": 42,
                                "options": None,
                                "required": False,
                                "requires_value": False,
                                "script_id": 29,
                                "sequence": 1,
                                "updated_at": "2018-05-10T16:22:42Z",
                            }
                        ],
                        "command": "getsid",
                        "description": "Enumerate local users and Security Identifiers (SID)",
                        "examples": "\u003egetsid\r\nUserName       SID\r\n",
                        "internal_only": False,
                        "runnable": True,
                        "sub_commands": [],
                    },
                    {
                        "args": [],
                        "command": "ipconfig",
                        "description": "Show network configuration information",
                        "examples": "",
                        "internal_only": False,
                        "runnable": True,
                        "sub_commands": [],
                    },
                    {
                        "args": [
                            {
                                "arg_name": "Path",
                                "arg_type": "arg",
                                "command_level": "non-destructive",
                                "created_at": "2019-02-12T16:44:59Z",
                                "data_type": "string",
                                "default_value": ".",
                                "description": "Directory to list",
                                "encoding": "",
                                "id": 12,
                                "options": None,
                                "required": False,
                                "requires_value": False,
                                "script_id": 14,
                                "sequence": 1,
                                "updated_at": "2019-02-12T16:44:59Z",
                            }
                        ],
                        "command": "ls",
                        "description": "Display the contents of the specified path",
                        "examples": "    C:\\Windows\u003e ls\r\n",
                        "internal_only": False,
                        "runnable": True,
                        "sub_commands": [],
                    },
                    {
                        "args": [],
                        "command": "mount",
                        "description": "List mounted filesystem volumes",
                        "examples": "    C:\\\u003e mount\r\n        Display local mounted volumes",
                        "internal_only": False,
                        "runnable": True,
                        "sub_commands": [],
                    },
                    {
                        "args": [],
                        "command": "netstat",
                        "description": "Display network statistics and active connections",
                        "examples": "",
                        "internal_only": False,
                        "runnable": True,
                        "sub_commands": [],
                    },
                    {
                        "args": [],
                        "command": "ps",
                        "description": "Display process information",
                        "examples": " C:\\\u003e ps\r\n\r\nName",
                        "internal_only": False,
                        "runnable": True,
                        "sub_commands": [],
                    },
                    {
                        "args": [],
                        "command": "pwd",
                        "description": "Get current working directory",
                        "examples": "",
                        "internal_only": True,
                        "runnable": True,
                        "sub_commands": [],
                    },
                    {
                        "args": [],
                        "command": "reg",
                        "description": "Windows registry manipulation.",
                        "examples": "",
                        "internal_only": False,
                        "runnable": False,
                        "sub_commands": [
                            {
                                "args": [
                                    {
                                        "arg_name": "Subkey",
                                        "arg_type": "arg",
                                        "command_level": "non-destructive",
                                        "created_at": "2019-12-05T17:37:38Z",
                                        "data_type": "string",
                                        "default_value": "",
                                        "description": "Registry subkey full path",
                                        "encoding": "",
                                        "id": 43,
                                        "options": None,
                                        "required": False,
                                        "requires_value": False,
                                        "script_id": 30,
                                        "sequence": 1,
                                        "updated_at": "2019-12-05T17:37:39Z",
                                    },
                                    {
                                        "arg_name": "Value",
                                        "arg_type": "arg",
                                        "command_level": "non-destructive",
                                        "created_at": "2019-12-05T17:37:38Z",
                                        "data_type": "string",
                                        "default_value": "",
                                        "description": "Name of value to query",
                                        "encoding": "",
                                        "id": 44,
                                        "options": None,
                                        "required": False,
                                        "requires_value": False,
                                        "script_id": 30,
                                        "sequence": 2,
                                        "updated_at": "2019-12-05T17:37:39Z",
                                    },
                                ],
                                "command": "query",
                                "description": "Query a registry subkey or value",
                                "examples": "    C:\\\u003e reg query\r\n",
                                "internal_only": False,
                                "runnable": True,
                                "sub_commands": [],
                            }
                        ],
                    },
                ],
                "session_id": f"{session_id}",
            }
        ],
    }
    mocker.patch.object(
        demisto, "args", return_value={"host_id": "edfd6a04ad134c4344f8fb119a3ad88e", "raw": "Write-Output 'Hello, World!"}
    )
    requests_mock.post(f"{SERVER_URL}/real-time-response/entities/refresh-session/v1", json=response, status_code=201)
    results = refresh_session_command()

    assert results["HumanReadable"] == f"CrowdStrike Session Refreshed: {session_id}"


class TestFetchFunctionsTimestampFormatting:
    """
    Test class for verifying timestamp formatting in fetch functions before calling update_last_run_object.
    All the tests verify that detection["occurred"] timestamps are converted to the correct
    date format before being passed to the update_last_run_object function.
    """

    @pytest.fixture(autouse=True)
    def setup_common_mocks(self, mocker):
        """Setup common mocks used across all test methods."""
        # CommonServerPython.update_last_run_object only formats detection["occurred"] field when offset is zero
        mocker.patch("CrowdStrikeFalcon.calculate_new_offset", return_value=0)

    def test_fetch_endpoint_detections__update_last_run_object(self, mocker):
        from CrowdStrikeFalcon import fetch_endpoint_detections

        mocker.patch("CrowdStrikeFalcon.get_fetch_detections", return_value={})

        # Mock API response with microsecond precision timestamp that needs conversion
        mocker.patch(
            "CrowdStrikeFalcon.get_detections_entities",
            return_value={"resources": [{"created_timestamp": "2024-02-13T09:24:00.841616429Z", "composite_id": "123"}]},
        )

        try:
            # Execute the function, it should handle timestamp formatting internally before calling update_last_run_object
            fetch_endpoint_detections({}, None, False)
        except Exception as e:
            pytest.fail(f"Unexpected error during fetch_endpoint_detections with non-zero offset: {str(e)}")

    @pytest.mark.parametrize(
        "product_type, detection_name_prefix",
        [
            ("ods", "On-Demand Scans Detection"),
            ("ofp", "OFP Detection"),
            ("idp", "IDP Detection"),
            ("ngsiem", "NGSIEM Detection"),
            ("thirdparty", "Third Party Detection"),
        ],
    )
    def test_fetch_detections_by_product_type__update_last_run_object(self, mocker, product_type, detection_name_prefix):
        from CrowdStrikeFalcon import fetch_detections_by_product_type

        mocker.patch("CrowdStrikeFalcon.get_detections_ids", return_value={"resources": ["123"]})

        # Mock API response with microsecond precision timestamp that needs conversion
        mocker.patch(
            "CrowdStrikeFalcon.get_detection_entities",
            return_value={
                "resources": [{"created_timestamp": "2024-02-13T09:24:00.841616429Z", "composite_id": "123", "name": "name123"}]
            },
        )

        try:
            # Execute the function, it should handle timestamp formatting internally before calling update_last_run_object
            fetch_detections_by_product_type({}, 0, product_type, "", "", detection_name_prefix, "created_timestamp")

        except Exception as e:
            pytest.fail(f"Unexpected error during fetch_endpoint_detections with non-zero offset: {str(e)}")


class TestFetchLimitPerFlow:
    """The last_run update uses base_fetch_limit: 10000 on XSIAM and INCIDENTS_PER_FETCH on XSOAR."""

    def test_fetch_endpoint_detections_uses_xsiam_fetch_limit(self, mocker):
        """
        Given: XSIAM fetch-events (is_fetch_events=True) with an empty last_run.
        When: fetch_endpoint_detections runs.
        Then: update_last_run_object gets MAX_FETCH_DETECTION_PER_API_CALL (10000).
        """
        from CrowdStrikeFalcon import MAX_FETCH_DETECTION_PER_API_CALL, fetch_endpoint_detections

        mocker.patch("CrowdStrikeFalcon.calculate_new_offset", return_value=0)
        mocker.patch("CrowdStrikeFalcon.get_fetch_detections", return_value={})
        mocker.patch("CrowdStrikeFalcon.get_detections_entities", return_value={"resources": []})
        update_last_run_mock = mocker.patch("CrowdStrikeFalcon.update_last_run_object", return_value={})

        fetch_endpoint_detections({}, look_back=0, is_fetch_events=True)

        assert update_last_run_mock.call_args.kwargs["fetch_limit"] == MAX_FETCH_DETECTION_PER_API_CALL

    def test_fetch_detections_by_product_type_uses_xsiam_fetch_limit(self, mocker):
        """
        Given: XSIAM fetch-events (is_fetch_events=True) with an empty last_run.
        When: fetch_detections_by_product_type runs.
        Then: filter and update_last_run_object get MAX_FETCH_DETECTION_PER_API_CALL (10000).
        """
        from CrowdStrikeFalcon import MAX_FETCH_DETECTION_PER_API_CALL, fetch_detections_by_product_type

        mocker.patch("CrowdStrikeFalcon.calculate_new_offset", return_value=0)
        mocker.patch("CrowdStrikeFalcon.get_detections_ids", return_value={"resources": ["123"]})
        mocker.patch(
            "CrowdStrikeFalcon.get_detection_entities",
            return_value={
                "resources": [{"created_timestamp": "2024-02-13T09:24:00.841616429Z", "composite_id": "123", "name": "name123"}]
            },
        )
        filter_mock = mocker.patch(
            "CrowdStrikeFalcon.filter_incidents_by_duplicates_and_limit",
            side_effect=lambda incidents_res, **kwargs: incidents_res,
        )
        update_last_run_mock = mocker.patch("CrowdStrikeFalcon.update_last_run_object", return_value={})

        fetch_detections_by_product_type(
            current_fetch_info={},
            look_back=0,
            product_type="idp",
            fetch_query="",
            detections_type="IDP Detection",
            detection_name_prefix="IDP Detection",
            start_time_key="created_timestamp",
            is_fetch_events=True,
        )

        assert filter_mock.call_args.kwargs["fetch_limit"] == MAX_FETCH_DETECTION_PER_API_CALL
        assert update_last_run_mock.call_args.kwargs["fetch_limit"] == MAX_FETCH_DETECTION_PER_API_CALL

    def test_fetch_endpoint_detections_xsoar_uses_fresh_incidents_per_fetch(self, mocker):
        """
        Given: XSOAR fetch-incidents (is_fetch_events=False) with a stale persisted 'limit'.
        When: fetch_endpoint_detections runs.
        Then: update_last_run_object gets the fresh INCIDENTS_PER_FETCH, not the stale 'limit'.
        """
        from CrowdStrikeFalcon import INCIDENTS_PER_FETCH, fetch_endpoint_detections

        mocker.patch("CrowdStrikeFalcon.calculate_new_offset", return_value=0)
        mocker.patch("CrowdStrikeFalcon.get_fetch_detections", return_value={})
        mocker.patch("CrowdStrikeFalcon.get_detections_entities", return_value={"resources": []})
        update_last_run_mock = mocker.patch("CrowdStrikeFalcon.update_last_run_object", return_value={})

        # last_run carries a stale, larger persisted limit from a prior run.
        fetch_endpoint_detections({"limit": 500}, look_back=0, is_fetch_events=False)

        assert update_last_run_mock.call_args.kwargs["fetch_limit"] == INCIDENTS_PER_FETCH

    def test_fetch_detections_by_product_type_xsoar_uses_fresh_incidents_per_fetch(self, mocker):
        """
        Given: XSOAR fetch-incidents (is_fetch_events=False) with a stale persisted 'limit'.
        When: fetch_detections_by_product_type runs.
        Then: update_last_run_object gets the fresh INCIDENTS_PER_FETCH, not the stale 'limit'.
        """
        from CrowdStrikeFalcon import INCIDENTS_PER_FETCH, fetch_detections_by_product_type

        mocker.patch("CrowdStrikeFalcon.calculate_new_offset", return_value=0)
        mocker.patch("CrowdStrikeFalcon.get_detections_ids", return_value={"resources": ["123"]})
        mocker.patch(
            "CrowdStrikeFalcon.get_detection_entities",
            return_value={
                "resources": [{"created_timestamp": "2024-02-13T09:24:00.841616429Z", "composite_id": "123", "name": "name123"}]
            },
        )
        mocker.patch(
            "CrowdStrikeFalcon.filter_incidents_by_duplicates_and_limit",
            side_effect=lambda incidents_res, **kwargs: incidents_res,
        )
        update_last_run_mock = mocker.patch("CrowdStrikeFalcon.update_last_run_object", return_value={})

        fetch_detections_by_product_type(
            current_fetch_info={"limit": 500},
            look_back=0,
            product_type="idp",
            fetch_query="",
            detections_type="IDP Detection",
            detection_name_prefix="IDP Detection",
            start_time_key="created_timestamp",
            is_fetch_events=False,
        )

        assert update_last_run_mock.call_args.kwargs["fetch_limit"] == INCIDENTS_PER_FETCH


class TestFetchLimitApiCap:
    """The limit sent to the API is capped so offset + limit never exceeds MAX_FETCH_SIZE."""

    def test_fetch_endpoint_detections_caps_api_limit_when_persisted_limit_exceeds_max(self, mocker):
        """
        Given: fetch-events where look_back grew the persisted 'limit' past MAX_FETCH_SIZE.
        When: fetch_endpoint_detections runs.
        Then: the API call receives limit <= MAX_FETCH_SIZE - offset, avoiding the API 400.
        """
        from CrowdStrikeFalcon import MAX_FETCH_SIZE, fetch_endpoint_detections

        mocker.patch("CrowdStrikeFalcon.calculate_new_offset", return_value=0)
        get_fetch_detections_mock = mocker.patch("CrowdStrikeFalcon.get_fetch_detections", return_value={})
        mocker.patch("CrowdStrikeFalcon.get_detections_entities", return_value={"resources": []})
        mocker.patch("CrowdStrikeFalcon.update_last_run_object", return_value={})

        # A prior look_back run persisted a grown limit above the API's hard ceiling.
        fetch_endpoint_detections({"limit": MAX_FETCH_SIZE + 50, "offset": 0}, look_back=2, is_fetch_events=True)

        assert get_fetch_detections_mock.call_args.kwargs["limit"] <= MAX_FETCH_SIZE

    def test_fetch_detections_by_product_type_caps_api_limit_when_persisted_limit_exceeds_max(self, mocker):
        """
        Given: fetch-events where look_back grew the persisted 'limit' past MAX_FETCH_SIZE.
        When: fetch_detections_by_product_type runs.
        Then: the API call receives limit <= MAX_FETCH_SIZE - offset, avoiding the API 400.
        """
        from CrowdStrikeFalcon import MAX_FETCH_SIZE, fetch_detections_by_product_type

        mocker.patch("CrowdStrikeFalcon.calculate_new_offset", return_value=0)
        get_detections_ids_mock = mocker.patch("CrowdStrikeFalcon.get_detections_ids", return_value={"resources": []})
        mocker.patch("CrowdStrikeFalcon.get_detection_entities", return_value={"resources": []})
        mocker.patch(
            "CrowdStrikeFalcon.filter_incidents_by_duplicates_and_limit",
            side_effect=lambda incidents_res, **kwargs: incidents_res,
        )
        mocker.patch("CrowdStrikeFalcon.update_last_run_object", return_value={})

        fetch_detections_by_product_type(
            current_fetch_info={"limit": MAX_FETCH_SIZE + 50, "offset": 0},
            look_back=2,
            product_type="idp",
            fetch_query="",
            detections_type="IDP Detection",
            detection_name_prefix="IDP Detection",
            start_time_key="created_timestamp",
            is_fetch_events=True,
        )

        assert get_detections_ids_mock.call_args.kwargs["limit"] <= MAX_FETCH_SIZE

    def test_api_limit_accounts_for_offset(self, mocker):
        """
        Given: fetch-events with a non-zero offset and a limit at MAX_FETCH_SIZE.
        When: fetch_endpoint_detections runs.
        Then: the API call receives limit <= MAX_FETCH_SIZE - offset (offset + limit stays within bound).
        """
        from CrowdStrikeFalcon import MAX_FETCH_SIZE, fetch_endpoint_detections

        offset = 200
        mocker.patch("CrowdStrikeFalcon.calculate_new_offset", return_value=offset)
        get_fetch_detections_mock = mocker.patch("CrowdStrikeFalcon.get_fetch_detections", return_value={})
        mocker.patch("CrowdStrikeFalcon.get_detections_entities", return_value={"resources": []})
        mocker.patch("CrowdStrikeFalcon.update_last_run_object", return_value={})

        fetch_endpoint_detections({"limit": MAX_FETCH_SIZE, "offset": offset}, look_back=2, is_fetch_events=True)

        api_limit = get_fetch_detections_mock.call_args.kwargs["limit"]
        assert api_limit + offset <= MAX_FETCH_SIZE

    def test_api_limit_is_noop_for_xsoar_small_limit(self, mocker):
        """
        Given: fetch-incidents (XSOAR) where the limit is the small INCIDENTS_PER_FETCH.
        When: fetch_endpoint_detections runs.
        Then: the API call receives the unchanged small limit (the cap is a no-op).
        """
        from CrowdStrikeFalcon import INCIDENTS_PER_FETCH, fetch_endpoint_detections

        mocker.patch("CrowdStrikeFalcon.calculate_new_offset", return_value=0)
        get_fetch_detections_mock = mocker.patch("CrowdStrikeFalcon.get_fetch_detections", return_value={})
        mocker.patch("CrowdStrikeFalcon.get_detections_entities", return_value={"resources": []})
        mocker.patch("CrowdStrikeFalcon.update_last_run_object", return_value={})

        fetch_endpoint_detections({}, look_back=0, is_fetch_events=False)

        assert get_fetch_detections_mock.call_args.kwargs["limit"] == INCIDENTS_PER_FETCH


class TestFetch:
    """Test the logic of the fetch"""

    @pytest.fixture()
    def set_up_mocks(self, requests_mock, mocker):
        """Sets up the mocks for the fetch."""
        mocker.patch.object(demisto, "setLastRun")
        requests_mock.get(
            f"{SERVER_URL}/detects/queries/detects/v1",
            json={"resources": ["ldt:1", "ldt:2"], "meta": {"pagination": {"total": 2}}},
        )
        requests_mock.post(
            f"{SERVER_URL}/detects/entities/summaries/GET/v1",
            json={
                "resources": [
                    {
                        "detection_id": "ldt:1",
                        "created_timestamp": "2020-09-04T09:16:11.000000Z",
                        "max_severity_displayname": "Low",
                    },
                    {
                        "detection_id": "ldt:2",
                        "created_timestamp": "2020-09-04T09:20:11.000000Z",
                        "max_severity_displayname": "Low",
                    },
                ]
            },
        )

    @freeze_time("2020-08-26 17:22:13 UTC")
    def test_old_fetch_to_new_fetch(self, set_up_mocks, mocker):
        """
        Tests the change of logic done in fetch. Validates that it's done smoothly
        Given:
            Old getLastRun which holds `first_behavior_time` and `last_detection_id`
        When:
            2 results are returned (which equals the FETCH_LIMIT)
        Then:
            The `first_behavior_time` doesn't change and an `offset` of 2 is added.

        """
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR, LastRunIndex, set_last_run_per_type

        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        set_last_run_per_type(last_run_object, index=LastRunIndex.DETECTIONS, data={"time": "2020-09-04T09:16:10Z"})

        mocker.patch.object(demisto, "params", return_value={})
        mocker.patch.object(
            demisto,
            "getLastRun",
            return_value={
                "first_behavior_detection_time": "2020-09-04T09:16:10.000000Z",
                "detection_offset": 2,
            },
        )
        fetch_items()
        assert demisto.setLastRun.mock_calls[0][1][0] == last_run_object

    @freeze_time("2020-09-04T09:16:10Z")
    def test_new_fetch(self, set_up_mocks, mocker, requests_mock):
        """
        Tests the correct flow of fetch
        Given:
            `getLastRun` which holds  `first_behavior_time` and `offset`
        When:
            1 result is returned (which is less than the FETCH_LIMIT)
        Then:
            The `first_behavior_time` changes and no `offset` is added.
        """

        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR, LastRunIndex, set_last_run_per_type

        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        set_last_run_per_type(
            last_run_object, index=LastRunIndex.DETECTIONS, data={"time": "2020-09-04T09:16:11.000000Z", "offset": 2}
        )

        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)
        # Override post to have 1 results so FETCH_LIMIT won't be reached
        requests_mock.get(
            f"{SERVER_URL}/alerts/queries/alerts/v2",
            json={
                "resources": [
                    {"composite_id": "ldt:1", "start": "2020-09-04T09:16:11Z", "created_timestamp": "2020-09-04T09:16:11Z"}
                ]
            },
        )

        # POST returns alert details with created_timestamp
        requests_mock.post(
            f"{SERVER_URL}/alerts/entities/alerts/v2",
            json={
                "resources": [
                    {"composite_id": "ldt:1", "start": "2020-09-04T09:16:11Z", "created_timestamp": "2020-09-04T09:16:11Z"}
                ]
            },
        )

        fetch_items()
        assert demisto.setLastRun.mock_calls[0][1][0][LastRunIndex.DETECTIONS] == {
            "time": "2020-09-04T09:16:11.000000Z",
            "limit": 2,
            "offset": 3,
            "found_incident_ids": {"Detection ID: ldt:1": 1599210970},
        }

    @freeze_time("2020-09-04T09:16:10Z")
    def test_fetch_with_offset(self, set_up_mocks, mocker, requests_mock):
        """
        Tests the correct flow of fetch with offset
        Given:
            `getLastRun` which holds  `first_behavior_time` and `offset`
        When:
            2 result is returned (which is less than the total which is 4)
        Then:
            - The offset increases to 2 in the next run, and the last time remains
            - In the next call, the offset will be reset to 0 and the last time will be the latest detection time

        """
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR, LastRunIndex, set_last_run_per_type

        # mock the total number of detections to be 4, so offset will be set
        requests_mock.get(
            f"{SERVER_URL}/alerts/queries/alerts/v2",
            json={"resources": ["ldt:1", "ldt:2"], "meta": {"pagination": {"total": 4}}},
        )

        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        set_last_run_per_type(
            last_run_object, index=LastRunIndex.DETECTIONS, data={"time": "2020-09-04T09:16:10.000000Z", "offset": 0}
        )

        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)
        # Override post to have 1 results so FETCH_LIMIT won't be reached
        requests_mock.post(
            f"{SERVER_URL}/alerts/entities/alerts/v2",
            json={
                "resources": [
                    {
                        "detection_id": "ldt:1",
                        "created_timestamp": "2020-09-04T09:16:11.000000Z",
                        "max_severity_displayname": "Low",
                    }
                ],
            },
        )

        fetch_items()
        # the offset should be increased to 2, and the time should be stay the same
        expected_last_run = {
            "time": "2020-09-04T09:16:10.000000Z",
            "limit": 2,
            "offset": 2,
            "found_incident_ids": {"Detection ID: ldt:1": 1599210970},
        }
        assert demisto.setLastRun.mock_calls[0][1][0][LastRunIndex.DETECTIONS] == expected_last_run

        requests_mock.get(
            f"{SERVER_URL}/alerts/queries/alerts/v2",
            json={"resources": ["ldt:3", "ldt:4"], "meta": {"pagination": {"total": 4}}},
        )

        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        set_last_run_per_type(last_run_object, index=LastRunIndex.DETECTIONS, data=expected_last_run)

        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)

        requests_mock.post(
            f"{SERVER_URL}/alerts/entities/alerts/v2",
            json={
                "resources": [
                    {
                        "detection_id": "ldt:2",
                        "created_timestamp": "2020-09-04T09:16:13.000000Z",
                        "max_severity_displayname": "Low",
                    }
                ],
            },
        )

        fetch_items()
        # the offset should be 0 because all detections were fetched, and the time should update to the latest detection
        assert demisto.setLastRun.mock_calls[1][1][0][LastRunIndex.DETECTIONS] == {
            "time": "2020-09-04T09:16:13.000000Z",
            "limit": 2,
            "offset": 0,
            "found_incident_ids": {"Detection ID: ldt:1": 1599210970, "Detection ID: ldt:2": 1599210970},
        }

    def test_fetch_incident_type(self, set_up_mocks, mocker, requests_mock):
        """
        Tests the addition of incident_type field to the context
        Given:
            Old getLastRun which holds `first_behavior_time` and `last_detection_id`
        When:
            2 results are returned (which equals the FETCH_LIMIT)
        Then:
            "incident_type": "detection" is in raw result returned by the indicator

        """
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR, LastRunIndex, set_last_run_per_type

        requests_mock.get(
            f"{SERVER_URL}/alerts/queries/alerts/v2",
            json={
                "resources": [
                    {"incident_id": "ldt:1", "start": "2020-09-04T09:16:11Z", "created_timestamp": "2020-09-04T09:16:11Z"}
                ]
            },
        )

        requests_mock.post(
            f"{SERVER_URL}/alerts/entities/alerts/v2",
            json={
                "resources": [
                    {"incident_id": "ldt:1", "start": "2020-09-04T09:16:11Z", "created_timestamp": "2020-09-04T09:16:11Z"}
                ]
            },
        )
        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        set_last_run_per_type(
            last_run_object,
            index=LastRunIndex.DETECTIONS,
            data={
                "time": "2020-09-04T09:16:10Z",
            },
        )
        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)
        _, incidents = fetch_items()
        for incident in incidents:
            assert '"incident_type": "detection"' in incident.get("rawJSON", "")

    @pytest.mark.parametrize(
        "detection_type, expected_name",
        [
            ("ngsiem", "NGSIEM Detection"),
            ("thirdparty", "Third Party Detection"),
        ],
    )
    def test_fetch_type_no_legacy_version(self, requests_mock, mocker, detection_type, expected_name):
        from CrowdStrikeFalcon import fetch_items, NGSIEM_DETECTION_FETCH_TYPE, THIRD_PARTY_DETECTION_FETCH_TYPE

        # Get the actual constants based on the parameter
        fetch_type = NGSIEM_DETECTION_FETCH_TYPE if detection_type == "ngsiem" else THIRD_PARTY_DETECTION_FETCH_TYPE

        mocker.patch.object(
            demisto, "params", return_value={"fetch_incidents_or_detections": [fetch_type], "legacy_version": False}
        )

        mocker.patch.object(demisto, "getLastRun", return_value={})

        requests_mock.get(
            f"{SERVER_URL}/alerts/queries/alerts/v2",
            json={"resources": [f"a:{detection_type}:1", f"a:{detection_type}:2"], "meta": {"pagination": {"total": 2}}},
        )
        requests_mock.post(
            f"{SERVER_URL}/alerts/entities/alerts/v2",
            json={
                "resources": [
                    {
                        "composite_id": f"a:{detection_type}:1",
                        "created_timestamp": "2025-03-11T16:45:21.571614153Z",
                        "start_time": "2025-03-11T15:46:00.426Z",
                    },
                    {
                        "composite_id": f"a:{detection_type}:2",
                        "created_timestamp": "2025-03-11T16:45:21.571614153Z",
                        "start_time": "2025-03-11T15:46:00.426Z",
                    },
                ]
            },
        )

        _, incidents = fetch_items()
        assert len(incidents) == 2

        for incident in incidents:
            assert expected_name in incident.get("name", "")

    @pytest.mark.parametrize(
        "expected_name, fetch_incidents_or_detections, incidents_len",
        [
            ("Detection ID:", ["Detections"], 2),
            ("Detection ID:", ["Endpoint Detection"], 2),
            ("IDP Detection ID: ", ["IDP Detection"], 2),
        ],
    )
    def test_fetch_returns_all_types(self, set_up_mocks, mocker, expected_name, fetch_incidents_or_detections, incidents_len):
        """
        Tests fetch_items by mocking all fetch functions to return predictable mock data.
        """
        from CrowdStrikeFalcon import (
            fetch_items,
            TOTAL_FETCH_TYPE_XSOAR,
            LastRunIndex,
            set_last_run_per_type,
        )

        last_run_object = [{} for _ in range(TOTAL_FETCH_TYPE_XSOAR)]
        set_last_run_per_type(last_run_object, index=LastRunIndex.DETECTIONS, data={"time": "2020-09-04T09:16:10Z"})
        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)

        mocker.patch.object(
            demisto,
            "params",
            return_value={
                "url": "https://mockserver",
                "proxy": True,
                "incidents_per_fetch": 2,
                "fetch_incidents_or_detections": fetch_incidents_or_detections,
                "fetch_time": "3 days",
            },
        )

        def make_mock_items(prefix):
            return [{"name": f"{prefix} 1"}, {"name": f"{prefix} 2"}], {"mock": True}

        # Mock all fetch functions unconditionally
        mocker.patch(
            "CrowdStrikeFalcon.fetch_endpoint_detections", side_effect=lambda lr, lb, efe: make_mock_items("Detection ID:")
        )
        mocker.patch(
            "CrowdStrikeFalcon.fetch_detections_by_product_type",
            side_effect=lambda lr, **kwargs: make_mock_items(f"{kwargs.get('detection_name_prefix', 'IDP Detection')} ID: "),
        )

        _, items = fetch_items()

        assert len(items) == incidents_len
        assert expected_name in items[0]["name"]
        assert expected_name in items[1]["name"]


def get_fetch_data():
    with open("./test_data/test_data.json") as f:
        return json.loads(f.read())


def get_fetch_data2():
    with open("./test_data/test_data2.json") as f:
        return json.loads(f.read())


test_data = get_fetch_data()
test_data2 = get_fetch_data2()


def test_get_indicator_device_id(mocker, requests_mock):
    from CrowdStrikeFalcon import get_indicator_device_id

    requests_mock.get("https://4.4.4.4/indicators/queries/devices/v1", json=test_data["response_for_get_indicator_device_id"])
    mocker.patch.object(demisto, "args", return_value={"type": "sha256", "value": "example_sha"})
    res = get_indicator_device_id()

    # Expecting both DeviceIOC and DeviceID outputs for BC.
    assert set(res.outputs.keys()) - {"DeviceIOC", "DeviceID"} == set()
    assert res.outputs["DeviceIOC"]["Type"] == "sha256"
    assert res.outputs["DeviceIOC"]["Value"] == "example_sha"
    assert res.outputs["DeviceIOC"]["DeviceID"] == res.outputs["DeviceID"]


def test_validate_response():
    from CrowdStrikeFalcon import validate_response

    true_res = validate_response({"resources": "1234"})
    false_res = validate_response({"error": "404"})
    assert true_res
    assert not false_res


def test_build_error_message():
    from CrowdStrikeFalcon import build_error_message

    res_error_data = build_error_message({"meta": 1234})
    assert res_error_data == "Error: error code: None, error_message: something got wrong, please try again."

    res_error_data_with_specific_error = build_error_message({"errors": [{"code": 1234, "message": "hi"}]})
    assert res_error_data_with_specific_error == "Error: error code: 1234, error_message: hi."


def test_search_iocs_command_does_not_exist(requests_mock):
    """
    Test cs-falcon-search-iocs when no ioc is found

    Given:
     - There is no ioc in the system
    When:
     - Searching for iocs using cs-falcon-search-iocs command
    Then:
     - Return a human readable result with appropriate message
     - Do not populate the entry context
    """
    from CrowdStrikeFalcon import search_iocs_command

    response = {"resources": []}
    requests_mock.get(f"{SERVER_URL}/indicators/queries/iocs/v1", json=response, status_code=200)
    results = search_iocs_command()
    assert results["HumanReadable"] == "Could not find any Indicators of Compromise."
    assert results["EntryContext"] is None


def test_search_iocs_command_exists(requests_mock):
    """
    Test cs-falcon-search-iocs when an ioc is found

    Given:
     - There is a single md5 ioc in the system
    When:
     - Searching for iocs using cs-falcon-search-iocs command
    Then:
     - Return a human readable result with appropriate message
     - Do populate the entry context with the right value
    """
    from CrowdStrikeFalcon import search_iocs_command

    id_response = {"resources": ["md5:testmd5"], "errors": []}
    ioc_response = {
        "resources": [
            {
                "type": "md5",
                "value": "testmd5",
                "policy": "detect",
                "share_level": "red",
                "description": "Eicar file",
                "created_timestamp": "2020-10-01T09:09:04Z",
                "modified_timestamp": "2020-10-01T09:09:04Z",
            }
        ]
    }
    requests_mock.get(f"{SERVER_URL}/indicators/queries/iocs/v1", json=id_response, status_code=200)
    requests_mock.get(f"{SERVER_URL}/indicators/entities/iocs/v1", json=ioc_response, status_code=200)
    results = search_iocs_command()
    assert results["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"][0]["Value"] == "testmd5"


def test_search_iocs_command__no_iocs(requests_mock, mocker):
    """
    Test cs-falcon-search-iocs when encountering an error

    Given:
     - No iocs exist
    When:
     - Searching for non existing iocs using cs-falcon-search-iocs command
    Then:
     - Display an appropriate info in the HR
    """
    from CrowdStrikeFalcon import search_iocs_command

    requests_mock.get(f"{SERVER_URL}/indicators/queries/iocs/v1", json={})
    mocker.patch.object(demisto, "results")
    res = search_iocs_command()
    assert "Could not find any Indicators of Compromise." in res["HumanReadable"]


def test_get_ioc_command_does_not_exist(requests_mock):
    """
    Test cs-falcon-get-ioc when no ioc is found

    Given:
     - There is no ioc in the system
    When:
     - Searching for iocs using cs-falcon-get-ioc command
     - The server returns an error
    Then:
     - Raise the error back from the server
    """
    from CrowdStrikeFalcon import get_ioc_command

    response = {"resources": [], "errors": [{"code": 404, "message": "md5:testmd5 - Resource Not Found"}]}
    requests_mock.get(f"{SERVER_URL}/indicators/entities/iocs/v1", json=response, status_code=200)
    with pytest.raises(DemistoException) as excinfo:
        get_ioc_command(ioc_type="md5", value="testmd5")
    assert excinfo.value.args[0] == [{"code": 404, "message": "md5:testmd5 - Resource Not Found"}]


def test_get_ioc_command_exists(requests_mock):
    """
    Test cs-falcon-get-ioc when an ioc is found

    Given:
     - There is a single md5 ioc in the system
    When:
     - Looking for iocs using cs-falcon-get-iocs command
    Then:
     - Return a human readable result with appropriate message
     - Do populate the entry context with the right value
    """
    from CrowdStrikeFalcon import get_ioc_command

    ioc_response = {
        "resources": [
            {
                "type": "md5",
                "value": "testmd5",
                "policy": "detect",
                "share_level": "red",
                "description": "Eicar file",
                "created_timestamp": "2020-10-01T09:09:04Z",
                "modified_timestamp": "2020-10-01T09:09:04Z",
            }
        ]
    }
    requests_mock.get(f"{SERVER_URL}/indicators/entities/iocs/v1", json=ioc_response, status_code=200)
    results = get_ioc_command(ioc_type="md5", value="testmd5")
    assert results["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"][0]["Value"] == "testmd5"


def test_upload_ioc_command_fail(requests_mock, mocker):
    """
    Test cs-falcon-upload-ioc where it fails to create the ioc

    Given:
     - The user tries to create an IOC
    When:
     - The server fails to create an IOC
    Then:
     - Display error message to user
    """
    from CrowdStrikeFalcon import upload_ioc_command

    upload_response = {"resources": []}
    get_response = {"resources": [], "errors": [{"code": 404, "message": "md5:testmd5 - Resource Not Found"}]}
    requests_mock.post(f"{SERVER_URL}/indicators/entities/iocs/v1", json=upload_response, status_code=200)
    requests_mock.get(f"{SERVER_URL}/indicators/entities/iocs/v1", json=get_response, status_code=200)
    with pytest.raises(DemistoException) as excinfo:
        upload_ioc_command(ioc_type="md5", value="testmd5")
    assert excinfo.value.args[0] == "Failed to create IOC. Please try again."


def test_upload_ioc_command_successful(requests_mock):
    """
    Test cs-falcon-upload-ioc when an upload is successful

    Given:
     - The user tries to create an IOC
    When:
     - The server creates an IOC
    Then:
     - Return a human readable result with appropriate message
     - Do populate the entry context with the right value
    """
    from CrowdStrikeFalcon import upload_ioc_command

    upload_response = {"resources": []}
    ioc_response = {
        "resources": [
            {
                "type": "md5",
                "value": "testmd5",
                "policy": "detect",
                "share_level": "red",
                "description": "Eicar file",
                "created_timestamp": "2020-10-01T09:09:04Z",
                "modified_timestamp": "2020-10-01T09:09:04Z",
            }
        ]
    }
    requests_mock.post(f"{SERVER_URL}/indicators/entities/iocs/v1", json=upload_response, status_code=200)
    requests_mock.get(f"{SERVER_URL}/indicators/entities/iocs/v1", json=ioc_response, status_code=200)
    results = upload_ioc_command(ioc_type="md5", value="testmd5")
    assert results["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"][0]["Value"] == "testmd5"


def test_search_custom_iocs_command_does_not_exist(requests_mock):
    """
    Test cs-falcon-search-custom-iocs when no ioc is found

    Given:
     - There is no ioc in the system
    When:
     - Searching for iocs using cs-falcon-search-custom-iocs command
    Then:
     - Return a human readable result with appropriate message
     - Do not populate the entry context
    """
    from CrowdStrikeFalcon import search_custom_iocs_command

    response = {"resources": []}
    requests_mock.get(f"{SERVER_URL}/iocs/combined/indicator/v1", json=response, status_code=200)
    results = search_custom_iocs_command()
    assert results["HumanReadable"] == "Could not find any Indicators of Compromise."
    assert results["EntryContext"] is None


def test_search_custom_iocs_command_exists(requests_mock):
    """
    Test cs-falcon-search-custom-iocs when an ioc is found

    Given:
     - There is a single md5 ioc in the system
    When:
     - Searching for iocs using cs-falcon-search-custom-iocs command
    Then:
     - Return a human readable result with appropriate message
     - Do populate the entry context with the right value
    """
    from CrowdStrikeFalcon import search_custom_iocs_command

    ioc_response = {
        "resources": [
            {
                "id": "4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r",
                "type": "md5",
                "value": "testmd5",
                "action": "prevent",
                "severity": "high",
                "description": "Eicar file",
                "created_on": "2020-10-01T09:09:04Z",
                "modified_on": "2020-10-01T09:09:04Z",
            }
        ]
    }
    requests_mock.get(f"{SERVER_URL}/iocs/combined/indicator/v1", json=ioc_response, status_code=200)
    results = search_custom_iocs_command()
    assert (
        "| 4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r | prevent |  | high | md5 |"
        in results[0]["HumanReadable"]
    )
    assert results[0]["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"][0]["Value"] == "testmd5"


def test_search_custom_iocs_command__no_iocs(requests_mock, mocker):
    """
    Test cs-falcon-search-custom-iocs when no iocs exist

    Given:
     - No iocs exist
    When:
     - Searching for non existing iocs using cs-falcon-search-custom-iocs command
    Then:
     - Display an appropriate info in HR
    """
    from CrowdStrikeFalcon import search_custom_iocs_command

    requests_mock.get(f"{SERVER_URL}/iocs/combined/indicator/v1", json={})
    mocker.patch.object(demisto, "results")
    mocker.patch(RETURN_ERROR_TARGET)
    res = search_custom_iocs_command()
    assert "Could not find any Indicators of Compromise." in res["HumanReadable"]


def test_search_custom_iocs_command_filter(requests_mock):
    """
    Test cs-falcon-search-custom-iocs when running with filter

    Given:
     - Domain IOC with test.com value
    When:
     - Searching for the domain IOC using cs-falcon-search-custom-iocs command
    Then:
     - Return a human readable result with appropriate message
     - Do populate the entry context with the right value
    """
    from CrowdStrikeFalcon import search_custom_iocs_command

    ioc_type = "domain"
    ioc_value = "test.com"
    ioc_response = {
        "resources": [
            {
                "id": "4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r",
                "type": ioc_type,
                "value": ioc_value,
                "action": "prevent",
                "severity": "high",
                "created_on": "2020-10-01T09:09:04Z",
                "modified_on": "2020-10-01T09:09:04Z",
            }
        ]
    }
    requests_mock.get(
        f"{SERVER_URL}/iocs/combined/indicator/v1?filter=type%3A%5B%27{ioc_type}%27%5D%2Bvalue%3A%5B%27{ioc_value}%27"
        f"%5D&limit=50",
        # noqa: E501
        json=ioc_response,
        status_code=200,
    )
    results = search_custom_iocs_command(
        types=ioc_type,
        values=ioc_value,
    )
    assert (
        f"| 4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r | prevent |  | high | {ioc_type} |"
        f" {ioc_value} |" in results[0]["HumanReadable"]
    )  # noqa: E501
    assert results[0]["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"][0]["Value"] == ioc_value


def test_get_custom_ioc_command_exists(requests_mock):
    """
    Test cs-falcon-get-custom-ioc when an ioc is found

    Given:
     - There is a single md5 ioc in the system
    When:
     - Looking for iocs using cs-falcon-get-custom-ioc command
    Then:
     - Return a human readable result with appropriate message
     - Do populate the entry context with the right value
    """
    from CrowdStrikeFalcon import get_custom_ioc_command

    ioc_type = "md5"
    ioc_value = "testmd5"
    ioc_response = {
        "resources": [
            {
                "id": "4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r",
                "type": ioc_type,
                "value": ioc_value,
                "action": "prevent",
                "severity": "high",
                "description": "Eicar file",
                "created_on": "2020-10-01T09:09:04Z",
                "modified_on": "2020-10-01T09:09:04Z",
            }
        ]
    }

    requests_mock.get(
        f"{SERVER_URL}/iocs/combined/indicator/v1?filter=type%3A%5B%27{ioc_type}%27%5D%2Bvalue%3A%5B%27{ioc_value}%27"
        f"%5D&limit=50",
        # noqa: E501
        json=ioc_response,
        status_code=200,
    )
    results = get_custom_ioc_command(ioc_type=ioc_type, value=ioc_value)
    assert results["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"][0]["Value"] == ioc_value


def test_get_custom_ioc_command_does_not_exist(requests_mock):
    """
    Test cs-falcon-get-custom-ioc when no ioc is found

    Given:
     - There is no ioc in the system
    When:
     - Searching for iocs using cs-falcon-get-custom-ioc command
     - The server returns an error
    Then:
     - Raise the error back from the server
    """
    from CrowdStrikeFalcon import get_custom_ioc_command

    response = {"resources": [], "errors": [{"code": 404, "message": "md5:testmd5 - Resource Not Found"}]}
    requests_mock.get(f"{SERVER_URL}/iocs/combined/indicator/v1", json=response, status_code=200)
    with pytest.raises(DemistoException) as excinfo:
        get_custom_ioc_command(ioc_type="md5", value="testmd5")
    assert excinfo.value.args[0] == [{"code": 404, "message": "md5:testmd5 - Resource Not Found"}]


def test_get_custom_ioc_command_by_id(requests_mock):
    """
    Given:
     - ID of IOC to retrieve
    When:
     - Looking for IOC using cs-falcon-get-custom-ioc command
    Then:
     - Do populate the entry context with the right ID
    """
    from CrowdStrikeFalcon import get_custom_ioc_command

    ioc_id = "4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r"
    ioc_response = {
        "resources": [
            {
                "id": ioc_id,
                "type": "domain",
                "value": "test.com",
                "action": "prevent",
                "severity": "high",
                "description": "Eicar file",
                "created_on": "2020-10-01T09:09:04Z",
                "modified_on": "2020-10-01T09:09:04Z",
            }
        ]
    }

    requests_mock.get(
        f"{SERVER_URL}/iocs/entities/indicators/v1?ids={ioc_id}",  # noqa: E501
        json=ioc_response,
        status_code=200,
    )
    results = get_custom_ioc_command(ioc_id=ioc_id)
    assert results["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"][0]["ID"] == ioc_id


def test_upload_custom_ioc_command_successful(requests_mock):
    """
    Test cs-falcon-upload-custom-ioc when an upload is successful

    Given:
     - The user tries to create an IOC
    When:
     - The server creates an IOC
    Then:
     - Return a human readable result with appropriate message
     - Do populate the entry context with the right value
    """
    from CrowdStrikeFalcon import upload_custom_ioc_command

    ioc_response = {
        "resources": [
            {
                "id": "4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r",
                "type": "md5",
                "value": "testmd5",
                "action": "prevent",
                "severity": "high",
                "description": "Eicar file",
                "created_on": "2020-10-01T09:09:04Z",
                "modified_on": "2020-10-01T09:09:04Z",
            }
        ]
    }
    requests_mock.post(
        f"{SERVER_URL}/iocs/entities/indicators/v1",
        json=ioc_response,
        status_code=200,
    )
    results = upload_custom_ioc_command(
        ioc_type="md5",
        value="testmd5",
        action="prevent",
        severity="high",
        platforms="mac,linux",
    )
    assert (
        "| 2020-10-01T09:09:04Z | Eicar file |  | 4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r |"
        in results[0]["HumanReadable"]
    )
    assert results[0]["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"][0]["Value"] == "testmd5"


def test_upload_custom_ioc_command_fail(requests_mock):
    """
    Test cs-falcon-upload-custom-ioc where it fails to create the ioc

    Given:
     - The user tries to create an IOC
    When:
     - The server fails to create an IOC
    Then:
     - Display error message to user
    """
    from CrowdStrikeFalcon import upload_custom_ioc_command

    response = {
        "resources": [
            {
                "row": 1,
                "value": None,
                "type": None,
                "message_type": "error",
                "field_name": "value",
                "message": "required string is missing",
            }
        ],
        "errors": [{"code": 400, "message": "one or more inputs are invalid"}],
    }
    requests_mock.post(f"{SERVER_URL}/iocs/entities/indicators/v1", json=response, status_code=200)
    with pytest.raises(DemistoException) as excinfo:
        upload_custom_ioc_command(
            ioc_type="md5",
            value="testmd5",
            action="prevent",
            severity="high",
            platforms="mac,linux",
        )
    assert response["errors"] == excinfo.value.args[0]


def test_upload_custom_ioc_command_duplicate(requests_mock, mocker):
    """
    Test cs-falcon-upload-custom-ioc where it fails to create the ioc due to duplicate

    Given:
     - IOC of type domain to upload
    When:
     - The API fails to create an IOC to duplication warning
    Then:
     - Display error message to user
    """
    from CrowdStrikeFalcon import upload_custom_ioc_command

    ioc_type = "domain"
    ioc_value = "test.com"
    response = {
        "errors": [{"code": 400, "message": "One or more indicators have a warning or invalid input"}],
        "resources": [
            {
                "row": 1,
                "value": "test2.com",
                "type": "domain",
                "message_type": "warning",
                "message": f"Warning: Duplicate type: '{ioc_type}' and value: '{ioc_value}' combination.",
            }
        ],
    }
    requests_mock.post(
        f"{SERVER_URL}/iocs/entities/indicators/v1",
        json=response,
        status_code=400,
        reason="Bad Request",
    )
    with pytest.raises(DemistoException) as error_info:
        upload_custom_ioc_command(
            ioc_type=ioc_type,
            value=ioc_value,
            action="prevent",
            severity="high",
            platforms="mac,linux",
        )
    assert response["resources"][0]["message"] in str(error_info.value)


def test_upload_custom_ioc_command_filename(requests_mock):
    """
    Test that providing a filename to custom ioc works as expected

    Given:
        - A filename attached to a custom IOC

    When:
        - The user tries to upload a custom IOC with a filename

    Then:
        - Make sure that the filename is included in the request to CrowdStrike
    """
    from CrowdStrikeFalcon import upload_custom_ioc_command

    mock = requests_mock.post(f"{SERVER_URL}/iocs/entities/indicators/v1", status_code=200, json={"result": "ok"})

    upload_custom_ioc_command(
        action="prevent", severity="high", platforms="mac,linux", ioc_type="sha256", value="testsha256", file_name="test.txt"
    )

    body = mock.last_request.json()
    assert body["indicators"][0]["metadata"]["filename"] == "test.txt"


def test_upload_custom_ioc_command_filename_nosha5(requests_mock):
    """
    Test that providing a filename to non-hash custom ioc being ignored

    Given:
        - A filename attached to a custom non hash IOC

    When:
        - The user tries to upload a custom non hash IOC with a filename

    Then:
        - Make sure that the filename is ignored in the request to CrowdStrike
    """
    from CrowdStrikeFalcon import upload_custom_ioc_command

    mock = requests_mock.post(f"{SERVER_URL}/iocs/entities/indicators/v1", status_code=200, json={"result": "ok"})
    upload_custom_ioc_command(
        action="prevent", severity="high", platforms="mac,linux", ioc_type="ip", value="someip", file_name="test.txt"
    )
    assert "metadata" not in mock.last_request.json()["indicators"][0]


def test_upload_custom_ioc_command_mobile_action(requests_mock):
    """
    Test that providing a mobile_action to custom ioc works as expected

    Given:
        - A mobile_action attached to a custom IOC

    When:
        - The user tries to upload a custom IOC with a mobile_action

    Then:
        - Make sure that the mobile_action is included in the request to CrowdStrike
    """
    from CrowdStrikeFalcon import upload_custom_ioc_command

    mock = requests_mock.post(f"{SERVER_URL}/iocs/entities/indicators/v1", status_code=200, json={"result": "ok"})

    upload_custom_ioc_command(
        action="no_action", platforms="mac,android", ioc_type="domain", value="test.com", mobile_action="prevent"
    )

    body = mock.last_request.json()
    assert body["indicators"][0]["mobile_action"] == "prevent"


def test_upload_custom_ioc_command_no_mobile_action(requests_mock):
    """
    Test that not providing a mobile_action omits it from the request

    Given:
        - No mobile_action provided

    When:
        - The user tries to upload a custom IOC without a mobile_action

    Then:
        - Make sure that mobile_action is not included in the request to CrowdStrike
    """
    from CrowdStrikeFalcon import upload_custom_ioc_command

    mock = requests_mock.post(f"{SERVER_URL}/iocs/entities/indicators/v1", status_code=200, json={"result": "ok"})

    upload_custom_ioc_command(action="no_action", platforms="mac,linux", ioc_type="domain", value="test.com")

    body = mock.last_request.json()
    assert "mobile_action" not in body["indicators"][0]


def test_upload_custom_ioc_command_mobile_action_without_mobile_platform():
    """
    Test that providing mobile_action without a mobile platform raises an error

    Given:
        - A mobile_action provided without android or ios in platforms

    When:
        - The user tries to upload a custom IOC with mobile_action but only desktop platforms

    Then:
        - Raise a ValueError indicating a mobile platform is required
    """
    from CrowdStrikeFalcon import upload_custom_ioc_command

    with pytest.raises(ValueError, match="mobile_action requires a mobile platform"):  # noqa: E501
        upload_custom_ioc_command(
            action="no_action", platforms="windows,linux", ioc_type="domain", value="test.com", mobile_action="prevent"
        )


def test_lift_host_containment_command_default(requests_mock, mocker):
    """
    Test cs-falcon-lift-host-containment with default behavior (lift_containment)

    Given:
        - Host IDs to lift containment from

    When:
        - The user runs cs-falcon-lift-host-containment without lift_filesystem_containment_all

    Then:
        - The API is called with action_name=lift_containment
    """
    from CrowdStrikeFalcon import lift_host_containment_command

    mocker.patch("CrowdStrikeFalcon.demisto.args", return_value={"ids": "test_host_id"})
    mock = requests_mock.post(
        f"{SERVER_URL}/devices/entities/devices-actions/v2",
        json={"resources": [{"id": "test_host_id"}], "meta": {"query_time": 0.001}},
        status_code=200,
    )

    result = lift_host_containment_command()

    assert mock.last_request.qs["action_name"] == ["lift_containment"]
    assert "Containment has been lifted off host" in result["HumanReadable"]


def test_lift_host_containment_command_filesystem(requests_mock, mocker):
    """
    Test cs-falcon-lift-host-containment with lift_filesystem_containment_all=true

    Given:
        - Host IDs to lift filesystem containment from

    When:
        - The user runs cs-falcon-lift-host-containment with lift_filesystem_containment_all=true

    Then:
        - The API is called with action_name=lift_filesystem_containment_all
        - The human readable output mentions filesystem containment
    """
    from CrowdStrikeFalcon import lift_host_containment_command

    mocker.patch(
        "CrowdStrikeFalcon.demisto.args",
        return_value={"ids": "test_host_id", "lift_filesystem_containment_all": "true"},
    )
    mock = requests_mock.post(
        f"{SERVER_URL}/devices/entities/devices-actions/v2",
        json={"resources": [{"id": "test_host_id"}], "meta": {"query_time": 0.001}},
        status_code=200,
    )

    result = lift_host_containment_command()

    assert mock.last_request.qs["action_name"] == ["lift_filesystem_containment_all"]
    assert "Filesystem containment has been lifted off host" in result["HumanReadable"]


def test_update_custom_ioc_command(requests_mock):
    """
    Test cs-falcon-update-custom-ioc when an upload is successful

    Given:
     - The user tries to update an IOC
    When:
     - The server updates an IOC
    Then:
     - Ensure the request is sent as expected
     - Return a human readable result with appropriate message
     - Do populate the entry context with the right value
    """
    from CrowdStrikeFalcon import update_custom_ioc_command

    ioc_id = "4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r"
    ioc_response = {
        "resources": [
            {
                "id": ioc_id,
                "type": "md5",
                "value": "testmd5",
                "action": "prevent",
                "severity": "high",
                "description": "Eicar file",
                "created_on": "2020-10-01T09:09:04Z",
                "modified_on": "2020-10-01T09:09:04Z",
            }
        ]
    }
    updated_severity = "medium"

    def match_req_body(request):
        if request.json() == {"indicators": [{"id": ioc_id, "severity": updated_severity}]}:
            return True
        return None

    requests_mock.patch(
        f"{SERVER_URL}/iocs/entities/indicators/v1",
        json=ioc_response,
        status_code=200,
        additional_matcher=match_req_body,
    )

    results = update_custom_ioc_command(
        ioc_id=ioc_id,
        severity=updated_severity,
    )
    assert "Custom IOC was updated successfully" in results["HumanReadable"]
    assert results["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"][0]["Value"] == "testmd5"


def test_update_custom_ioc_command_filename(requests_mock):
    """
    Test that providing a filename to custom ioc works as expected

    Given:
        - A filename attached to a custom IOC

    When:
        - The user tries to update a custom IOC with a filename

    Then:
        - Make sure that the filename is included in the request to CrowdStrike
    """
    from CrowdStrikeFalcon import update_custom_ioc

    mock = requests_mock.patch(f"{SERVER_URL}/iocs/entities/indicators/v1", status_code=200, json={"result": "ok"})

    update_custom_ioc(ioc_id="3", file_name="test.txt")

    body = mock.last_request.json()
    assert body["indicators"][0]["metadata"]["filename"] == "test.txt"


def test_delete_custom_ioc_command(requests_mock):
    """
    Test cs-falcon-delete-custom-ioc where it deletes IOC successfully

    Given:
     - The user tries to delete an IOC
    When:
     - Running the command to delete an IOC
    Then:
     - Ensure expected output is returned
    """
    from CrowdStrikeFalcon import delete_custom_ioc_command

    ioc_id = "4f8c43311k1801ca4359fc07t319610482c2003mcde8934d5412b1781e841e9r"
    response = {"resources": [ioc_id], "errors": []}
    requests_mock.delete(f"{SERVER_URL}/iocs/entities/indicators/v1?ids={ioc_id}", json=response, status_code=200)
    command_res = delete_custom_ioc_command(ioc_id)
    assert f"Custom IOC {ioc_id} was successfully deleted." in command_res["HumanReadable"]


def test_get_ioc_device_count_command_does_not_exist(requests_mock, mocker):
    """
    Test cs-falcon-device-count-ioc with an unsuccessful query (doesn't exist)

    Given
     - There is no device with a process that ran md5:testmd5
    When
     - The user is running cs-falcon-device-count-ioc with md5:testmd5
    Then
     - Raise an error
    """
    from CrowdStrikeFalcon import get_ioc_device_count_command

    expected_error = [{"code": 404, "message": "md5:testmd5 - Resource Not Found"}]
    response = {"resources": [], "errors": expected_error}
    requests_mock.get(f"{SERVER_URL}/indicators/aggregates/devices-count/v1", json=response, status_code=404, reason="Not found")
    mocker.patch(RETURN_ERROR_TARGET)
    res = get_ioc_device_count_command(ioc_type="md5", value="testmd5")
    assert res == "No results found for md5 - testmd5"


def test_get_ioc_device_count_command_exists(requests_mock):
    """
    Test cs-falcon-device-count-ioc with a successful query

    Given
     - There is a device with a process that ran md5:testmd5
    When
     - The user is running cs-falcon-device-count-ioc with md5:testmd5
    Then
     - Return a human readable result with appropriate message
     - Do populate the entry context with the right value
    """
    from CrowdStrikeFalcon import get_ioc_device_count_command

    response = {"resources": [{"id": "md5:testmd5", "type": "md5", "value": "testmd5", "device_count": 1}]}
    requests_mock.get(
        f"{SERVER_URL}/indicators/aggregates/devices-count/v1",
        json=response,
        status_code=200,
    )
    result = get_ioc_device_count_command(ioc_type="md5", value="testmd5")
    assert result["HumanReadable"] == "Indicator of Compromise **md5:testmd5** device count: **1**"
    assert result["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"][0]["ID"] == "md5:testmd5"


def test_get_ioc_device_count_command_rate_limit_exceeded(requests_mock):
    """
    Test cs-falcon-device-count-ioc with rate limit exceeded

    Given
    - There is a rate limit in CS side
    When
    - The user is running cs-falcon-device-count-ioc with md5:testmd5
    Then
    - ensure the correct count is returned by the offset mechanism
    """
    from CrowdStrikeFalcon import get_ioc_device_count_command

    response = {
        "resources": [{"id": "md5:testmd5", "type": "md5", "value": "testmd5", "limit_exceeded": "true", "device_count": 1}]
    }
    indicators_queries_res = {"resources": ["res_1", "res_2", "res_3"]}
    indicators_queries_res_with_offset = indicators_queries_res | {"meta": {"pagination": {"offset": 1}}}
    requests_mock.get(
        f"{SERVER_URL}/indicators/aggregates/devices-count/v1",
        json=response,
        status_code=200,
    )
    requests_mock.get(
        f"{SERVER_URL}/indicators/queries/devices/v1",
        json=indicators_queries_res_with_offset,
        status_code=200,
    )
    requests_mock.get(
        f"{SERVER_URL}/indicators/queries/devices/v1?type=md5&value=testmd5&offset=1",
        json=indicators_queries_res,
        status_code=200,
    )

    res = get_ioc_device_count_command(ioc_type="md5", value="testmd5")

    assert "device count: **6**" in res["HumanReadable"]


def test_get_process_details_command_not_exists(requests_mock, mocker):
    """
    Test cs-falcon-process-details with an unsuccessful query (doesn't exist)

    Given
     - There is no device with a process `pid:fake:process`
    When
     - The user is running cs-falcon-process-details with pid:fake:process
    Then
     - Raise an error
    """
    from CrowdStrikeFalcon import get_process_details_command

    expected_error = [{"code": 404, "message": "pid:fake:process"}]
    response = {"resources": [], "errors": expected_error}
    requests_mock.get(
        f"{SERVER_URL}/processes/entities/processes/v1",
        json=response,
        status_code=200,
    )
    mocker.patch(RETURN_ERROR_TARGET)
    with pytest.raises(DemistoException) as excinfo:
        get_process_details_command(ids="pid:fake:process")
    assert expected_error == excinfo.value.args[0]


def test_get_process_details_command_exists(requests_mock):
    """
    Test cs-falcon-process-details with a successful query

    Given
     - There is a device with a process `pid:fake:process`
    When
     - The user is running cs-falcon-process-details with pid:fake:process
    Then
     - Return a human readable result with appropriate message
     - Do populate the entry context with the right value
    """
    from CrowdStrikeFalcon import get_process_details_command

    resources = {
        "device_id": "process",
        "command_line": "command_line",
        "start_timestamp": "2020-10-01T09:05:51Z",
        "start_timestamp_raw": "132460167512852140",
        "stop_timestamp": "2020-10-02T06:43:45Z",
        "stop_timestamp_raw": "132460946259334768",
    }
    response = {"resources": [resources]}
    requests_mock.get(
        f"{SERVER_URL}/processes/entities/processes/v1",
        json=response,
        status_code=200,
    )
    result = get_process_details_command(ids="pid:fake:process")
    assert "| command_line | process | 2020-10-01T09:05:51Z | 132460167512852140 |" in result["HumanReadable"]
    assert resources == result["EntryContext"]["CrowdStrike.Process(val.process_id === obj.process_id)"][0]


def test_get_proccesses_ran_on_command_exists(requests_mock):
    """
    Test cs-falcon-processes-ran-on with a successful query

    Given
     - There is a device with a process `pid:fake:process`
    When
     - The user is running cs-falcon-processes-ran-on with pid:fake:process
    Then
     - Return a human readable result with appropriate message
     - Do populate the entry context with the right value
    """
    from CrowdStrikeFalcon import get_proccesses_ran_on_command

    response = {"resources": ["pid:fake:process"]}
    requests_mock.get(
        f"{SERVER_URL}/indicators/queries/processes/v1",
        json=response,
        status_code=200,
    )
    result = get_proccesses_ran_on_command(ioc_type="test", value="mock", device_id="123")
    assert "### Processes with custom IOC test:mock on device 123." in result["HumanReadable"]
    assert "| pid:fake:process |" in result["HumanReadable"]

    expected_proc_result = {"DeviceID": "123", "ID": ["pid:fake:process"]}
    actual_proc_result = result["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"]["Process"]
    assert expected_proc_result == actual_proc_result


def test_get_proccesses_ran_on_command_not_exists(requests_mock):
    """
    Test cs-falcon-processes-ran-on with an unsuccessful query

    Given
     - There is no device with a process `pid:fake:process`
    When
     - The user is running cs-falcon-processes-ran-on with pid:fake:process
    Then
     - Raise an error
    """
    from CrowdStrikeFalcon import get_proccesses_ran_on_command

    expected_error = [{"code": 404, "message": "pid:fake:process - Resource Not Found"}]
    response = {"resources": [], "errors": expected_error}
    requests_mock.get(
        f"{SERVER_URL}/indicators/queries/processes/v1",
        json=response,
        status_code=200,
    )
    with pytest.raises(DemistoException) as excinfo:
        get_proccesses_ran_on_command(ioc_type="test", value="mock", device_id="123")
    assert expected_error == excinfo.value.args[0]


def test_search_device_command(requests_mock):
    """
    Test search_device_command with a successful id
    Given
     - There is a device that is found
    When
     - The user is running cs-falcon-search-device with an id
    Then
     - Return a CrowdStrike context output
     - Return an Endpoint context output
    """
    from CrowdStrikeFalcon import search_device_command

    response = {
        "resources": {
            "meta": {
                "query_time": 0.010188508,
                "pagination": {"offset": 1, "limit": 100, "total": 1},
                "powered_by": "device-api",
                "trace_id": "c876614b-da71-4942-88db-37b939a78eb3",
            },
            "resources": ["15dbb9d8f06b45fe9f61eb46e829d986"],
            "errors": [],
        }
    }
    device_context = {
        "ID": "15dbb9d8f06b45fe9f61eb46e829d986",
        "ExternalIP": "1.1.1.1",
        "MacAddress": "42-01-0a-80-00-07",
        "Hostname": "FALCON-CROWDSTR",
        "FirstSeen": "2020-02-10T12:40:18Z",
        "LastSeen": "2021-04-05T13:48:12Z",
        "LocalIP": "1.1.1.1",
        "OS": "Windows Server 2019",
        "Status": "normal",
    }
    endpoint_context = {
        "Hostname": "FALCON-CROWDSTR",
        "ID": "15dbb9d8f06b45fe9f61eb46e829d986",
        "IPAddress": "1.1.1.1",
        "MACAddress": "42-01-0a-80-00-07",
        "OS": "Windows",
        "OSVersion": "Windows Server 2019",
        "Status": "Offline",
        "Vendor": "CrowdStrike Falcon",
    }
    status_res = {
        "meta": {"query_time": 0.002455124, "powered_by": "device-api", "trace_id": "c876614b-da71-4942-88db-37b939a78eb3"},
        "resources": [
            {
                "id": "15dbb9d8f06b45fe9f61eb46e829d986",
                "cid": "20879a8064904ecfbb62c118a6a19411",
                "last_seen": "2022-09-03T10:48:12Z",
                "state": "offline",
            }
        ],
        "errors": [],
    }

    requests_mock.get(
        f"{SERVER_URL}/devices/queries/devices/v1",
        json=response,
        status_code=200,
    )
    requests_mock.post(
        f"{SERVER_URL}/devices/entities/devices/v2",
        json=test_data2,
        status_code=200,
    )

    requests_mock.get(
        f"{SERVER_URL}/devices/entities/online-state/v1",
        json=status_res,
        status_code=200,
    )

    outputs = search_device_command()
    result = outputs[0].to_context()

    context = result.get("EntryContext")
    for key, _value in context.items():
        if "Device" in key:
            assert context[key] == device_context
        if "Endpoint" in key:
            assert context[key] == [endpoint_context]


def test_get_endpoint_command(requests_mock, mocker):
    """
    Test get_endpint_command with a successful id
    Given
     - There is a device that is found
    When
     - The user is running cs-falcon-search-device with an id
    Then
     - Return an Endpoint context output
    """
    from CrowdStrikeFalcon import get_endpoint_command

    response = {
        "resources": {
            "meta": {
                "query_time": 0.010188508,
                "pagination": {"offset": 1, "limit": 100, "total": 1},
                "powered_by": "device-api",
                "trace_id": "c876614b-da71-4942-88db-37b939a78eb3",
            },
            "resources": ["15dbb9d8f06b45fe9f61eb46e829d986"],
            "errors": [],
        }
    }
    endpoint_context = {
        "Hostname": "FALCON-CROWDSTR",
        "ID": "15dbb9d8f06b45fe9f61eb46e829d986",
        "IPAddress": "1.1.1.1",
        "MACAddress": "42-01-0a-80-00-07",
        "OS": "Windows",
        "OSVersion": "Windows Server 2019",
        "Status": "Online",
        "Vendor": "CrowdStrike Falcon",
    }

    status_res = {
        "meta": {"query_time": 0.002455124, "powered_by": "device-api", "trace_id": "c876614b-da71-4942-88db-37b939a78eb3"},
        "resources": [
            {
                "id": "15dbb9d8f06b45fe9f61eb46e829d986",
                "cid": "20879a8064904ecfbb62c118a6a19411",
                "last_seen": "2022-09-03T10:48:12Z",
                "state": "online",
            }
        ],
        "errors": [],
    }

    requests_mock.get(
        f"{SERVER_URL}/devices/entities/online-state/v1",
        json=status_res,
        status_code=200,
    )

    query_mocker = requests_mock.get(
        f"{SERVER_URL}/devices/queries/devices/v1",
        json=response,
        status_code=200,
    )
    requests_mock.post(
        f"{SERVER_URL}/devices/entities/devices/v2",
        json=test_data2,
        status_code=200,
    )

    mocker.patch.object(demisto, "args", return_value={"id": "identifier_numbe", "hostname": "falcon-crowdstr"})

    outputs = get_endpoint_command()
    result = outputs[0].to_context()
    context = result.get("EntryContext")

    api_query = "filter=device_id:'identifier_numbe',hostname:'falcon-crowdstr'&limit=50&offset=0&sort="
    assert unquote(query_mocker.last_request.query) == api_query
    assert context["Endpoint(val.ID && val.ID == obj.ID && val.Vendor == obj.Vendor)"] == [endpoint_context]


def test_create_hostgroup_invalid(requests_mock, mocker):
    """
    Test Create hostgroup with valid args with unsuccessful args
    Given
     - Invalid arguments for hostgroup
    When
     - Calling create hostgroup command
    Then
     - Throw an error
    """
    from CrowdStrikeFalcon import create_host_group_command

    response_data = load_json("test_data/test_create_hostgroup_invalid_data.json")
    requests_mock.post(f"{SERVER_URL}/devices/entities/host-groups/v1", json=response_data, status_code=400, reason="Bad Request")
    with pytest.raises(DemistoException):
        create_host_group_command(
            name="dem test",
            description="dem des",
            group_type="static",
            assignment_rule="device_id:[''],hostname:['falcon-crowdstrike-sensor-centos7']",
        )


def test_update_hostgroup_invalid(requests_mock):
    """
    Test Create hostgroup with valid args with unsuccessful args
    Given
     - Invalid arguments for hostgroup
    When
     - Calling create hostgroup command
    Then
     - Throw an error
    """
    from CrowdStrikeFalcon import update_host_group_command

    response_data = load_json("test_data/test_create_hostgroup_invalid_data.json")
    requests_mock.patch(
        f"{SERVER_URL}/devices/entities/host-groups/v1", json=response_data, status_code=400, reason="Bad Request"
    )
    with pytest.raises(DemistoException):
        update_host_group_command(
            host_group_id="b1a0cd73ecab411581cbe467fc3319f5",
            name="dem test",
            description="dem des",
            assignment_rule="device_id:[''],hostname:['falcon-crowdstrike-sensor-centos7']",
        )


def test_list_host_group_members(requests_mock):
    """
    Test list host group members with not arguments given
    Given
     - No arguments given, as is
    When
     - Calling list_host_group_members_command
    Then
     - Return all the hosts
    """
    from CrowdStrikeFalcon import list_host_group_members_command

    test_list_hostgroup_members_data = load_json("test_data/test_list_hostgroup_members_data.json")
    requests_mock.get(
        f"{SERVER_URL}/devices/combined/host-group-members/v1", json=test_list_hostgroup_members_data, status_code=200
    )
    command_results = list_host_group_members_command()
    expected_results = load_json("test_data/expected_list_hostgroup_members_results.json")
    for expected_result, ectual_results in zip(expected_results, command_results.outputs):
        assert expected_result == ectual_results


def test_upload_batch_custom_ioc_command(requests_mock):
    """
    Test cs-falcon-batch-upload-custom-ioc when an upload of iocs batch is successful

    Given:
     - The user tries to create multiple IOCs
    When:
     - The server creates IOCs
    Then:
     - Return a human readable result with appropriate message
     - Do populate the entry context with the right values
    """
    from CrowdStrikeFalcon import upload_batch_custom_ioc_command

    ioc_response = {
        "meta": {
            "query_time": 0.132378491,
            "pagination": {"limit": 0, "total": 2},
            "powered_by": "ioc-manager",
            "trace_id": "121f377b-016a-4e34-bca7-992cec821ab3",
        },
        "errors": None,
        "resources": [
            {
                "id": "1196afeae04528228e782d4efc0c1d8257554dcd99552e1151ca3a3d2eed03f1",
                "type": "ipv4",
                "value": "8.9.6.8",
                "source": "Cortex XSOAR",
                "action": "no_action",
                "mobile_action": "no_action",
                "severity": "informational",
                "platforms": ["linux"],
                "expiration": "2022-02-16T11:41:01Z",
                "expired": False,
                "deleted": False,
                "applied_globally": True,
                "from_parent": False,
                "created_on": "2022-02-15T11:42:17.397548307Z",
                "created_by": "2bf188d347e44e08946f2e61ef590c24",
                "modified_on": "2022-02-15T11:42:17.397548307Z",
                "modified_by": "2bf188d347e44e08946f2e61ef590c24",
            },
            {
                "id": "1156f19c5a384117e7e6023f467ed3b58412ddd5d0591872f3a111335fae79a5",
                "type": "ipv4",
                "value": "4.5.8.6",
                "source": "Cortex XSOAR",
                "action": "no_action",
                "mobile_action": "no_action",
                "severity": "informational",
                "platforms": ["linux"],
                "expiration": "2022-02-16T11:40:47Z",
                "expired": False,
                "deleted": False,
                "applied_globally": True,
                "from_parent": False,
                "created_on": "2022-02-15T11:42:17.397548307Z",
                "created_by": "2bf188d347e44e08946f2e61ef590c24",
                "modified_on": "2022-02-15T11:42:17.397548307Z",
                "modified_by": "2bf188d347e44e08946f2e61ef590c24",
            },
        ],
    }

    requests_mock.post(
        f"{SERVER_URL}/iocs/entities/indicators/v1",
        json=ioc_response,
        status_code=200,
    )
    results = upload_batch_custom_ioc_command(json.dumps(IOCS_JSON_LIST))
    assert (
        "2022-02-16T11:41:01Z | 1196afeae04528228e782d4efc0c1d8257554dcd99552e1151ca3a3d2eed03f1 | "
        "no_action | 2bf188d347e44e08946f2e61ef590c24 | 2022-02-15T11:42:17.397548307Z | linux | informational | Cortex XSOAR "
        "| ipv4 | 8.9.6.8 |" in results[0]["HumanReadable"]
    )

    assert (
        "2022-02-16T11:40:47Z | 1156f19c5a384117e7e6023f467ed3b58412ddd5d0591872f3a111335fae79a5 | "
        "no_action | 2bf188d347e44e08946f2e61ef590c24 | 2022-02-15T11:42:17.397548307Z | linux | informational | Cortex XSOAR "
        "| ipv4 | 4.5.8.6 |" in results[1]["HumanReadable"]
    )

    assert results[0]["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"][0]["Value"] == "8.9.6.8"
    assert results[1]["EntryContext"]["CrowdStrike.IOC(val.ID === obj.ID)"][0]["Value"] == "4.5.8.6"


@pytest.mark.parametrize(
    "endpoint_status, status, is_isolated",
    [
        ("Normal", "Online", ""),
        ("normal", "Online", ""),
        ("containment_pending", "", "Pending isolation"),
        ("contained", "", "Yes"),
        ("lift_containment_pending", "", "Pending unisolation"),
    ],
)
def test_get_isolation_status(endpoint_status, status, is_isolated):
    """
    Test valid call for generate status field
    Given
     - valid status
    When
     - Calling generate_status_field function
    Then
     - Return status and is_isolated
    """
    from CrowdStrikeFalcon import get_isolation_status

    assert is_isolated == get_isolation_status(endpoint_status)


def test_get_isolation_status_invalid():
    """
    Test invalid call for generate status field
    Given
     - invalid status
    When
     - Calling generate_status_field function
    Then
     - Raise an exception
    """
    from CrowdStrikeFalcon import get_isolation_status

    with pytest.raises(DemistoException):
        get_isolation_status("unknown status")


def test_parse_rtr_command_response_host_exists_stderr_output():
    from CrowdStrikeFalcon import parse_rtr_command_response

    response_data = load_json("test_data/rtr_outputs_with_stderr.json")
    parsed_result = parse_rtr_command_response(response_data, ["1"])
    assert len(parsed_result) == 1
    assert parsed_result[0].get("HostID") == "1"
    assert parsed_result[0].get("Error") == "Cannot find a process with the process identifier 5260."


def test_parse_rtr_command_response_host_exists_error_output():
    from CrowdStrikeFalcon import parse_rtr_command_response

    response_data = load_json("test_data/rtr_outputs_with_error.json")
    parsed_result = parse_rtr_command_response(response_data, ["1"])
    assert len(parsed_result) == 1
    assert parsed_result[0].get("HostID") == "1"
    assert parsed_result[0].get("Error") == "Some error"


def test_parse_rtr_command_response_host_not_exist():
    from CrowdStrikeFalcon import parse_rtr_command_response

    response_data = load_json("test_data/rtr_outputs_host_not_exist.json")
    parsed_result = parse_rtr_command_response(response_data, ["1", "2"])
    assert len(parsed_result) == 2
    for res in parsed_result:
        if res.get("HostID") == "1":
            assert res.get("Error") == "Success"
        elif res.get("HostID") == "2":
            assert res.get("Error") == "The host ID was not found."


def test_parse_rtr_stdout_response(mocker):
    from CrowdStrikeFalcon import parse_rtr_stdout_response

    response_data = load_json("test_data/rtr_list_processes_response.json")
    mocker.patch(
        "CrowdStrikeFalcon.fileResult",
        return_value={"Contents": "", "ContentsFormat": "text", "Type": 3, "File": "netstat-1", "FileID": "c"},
    )
    parsed_result = parse_rtr_stdout_response(["1"], response_data, "netstat")
    assert parsed_result[0][0].get("Stdout") == "example stdout"
    assert parsed_result[0][0].get("FileName") == "netstat-1"
    assert parsed_result[1][0].get("File") == "netstat-1"


@pytest.mark.parametrize(
    "failed_devices, all_requested_devices, expected_result",
    [
        ({}, ["id1", "id2"], ""),
        (
            {"id1": "some error"},
            ["id1", "id2"],
            "Note: you don't see the following IDs in the results as the request was"
            " failed for them. \nID id1 failed as it was not found. \n",
        ),
    ],
)
def test_add_error_message(failed_devices, all_requested_devices, expected_result):
    from CrowdStrikeFalcon import add_error_message

    assert add_error_message(failed_devices, all_requested_devices) == expected_result


@pytest.mark.parametrize(
    "failed_devices, all_requested_devices",
    [
        ({"id1": "some error", "id2": "some error"}, ["id1", "id2"]),
        ({"id1": "some error1", "id2": "some error2"}, ["id1", "id2"]),
    ],
)
def test_add_error_message_raise_error(failed_devices, all_requested_devices):
    from CrowdStrikeFalcon import add_error_message

    with pytest.raises(DemistoException, match=f"CrowdStrike Falcon The command was failed with the errors: {failed_devices}"):
        add_error_message(failed_devices, all_requested_devices)


def test_rtr_kill_process_command(mocker):
    from CrowdStrikeFalcon import rtr_kill_process_command

    mocker.patch("CrowdStrikeFalcon.init_rtr_batch_session", return_value="1")
    response_data = load_json("test_data/rtr_general_response.json")
    args = {"host_id": "1", "process_ids": "2,3"}
    mocker.patch("CrowdStrikeFalcon.execute_run_batch_write_cmd_with_timer", return_value=response_data)
    parsed_result = rtr_kill_process_command(args).outputs
    for res in parsed_result:
        assert res.get("Error") == "Success"


@pytest.mark.parametrize(
    "operating_system, expected_result",
    [
        ("Windows", "rm 'test.txt' --force"),
        ("Linux", "rm 'test.txt' -r -d"),
        ("Mac", "rm 'test.txt' -r -d"),
        ("bla", ""),
    ],
)
def test_match_remove_command_for_os(operating_system, expected_result):
    from CrowdStrikeFalcon import match_remove_command_for_os

    assert match_remove_command_for_os(operating_system, "test.txt") == expected_result


def test_rtr_remove_file_command(mocker):
    from CrowdStrikeFalcon import rtr_remove_file_command

    mocker.patch("CrowdStrikeFalcon.init_rtr_batch_session", return_value="1")
    response_data = load_json("test_data/rtr_general_response.json")
    args = {"host_ids": "1", "file_path": "c:\\test", "os": "Windows"}
    mocker.patch("CrowdStrikeFalcon.execute_run_batch_write_cmd_with_timer", return_value=response_data)
    parsed_result = rtr_remove_file_command(args).outputs
    for res in parsed_result:
        assert res.get("Error") == "Success"


def test_rtr_read_registry_keys_command(mocker):
    from CrowdStrikeFalcon import rtr_read_registry_keys_command

    mocker.patch("CrowdStrikeFalcon.init_rtr_batch_session", return_value="1")
    response_data = load_json("test_data/rtr_general_response.json")
    args = {"host_ids": "1", "registry_keys": "key", "os": "Windows"}
    mocker.patch("CrowdStrikeFalcon.execute_run_batch_write_cmd_with_timer", return_value=response_data)
    mocker.patch(
        "CrowdStrikeFalcon.fileResult",
        return_value={"Contents": "", "ContentsFormat": "text", "Type": 3, "File": "netstat-1", "FileID": "c"},
    )
    parsed_result = rtr_read_registry_keys_command(args)
    assert len(parsed_result) == 2
    assert "reg-1key" in parsed_result[0].readable_output


@pytest.mark.parametrize(
    "remote_id, close_incident, detection_status, mirrored_object, entries",
    [args for args in input_data.get_remote_data_command_args if args[0] == input_data.remote_detection_id],
)
def test_get_remote_data_command(mocker, remote_id, close_incident, detection_status, mirrored_object, entries):
    """
    Given
        - arguments - id and lastUpdate time set to a lower than detection modification time
        - a raw update (get_detections_entities results)
        - the state of the detection in CrowdStrike Falcon
    When
        - running get_remote_data_command with changes to make
    Then
        - the mirrored_object in the GetRemoteDataResponse contains the modified detection fields
        - the entries in the GetRemoteDataResponse contain expected entries (a detection closure/reopen entry when needed)
    """
    from CrowdStrikeFalcon import get_remote_data_command

    detection_entity = input_data.response_detection.copy()
    detection_entity["status"] = detection_status
    mocker.patch("CrowdStrikeFalcon.get_detections_entities", return_value={"resources": [detection_entity]})
    reopen_statuses = "New,In progress,True positive,False positive,Reopened,Ignored"
    mocker.patch.object(demisto, "params", return_value={"close_incident": close_incident, "reopen_statuses": reopen_statuses})

    result = get_remote_data_command({"id": remote_id, "lastUpdate": "2022-03-08T08:17:09Z"})
    assert result.mirrored_object == mirrored_object
    assert result.entries == entries


def test_get_remote_data_command_deprecated_incident(mocker):
    """
    Given:
        - A remote incident ID with 'inc:' prefix (deprecated Endpoint Incident).
    When:
        - Running get_remote_data_command.
    Then:
        - The function returns an empty GetRemoteDataResponse without raising an exception.
        - A debug log is emitted about the deprecated incident type.
    """
    from CrowdStrikeFalcon import get_remote_data_command

    debug_mock = mocker.patch.object(demisto, "debug")
    mocker.patch.object(demisto, "params", return_value={"close_incident": False, "reopen_statuses": ""})

    result = get_remote_data_command({"id": "inc:abc123:def456", "lastUpdate": "2022-03-08T08:17:09Z"})

    assert result.entries == []
    assert "in_mirror_error" not in result.mirrored_object
    assert any("deprecated Endpoint Incident" in str(call) for call in debug_mock.call_args_list)


def test_find_incident_type():
    """
    Given
        - a detection ID on the remote system
    When
        - running get_remote_data_command or update_remote_system_command when we want to know the relevant incident type
    Then
        - returns the right incident type
    """
    from CrowdStrikeFalcon import IncidentType, find_incident_type

    assert find_incident_type(input_data.remote_detection_id) == IncidentType.LEGACY_ENDPOINT_DETECTION
    assert find_incident_type(input_data.remote_ngsiem_detection_id) == IncidentType.NGSIEM_DETECTION
    assert find_incident_type(input_data.remote_third_party_detection_id) == IncidentType.THIRD_PARTY
    assert find_incident_type("") is None


def test_get_remote_detection_data(mocker):
    """
    Given
        - a detection ID on the remote system
    When
        - running get_remote_data_command with changes to make on a detection
    Then
        - returns the relevant detection entity from the remote system with the relevant incoming mirroring fields
    """
    from CrowdStrikeFalcon import get_remote_detection_data

    detection_entity = input_data.response_detection.copy()
    mocker.patch("CrowdStrikeFalcon.get_detections_entities", return_value={"resources": [detection_entity.copy()]})
    mirrored_data, updated_object = get_remote_detection_data(input_data.remote_detection_id)
    detection_entity["severity"] = 0  # severity won't change in this case because we are using
    # the new field "severity_name" and not the "max_severity_displayname" as was in legacy
    assert mirrored_data == detection_entity
    assert updated_object == {  # since we removed legacy support, the mapping for the args doesn't exist
        "status": "new",
        "device.hostname": "FALCON-CROWDSTR",
        "incident_type": "detection",
    }


def test_get_remote_detection_data_for_multiple_types__idp(mocker):
    """
    Given
        - an idp detection ID on the remote system
    When
        - running get_remote_data_command with changes to make on a detection
    Then
        - returns the relevant detection entity from the remote system with the relevant incoming mirroring fields
    """
    from CrowdStrikeFalcon import get_remote_detection_data_for_multiple_types

    detection_entity = input_data.response_idp_detection.copy()
    mocker.patch("CrowdStrikeFalcon.get_detection_entities", return_value={"resources": [detection_entity.copy()]})
    mocker.patch.object(demisto, "debug", return_value=None)
    mirrored_data, updated_object, detection_type = get_remote_detection_data_for_multiple_types(
        input_data.remote_idp_detection_id
    )
    detection_entity["severity"] = 2
    assert mirrored_data == detection_entity
    assert detection_type == "IDP"
    assert updated_object == {
        "incident_type": "IDP detection",
        "status": "closed",
        "id": "ind:20879a8064904ecfbb62c118a6a19411:C0BB6ACD-8FDC-4CBA-9CF9-EBF3E28B3E56",
        "assigned_to_uid": "1",
        "comments": [{"falcon_user_id": "1", "timestamp": "2025-06-10T10:39:02.408980782Z", "value": "1"}],
        "tags": ["tag"],
    }


def test_get_remote_detection_data_for_multiple_types__mobile_detection(mocker):
    """
    Given
        - an idp detection ID on the remote system
    When
        - running get_remote_data_command with changes to make on a detection
    Then
        - returns the relevant detection entity from the remote system with the relevant incoming mirroring fields
    """
    from CrowdStrikeFalcon import get_remote_detection_data_for_multiple_types

    detection_entity = input_data.response_mobile_detection.copy()
    mocker.patch("CrowdStrikeFalcon.get_detection_entities", return_value={"resources": [detection_entity.copy()]})
    mocker.patch.object(demisto, "debug", return_value=None)
    mirrored_data, updated_object, detection_type = get_remote_detection_data_for_multiple_types(
        input_data.remote_mobile_detection_id
    )
    detection_entity["severity"] = 90
    assert mirrored_data == detection_entity
    assert detection_type == "Mobile"
    assert updated_object == {"incident_type": "MOBILE detection", "status": "new", "mobile_detection_id": "1111111111111111111"}


def test_get_remote_detection_data_for_multiple_types__endpoint_detection(mocker):
    """
    Given
        - an endpoint detection ID on the remote system
    When
        - running get_remote_data_command with changes to make on a detection
    Then
        - returns the relevant detection entity from the remote system with the relevant incoming mirroring fields
    """
    from CrowdStrikeFalcon import get_remote_detection_data_for_multiple_types

    detection_entity = input_data.response_detection_new_version.copy()
    mocker.patch("CrowdStrikeFalcon.get_detection_entities", return_value={"resources": [detection_entity.copy()]})
    mocker.patch.object(demisto, "debug", return_value=None)
    mirrored_data, updated_object, detection_type = get_remote_detection_data_for_multiple_types(
        input_data.remote_detection_id_new_version
    )
    detection_entity["severity"] = 90
    assert mirrored_data == detection_entity
    assert detection_type == "Detection"
    assert updated_object == {"incident_type": "detection", "status": "new", "severity": 90}


@pytest.mark.parametrize(
    "detection_type, incident_type, entity_modifications",
    [
        ("ngsiem", "ngsiem_detection", {}),
        ("Detection", "detection", {"type": "ldt", "product": "epp"}),
        ("ofp", "OFP detection", {"type": "ofp"}),
        ("ods", "On-Demand Scans detection", {"type": "ods", "product": "epp"}),
    ],
)
def test_get_remote_detection_data_for_multiple_types(mocker, detection_type, incident_type, entity_modifications):
    """
    Given
        - an endpoint detection ID on the remote system
    When
        - running get_remote_data_command with changes to make on a detection
    Then
        - returns the relevant detection entity from the remote system with the relevant incoming mirroring fields
    """
    from CrowdStrikeFalcon import get_remote_detection_data_for_multiple_types

    generic_detection_id = input_data.remote_ngsiem_detection_id

    detection_entity = input_data.response_ngsiem_detection.copy()
    detection_entity.update(entity_modifications)

    mocker.patch("CrowdStrikeFalcon.get_detection_entities", return_value={"resources": [detection_entity.copy()]})
    mocker.patch.object(demisto, "debug", return_value=None)

    mirrored_data, updated_object, returned_detection_type = get_remote_detection_data_for_multiple_types(generic_detection_id)

    assert mirrored_data == detection_entity
    assert returned_detection_type == detection_type
    assert updated_object == {
        "incident_type": incident_type,
        "status": mirrored_data["status"],
        "severity": mirrored_data["severity"],
        "tactic": mirrored_data["tactic"],
        "technique": mirrored_data["technique"],
        "composite_id": mirrored_data["composite_id"],
        "display_name": mirrored_data["display_name"],
        "tags": mirrored_data["tags"],
        "comments": mirrored_data["comments"],
        "assigned_to_uid": mirrored_data["assigned_to_uid"],
    }


@pytest.mark.parametrize(
    "function_name, num_ids, expected_calls",
    [
        # These are two different functions - calling both of them to verify the call architecture applies to both.
        ("get_detections_entities", 0, 0),  # Edge case: no IDs
        ("get_detections_entities", 500, 1),  # Less than the limit
        ("get_detections_entities", 1000, 1),  # Exactly the limit
        ("get_detections_entities", 1001, 2),  # More than the limit (specific request)
        ("get_detections_entities", 2500, 3),  # More than the limit (many calls)
        ("get_detection_entities", 0, 0),
        ("get_detection_entities", 500, 1),
        ("get_detection_entities", 1000, 1),
        ("get_detection_entities", 1001, 2),
        ("get_detection_entities", 2500, 3),
    ],
)
def test_get_detections_entities_batches_requests(mocker, function_name, num_ids, expected_calls):
    """
    Given
        - Number of ID's to fetch from the CrowdStrike Falcon Entity API.
    When
        - Running fetch detections entities functions with the ID's list
    Then
        - Return the number of calls to http_request based on the number of IDs provided.
        - Return the number of resources based on the number of IDs provided.
    """
    import CrowdStrikeFalcon

    mock_http_request = mocker.patch.object(CrowdStrikeFalcon, "http_request")

    # Configure a side effect to return responses with the correct number of resources
    def side_effect(method, url, data):
        data_dict = json.loads(data)
        ids_in_batch = data_dict.get("composite_ids") or data_dict.get("ids")
        return {"meta": {"trace_id": "test_trace"}, "resources": [{"id": i} for i in ids_in_batch]}

    mock_http_request.side_effect = side_effect

    # Get the function to test dynamically
    function_to_test = getattr(CrowdStrikeFalcon, function_name)

    # Load and slice the IDs list from the JSON file
    id_list_full = load_json("./test_data/mock_detections_id_list.json").get("Ids", {})[:num_ids]
    detections_ids = id_list_full[:num_ids]

    # Call the function with the mock IDs
    result = function_to_test(detections_ids)

    # Check that the number of http_request calls matches the expectation
    assert mock_http_request.call_count == expected_calls

    # Check that the number of resources returned matches the number of IDs
    if "resources" in result:
        assert len(result["resources"]) == num_ids


@pytest.mark.parametrize("updated_object", input_data.check_reopen_set_xsoar_incident_entries_args)
def test_set_xsoar_entries__reopen(mocker, updated_object):
    """
    Given
        - the incident status from the remote system
        - the close_incident parameter that was set when setting the integration
        - the reopen statuses set.
    When
        - running get_remote_data_command with changes to make on an incident
    Then
        - add the relevant entries only if the status is Reopened.
    """
    from CrowdStrikeFalcon import set_xsoar_entries

    mocker.patch.object(demisto, "params", return_value={"close_incident": True})
    mocker.patch.object(demisto, "debug", return_value=None)
    entries = []
    reopen_statuses = ["Reopened"]  # Add a reopen entry only if the status in CS Falcon is reopened
    set_xsoar_entries(updated_object, entries, input_data.remote_incident_id, "Incident", reopen_statuses)
    if updated_object.get("status") == "Reopened":
        assert "dbotIncidentReopen" in entries[0].get("Contents")
    else:
        assert entries == []


@pytest.mark.parametrize("updated_object", input_data.check_reopen_set_xsoar_incident_entries_args)
def test_set_xsoar_entries__empty(mocker, updated_object):
    """
    Given
        - the incident status from the remote system
        - the close_incident parameter that was set when setting the integration
        - empty reopen statuses set.
    When
        - running get_remote_data_command with reopen_statuses = []
    Then
        - A reopen entry wasn't added in any case.
    """
    from CrowdStrikeFalcon import set_xsoar_entries

    mocker.patch.object(demisto, "params", return_value={"close_incident": True})
    mocker.patch.object(demisto, "debug", return_value=None)
    entries = []
    reopen_statuses = []  # don't add a reopen entry in any case
    set_xsoar_entries(updated_object, entries, input_data.remote_incident_id, "Incident", reopen_statuses)
    assert entries == []


@pytest.mark.parametrize("updated_object", input_data.check_reopen_set_xsoar_detections_entries_args)
def test_set_xsoar_detection_entries_empty_check(mocker, updated_object):
    """
    Given
        - the incident status from the remote system
        - the close_incident parameter that was set when setting the integration
        - empty reopen statuses set.
    When
        - running get_remote_data_command with changes to make on a detection
    Then
        - add the relevant entries only if the status is Reopened.
    """
    from CrowdStrikeFalcon import set_xsoar_entries

    mocker.patch.object(demisto, "params", return_value={"close_incident": True})
    mocker.patch.object(demisto, "debug", return_value=None)
    entries = []
    reopen_statuses = []  # don't add a reopen entry in any case
    set_xsoar_entries(updated_object, entries, input_data.remote_detection_id, "Detection", reopen_statuses)
    assert entries == []


@pytest.mark.parametrize("updated_object", input_data.set_xsoar_idp_or_mobile_detection_entries)
def test_set_xsoar_entries___idp_or_mobile_detection(mocker, updated_object):
    """
    Given
        - the incident status from the remote system
        - the close_incident parameter that was set when setting the integration
        - the reopen statuses set.
    When
        - running get_remote_data_command with changes to make on a detection
    Then
        - add the relevant entries only if the status is Reopened.
    """
    from CrowdStrikeFalcon import set_xsoar_entries

    mocker.patch.object(demisto, "params", return_value={"close_incident": True})
    mocker.patch.object(demisto, "debug", return_value=None)
    entries = []
    reopen_statuses = ["Reopened"]  # Add a reopen entry only if the status in CS Falcon is reopened
    set_xsoar_entries(updated_object, entries, input_data.remote_idp_detection_id, "IDP", reopen_statuses)
    if updated_object.get("status") == "reopened":
        assert "dbotIncidentReopen" in entries[0].get("Contents")
    elif updated_object.get("status") == "closed":
        assert "dbotIncidentClose" in entries[0].get("Contents")
        assert "closeReason" in entries[0].get("Contents")
        assert entries[0].get("Contents", {}).get("closeReason") == "IDP was closed on CrowdStrike Falcon"
    else:
        assert entries == []


@pytest.mark.parametrize("updated_object", input_data.set_xsoar_idp_or_mobile_detection_entries)
def test_set_xsoar_entries__empty_reopen_statuses(mocker, updated_object):
    """
    Given
        - the incident status from the remote system
        - the close_incident parameter that was set when setting the integration
        - empty reopen statuses set.
    When
        - running get_remote_data_command with changes to make on a detection
    Then
        - add the relevant entries.
    """
    from CrowdStrikeFalcon import set_xsoar_entries

    mocker.patch.object(demisto, "params", return_value={"close_incident": True})
    mocker.patch.object(demisto, "debug", return_value=None)
    entries = []
    reopen_statuses = []  # don't add a reopen entry in any case
    set_xsoar_entries(updated_object, entries, input_data.remote_idp_detection_id, "IDP", reopen_statuses)
    if updated_object.get("status") == "closed":
        assert "dbotIncidentClose" in entries[0].get("Contents")
        assert "closeReason" in entries[0].get("Contents")
        assert entries[0].get("Contents", {}).get("closeReason") == "IDP was closed on CrowdStrike Falcon"
    else:
        assert entries == []


@pytest.mark.parametrize("updated_object, mirrored_data, mirroring_fields, output", input_data.set_updated_object_args)
def test_set_updated_object(updated_object, mirrored_data, mirroring_fields, output):
    """
    Given
        - an entity from the remote system
        - the relevant incoming mirroring fields
    When
        - get-remote-data command runs when mirroring in and determines what the updated object is
    Then
        - the updated object is set correctly, also for nested mirroring fields
    """
    from CrowdStrikeFalcon import set_updated_object

    set_updated_object(updated_object, mirrored_data, mirroring_fields)
    assert updated_object == output


def test_get_modified_remote_data_command(mocker):
    """
    Given
        - arguments - lastUpdate time
        - raw detections and idp_detection (results of get_fetch_detections and get_detections_ids)
    When
        - running get_modified_remote_data_command
    Then
        - returns a list of detections and idp detections IDs that were modified since the lastUpdate time
    """
    from CrowdStrikeFalcon import get_modified_remote_data_command

    mock_get_detections = mocker.patch(
        "CrowdStrikeFalcon.get_fetch_detections", return_value={"resources": [input_data.remote_detection_id]}
    )
    last_update = "2022-03-08T08:17:09Z"
    result = get_modified_remote_data_command({"lastUpdate": last_update})
    assert mock_get_detections.call_args.kwargs["last_updated_timestamp"] == last_update
    assert result.modified_incident_ids == [input_data.remote_detection_id]


@pytest.mark.parametrize("status", ["new", "in_progress", "closed", "reopened"])
def test_update_detection_request_good__legacy(mocker, status):
    """
    Given
        - list of detections IDs
        - status to change for the given detection in the remote system, which is one of the permitted statuses
    When
        - running update_remote_system_command
    Then
        - the resolve_detection command is called successfully with the right arguments
    """
    from CrowdStrikeFalcon import update_detection_request

    mock_resolve_detection = mocker.patch("CrowdStrikeFalcon.resolve_detection")
    update_detection_request([input_data.remote_detection_id], status)
    assert mock_resolve_detection.call_args[1]["ids"] == [input_data.remote_detection_id]
    assert mock_resolve_detection.call_args[1]["status"] == status


@pytest.mark.parametrize("status", ["new", "in_progress", "closed", "reopened"])
def test_update_detection_request_good(mocker, status):
    """
    Given
        - list of detections IDs
        - status to change for the given detection in the remote system, which is one of the permitted statuses
    When
        - running update_remote_system_command
    Then
        - the resolve_detection command is called successfully with the right arguments
    """
    from CrowdStrikeFalcon import update_detection_request

    mock_resolve_detection = mocker.patch("CrowdStrikeFalcon.resolve_detection")
    update_detection_request([input_data.remote_detection_id], status)
    assert mock_resolve_detection.call_args[1]["ids"] == [input_data.remote_detection_id]
    assert mock_resolve_detection.call_args[1]["status"] == status


@pytest.mark.parametrize("status", ["other", ""])
def test_update_detection_request_bad__lagacy(status):
    """
    Given
        - list of detections IDs
        - status to change for the given detection in the remote system, which is not one of the permitted statuses
    When
        - running update_remote_system_command
    Then
        - an exception is raised
    """
    from CrowdStrikeFalcon import update_detection_request

    with pytest.raises(DemistoException) as de:
        update_detection_request([input_data.remote_detection_id], status)
    assert "CrowdStrike Falcon Error" in str(de.value)


@pytest.mark.parametrize("status", ["true_positive", ""])
def test_update_detection_request_bad(status, mocker):
    """
    Given
        - list of detections IDs
        - status to change for the given detection in the remote system, which is not one of the permitted statuses
            'true_positive' is not a valid status for the new version of the API
    When
        - running update_remote_system_command
    Then
        - an exception is raised
    """
    from CrowdStrikeFalcon import update_detection_request

    with pytest.raises(DemistoException) as de:
        update_detection_request([input_data.remote_detection_id], status)
    assert "CrowdStrike Falcon Error" in str(de.value)


@pytest.mark.parametrize(
    "remote_id, expected_mock",
    [
        (input_data.remote_detection_id, "update_remote_detection"),
        (input_data.remote_idp_detection_id, "update_remote_for_multiple_detection_types"),
        (input_data.remote_ngsiem_detection_id, "update_remote_for_multiple_detection_types"),
        (input_data.remote_third_party_detection_id, "update_remote_for_multiple_detection_types"),
    ],
)
def test_update_remote_system_command(mocker, remote_id, expected_mock):
    """
    Given:
        - A remote incident ID of various types (legacy detection, IDP, NGSIEM, third-party).
    When:
        - Running update_remote_system_command with incident_changed=True and a status delta.
    Then:
        - The correct downstream update function is called based on the incident type.
        - The function returns the remote_incident_id.
    """
    from CrowdStrikeFalcon import update_remote_system_command

    mock_func = mocker.patch(f"CrowdStrikeFalcon.{expected_mock}")
    mocker.patch.object(demisto, "params", return_value={"close_in_cs_falcon": True})

    args = {
        "remoteId": remote_id,
        "incidentChanged": True,
        "data": {"status": "closed"},
        "status": 2,
        "delta": {"status": "closed"},
    }

    result = update_remote_system_command(args)

    assert result == remote_id
    assert mock_func.called


def test_update_remote_system_command_no_change(mocker):
    """
    Given:
        - A remote incident ID with incident_changed=False.
    When:
        - Running update_remote_system_command.
    Then:
        - No downstream update function is called.
        - The function returns the remote_incident_id.
    """
    from CrowdStrikeFalcon import update_remote_system_command

    mock_detection = mocker.patch("CrowdStrikeFalcon.update_remote_detection")
    mock_multiple = mocker.patch("CrowdStrikeFalcon.update_remote_for_multiple_detection_types")
    mocker.patch.object(demisto, "params", return_value={"close_in_cs_falcon": True})

    remote_id = input_data.remote_detection_id
    args = {
        "remoteId": remote_id,
        "incidentChanged": False,
        "data": {},
        "status": 1,
        "delta": {},
    }

    result = update_remote_system_command(args)

    assert result == remote_id
    assert not mock_detection.called
    assert not mock_multiple.called


def test_update_remote_system_command_deprecated_incident(mocker):
    """
    Given:
        - A remote incident ID with 'inc:' prefix (deprecated Endpoint Incident) and incident_changed=True.
    When:
        - Running update_remote_system_command.
    Then:
        - The function returns the remote_incident_id without raising an exception.
        - No downstream update functions are called.
        - A debug log is emitted about the deprecated incident type.
    """
    from CrowdStrikeFalcon import update_remote_system_command

    debug_mock = mocker.patch.object(demisto, "debug")
    mock_detection = mocker.patch("CrowdStrikeFalcon.update_remote_detection")
    mock_multiple = mocker.patch("CrowdStrikeFalcon.update_remote_for_multiple_detection_types")
    mocker.patch.object(demisto, "params", return_value={"close_in_cs_falcon": True})

    remote_id = "inc:abc123:def456"
    args = {
        "remoteId": remote_id,
        "incidentChanged": True,
        "data": {"status": "closed"},
        "status": 2,
        "delta": {"status": "closed"},
    }

    result = update_remote_system_command(args)

    assert result == remote_id
    assert not mock_detection.called
    assert not mock_multiple.called
    assert any("deprecated Endpoint Incident" in str(call) for call in debug_mock.call_args_list)


@pytest.mark.parametrize("delta, close_in_cs_falcon_param, to_close", input_data.close_in_cs_falcon_args)
def test_close_in_cs_falcon(mocker, delta, close_in_cs_falcon_param, to_close):
    """
    Given
        - incident or detection changes (one of the mirroring field changed or it was closed in XSOAR)
        - the close_in_cs_falcon parameter that was set when setting the integration
    When
        - outgoing mirroring triggered by a change in the incident/detection
    Then
        - returns true if the incident/detection was closed in XSOAR and the close_in_cs_falcon parameter was set to true
    """
    from CrowdStrikeFalcon import close_in_cs_falcon

    mocker.patch.object(demisto, "params", return_value={"close_in_cs_falcon": close_in_cs_falcon_param})
    assert close_in_cs_falcon(delta) == to_close


@pytest.mark.parametrize(
    "delta, inc_status, close_in_cs_falcon, detection_request_status", input_data.update_remote_detection_args
)
def test_update_remote_detection(mocker, delta, inc_status, close_in_cs_falcon, detection_request_status):
    """
    Given
        - detection changes (one of the mirroring field changed or it was closed in XSOAR)
        - arguments - delta (the change in the relevant fields), XSOAR status and remote detection id
    When
        - outgoing mirroring triggered by a change in the detection
    Then
        - the relevant detection is updated with the corresponding fields in the remote system
    """
    from CrowdStrikeFalcon import update_remote_detection

    mocker.patch.object(demisto, "params", return_value={"close_in_cs_falcon": close_in_cs_falcon})
    mock_update_detection_request = mocker.patch("CrowdStrikeFalcon.update_detection_request")
    update_remote_detection(delta, inc_status, input_data.remote_detection_id)
    if detection_request_status:
        assert mock_update_detection_request.call_args[0][1] == detection_request_status
    else:
        assert mock_update_detection_request.call_count == 0


def test_get_mapping_fields_command(mocker):
    """
    Given
        - nothing
    When
        - running get_mapping_fields_command on the new version of the API
    Then
        - the result fits the expected mapping scheme
    """
    from CrowdStrikeFalcon import get_mapping_fields_command

    result = get_mapping_fields_command()
    assert result.scheme_types_mappings[0].type_name == "CrowdStrike Falcon Detection"
    assert result.scheme_types_mappings[0].fields.keys() == {"status"}
    assert result.scheme_types_mappings[1].type_name == "CrowdStrike Falcon OFP Detection"
    assert result.scheme_types_mappings[1].fields.keys() == {"status"}
    assert result.scheme_types_mappings[2].type_name == "CrowdStrike Falcon On-Demand Scans Detection"
    assert result.scheme_types_mappings[2].fields.keys() == {"status"}


def test_get_mapping_fields_command__legacy(mocker):
    """
    Given
        - nothing
    When
        - running get_mapping_fields_command on the legacy version of the API
    Then
        - the result fits the expected mapping scheme
    """
    from CrowdStrikeFalcon import get_mapping_fields_command

    result = get_mapping_fields_command()
    assert result.scheme_types_mappings[0].type_name == "CrowdStrike Falcon Detection"
    assert result.scheme_types_mappings[0].fields.keys() == {"status"}
    assert len(result.scheme_types_mappings) == 3


ARGS_vulnerability = [
    (
        {"display_remediation_info": "True", "display_evaluation_logic_info": "True", "display_host_info": "False", "limit": "1"},
        False,
        None,
        "Please add a at least one filter argument",
    ),
    (
        {
            "cve_severity": "LOW",
            "display_remediation_info": "True",
            "display_evaluation_logic_info": "True",
            "display_host_info": "False",
            "status": "open,closed",
        },
        True,  # Valid case
        {
            "resources": [
                {
                    "id": "id1",
                    "cid": "cid1",
                    "aid": "aid1",
                    "created_timestamp": "2021-09-16T15:12:42Z",
                    "updated_timestamp": "2022-10-19T00:54:43Z",
                    "status": "open",
                    "cve": {
                        "id": "cveid1",
                        "base_score": 3.3,
                        "severity": "LOW",
                        "exploit_status": 0,
                        "exprt_rating": "LOW",
                        "remediation_level": "O",
                        "spotlight_published_date": "2021-09-15T18:33:00Z",
                        "description": "secd",
                        "published_date": "2021-09-15T12:15:00Z",
                    },
                },
                {
                    "id": "ID2",
                    "cid": "cid2",
                    "aid": "aid2",
                    "created_timestamp": "2022-10-12T22:12:49Z",
                    "updated_timestamp": "2022-10-18T02:54:43Z",
                    "status": "open",
                    "cve": {
                        "id": "idcve4",
                        "spotlight_published_date": "2022-10-12T14:57:00Z",
                        "description": "desc3",
                        "published_date": "2022-10-11T19:15:00Z",
                        "exploitability_score": 1.8,
                        "impact_score": 1.4,
                    },
                },
            ]
        },
        "### List Vulnerabilities\n"
        "|ID|Severity|Status|Base Score|Published Date|Impact Score|Exploitability Score|\n"
        "|---|---|---|---|---|---|---|\n"
        "| cveid1 | LOW | open | 3.3 | 2021-09-15T12:15:00Z |  |  |\n"
        "| idcve4 |  | open |  | 2022-10-11T19:15:00Z | 1.4 | 1.8 |\n",  # args list
    ),
]


@pytest.mark.parametrize("args, is_valid, result_key_json, expected_hr", ARGS_vulnerability)
def test_cs_falcon_spotlight_search_vulnerability_command(mocker, args, is_valid, result_key_json, expected_hr):
    """
    Test cs_falcon_spotlight_search_vulnerability_command,
        with a the filters:  cve_severity, status
    Given
     - There is a vulnerability that are found
    When
     - The user is running cs_falcon_spotlight_search_vulnerability_command with an id
    Then
     - Return a CrowdStrike Falcon Vulnerability context output
     - Return an Endpoint context output
    """
    from CommonServerPython import DemistoException
    from CrowdStrikeFalcon import cs_falcon_spotlight_search_vulnerability_command

    mocker.patch("CrowdStrikeFalcon.http_request", return_value=result_key_json)
    if is_valid:
        outputs = cs_falcon_spotlight_search_vulnerability_command(args)
        assert outputs[0].readable_output == expected_hr
    else:
        with pytest.raises(DemistoException) as e:
            cs_falcon_spotlight_search_vulnerability_command(args)
        assert str(e.value) == expected_hr


def _find_token_cr(results):
    """Return the CommandResults whose outputs_prefix is the pagination-token prefix, or None."""
    for cr in results:
        if getattr(cr, "outputs_prefix", None) == "CrowdStrike.VulnerabilityNextToken":
            return cr
    return None


def test_cs_falcon_spotlight_search_vulnerability_command_pagination(mocker):
    """
    Verify the pagination contract: the cursor is always emitted to context when the
    API returns a non-empty `after`, and is never rendered in human-readable output.

    (a) next_token is forwarded to the API as `&after=`.
    (b) A populated server cursor is surfaced as `CrowdStrike.VulnerabilityNextToken`.
    (c) An empty server cursor causes the cursor entry to be omitted entirely.
    (d) limit > 2500 is silently capped to 2500 in the outgoing URL.
    (e) The cursor entry is emitted regardless of the requested limit when the API
        returned one.
    (f) Over-cap limit still emits the cursor entry.
    (g) The cursor entry's `readable_output` is empty while the data entry carries
        the markdown table.
    (h) 404 "Search context expired" path raises the friendly intercept.
    (i) next_token with URL-special chars is correctly percent-encoded.
    """
    from CrowdStrikeFalcon import cs_falcon_spotlight_search_vulnerability_command

    # display_* args must be supplied because the production code unconditionally
    # routes them through argToBoolean(), which raises ValueError on None.
    base_args = {
        "filter": "status:'open'",
        "display_remediation_info": "False",
        "display_evaluation_logic_info": "False",
        "display_host_info": "False",
    }

    # ---- (a) + (b): cursor forwarded; populated server cursor surfaces ----
    http_mock = mocker.patch(
        "CrowdStrikeFalcon.http_request",
        return_value={
            "resources": [{"id": "vuln-1"}],
            "meta": {"pagination": {"after": "CURSOR_VALUE"}},
        },
    )
    results = cs_falcon_spotlight_search_vulnerability_command({**base_args, "limit": "1", "next_token": "PREV_CURSOR"})
    call_args, _ = http_mock.call_args
    url_suffix = call_args[1] if len(call_args) > 1 else http_mock.call_args.kwargs.get("url_suffix")
    assert "after=PREV_CURSOR" in url_suffix  # (a)
    token_cr = _find_token_cr(results)
    assert token_cr is not None  # (b)
    assert token_cr.outputs == "CURSOR_VALUE"  # (b)

    # ---- (c): empty server cursor -> cursor entry omitted ----
    mocker.resetall()
    http_mock = mocker.patch(
        "CrowdStrikeFalcon.http_request",
        return_value={
            "resources": [{"id": "vuln-2"}],
            "meta": {"pagination": {"after": ""}},
        },
    )
    results = cs_falcon_spotlight_search_vulnerability_command({**base_args, "limit": "1"})
    assert len(results) == 1
    assert _find_token_cr(results) is None  # (c)

    # ---- (d): limit > 2500 is silently capped to 2500 ----
    mocker.resetall()
    http_mock = mocker.patch(
        "CrowdStrikeFalcon.http_request",
        return_value={
            "resources": [{"id": "vuln-3"}],
            "meta": {"pagination": {"after": "ANY"}},
        },
    )
    cs_falcon_spotlight_search_vulnerability_command({**base_args, "limit": "3000"})
    call_args, _ = http_mock.call_args
    url_suffix = call_args[1] if len(call_args) > 1 else http_mock.call_args.kwargs.get("url_suffix")
    assert "limit=2500" in url_suffix
    assert "limit=3000" not in url_suffix  # (d)

    # ---- (e): small limit + server cursor -> cursor entry still emitted ----
    mocker.resetall()
    mocker.patch(
        "CrowdStrikeFalcon.http_request",
        return_value={
            "resources": [{"id": "vuln-4"}],
            "meta": {"pagination": {"after": "ANY"}},
        },
    )
    results = cs_falcon_spotlight_search_vulnerability_command({**base_args, "limit": "100"})
    assert len(results) == 2
    assert _find_token_cr(results) is not None  # (e)

    # ---- (f): over-cap limit -> cursor entry still emitted ----
    mocker.resetall()
    mocker.patch(
        "CrowdStrikeFalcon.http_request",
        return_value={
            "resources": [{"id": "vuln-5"}],
            "meta": {"pagination": {"after": "ANY"}},
        },
    )
    results = cs_falcon_spotlight_search_vulnerability_command({**base_args, "limit": "3000"})
    assert len(results) == 2
    assert _find_token_cr(results) is not None  # (f)

    # ---- (g): cursor entry advertises the token via its readable_output;
    # data entry carries the markdown table with the vulnerability rows. ----
    mocker.resetall()
    mocker.patch(
        "CrowdStrikeFalcon.http_request",
        return_value={
            "resources": [{"cve": {"id": "CVE-2024-0001"}, "status": "open"}],
            "meta": {"pagination": {"after": "ANY"}},
        },
    )
    results = cs_falcon_spotlight_search_vulnerability_command({**base_args, "limit": "100"})
    assert len(results) == 2
    token_cr = _find_token_cr(results)
    assert token_cr is not None
    assert token_cr.readable_output
    assert "VulnerabilityNextToken" in token_cr.readable_output
    data_cr = next(cr for cr in results if cr is not token_cr)
    assert data_cr.readable_output
    assert "CVE-2024-0001" in data_cr.readable_output

    # ---- (i): next_token with URL-special chars is correctly percent-encoded ----
    mocker.resetall()
    http_mock = mocker.patch(
        "CrowdStrikeFalcon.http_request",
        return_value={"resources": [], "meta": {"pagination": {"after": ""}}},
    )
    cs_falcon_spotlight_search_vulnerability_command({**base_args, "limit": "1", "next_token": "a+b/c=d&e"})
    call_args = http_mock.call_args.args
    url_suffix = call_args[1]
    assert "after=a%2Bb%2Fc%3Dd%26e" in url_suffix

    # ---- (h): 404 "Search context expired" path raises the friendly intercept ----
    mocker.resetall()
    http_mock = mocker.patch(
        "CrowdStrikeFalcon.http_request",
        side_effect=DemistoException(
            "Error in API call to CrowdStrike Falcon: code: 404 - reason: Not Found\n"
            "Search context expired, 'after' key no longer valid"
        ),
    )
    return_error_mock = mocker.patch("CrowdStrikeFalcon.return_error", side_effect=SystemExit)
    with pytest.raises(SystemExit):
        cs_falcon_spotlight_search_vulnerability_command({**base_args, "limit": "1"})
    assert return_error_mock.call_count == 1
    assert "cursor has expired" in return_error_mock.call_args.args[0]


def test_cs_falcon_spotlight_search_vulnerability_host_by_command(mocker):
    """
    Test cs_falcon_spotlight_list_host_by_vulnerability_command,
        with a the filters:  cve_severity, status
    Given
     - There is a vulnerability that are found
    When
     - The user is running cs_falcon_spotlight_list_host_by_vulnerability_command with an id
    Then
     - Return a CrowdStrike Falcon Vulnerability context output
     - Return an Endpoint context output
    """
    from CrowdStrikeFalcon import cs_falcon_spotlight_list_host_by_vulnerability_command

    result_key_json = {
        "resources": [
            {
                "id": "id1",
                "cid": "cid1",
                "aid": "aid1",
                "created_timestamp": "2022-01-25T22:44:53Z",
                "updated_timestamp": "2022-10-19T13:56:17Z",
                "status": "open",
                "host_info": {
                    "hostname": "host",
                    "local_ip": "ip_addr",
                    "machine_domain": "",
                    "os_version": "os_ver_example",
                    "ou": "",
                    "site_name": "",
                    "system_manufacturer": "manu_example",
                    "tags": [],
                    "platform": "Windows",
                    "instance_id": "int_id",
                    "service_provider_account_id": "id1_account",
                    "service_provider": "id_ser_prov",
                    "os_build": "1",
                    "product_type_desc": "Server",
                },
                "cve": {"id": "CVE-2013-3900"},
            }
        ]
    }
    expected_hr = (
        "### List Vulnerabilities For Host\n"
        "|CVE ID|hostname|os Version|Product Type Desc|Local IP|\n"
        "|---|---|---|---|---|\n"
        "| CVE-2013-3900 | host | os_ver_example | Server | ip_addr |\n"
    )
    args = {"cve_ids": "CVE-2013-3900", "limit": 1}
    mocker.patch("CrowdStrikeFalcon.http_request", return_value=result_key_json)

    outputs = cs_falcon_spotlight_list_host_by_vulnerability_command(args)
    assert outputs.readable_output == expected_hr


def test_create_ml_exclusion_command(requests_mock):
    from CrowdStrikeFalcon import create_ml_exclusion_command

    requests_mock.post(f"{SERVER_URL}/policy/entities/ml-exclusions/v1", json=load_json("test_data/create_ml_exclusion.json"))

    results = create_ml_exclusion_command({"value": "/test", "excluded_from": ["blocking"], "groups": 123456})

    assert len(results.outputs) == 1
    assert results.outputs[0].get("id") == "123456"
    assert results.outputs[0].get("value") == "/test"


def test_update_ml_exclusion_command_with_args(requests_mock):
    from CrowdStrikeFalcon import update_ml_exclusion_command

    requests_mock.patch(f"{SERVER_URL}/policy/entities/ml-exclusions/v1", json=load_json("test_data/create_ml_exclusion.json"))

    results = update_ml_exclusion_command({"id": 123456, "value": "/test", "excluded_from": ["blocking"], "groups": 123456})

    assert len(results.outputs) == 1
    assert results.outputs[0].get("id") == "123456"
    assert results.outputs[0].get("value") == "/test"


def test_update_ml_exclusion_command_without_args(requests_mock):
    from CrowdStrikeFalcon import update_ml_exclusion_command

    requests_mock.patch(f"{SERVER_URL}/policy/entities/ml-exclusions/v1", json=load_json("test_data/create_ml_exclusion.json"))

    with pytest.raises(Exception) as e:
        update_ml_exclusion_command({"id": 123456})

    assert str(e.value) == "At least one argument (besides the id argument) should be provided to update the exclusion."


def test_delete_ml_exclusion_command(requests_mock):
    from CrowdStrikeFalcon import delete_ml_exclusion_command

    requests_mock.delete(f"{SERVER_URL}/policy/entities/ml-exclusions/v1", json=load_json("test_data/create_ml_exclusion.json"))

    results = delete_ml_exclusion_command({"ids": "123456 789456"})

    assert results.readable_output == "The machine learning exclusions with IDs 123456 789456 was successfully deleted."


def test_search_ml_exclusion_command_by_ids(requests_mock):
    from CrowdStrikeFalcon import search_ml_exclusion_command

    requests_mock.get(
        f"{SERVER_URL}/policy/entities/ml-exclusions/v1?ids=123456&ids=789012",
        json=load_json("test_data/create_ml_exclusion.json"),
    )

    results = search_ml_exclusion_command({"ids": "123456,789012"})

    assert len(results.outputs) == 1
    assert results.outputs[0].get("id") == "123456"
    assert results.outputs[0].get("value") == "/test"


def test_search_ml_exclusion_command_by_value(requests_mock):
    from CrowdStrikeFalcon import search_ml_exclusion_command

    requests_mock.get(
        f"{SERVER_URL}/policy/queries/ml-exclusions/v1?filter=value%3A%27%2Ftest%27", json={"resources": ["123456"]}
    )
    requests_mock.get(
        f"{SERVER_URL}/policy/entities/ml-exclusions/v1?ids=123456", json=load_json("test_data/create_ml_exclusion.json")
    )

    results = search_ml_exclusion_command({"value": "/test"})

    assert len(results.outputs) == 1
    assert results.outputs[0].get("id") == "123456"
    assert results.outputs[0].get("value") == "/test"


def test_search_ml_exclusion_command_by_value_no_results(requests_mock):
    from CrowdStrikeFalcon import search_ml_exclusion_command

    requests_mock.get(f"{SERVER_URL}/policy/queries/ml-exclusions/v1?filter=value%3A%27%2Ftest-mock%27", json={})

    results = search_ml_exclusion_command({"value": "/test-mock"})

    assert results.readable_output == "The arguments/filters you provided did not match any exclusion."


def test_search_ml_exclusion_command_by_filter(requests_mock):
    from CrowdStrikeFalcon import search_ml_exclusion_command

    requests_mock.get(
        f"{SERVER_URL}/policy/queries/ml-exclusions/v1?filter=value%3A%27%2Ftest%27", json={"resources": ["123456"]}
    )
    requests_mock.get(
        f"{SERVER_URL}/policy/entities/ml-exclusions/v1?ids=123456", json=load_json("test_data/create_ml_exclusion.json")
    )

    results = search_ml_exclusion_command({"filter": "value:'/test'"})

    assert len(results.outputs) == 1
    assert results.outputs[0].get("id") == "123456"
    assert results.outputs[0].get("value") == "/test"


def test_create_ioa_exclusion_command(requests_mock):
    from CrowdStrikeFalcon import create_ioa_exclusion_command

    requests_mock.post(f"{SERVER_URL}/policy/entities/ioa-exclusions/v1", json=load_json("test_data/create_ioa_exclusion.json"))

    results = create_ioa_exclusion_command({"exclusion_name": "test", "pattern_id": 123456, "groups": 123456})

    assert len(results.outputs) == 1
    assert results.outputs[0].get("id") == "123456"
    assert results.outputs[0].get("name") == "test"


def test_update_ioa_exclusion_command_with_args(requests_mock):
    from CrowdStrikeFalcon import update_ioa_exclusion_command

    requests_mock.patch(f"{SERVER_URL}/policy/entities/ioa-exclusions/v1", json=load_json("test_data/create_ioa_exclusion.json"))

    results = update_ioa_exclusion_command({"id": 123456, "exclusion_name": "test"})

    assert len(results.outputs) == 1
    assert results.outputs[0].get("id") == "123456"
    assert results.outputs[0].get("name") == "test"


def test_update_ioa_exclusion_command_without_args(requests_mock):
    from CrowdStrikeFalcon import update_ioa_exclusion_command

    requests_mock.patch(f"{SERVER_URL}/policy/entities/ioa-exclusions/v1", json=load_json("test_data/create_ioa_exclusion.json"))

    with pytest.raises(Exception) as e:
        update_ioa_exclusion_command({"id": 123456})

    assert str(e.value) == "At least one argument (besides the id argument) should be provided to update the exclusion."


def test_delete_ioa_exclusion_command(requests_mock):
    from CrowdStrikeFalcon import delete_ioa_exclusion_command

    requests_mock.delete(f"{SERVER_URL}/policy/entities/ioa-exclusions/v1", json=load_json("test_data/create_ioa_exclusion.json"))

    results = delete_ioa_exclusion_command({"ids": "123456, 456789"})

    assert results.readable_output == "The IOA exclusions with IDs 123456 456789 was successfully deleted."


def test_search_ioa_exclusion_command_by_ids(requests_mock):
    from CrowdStrikeFalcon import search_ioa_exclusion_command

    requests_mock.get(
        f"{SERVER_URL}/policy/entities/ioa-exclusions/v1?ids=123456&ids=789012",
        json=load_json("test_data/create_ioa_exclusion.json"),
    )

    results = search_ioa_exclusion_command({"ids": "123456,789012"})

    assert len(results.outputs) == 1
    assert results.outputs[0].get("id") == "123456"
    assert results.outputs[0].get("name") == "test"


def test_search_ioa_exclusion_command_by_name(requests_mock):
    from CrowdStrikeFalcon import search_ioa_exclusion_command

    requests_mock.get(f"{SERVER_URL}/policy/queries/ioa-exclusions/v1?filter=name%3A~%27test%27", json={"resources": ["123456"]})
    requests_mock.get(
        f"{SERVER_URL}/policy/entities/ioa-exclusions/v1?ids=123456", json=load_json("test_data/create_ioa_exclusion.json")
    )

    results = search_ioa_exclusion_command({"name": "test"})

    assert len(results.outputs) == 1
    assert results.outputs[0].get("id") == "123456"
    assert results.outputs[0].get("name") == "test"


def test_search_ioa_exclusion_command_by_name_no_results(requests_mock):
    from CrowdStrikeFalcon import search_ioa_exclusion_command

    requests_mock.get(f"{SERVER_URL}/policy/queries/ioa-exclusions/v1?filter=name%3A~%27test-mock%27", json={})

    results = search_ioa_exclusion_command({"name": "test-mock"})

    assert results.readable_output == "The arguments/filters you provided did not match any exclusion."


def test_search_ioa_exclusion_command_by_filter(requests_mock):
    from CrowdStrikeFalcon import search_ioa_exclusion_command

    requests_mock.get(f"{SERVER_URL}/policy/queries/ioa-exclusions/v1?filter=name%3A%27test%27", json={"resources": ["123456"]})
    requests_mock.get(
        f"{SERVER_URL}/policy/entities/ioa-exclusions/v1?ids=123456", json=load_json("test_data/create_ioa_exclusion.json")
    )

    results = search_ioa_exclusion_command({"filter": "name:'test'"})

    assert len(results.outputs) == 1
    assert results.outputs[0].get("id") == "123456"
    assert results.outputs[0].get("name") == "test"


def test_list_quarantined_file_command(requests_mock):
    from CrowdStrikeFalcon import list_quarantined_file_command

    requests_mock.get(
        f"{SERVER_URL}/quarantine/queries/quarantined-files/v1?q=hostname%3A%27INSTANCE-1%27&limit=50",
        json={"resources": ["121212", "171717"]},
    )
    requests_mock.post(
        f"{SERVER_URL}/quarantine/entities/quarantined-files/GET/v1", json=load_json("test_data/list_quarantine_files.json")
    )

    results = list_quarantined_file_command({"hostname": "INSTANCE-1"})

    assert len(results.outputs) == 2
    assert results.outputs[0].get("id") == "121212"
    assert results.outputs[1].get("id") == "171717"


def test_list_quarantined_file_command_no_results(requests_mock):
    from CrowdStrikeFalcon import list_quarantined_file_command

    requests_mock.get(f"{SERVER_URL}/quarantine/queries/quarantined-files/v1?q=hostname%3A%27INSTANCE-1%27&limit=50", json={})

    results = list_quarantined_file_command({"hostname": "INSTANCE-1"})

    assert results.readable_output == "The arguments/filters you provided did not match any files."


def test_apply_quarantine_file_action_command(requests_mock):
    from CrowdStrikeFalcon import apply_quarantine_file_action_command

    requests_mock.get(
        f"{SERVER_URL}/quarantine/queries/quarantined-files/v1?q=hostname%3A%27INSTANCE-1%27&limit=50",
        json={"resources": ["121212", "171717"]},
    )
    mock_request = requests_mock.patch(f"{SERVER_URL}/quarantine/entities/quarantined-files/v1", json={})

    results = apply_quarantine_file_action_command({"hostname": "INSTANCE-1", "comment": "Added a test comment."})

    assert results.readable_output == "The Quarantined File with IDs ['121212', '171717'] was successfully updated."
    assert mock_request.last_request.text == '{"ids": ["121212", "171717"], "comment": "Added a test comment."}'


def test_apply_quarantine_file_action_command_no_matches(requests_mock):
    """
    Given:
        - search arguments (filename) that do not match any quarantined file in CrowdStrike.
    When:
        - apply_quarantine_file_action_command is called.
    Then:
        - The command returns a friendly readable output and does NOT issue a PATCH
          to /quarantine/entities/quarantined-files/v1 (which would 400 with no ids).
        - Verifies the fix for XSUP-68290.
    """
    from CrowdStrikeFalcon import apply_quarantine_file_action_command

    requests_mock.get(
        f"{SERVER_URL}/quarantine/queries/quarantined-files/v1?q=filename%3A%27a.txt%27&limit=50",
        json={"resources": []},
    )
    patch_mock = requests_mock.patch(f"{SERVER_URL}/quarantine/entities/quarantined-files/v1", json={})

    results = apply_quarantine_file_action_command({"filename": "a.txt", "action": "unrelease", "comment": "test"})

    assert results.readable_output == "The arguments/filters you provided did not match any files."
    assert patch_mock.call_count == 0


@pytest.mark.parametrize(
    "query_params, expected",
    [
        # empty dict -> empty string
        ({}, ""),
        # scalar value (back-compat)
        ({"name": "test"}, "name:'test'"),
        # multiple scalar values joined with '+'
        ({"name": "test", "os_name": "WINDOWS"}, "name:'test'+os_name:'WINDOWS'"),
        # single-element list -> unwrapped (regression test for XSUP-68290)
        ({"filename": ["a.txt"]}, "filename:'a.txt'"),
        # multi-element list -> FQL bracket multi-value notation
        ({"filename": ["a.txt", "b.txt"]}, "filename:['a.txt','b.txt']"),
        # mixed scalar + single-element list
        ({"hostname": "INSTANCE-1", "filename": ["a.txt"]}, "hostname:'INSTANCE-1'+filename:'a.txt'"),
        # empty list -> key is skipped
        ({"filename": []}, ""),
        # empty list mixed with scalar -> only scalar is rendered
        ({"hostname": "INSTANCE-1", "filename": []}, "hostname:'INSTANCE-1'"),
        # None value -> key is skipped
        ({"hostname": "INSTANCE-1", "filename": None}, "hostname:'INSTANCE-1'"),
        # single quote in value -> escaped with backslash
        ({"filename": "O'Brien.txt"}, "filename:'O\\'Brien.txt'"),
        # single quote in list value -> escaped with backslash
        ({"filename": ["O'Brien.txt", "b.txt"]}, "filename:['O\\'Brien.txt','b.txt']"),
    ],
)
def test_build_query_params(query_params, expected):
    """
    Given:
        - A dict of FQL query params with scalar and/or list values.
    When:
        - build_query_params is called (used to construct the ``q=`` parameter for
          /quarantine/queries/quarantined-files/v1).
    Then:
        - List values are unwrapped (single element) or rendered in FQL multi-value
          bracket notation (multiple elements). Without this, list values were
          interpolated as a Python repr (``filename:'['a.txt']'``) and silently
          returned zero matches, ultimately causing XSUP-68290.
    """
    from CrowdStrikeFalcon import build_query_params

    assert build_query_params(query_params) == expected


filter_args = {"key1": "val1,val2", "key2": "val3", "key3": None}
custom_filter = 'key1:"val1"+key2:["val3","val4"]'


@pytest.mark.parametrize(
    "filter_args, custom_filter, output_filter",
    (
        (filter_args, custom_filter, "key1:\"val1\"%2Bkey2:[\"val3\",\"val4\"]%2Bkey1:['val1', 'val2']%2Bkey2:['val3']"),
        (filter_args, None, "key1:['val1', 'val2']%2Bkey2:['val3']"),
        ({}, custom_filter, 'key1:"val1"%2Bkey2:["val3","val4"]'),
    ),
)
def test_build_cs_falcon_filter(filter_args, custom_filter, output_filter):
    """
    Test build_cs_falcon_filter.

    Given
        - A dictionary filter and a custom filter.

    When
        - Before an cs-falcon query.

    Then
        - Return a merged FQL filter as a single string.
    """
    from CrowdStrikeFalcon import build_cs_falcon_filter

    result = build_cs_falcon_filter(custom_filter, **filter_args)

    assert output_filter == result


@pytest.mark.parametrize(
    "command_args, query_result, entites_result, readable_output",
    (
        ({"wait_for_result": False}, [], {}, "No scans match the arguments/filter."),
        (
            {"wait_for_result": False},
            ["123456"],
            {"resources": [{"id": "123456"}]},
            (
                "### CrowdStrike Falcon ODS Scans\n"
                "|ID|Status|Severity|File Count|Description|Hosts/Host groups|End time|Start time|Run by|\n"
                "|---|---|---|---|---|---|---|---|---|\n"
                "| 123456 |  |  |  |  |  |  |  |  |\n"
            ),
        ),
        ({"wait_for_result": True, "ids": "123456"}, [], {"resources": [{"status": "pending"}]}, "Retrieving scan results:"),
    ),
)
def test_cs_falcon_ODS_query_scans_command(mocker, command_args, query_result, entites_result, readable_output):
    """
    Test cs_falcon_ODS_query_scans_command.

    Given
        - A request for a list of ODS endpoint scans by id.

    When
        - The user runs the "cs-falcon-ods-query-scan" command or the "cs-falcon-ods-create-scan".

    Then
        - Get a list of scans from CS Falcon and poll for results if wait_for_results is True.
    """

    from CrowdStrikeFalcon import cs_falcon_ODS_query_scans_command

    mocker.patch.object(ScheduledCommand, "raise_error_if_not_supported")
    mocker.patch("CrowdStrikeFalcon.get_ODS_scan_ids", return_value=query_result)
    mocker.patch("CrowdStrikeFalcon.ODS_get_scans_by_id_request", return_value=entites_result)

    result = cs_falcon_ODS_query_scans_command(command_args)

    assert result.readable_output == readable_output


@pytest.mark.parametrize(
    "input_params, call_params",
    (({"key1": "val1", "key2": None}, "key1=val1"), ({"key1": "val1", "key2": "val2"}, "key1=val1&key2=val2")),
)
def test_ODS_query_scans_request(mocker, input_params, call_params):
    """
    Test ODS_query_scans_request.

    Given
        - A request for a list of ODS endpoint scans by id.

    When
        - The user runs the "cs-falcon-ods-query-scan" command without specifying ids.

    Then
        - Call /ods/queries/scans/v1 with a filter, limit and offset if given and return the ids in response.
    """

    from CrowdStrikeFalcon import ODS_query_scans_request

    http_request = mocker.patch("CrowdStrikeFalcon.http_request")
    ODS_query_scans_request(**input_params)
    http_request.assert_called_with("GET", f"/ods/queries/scans/v1?{call_params}")


def test_ODS_get_scans_by_id_request(mocker):
    """
    Test ODS_get_scans_by_id_request.

    Given
        - A request for info on ODS endpoint scans.

    When
        - The user runs the "cs-falcon-ods-query-scan" command and we obtain a non-empty list of ids.

    Then
        - Call /ods/entities/scans/v1 with the ids and return the response.
    """

    from CrowdStrikeFalcon import ODS_get_scans_by_id_request

    ids_list = ["<id1>", "<id2>", "<id3>"]
    ids_string = "ids=<id1>&ids=<id2>&ids=<id3>"

    http_request = mocker.patch("CrowdStrikeFalcon.http_request")

    ODS_get_scans_by_id_request(ids_list)
    http_request.assert_called_with("GET", f"/ods/entities/scans/v1?{ids_string}")


def test_map_scan_resource_to_UI(mocker):
    """
    Test map_scan_resource_to_UI.

    Given
        - A dictionary response from /ods/entities/scans.

    When
        - The user runs the "cs-falcon-ods-query-scan" command

    Then
        - Return a dict with keys corresponding the cs-falcon UI.
    """
    from CrowdStrikeFalcon import map_scan_resource_to_UI

    resource = {
        "id": "91000dbf0a4e4f5eb2a02528c00fa902",
        "cid": "20879a8064904ecfbb62c118a6a19411",
        "profile_id": "0e313756da21480c8eb5cf37da77a97a",
        "description": "desc3456346",
        "scan_inclusions": ["*"],
        "initiated_from": "cloud_scheduled",
        "quarantine": True,
        "cpu_priority": 2,
        "preemption_priority": 15,
        "metadata": [
            {
                "host_id": "046761c46ec84f40b27b6f79ce7cd32c",
                "host_scan_id": "38588c1b29aa9946a3de95e997ad7948",
                "scan_host_metadata_id": "6aec6c04ab2e4c99b4e843637d3e37d0",
                "filecount": {"scanned": 0, "malicious": 0, "quarantined": 0, "skipped": 0, "traversed": 518464},
                "status": "completed",
                "started_on": "2023-03-15T15:57:37.59543591Z",
                "completed_on": "2023-03-15T16:02:20.845829991Z",
                "last_updated": "2023-03-15T16:02:20.845909034Z",
            },
            {
                "host_id": "15dbb9d8f06b45fe9f61eb46e829d986",
                "scan_host_metadata_id": "2e99e4fc7a4f4b1e9254e0af210a6994",
                "filecount": {"scanned": 0, "malicious": 0, "quarantined": 0, "skipped": 0, "traversed": 209},
                "status": "failed",
                "last_updated": "2023-04-05T02:23:10.316500752Z",
            },
        ],
        "filecount": {},
        "status": "failed",
        "host_groups": ["7471ba0636b34cbb8c65fae7979a6a9b"],
        "endpoint_notification": True,
        "pause_duration": 2,
        "max_duration": 2,
        "max_file_size": 60,
        "sensor_ml_level_detection": 2,
        "sensor_ml_level_prevention": 2,
        "cloud_ml_level_detection": 2,
        "cloud_ml_level_prevention": 2,
        "policy_setting": [
            26439818674573,
            26439818674574,
        ],
        "scan_started_on": "2023-03-15T15:57:37.59543591Z",
        "scan_completed_on": "2023-04-18T14:56:38.527255649Z",
        "created_on": "2023-03-15T15:57:37.59543591Z",
        "created_by": "f7acf1bd5d3d4b40afe77546cbbaefde",
        "last_updated": "2023-04-05T02:23:10.316500752Z",
    }
    mapped_resource = {
        "ID": "91000dbf0a4e4f5eb2a02528c00fa902",
        "Status": "failed",
        "Severity": None,
        "Description": "desc3456346",
        "File Count": (
            "scanned: 0\nmalicious: 0\n"
            "quarantined: 0\nskipped: 0\ntraversed: 518464"
            "\n-\nscanned: 0\nmalicious: 0\n"
            "quarantined: 0\nskipped: 0\ntraversed: 209"
        ),
        "Hosts/Host groups": ["7471ba0636b34cbb8c65fae7979a6a9b"],
        "Start time": "2023-03-15T15:57:37.59543591Z",
        "End time": "2023-04-18T14:56:38.527255649Z",
        "Run by": "f7acf1bd5d3d4b40afe77546cbbaefde",
    }

    output = map_scan_resource_to_UI(resource)

    assert output == mapped_resource


@pytest.mark.parametrize(
    "input_params, call_params",
    (({"key1": "val1", "key2": None}, "key1=val1"), ({"key1": "val1", "key2": "val2"}, "key1=val1&key2=val2")),
)
def test_ODS_query_scheduled_scans_request(mocker, input_params, call_params):
    """
    Test ODS_query_scheduled_scans_request.

    Given
        - A request for a list of ODS endpoint scheduled scans by id.

    When
        - The user runs the "cs-falcon-ods-query-scheduled-scan" command without specifying ids.

    Then
        - Call /ods/queries/scheduled-scans/v1 with a filter, limit and offset if given and return the ids in response.
    """

    from CrowdStrikeFalcon import ODS_query_scheduled_scans_request

    http_request = mocker.patch("CrowdStrikeFalcon.http_request")
    ODS_query_scheduled_scans_request(**input_params)
    http_request.assert_called_with("GET", f"/ods/queries/scheduled-scans/v1?{call_params}")


def test_ODS_get_scheduled_scans_by_id_request(mocker):
    """
    Test ODS_get_scheduled_scans_by_id_request.

    Given
        - A request for info on ODS endpoint scheduled scans.

    When
        - The user runs the "cs-falcon-ods-query-scheduled-scan" command and we obtain a non-empty list of ids.

    Then
        - Call /ods/entities/scheduled-scans/v1 with the ids and return the response.
    """

    from CrowdStrikeFalcon import ODS_get_scheduled_scans_by_id_request

    ids_list = ["<id1>", "<id2>", "<id3>"]
    ids_string = "ids=<id1>&ids=<id2>&ids=<id3>"

    http_request = mocker.patch("CrowdStrikeFalcon.http_request")

    ODS_get_scheduled_scans_by_id_request(ids_list)
    http_request.assert_called_with("GET", f"/ods/entities/scheduled-scans/v1?{ids_string}")


def test_map_scheduled_scan_resource_to_UI(mocker):
    """
    Test map_scan_resource_to_UI.

    Given
        - A dictionary response from /ods/entities/scheduled-scans.

    When
        - The user runs the "cs-falcon-ods-query-scheduled-scan" command

    Then
        - Return a dict with keys corresponding the cs-falcon UI.
    """
    from CrowdStrikeFalcon import map_scheduled_scan_resource_to_UI

    resource = {
        "id": "9055945bdfbc4b42bf7c9c16976186ca",
        "cid": "20879a8064904ecfbb62c118a6a19411",
        "description": "desc3456346",
        "scan_inclusions": ["*"],
        "initiated_from": "cloud_scheduled",
        "quarantine": True,
        "cpu_priority": 2,
        "preemption_priority": 15,
        "metadata": [{"host_id": "046761c46ec84f40b27b6f79ce7cd32c", "last_updated": "2023-05-01T13:54:48.51553853Z"}],
        "status": "scheduled",
        "host_groups": ["7471ba0636b34cbb8c65fae7979a6a9b"],
        "endpoint_notification": True,
        "pause_duration": 2,
        "max_duration": 2,
        "max_file_size": 60,
        "sensor_ml_level_detection": 2,
        "sensor_ml_level_prevention": 2,
        "cloud_ml_level_detection": 2,
        "cloud_ml_level_prevention": 2,
        "policy_setting": [
            26439818674573,
        ],
        "schedule": {"start_timestamp": "2023-06-15T15:57", "interval": 0},
        "created_on": "2023-05-01T13:54:48.51553853Z",
        "created_by": "f7acf1bd5d3d4b40afe77546cbbaefde",
        "last_updated": "2023-05-01T13:54:48.51553853Z",
        "deleted": False,
    }

    mapped_resource = {
        "ID": "9055945bdfbc4b42bf7c9c16976186ca",
        "Hosts targeted": 1,
        "Description": "desc3456346",
        "Host groups": ["7471ba0636b34cbb8c65fae7979a6a9b"],
        "Start time": "2023-06-15T15:57",
        "Created by": "f7acf1bd5d3d4b40afe77546cbbaefde",
    }

    output = map_scheduled_scan_resource_to_UI(resource)

    assert output == mapped_resource


@pytest.mark.parametrize(
    "input_params, call_params",
    (({"key1": "val1", "key2": None}, "key1=val1"), ({"key1": "val1", "key2": "val2"}, "key1=val1&key2=val2")),
)
def test_ODS_query_scan_hosts_request(mocker, input_params, call_params):
    """
    Test ODS_query_scan_hosts_request.

    Given
        - A request for a list of ODS endpoint scan hosts by id.

    When
        - The user runs the "cs-falcon-ods-query-scan-host" command without specifying ids.

    Then
        - Call /ods/queries/scan-hosts/v1 with a filter, limit and offset if given and return the ids in response.
    """

    from CrowdStrikeFalcon import ODS_query_scan_hosts_request

    http_request = mocker.patch("CrowdStrikeFalcon.http_request")
    ODS_query_scan_hosts_request(**input_params)
    http_request.assert_called_with("GET", f"/ods/queries/scan-hosts/v1?{call_params}")


def test_ODS_get_scan_hosts_by_id_request(mocker):
    """
    Test ODS_get_scan_hosts_by_id_request.

    Given
        - A request for info on ODS endpoint scan hosts.

    When
        - The user runs the "cs-falcon-ods-query-scan-hosts" command and we obtain a non-empty list of ids.

    Then
        - Call /ods/entities/scan-hosts/v1 with the ids and return the response.
    """

    from CrowdStrikeFalcon import ODS_get_scan_hosts_by_id_request

    ids_list = ["<id1>", "<id2>", "<id3>"]
    ids_string = "ids=<id1>&ids=<id2>&ids=<id3>"

    http_request = mocker.patch("CrowdStrikeFalcon.http_request")

    ODS_get_scan_hosts_by_id_request(ids_list)
    http_request.assert_called_with("GET", f"/ods/entities/scan-hosts/v1?{ids_string}")


def test_map_scan_host_resource_to_UI(mocker):
    """
    Test map_scan_resource_to_UI.

    Given
        - A dictionary response from /ods/entities/scan-hosts.

    When
        - The user runs the "cs-falcon-ods-query-scan-host" command

    Then
        - Return a dict with keys corresponding the cs-falcon UI.
    """
    from CrowdStrikeFalcon import map_scan_host_resource_to_UI

    resource = {
        "id": "185a0ad5e159418e8927d956c1a793d8",
        "cid": "3c74ca9ad4k43592ea2adf4ca94k4359",
        "scan_id": "fadde07ee8a44a07988e009b3152e339",
        "profile_id": "ddf8914cca5f4ac595272fe8122e308f",
        "host_id": "82395m302t8zea2u25978416be1973c5",
        "host_scan_id": "7e80aa16a44d30cb819e27144d2603b0",
        "filecount": {"scanned": 1021, "malicious": 104, "quarantined": 0, "skipped": 9328},
        "status": "completed",
        "severity": 70,
        "started_on": "2022-11-01T18:54:59.39861174Z",
        "completed_on": "2022-11-01T19:08:17.903700092Z",
        "last_updated": "2022-11-01T19:08:17.903732519Z",
    }

    mapped_resource = {
        "ID": "185a0ad5e159418e8927d956c1a793d8",
        "Scan ID": "fadde07ee8a44a07988e009b3152e339",
        "Host ID": "82395m302t8zea2u25978416be1973c5",
        "Filecount": {"scanned": 1021, "malicious": 104, "quarantined": 0, "skipped": 9328},
        "Status": "completed",
        "Severity": 70,
        "Started on": "2022-11-01T18:54:59.39861174Z",
    }

    output = map_scan_host_resource_to_UI(resource)

    assert output == mapped_resource


@pytest.mark.parametrize(
    "input_params, call_params",
    (({"key1": "val1", "key2": None}, "key1=val1"), ({"key1": "val1", "key2": "val2"}, "key1=val1&key2=val2")),
)
def test_ODS_query_malicious_files_request(mocker, input_params, call_params):
    """
    Test ODS_query_malicious_files_request.

    Given
        - A request for a list of ODS endpoint malicious files by id.

    When
        - The user runs the "cs-falcon-ods-query-malicious-file" command without specifying ids.

    Then
        - Call /ods/queries/malicious-files/v1 with a filter, limit and offset if given and return the ids in response.
    """

    from CrowdStrikeFalcon import ODS_query_malicious_files_request

    http_request = mocker.patch("CrowdStrikeFalcon.http_request")
    ODS_query_malicious_files_request(**input_params)
    http_request.assert_called_with("GET", f"/ods/queries/malicious-files/v1?{call_params}")


def test_ODS_get_malicious_files_by_id_request(mocker):
    """
    Test ODS_get_malicious_files_by_id_request.

    Given
        - A request for info on ODS endpoint malicious files.

    When
        - The user runs the "cs-falcon-ods-query-malicious-files" command and we obtain a non-empty list of ids.

    Then
        - Call /ods/entities/malicious-files/v1 with the ids and return the response.
    """

    from CrowdStrikeFalcon import ODS_get_malicious_files_by_id_request

    ids_list = ["<id1>", "<id2>", "<id3>"]
    ids_string = "ids=<id1>&ids=<id2>&ids=<id3>"

    http_request = mocker.patch("CrowdStrikeFalcon.http_request")

    ODS_get_malicious_files_by_id_request(ids_list)
    http_request.assert_called_with("GET", f"/ods/entities/malicious-files/v1?{ids_string}")


def test_map_malicious_file_resource_to_UI(mocker):
    """
    Test map_scan_resource_to_UI.

    Given
        - A dictionary response from /ods/entities/malicious-files.

    When
        - The user runs the "cs-falcon-ods-query-malicious-file" command

    Then
        - Return a dict with keys corresponding the cs-falcon UI.
    """
    from CrowdStrikeFalcon import map_malicious_file_resource_to_UI

    resource = {
        "id": "d684849d4cea435daec706e473743863",
        "cid": "91a0649f84749a38f6d939423bed5576",
        "scan_id": "81c8009a59be4570b5c66f8946559205",
        "host_id": "3c7be1c5ea21849fa5c74ca9842f46a9",
        "host_scan_id": "4f9fea030a0626ed4dc53a7dec70a100",
        "filepath": "C:\\\\Windows\\Malicious\\Mimikatz_newzipp\\Mimikatz\\x86\\mimilib.dll",
        "filename": "mimilib.dll",
        "hash": "9ff1a527861a69b436b51a8d464aaee8d416e39ff1a52aee16e39b436b564a78",
        "pattern_id": 4004,
        "severity": 70,
        "quarantined": True,
        "last_updated": "2022-11-01T17:06:18.900620631Z",
    }
    mapped_resource = {
        "ID": "d684849d4cea435daec706e473743863",
        "Scan id": "81c8009a59be4570b5c66f8946559205",
        "Filename": "mimilib.dll",
        "Hash": "9ff1a527861a69b436b51a8d464aaee8d416e39ff1a52aee16e39b436b564a78",
        "Severity": 70,
        "Last updated": "2022-11-01T17:06:18.900620631Z",
    }

    output = map_malicious_file_resource_to_UI(resource)

    assert output == mapped_resource


@pytest.mark.parametrize(
    "args, is_scheduled, expected_result",
    (
        (
            {"quarantine": "false", "schedule_interval": "every other week", "schedule_start_timestamp": "tomorrow"},
            True,
            {"quarantine": False},
        ),
        ({"cpu_priority": "Low", "max_duration": 1}, False, {"cpu_priority": 2, "max_duration": 1}),
    ),
)
def test_make_create_scan_request_body(args, is_scheduled, expected_result):
    """
    Test make_create_scan_request_body.

    Given
        - Arguments to create a scan/scheduled-scan.

    When
        - The user runs the "cs-falcon-ods-create-scan" command

    Then
        - Return a dict to send as the body for a create scan request.
    """

    from CrowdStrikeFalcon import make_create_scan_request_body

    output = make_create_scan_request_body(args, is_scheduled)

    if is_scheduled:
        assert "hosts" not in output
        assert isinstance(output["schedule"]["interval"], int)  # function doesn't enforce this
    else:
        assert "hosts" in output
        assert "schedule" not in output

    for key, value in expected_result.items():
        assert output[key] == value


@pytest.mark.parametrize(
    "args, is_error, expected_error_info",
    (
        ({}, True, "MUST set either hosts OR host_groups."),
        ({"hosts": "john doe"}, True, "MUST set either file_paths OR scan_inclusions."),
        ({"hosts": "john doe", "file_paths": "*"}, False, None),
    ),
)
def test_ODS_verify_create_scan_command(args, is_error, expected_error_info):
    """
    Test ODS_verify_create_scan_command.

    Given
        - Arguments to create a scan/scheduled-scan.

    When
        - The user runs the "cs-falcon-ods-create-scan" command

    Then
        - Return a dict to send as the body for a create scan request.
    """
    from CrowdStrikeFalcon import ODS_verify_create_scan_command

    if is_error:
        with pytest.raises(DemistoException) as error_info:
            ODS_verify_create_scan_command(args)
        assert str(error_info.value) == expected_error_info
    else:
        ODS_verify_create_scan_command(args)


def test_cs_falcon_ods_create_scan_command(mocker):
    """
    Test cs_falcon_ods_create_scan_command.

    Given
        - Arguments to create a scan.

    When
        - The user runs the "cs-falcon-ods-create-scan" command

    Then
        - Create an ODS scan.
    """

    from CrowdStrikeFalcon import cs_falcon_ods_create_scan_command

    mocker.patch("CrowdStrikeFalcon.ods_create_scan", return_value={"id": "random_id"})
    query_scans_command = mocker.patch("CrowdStrikeFalcon.cs_falcon_ODS_query_scans_command")

    cs_falcon_ods_create_scan_command({"interval_in_seconds": 1, "timeout_in_seconds": 1})

    query_scans_command.assert_called_with(
        {
            "ids": "random_id",
            "wait_for_result": True,
            "interval_in_seconds": 1,
            "timeout_in_seconds": 1,
        }
    )


def test_cs_falcon_ods_create_scan_command_no_polling(mocker):
    """
    Test cs_falcon_ods_create_scan_command with polling=false.

    Given
        - Arguments to create a scan with polling set to false.

    When
        - The user runs the "cs-falcon-ods-create-scan" command with polling=false.

    Then
        - Create an ODS scan and return CommandResults immediately without calling cs_falcon_ODS_query_scans_command.
    """

    from CrowdStrikeFalcon import cs_falcon_ods_create_scan_command

    mocker.patch("CrowdStrikeFalcon.ods_create_scan", return_value={"id": "random_id"})
    query_scans_command = mocker.patch("CrowdStrikeFalcon.cs_falcon_ODS_query_scans_command")

    result = cs_falcon_ods_create_scan_command({"interval_in_seconds": 1, "timeout_in_seconds": 1, "polling": "false"})

    query_scans_command.assert_not_called()
    assert result.readable_output == "Successfully created scan with ID: random_id"
    assert result.outputs == {"id": "random_id"}
    assert result.outputs_prefix == "CrowdStrike.ODSScan"


def test_cs_falcon_ods_create_scheduled_scan_command(mocker):
    """
    Test cs_falcon_ods_create_scheduled_scan_command.

    Given
        - Arguments to create a scheduled-scan.

    When
        - The user runs the "cs-falcon-ods-create-scheduled-scan" command

    Then
        - Create a scheduled scan.
    """

    from CrowdStrikeFalcon import cs_falcon_ods_create_scheduled_scan_command

    mocker.patch("CrowdStrikeFalcon.ods_create_scan", return_value={"id": "random_id"})
    result = cs_falcon_ods_create_scheduled_scan_command({"quarantine": "false", "schedule_interval": "every other week"})
    assert result.readable_output == "Successfully created scheduled scan with ID: random_id"


@pytest.mark.parametrize(
    "args, is_scheduled, body",
    (
        (
            {"quarantine": "false", "schedule_interval": "every other week", "schedule_start_timestamp": "tomorrow"},
            True,
            {"quarantine": False, "schedule": {"interval": 14, "start_timestamp": "2020-09-27T17:22"}},
        ),
        ({"cpu_priority": "Low"}, False, {"cpu_priority": 2}),
    ),
)
@freeze_time("2020-09-26 17:22:13 UTC")
def test_ODS_create_scan_request(mocker, args, is_scheduled, body):
    """
    Test ODS_create_scan_request.

    Given
        - Arguments to create a scan/scheduled-scan.

    When
        - The user runs the "cs-falcon-ods-create-scan" command

    Then
        - Create a scan/scheduled-scan.
    """

    from CrowdStrikeFalcon import ODS_create_scan_request

    http_request = mocker.patch("CrowdStrikeFalcon.http_request")
    ODS_create_scan_request(args, is_scheduled)
    http_request.assert_called_with("POST", f'/ods/entities/{"scheduled-scans" if is_scheduled else "scans"}/v1', json=body)


@pytest.mark.parametrize(
    "ids, scans_filter, url_params",
    (
        (["id1", "id2"], None, "ids=id1&ids=id2"),
        ([], "key1:val1+key2:val2", "filter=key1:val1%2Bkey2:val2"),
        (["id1", "id2"], "key1:val1+key2:val2", "ids=id1&ids=id2&filter=key1:val1%2Bkey2:val2"),
    ),
)
def test_ODS_delete_scheduled_scans_request(mocker, ids, scans_filter, url_params):
    """
    Test ODS_delete_scheduled_scans_request.

    Given
        - Arguments to delete a scheduled-scans.

    When
        - The user runs the "cs-falcon-ods-delete-scheduled-scan" command

    Then
        - Delete ODS scheduled scans.
    """

    from CrowdStrikeFalcon import ODS_delete_scheduled_scans_request

    http_request = mocker.patch("CrowdStrikeFalcon.http_request")
    ODS_delete_scheduled_scans_request(ids, scans_filter)
    http_request.assert_called_with("DELETE", f"/ods/entities/scheduled-scans/v1?{url_params}", status_code=500)


class mocker_gql_client:
    def __init__(self, mock_responses, expected_after):
        self.mock_responses = mock_responses
        self.expected_after = expected_after
        self.index = 0

    def execute(self, idp_query, variable_values):
        if "after" not in variable_values or self.expected_after == variable_values.get("after", ""):
            response = self.mock_responses[self.index]
            self.index += 1
            return response
        return None


@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2"])
def test_list_identity_entities_command(mocker, test_case):
    """
    Given:
    - test case that point to the relevant test case in the json test data which include:
      args, response mock, expected_after, expected_raw_response_len, expected hr, and expected_ec.
    - Case 1: args with limit=1, some filter args, mock_response with 1 identity entity, and an empty expected_after
    - Case 2: args with limit=50, page=size=1, page=2 mock_response with 2 response each have 1 identity entity,
    and an empty expected_after that matches the endCursor of the first response.

    When:
    - Running list_identity_entities_command.

    Then:
    - Ensure that the response was parsed correctly and right HR, raw_response, and EC are returned.
    - Case 1: Should return the parsed identity from the response and 1 response in the rew_response list.
    - Case 2: Should return onle the second identity entity, and have 2 responses in the rew_response list.
    """
    import CrowdStrikeFalcon
    from CrowdStrikeFalcon import list_identity_entities_command

    test_data = load_json("./test_data/test_list_identity_entities_command.json").get(test_case, {})
    expected_after = test_data.get("expected_after", "")
    mock_responses = test_data.get("mock_responses", "")
    mock_client = mocker_gql_client(mock_responses, expected_after)
    mocker.patch.object(CrowdStrikeFalcon, "create_gql_client", return_value=mock_client)
    args = test_data.get("args", {})
    command_results = list_identity_entities_command(args)
    assert test_data.get("expected_hr") == command_results.readable_output
    assert test_data.get("expected_ec") == command_results.outputs
    assert test_data.get("expected_res_len") == len(command_results.raw_response)


@pytest.mark.parametrize("timeout, expected_timeout", [(60, 60), (None, 30)])
def test_run_batch_write_cmd_timeout_argument(mocker, timeout, expected_timeout):
    """
    Given
        - Different timeout argument
    When
        - Run the run_batch_write_cmd function
    Then
        - Asserst the expected timeout called with the http request function
    """
    from CrowdStrikeFalcon import run_batch_write_cmd

    batch_id = "12345"
    command_type = "ls"
    full_command = "ls -l"
    request_mock = mocker.patch("CrowdStrikeFalcon.http_request", return_value={})
    run_batch_write_cmd(batch_id, command_type, full_command, timeout=timeout)
    assert request_mock.call_args[1].get("params").get("timeout") == expected_timeout


def assert_command_results(
    command_results_to_assert: CommandResults,
    expected_outputs: list | dict,
    expected_outputs_key_field: str | list[str],
    expected_outputs_prefix: str,
):
    """This function is used to assert the command results object returned from running command using mocked data.
    It checks the three important fields, which are:
    1. outputs
    2. outputs_key_field
    3. outputs_prefix

    Args:
        command_results_to_check (CommandResults): The command results object to assert.
        expected_outputs (list | dict): The expected outputs object.
        expected_outputs_key_field (str | list[str]): The expected outputs key field object.
        expected_outputs_prefix (str): The expected outputs prefix object.
    """
    assert command_results_to_assert.outputs == expected_outputs
    assert command_results_to_assert.outputs_key_field == expected_outputs_key_field
    assert command_results_to_assert.outputs_prefix == expected_outputs_prefix


class TestCSFalconCSPMListPolicyDetialsCommand:
    def test_http_request_with_status_code_400_500_207(self, mocker: MockerFixture):
        """
        Given:
            - Policy IDs to retrieve their details.
        When
            - Making a http request for the cs-falcon-cspm-list-policy-details command.
        Then
            - Validate that the http_request function accepts the status codes 500, 400, and 207,
            since we deal with them manually.
        """
        from CrowdStrikeFalcon import cspm_list_policy_details_request

        http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")
        cspm_list_policy_details_request(policy_ids=["1", "2"])
        assert http_request_mocker.call_args_list[0][1].get("status_code") == [500, 400, 207]

    def test_get_policy_details(self, mocker: MockerFixture):
        """
        Given:
            - Policy IDs to retrieve their details.
        When
            - Calling the cs-falcon-cspm-list-policy-details command.
        Then
            - Validate the data of the CommandResults object returned.
        """
        from CrowdStrikeFalcon import cs_falcon_cspm_list_policy_details_command

        raw_response = load_json("test_data/policy_details/policy_details_raw_response.json")
        mocker.patch("CrowdStrikeFalcon.http_request", return_value=raw_response)
        command_results = cs_falcon_cspm_list_policy_details_command(args={"policy_ids": "1,2"})
        expected_context_data = load_json("test_data/policy_details/policy_details_context_data.json")
        assert_command_results(
            command_results_to_assert=command_results,
            expected_outputs=expected_context_data,
            expected_outputs_key_field="ID",
            expected_outputs_prefix="CrowdStrike.CSPMPolicy",
        )

    def test_get_policy_details_error_500(self, mocker: MockerFixture):
        """
        Given
            - A wrong a policy id.
        When
            - Running the cs-falcon-cspm-list-policy-details command, and receiving a 500 status code.
        Then
            - Validate that we output an error with the correct message.
        """
        from CrowdStrikeFalcon import cs_falcon_cspm_list_policy_details_command

        raw_response = load_json("test_data/policy_details/policy_details_error_500_raw_response.json")
        mocker.patch("CrowdStrikeFalcon.http_request", return_value=raw_response)
        with pytest.raises(DemistoException) as e:
            cs_falcon_cspm_list_policy_details_command(args={"policy_ids": "12123123123123"})
        assert "Perhaps the policy IDs are invalid?" in str(e)

    def test_get_policy_details_error_400(self, mocker: MockerFixture):
        """
        Given
            - A wrong a policy id.
        When
            - Running the cs-falcon-cspm-list-policy-details command, and receiving a 400 status code.
        Then
            - Validate that we output a warning with the correct message.
        """
        from CrowdStrikeFalcon import cs_falcon_cspm_list_policy_details_command

        raw_response = load_json("test_data/policy_details/policy_details_error_400_raw_response.json")
        mocker.patch("CrowdStrikeFalcon.http_request", return_value=raw_response)
        demisto_results_mocker = mocker.patch.object(demisto, "results")
        command_results = cs_falcon_cspm_list_policy_details_command(args={"policy_ids": "1,121231"})
        expected_context_data = load_json("test_data/policy_details/policy_details_error_400_context_data.json")
        assert_command_results(
            command_results_to_assert=command_results,
            expected_outputs=expected_context_data,
            expected_outputs_key_field="ID",
            expected_outputs_prefix="CrowdStrike.CSPMPolicy",
        )
        # Entry type '11' means warning
        assert demisto_results_mocker.call_args_list[0][0][0].get("Type") == 11
        assert "Invalid policy ID 121231 provided" in demisto_results_mocker.call_args_list[0][0][0].get("Contents")


class TestCSFalconCSPMListServicePolicySettingsCommand:
    def test_http_request_arguments(self, mocker: MockerFixture):
        """
        Given:
            - Policy ID to retrieve their details.
        When
            - Making a http request for the cs-falcon-cspm-list-service-policy-settings command.
        Then
            - Validate that the http_request function accepts the status code 207, since we deal with it manually,
            and that the arguments are mapped correctly to the appropriate params.
        """
        from CrowdStrikeFalcon import cspm_list_service_policy_settings_request

        http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")
        cspm_list_service_policy_settings_request(policy_id="1", cloud_platform="aws", service="IAM")
        assert http_request_mocker.call_args_list[0][1].get("status_code") == [207]
        assert http_request_mocker.call_args_list[0][1].get("params") == {
            "service": "IAM",
            "policy-id": "1",
            "cloud-platform": "aws",
        }

    def test_get_service_policy_settings(self, mocker: MockerFixture):
        """
        Given:
            - Arguments for the command.
        When
            - Calling the cs-falcon-cspm-list-service-policy-settings command.
        Then
            - Validate the data of the CommandResults object returned.
        """
        from CrowdStrikeFalcon import cs_falcon_cspm_list_service_policy_settings_command

        raw_response = load_json("test_data/service_policy_settings/policy_settings_raw_response.json")
        mocker.patch("CrowdStrikeFalcon.http_request", return_value=raw_response)
        command_results = cs_falcon_cspm_list_service_policy_settings_command(args={"cloud_platform": "aws", "service": "IAM"})
        expected_context_data = load_json("test_data/service_policy_settings/policy_settings_context_data.json")
        assert_command_results(
            command_results_to_assert=command_results,
            expected_outputs=expected_context_data,
            expected_outputs_key_field="policy_id",
            expected_outputs_prefix="CrowdStrike.CSPMPolicySetting",
        )

    def test_get_service_policy_settings_manual_pagination(self, mocker: MockerFixture):
        """
        Given:
            - Arguments for the command, with the limit argument.
        When
            - Calling the cs-falcon-cspm-list-service-policy-settings command.
        Then
            - Validate the code does a manual pagination, since the API does not offer it.
        """
        # The raw response in the test data has 2 values, we set a limit of 1 to assert the manual pagination
        from CrowdStrikeFalcon import cs_falcon_cspm_list_service_policy_settings_command

        raw_response = load_json("test_data/service_policy_settings/policy_settings_raw_response.json")
        mocker.patch("CrowdStrikeFalcon.http_request", return_value=raw_response)
        command_results = cs_falcon_cspm_list_service_policy_settings_command(
            args={"cloud_platform": "aws", "service": "IAM", "limit": "1"}
        )
        assert isinstance(command_results.outputs, list)
        assert len(command_results.outputs) == 1


class TestCSFalconCSPMUpdatePolicySettingsCommand:
    def test_http_request_arguments(self, mocker: MockerFixture):
        """
        Given:
            - Arguments for the cs-falcon-cspm-update-policy_settings command.
        When
            - Making a http request.
        Then
            - Validate that the http_request function accepts the status code 500, since we deal with it manually,
            and that the arguments are mapped correctly to the json body.
        """
        from CrowdStrikeFalcon import cspm_update_policy_settings_request

        http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")
        cspm_update_policy_settings_request(
            account_id="12", enabled=True, policy_id=1, regions=["eu-west", "eu-east"], severity="high", tag_excluded=False
        )
        assert http_request_mocker.call_args_list[0][1].get("status_code") == 500
        assert http_request_mocker.call_args_list[0][1].get("json") == {
            "resources": [
                {
                    "account_id": "12",
                    "enabled": True,
                    "policy_id": 1,
                    "regions": ["eu-west", "eu-east"],
                    "severity": "high",
                    "tag_excluded": False,
                }
            ]
        }

    def test_update_policy_settings_error_500(self, mocker: MockerFixture):
        """
        Given
            - A wrong a account id.
        When
            - Running the cs-falcon-cspm-update-policy_settings command, and receiving a 500 status code.
        Then
            - Validate that we output an error with the correct message.
        """
        from CrowdStrikeFalcon import cs_falcon_cspm_update_policy_settings_command

        raw_response = load_json("test_data/update_policy_settings/update_settings_error_500_raw_response.json")
        mocker.patch("CrowdStrikeFalcon.http_request", return_value=raw_response)
        with pytest.raises(DemistoException) as e:
            cs_falcon_cspm_update_policy_settings_command(args={"account_id": "wrong_account_id", "policy_id": 1})
        assert "Perhaps the policy ID or account ID are invalid?" in str(e)

    def test_update_policy_settings(self, mocker: MockerFixture):
        """
        Given:
            - Arguments for the command.
        When
            - Calling the cs-falcon-cspm-update-policy_settings command.
        Then
            - Validate the data of the CommandResults object returned.
        """
        from CrowdStrikeFalcon import cs_falcon_cspm_update_policy_settings_command

        raw_response = load_json("test_data/update_policy_settings/update_settings_raw_response.json")
        mocker.patch("CrowdStrikeFalcon.http_request", return_value=raw_response)
        command_results = cs_falcon_cspm_update_policy_settings_command(args={"policy_id": 1})
        assert isinstance(command_results.readable_output, str)
        assert "Policy 1 was updated successfully" in command_results.readable_output


class TestCSFalconResolveIdentityDetectionCommand:
    @pytest.mark.parametrize(
        "url_suffix, ids_request_key",
        [("/alerts/entities/alerts/v3", "composite_ids")],
    )
    def test_http_request_arguments(self, mocker: MockerFixture, url_suffix, ids_request_key):
        """
        Given:
            - Arguments for the cs-falcon-resolve-identity-detection command.
            case 1: Legacy_version is False
            case 2: Legacy_version is True
        When
            - Making a http request.
        Then
            - Validate that the arguments are mapped correctly to the json body.
            - Validate the url_suffix and the ids_request_key according to the Legacy_version value:
                case 1: url_suffix should be '/alerts/entities/alerts/v3' and the ids_request_key should be 'composite_ids'
                case 2: url_suffix should be '/alerts/entities/alerts/v2' and the ids_request_key should be 'ids'
        """
        from CrowdStrikeFalcon import resolve_detections_request

        http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")
        ids = ["1,2"]
        action_param_values = {"update_status": "new", "assign_to_name": "bot"}
        action_params_http_body = [{"name": "update_status", "value": "new"}, {"name": "assign_to_name", "value": "bot"}]
        resolve_detections_request(ids=ids, **action_param_values)
        assert http_request_mocker.call_args_list[0][1].get("url_suffix") == url_suffix
        assert http_request_mocker.call_args_list[0][1].get("json") == {
            "action_parameters": action_params_http_body,
            ids_request_key: ids,
        }

    def test_resolve_identity_detection(self, mocker: MockerFixture):
        """
        Given:
            - Arguments for the command.
        When
            - Calling the cs-falcon-resolve-identity-detection command.
        Then
            - Validate the data of the CommandResults object returned.
        """
        from CrowdStrikeFalcon import cs_falcon_resolve_identity_detection

        mocker.patch("CrowdStrikeFalcon.http_request", return_value=requests.Response())
        command_results = cs_falcon_resolve_identity_detection(args={"ids": "1,2"})
        assert isinstance(command_results.readable_output, str)
        assert "IDP Detection(s) 1, 2 were successfully updated" in command_results.readable_output

    def test_resolve_mobile_detection(self, mocker: MockerFixture):
        """
        Given:
            - Arguments for the command.
        When
            - Calling the cs-falcon-resolve-mobile-detection command.
        Then
            - Validate the data of the CommandResults object returned.
        """
        from CrowdStrikeFalcon import cs_falcon_resolve_mobile_detection

        mocker.patch("CrowdStrikeFalcon.http_request", return_value=requests.Response())
        command_results = cs_falcon_resolve_mobile_detection(args={"ids": "1,2"})
        assert isinstance(command_results.readable_output, str)
        assert "Mobile Detection(s) 1, 2 were successfully updated" in command_results.readable_output

    def test_handle_resolve_detections(self, mocker: MockerFixture):
        """
        Given:
            - Arguments for the handle_resolve_detections function.
        When:
            - Calling the handle_resolve_detections function.
        Then:
            - Verify that the http_request function is called once with the correct arguments.
            - Validate the error message when assigning both assign_to_name and assign_to_user_id.
        """
        from CrowdStrikeFalcon import handle_resolve_detections

        http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")
        args = {"assign_to_name": "name"}
        handle_resolve_detections(args, "")
        assert http_request_mocker.call_count == 1

        with pytest.raises(ValueError) as e:
            args = {"assign_to_name": "name", "assign_to_user_id": "id"}
            handle_resolve_detections(args, "")
        assert "Only one of the arguments assign_to_uuid, assign_to_name, assign_to_user_id should be provided." in str(e)


class TestIOAFetch:
    """Tests for IOA fetching via the new /alerts/queries/alerts/v2 + /alerts/entities/alerts/v2 flow."""

    IOA_SAMPLE_ALERT = {
        "composite_id": "20879a8064904ecfbb62c118a6a19411:fcs:ioa-211:d4724ce3-669d-43e7-9a08-5da491879e97",
        "created_timestamp": "2026-04-20T22:49:16.583865066Z",
        "updated_timestamp": "2026-04-20T22:49:16.583865066Z",
        "id": "fcs:ioa-211:d4724ce3-669d-43e7-9a08-5da491879e97",
        "product": "fcs",
        "type": "cloud-ioa",
        "severity": 70,
        "severity_name": "High",
        "name": "EC2 security group modified with new ingress rule allowing traffic from the public internet",
        "display_name": "EC2 security group modified with new ingress rule allowing traffic from the public internet",
        "cloud_provider": "aws",
        "cloud_account_id": "537409938058",
        "status": "new",
    }

    @staticmethod
    def _build_query_response(composite_ids):
        return {
            "meta": {"pagination": {"offset": 0, "limit": 100, "total": len(composite_ids)}},
            "resources": composite_ids,
        }

    @staticmethod
    def _patch_xsoar_params(mocker, ioa_fetch_query: str = ""):
        mocker.patch.object(
            demisto,
            "params",
            return_value={
                "fetch_incidents_or_detections": ["Indicator of Attack"],
                "ioa_fetch_query": ioa_fetch_query,
                "incidents_per_fetch": 15,
                "fetch_time": "3 days",
            },
        )

    @staticmethod
    def _last_run_with_ioa(last_run_size: int, ioa_data: dict | None = None):
        last_run = create_empty_last_run(last_run_size)
        if ioa_data is not None:
            from CrowdStrikeFalcon import LastRunIndex, set_last_run_per_type

            set_last_run_per_type(last_run, index=LastRunIndex.IOA, data=ioa_data)
        return last_run

    def test_fetch_items_ioa_happy_path(self, mocker: MockerFixture):
        """
        Given:
            - The IOA fetch type is selected and the query+entities endpoints return one alert.
        When:
            - Calling fetch_items.
        Then:
            - Validate the alerts/v2 query endpoint is hit with the compound product:'fcs'+type:'cloud-ioa' filter.
            - Validate the alerts/v2 entities endpoint is hit with the composite id payload.
            - Validate the resulting incident has the expected name/occurred/severity.
        """
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR

        self._patch_xsoar_params(mocker)
        mocker.patch.object(demisto, "getLastRun", return_value=self._last_run_with_ioa(TOTAL_FETCH_TYPE_XSOAR))

        composite_id = self.IOA_SAMPLE_ALERT["composite_id"]
        query_response = self._build_query_response([composite_id])
        entities_response = {"resources": [dict(self.IOA_SAMPLE_ALERT)]}

        http_request_mocker = mocker.patch(
            "CrowdStrikeFalcon.http_request",
            side_effect=[query_response, entities_response],
        )
        mocker.patch.object(demisto, "setLastRun")

        _, fetched_incidents = fetch_items()

        # Assert at least one HTTP request was issued and the first one targets the queries endpoint.
        assert http_request_mocker.call_count >= 2
        query_call = http_request_mocker.call_args_list[0]
        query_url = query_call[0][1]
        assert query_url.startswith("/alerts/queries/alerts/v2?filter=")
        # The URL-encoded compound product+type filter must appear in the URL.
        assert "product%3A%27fcs%27%2Btype%3A%27cloud-ioa%27" in query_url

        # The second call should be the entities POST with the composite_ids payload.
        entities_call = http_request_mocker.call_args_list[1]
        assert entities_call[0][0] == "POST"
        assert entities_call[0][1] == "/alerts/entities/alerts/v2"
        entities_payload = json.loads(entities_call[1]["data"])
        assert entities_payload == {"composite_ids": [composite_id]}

        assert len(fetched_incidents) == 1
        incident = fetched_incidents[0]
        assert incident["name"] == f"Indicator of Attack ID: {composite_id}"
        assert incident["occurred"] == self.IOA_SAMPLE_ALERT["created_timestamp"][:26] + "Z"
        # severity_name="High" -> severity_string_to_int returns 3.
        assert incident["severity"] == 3
        raw = json.loads(incident["rawJSON"])
        assert raw["composite_id"] == composite_id
        assert raw["incident_type"] == "ioa_detection"

    def test_fetch_items_ioa_empty_response(self, mocker: MockerFixture):
        """
        Given:
            - The IOA fetch type is selected and the queries endpoint returns zero composite ids.
        When:
            - Calling fetch_items.
        Then:
            - Validate that only the queries endpoint is hit (the entities endpoint is skipped).
            - Validate that no incidents are produced.
            - Validate that setLastRun is still invoked so the cursor advances normally.
        """
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR

        self._patch_xsoar_params(mocker)
        mocker.patch.object(demisto, "getLastRun", return_value=self._last_run_with_ioa(TOTAL_FETCH_TYPE_XSOAR))

        # Queries endpoint returns an empty `resources` list -> entities endpoint must not be called.
        query_response = self._build_query_response([])
        http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request", return_value=query_response)
        set_last_run_mocker = mocker.patch.object(demisto, "setLastRun")

        _, fetched_incidents = fetch_items()

        # Exactly one HTTP call: the queries endpoint. No entities POST when there are no ids.
        assert http_request_mocker.call_count == 1
        assert http_request_mocker.call_args_list[0][0][1].startswith("/alerts/queries/alerts/v2?filter=")
        # No incidents from an empty response.
        assert fetched_incidents == []
        # last_run is still persisted so the time window advances.
        assert set_last_run_mocker.call_count == 1

    def test_fetch_items_ioa_api_error_propagates(self, mocker: MockerFixture):
        """
        Given:
            - The IOA fetch type is selected and the queries endpoint raises a DemistoException
              (e.g. the alerts/v2 endpoint returned a non-200 response).
        When:
            - Calling fetch_items.
        Then:
            - The DemistoException propagates out of fetch_items (no silent swallow).
            - The entities endpoint is never called and no last_run is persisted for this run.
        """
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR

        self._patch_xsoar_params(mocker)
        mocker.patch.object(demisto, "getLastRun", return_value=self._last_run_with_ioa(TOTAL_FETCH_TYPE_XSOAR))

        api_error = DemistoException("Error in API call to CrowdStrike Falcon: code: 500 - reason: Internal Server Error")
        http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request", side_effect=api_error)
        set_last_run_mocker = mocker.patch.object(demisto, "setLastRun")

        with pytest.raises(DemistoException) as exc_info:
            fetch_items()

        assert "Internal Server Error" in str(exc_info.value)
        # Only the queries call was attempted; the entities POST never happened.
        assert http_request_mocker.call_count == 1
        # Last run must not advance when the fetch failed.
        set_last_run_mocker.assert_not_called()

    def test_fetch_items_ioa_user_fetch_query_is_anded(self, mocker: MockerFixture):
        """
        Given:
            - A user-provided ioa_fetch_query value.
        When:
            - Calling fetch_items.
        Then:
            - Validate the filter URL is wrapped as `(<base>)+(<fetch_query>)`, URL-encoded.
        """
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR

        user_fetch_query = "cloud_provider:'aws'"
        self._patch_xsoar_params(mocker, ioa_fetch_query=user_fetch_query)
        mocker.patch.object(demisto, "getLastRun", return_value=self._last_run_with_ioa(TOTAL_FETCH_TYPE_XSOAR))

        query_response = self._build_query_response([])
        http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request", return_value=query_response)
        mocker.patch.object(demisto, "setLastRun")

        fetch_items()

        query_url = http_request_mocker.call_args_list[0][0][1]
        # The encoded base filter should appear wrapped in parentheses, followed by the encoded user query.
        assert unquote(query_url).startswith("/alerts/queries/alerts/v2?filter=")
        decoded_filter = unquote(query_url).split("?filter=", 1)[1]
        assert decoded_filter.startswith("(product:'fcs'+type:'cloud-ioa'+created_timestamp:>'")
        assert decoded_filter.endswith(f")+({user_fetch_query})")

    def test_fetch_items_ioa_dedup_on_second_run(self, mocker: MockerFixture):
        """
        Given:
            - A first fetch that consumes one IOA alert.
        When:
            - Running fetch_items a second time with the produced last_run and the same alert returned by the API.
        Then:
            - The second fetch must not produce duplicate incidents.
        """
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR

        self._patch_xsoar_params(mocker)

        composite_id = self.IOA_SAMPLE_ALERT["composite_id"]
        query_response = self._build_query_response([composite_id])
        entities_response = {"resources": [dict(self.IOA_SAMPLE_ALERT)]}

        # First fetch starts with an empty last_run and returns the alert.
        # Second fetch reuses the produced last_run and is presented with the same alert again,
        # which should be filtered out by the dedup machinery (latest_ids in update_last_run_object).
        last_run_first = self._last_run_with_ioa(TOTAL_FETCH_TYPE_XSOAR)
        last_run_for_fetch: list = [last_run_first]
        mocker.patch.object(demisto, "getLastRun", side_effect=lambda: last_run_for_fetch[-1])

        # Two fetch_items calls -> 4 http_request calls in total
        # (1 query + 1 entities per call), all returning the same alert.
        mocker.patch(
            "CrowdStrikeFalcon.http_request",
            side_effect=[query_response, entities_response, query_response, entities_response],
        )

        captured_last_runs: list = []
        mocker.patch.object(demisto, "setLastRun", side_effect=lambda lr: captured_last_runs.append(lr))

        _, first_incidents = fetch_items()
        assert len(first_incidents) == 1
        assert captured_last_runs, "Expected setLastRun to be invoked after the first fetch"

        # Hand the produced last_run back to the next fetch via the side_effect closure.
        last_run_for_fetch.append(captured_last_runs[-1])
        _, second_incidents = fetch_items()
        assert second_incidents == []

    @pytest.mark.parametrize(
        "severity_name, expected_xsoar_severity",
        [
            ("Critical", 3),
            ("High", 3),
            ("Medium", 2),
            ("Low", 2),
            ("Informational", 0),
        ],
    )
    def test_ioa_severity_name_to_xsoar_severity(self, severity_name, expected_xsoar_severity):
        """
        Given:
            - A severity_name string from an IOA alert payload.
        When:
            - Mapping it to an XSOAR severity via the shared severity_string_to_int helper.
        Then:
            - The expected XSOAR severity bucket is returned.
        """
        from CrowdStrikeFalcon import severity_string_to_int

        assert severity_string_to_int(severity_name) == expected_xsoar_severity


class TestIOMFetch:
    # Since this integration fetches multiple incidents, the last run object contains a list of
    # last run objects for each incident type, for IOM, that is the 7th position
    def test_validate_iom_fetch_query(self):
        """
        Given:
            - An incorrect IOM fetch query to validate.
        When
            - Validating the query supplied by the user.
        Then
            - Validate that the correct error message is returned for the incorrect fetch query.
        """
        from CrowdStrikeFalcon import validate_iom_fetch_query

        with pytest.raises(DemistoException) as e:
            validate_iom_fetch_query(iom_fetch_query="scan_time: >some_time")
        assert "scan_time is not allowed as part of the IOM fetch query" in str(e)

    def test_fetch_query_with_paginating(self, mocker: MockerFixture):
        """
        Given:
            - The query of the last fetch, and the next token.
        When
            - Performing pagination and receiving the next token from the previous run.
        Then
            - Validate that the last fetch query is used in the current run, and the next token is added to the API call.
        """
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR, LastRunIndex, set_last_run_per_type

        fetch_filter = "cloud_provider: 'aws'"
        last_fetch_filter = f"scan_time: some_time+{fetch_filter}"
        iom_next_token = "dummy_token"

        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        set_last_run_per_type(
            last_run_object,
            index=LastRunIndex.IOM,
            data={
                "iom_next_token": iom_next_token,
                "last_fetch_filter": last_fetch_filter,
                "last_scan_time": "2023-01-01T00:00:00.000000Z",
            },
        )
        mocker.patch.object(
            demisto,
            "params",
            return_value={"fetch_incidents_or_detections": "Indicator of Misconfiguration", "iom_fetch_query": fetch_filter},
        )
        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)
        http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")
        fetch_items()
        assert http_request_mocker.call_args_list[0][1].get("params").get("filter") == last_fetch_filter
        assert http_request_mocker.call_args_list[0][1].get("params").get("next_token") == iom_next_token

    def test_fetch_query_with_paginating_empty_last_filter_error(self, mocker: MockerFixture):
        """
        Given:
            - An empty filter as the last fetch filter, and the next token.
        When
            - Performing pagination and receiving the next token from the previous run.
        Then
            - Validate that an error is thrown if the last fetch filter is an empty string.
        """
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR, LastRunIndex, set_last_run_per_type

        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        set_last_run_per_type(
            last_run_object,
            index=LastRunIndex.IOM,
            data={"iom_next_token": "dummy_token", "last_fetch_filter": "", "last_scan_time": "2023-01-01T00:00:00.000000Z"},
        )
        mocker.patch.object(
            demisto,
            "params",
            return_value={
                "fetch_incidents_or_detections": "Indicator of Misconfiguration",
                "iom_fetch_query": "cloud_provider: 'aws'",
            },
        )
        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)
        mocker.patch("CrowdStrikeFalcon.http_request")
        with pytest.raises(DemistoException) as e:
            fetch_items()
        assert "Last fetch filter must not be empty when doing pagination" in str(e)

    def test_fetch_query_without_pagination_and_not_first_run(self, mocker: MockerFixture):
        """
        Given:
            - The last_scan_time date from the previous fetch, and the fetch query..
        When
            - Performing fetch without pagination, and this is not the first fetch run.
        Then
            - Validate that we append the last_scan_time date, while using '>' in the fetch query.
        """
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR, LastRunIndex, set_last_run_per_type

        last_scan_time = "2023-01-01T00:00:00.000000Z"
        fetch_filter = "cloud_provider: 'aws'"
        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        set_last_run_per_type(last_run_object, index=LastRunIndex.IOM, data={"last_scan_time": last_scan_time})
        mocker.patch.object(
            demisto,
            "params",
            return_value={"fetch_incidents_or_detections": "Indicator of Misconfiguration", "iom_fetch_query": fetch_filter},
        )
        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)
        http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")
        fetch_items()
        assert f"scan_time: >'{last_scan_time}'+{fetch_filter}" == http_request_mocker.call_args_list[0][1].get("params").get(
            "filter"
        )

    @freeze_time("2023-01-04T00:00:00Z")
    def test_fetch_query_without_pagination_and_first_run(self, mocker: MockerFixture):
        """
        Given:
            - The fetch query.
        When
            - Performing fetch without pagination, and this is the first fetch run.
        Then
            - Validate that we append the last_scan_time date, while using '>=' in the fetch query.
        """
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR

        # The date configured in @freeze_time minues 3 days, which is the default FETCH_TIME
        last_scan_time = "2023-01-01T00:00:00.000000Z"
        fetch_filter = "cloud_provider: 'aws'"
        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        mocker.patch.object(
            demisto,
            "params",
            return_value={"fetch_incidents_or_detections": "Indicator of Misconfiguration", "iom_fetch_query": fetch_filter},
        )
        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)
        http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")
        fetch_items()
        assert f"scan_time: >='{last_scan_time}'+{fetch_filter}" == http_request_mocker.call_args_list[0][1].get("params").get(
            "filter"
        )

    @pytest.mark.parametrize(
        "next_toke_object, expected_next_token", [({"next_token": "dummy_token"}, "dummy_token"), ({}, None)]
    )
    def test_return_values_get_iom_resource_ids(self, mocker: MockerFixture, next_toke_object, expected_next_token):
        """
        Given:
            - The response of the API when a pagination object is returned or not.
        When
            - Doing an API call to retrieve the IOM resources.
        Then
            - Validate that we extract the resources and next token from the raw response, if they exist.
        """
        from CrowdStrikeFalcon import get_iom_ids_for_fetch

        exepcted_resource_ids = ["resource_1", "resource_2"]
        raw_response = {"meta": {"pagination": next_toke_object}, "resources": exepcted_resource_ids}
        mocker.patch("CrowdStrikeFalcon.http_request", return_value=raw_response)
        resource_ids, next_token = get_iom_ids_for_fetch(filter="some_filter", iom_next_token="not_important")
        assert exepcted_resource_ids == resource_ids
        assert expected_next_token == next_token

    def test_iom_events_pagination(self, mocker: MockerFixture):
        """
        Given:
            - 2 responses from the API that includes a pagination object.
        When
            - Fetching incidents, and the fetch limit is greater than the API limit of a single call (If the fetch limit is 4,
            and the API limit is 2, that means in each fetch, we should do 2 API calls, using pagination, to acquire 4 results, or
            until no more results are found).
        Then
            - Validate that we do API calls using the correct pagination arguments, and that we get the next token so it can be
            used in the next fetch round.
        """
        # Save two responses, where both of them return a next token with them. Make the api_limit=2,
        # and the fetch_limit=3, that way, we would need to do a request twice, and on the second request,
        # we would make it while having a limit of 1. We will check the arguments of the method get_iom_ids_for_fetch,
        # and the return values of iom_ids_pagination.
        from CrowdStrikeFalcon import get_iom_ids_for_fetch, iom_ids_pagination

        page_1_raw_response = load_json("test_data/iom_fetch_incidents/iom_resource_ids_page_1_raw_response.json")
        page_2_raw_response = load_json("test_data/iom_fetch_incidents/iom_resource_ids_page_2_raw_response.json")
        mocker.patch("CrowdStrikeFalcon.http_request", side_effect=[page_1_raw_response, page_2_raw_response])
        get_events_for_fetch_mocker = mocker.patch("CrowdStrikeFalcon.get_iom_ids_for_fetch", side_effect=get_iom_ids_for_fetch)
        events, next_token = iom_ids_pagination(filter="dummy_filter", iom_next_token="dummy_token", fetch_limit=3, api_limit=2)
        # We retrieved 3 events from the pagination phase, therefore, we assert that we acquire them
        assert events == ["resource_1", "resource_2", "resource_3"]
        # The first time we do pagination, we won't have any fetched incidents, and since the fetch limit is 3,
        # and api limit is 2, that means we do an API request to retrieve the first 2 events
        assert get_events_for_fetch_mocker.call_args_list[0][1].get("limit") == 2
        # After the first API request, the second one should use the token that was retrieved from the previous request
        assert get_events_for_fetch_mocker.call_args_list[1][1].get("iom_next_token") == "next_token_1"
        # After the first pagination, we would have fetched two incidents, and only 1 incident is left, therefore, we
        # do an API request with a limit of 1 in order to get the last incident of the current round
        assert get_events_for_fetch_mocker.call_args_list[1][1].get("limit") == 1
        # Since there are more results to be returned from the API, we assert that we get the next token so we can
        # use it in the next fetching round
        assert next_token == "next_token_2"

    def test_no_iom_resources_added_if_found_in_last_run(self, mocker: MockerFixture):
        """
        Given:
            - The resources ids of the last fetch run.
        When
            - Converting the fetched resources to incidents.
        Then
            - Validate that we do not create incidents of resources that have been fetched in the previous round.
        """
        # Make last_resource_ids have the values ['1', '2'], and return the values ['2', '3'] when fetching,
        # and once we enter the for loop to go over the fetched resources, '2' will not get picked up, since it
        # was already fetched, therfore, we check that in the returned incidents object, only the resource with id '3'
        # was added as an incident
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR, LastRunIndex, set_last_run_per_type

        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        set_last_run_per_type(last_run_object, index=LastRunIndex.IOM, data={"last_resource_ids": ["1", "2"]})
        mocker.patch.object(
            demisto,
            "params",
            return_value={
                "fetch_incidents_or_detections": "Indicator of Misconfiguration",
                "iom_fetch_query": "cloud_provider: 'aws'",
            },
        )
        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)
        # The function iom_ids_pagination returns the ids of the fetched events, and the
        # next token (for the sake of testing, it is None)
        mocker.patch("CrowdStrikeFalcon.iom_ids_pagination", return_value=(["2", "3"], None))
        mocker.patch(
            "CrowdStrikeFalcon.get_iom_resources",
            return_value=[
                {"id": "2", "scan_time": "2023-01-01T00:00:00.00Z"},
                {"id": "3", "scan_time": "2023-01-01T00:00:00.00Z"},
            ],
        )
        mocker.patch("CrowdStrikeFalcon.reformat_timestamp", return_value="2023-01-01T00:00:00.00Z")
        _, fetched_incidents = fetch_items()
        assert len(fetched_incidents) == 1
        rawJSON = json.loads(fetched_incidents[0].get("rawJSON"))
        assert rawJSON.get("incident_type") == "iom_configurations"
        assert rawJSON.get("id") == "3"

    def test_save_fetched_resources_when_paginating(self, mocker: MockerFixture):
        """
        Given:
            - The resource ids of the last fetch run.
        When
            - Saving the fetched resource ids.
        Then
            - Validate that we add the newly fetched resource ids to the previous ones, and not override them, when we are
            doing pagination.
        """
        # Make sure that we save all the resources that have been fetched throught the whole pagination process,
        # which can span on many fetches. We will have ids in last_resource_ids (['1']), and configure that we are
        # doing pagination, and that we fetched resource '2', and in the new returned last run, the key last_resource_ids
        # has a value of ['1', '2']
        from CrowdStrikeFalcon import fetch_items, LastRunIndex, TOTAL_FETCH_TYPE_XSOAR, set_last_run_per_type

        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        set_last_run_per_type(last_run_object, index=LastRunIndex.IOM, data={"last_resource_ids": ["1"]})
        mocker.patch.object(
            demisto,
            "params",
            return_value={
                "fetch_incidents_or_detections": "Indicator of Misconfiguration",
                "iom_fetch_query": "cloud_provider: 'aws'",
            },
        )
        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)
        mocker.patch("CrowdStrikeFalcon.iom_ids_pagination", return_value=(["2"], "next_token"))
        mocker.patch("CrowdStrikeFalcon.get_iom_resources", return_value=[{"id": "2", "scan_time": "2023-01-01T00:00:00.00Z"}])
        mocker.patch("CrowdStrikeFalcon.reformat_timestamp", return_value="2023-01-01T00:00:00.00Z")
        set_last_run_mocker = mocker.patch.object(demisto, "setLastRun", side_effect=demisto.setLastRun)
        fetch_items()
        assert set_last_run_mocker.call_args_list[0][0][0][LastRunIndex.IOM].get("last_resource_ids") == ["2", "1"]

    def test_save_fetched_resources_when_starting_pagination(self, mocker: MockerFixture):
        """
        Given:
            - The resource ids of the last fetch run.
        When
            - Saving the fetched resource ids.
        Then
            - Validate that we add the newly fetched resource ids to the previous ones, and not override them, when we are
            going to start pagination in the next fetch run.
        """
        # Make sure that we save all the resources that have been fetched before when starting the pagination process.
        # We will have ids in last_resource_ids (['1']), and configure that we are, doing pagination, and that we fetched
        # resource '2', and in the new returned last run, the key last_resource_ids has a value of ['1', '2']
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR, LastRunIndex, set_last_run_per_type

        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        set_last_run_per_type(
            last_run_object,
            index=LastRunIndex.IOM,
            data={"last_resource_ids": ["1"], "iom_next_token": "next_token", "last_fetch_filter": "previous_filter"},
        )
        mocker.patch.object(
            demisto,
            "params",
            return_value={
                "fetch_incidents_or_detections": "Indicator of Misconfiguration",
                "iom_fetch_query": "cloud_provider: 'aws'",
            },
        )
        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)
        mocker.patch("CrowdStrikeFalcon.iom_ids_pagination", return_value=(["2"], None))
        mocker.patch("CrowdStrikeFalcon.get_iom_resources", return_value=[{"id": "2", "scan_time": "2023-01-01T00:00:00.00Z"}])
        mocker.patch("CrowdStrikeFalcon.reformat_timestamp", return_value="2023-01-01T00:00:00.00Z")
        set_last_run_mocker = mocker.patch.object(demisto, "setLastRun", side_effect=demisto.setLastRun)
        fetch_items()
        assert set_last_run_mocker.call_args_list[0][0][0][LastRunIndex.IOM].get("last_resource_ids") == ["2", "1"]

    def test_fetch_iom_events(self, mocker: MockerFixture):
        """
        Given:
            - A last run object.
        When
            - Fetching IOM resources.
        Then
            - Validate that we construct the correct last run object for the next run by:
                1. The next token is saved.
                2. The largest scan_time date between the dates of all fetched resources is saved.
                3. The fetch query that was used in the API call is saved.
                4. The fetched resource ids are saved.
        """
        # A successful fetch of incidents
        from CrowdStrikeFalcon import fetch_items, TOTAL_FETCH_TYPE_XSOAR, LastRunIndex, set_last_run_per_type

        last_run_object = create_empty_last_run(TOTAL_FETCH_TYPE_XSOAR)
        set_last_run_per_type(
            last_run_object,
            index=LastRunIndex.IOM,
            data={
                "last_resource_ids": ["1"],
                "iom_next_token": "next_token",
                "last_fetch_filter": "last_dummy_filter",
                "last_scan_time": "2022-01-01T00:00:00.00Z",
            },
        )
        mocker.patch.object(
            demisto,
            "params",
            return_value={
                "fetch_incidents_or_detections": "Indicator of Misconfiguration",
                "iom_fetch_query": "cloud_provider: 'aws'",
            },
        )
        mocker.patch.object(demisto, "getLastRun", return_value=last_run_object)

        mocker.patch("CrowdStrikeFalcon.iom_ids_pagination", return_value=(["3", "2"], "new_next_token"))
        mocker.patch(
            "CrowdStrikeFalcon.get_iom_resources",
            return_value=[
                {"id": "3", "scan_time": "2024-01-01T00:00:00.00Z"},
                {"id": "2", "scan_time": "2023-01-01T00:00:00.00Z"},
            ],
        )
        set_last_run_mocker = mocker.patch.object(demisto, "setLastRun", side_effect=demisto.setLastRun)
        _, fetched_incidents = fetch_items()
        assert set_last_run_mocker.call_args_list[0][0][0][LastRunIndex.IOM] == {
            "iom_next_token": "new_next_token",
            "last_scan_time": "2024-01-01T00:00:00.000000Z",
            "last_fetch_filter": "last_dummy_filter",
            "last_resource_ids": ["3", "2", "1"],
        }
        assert len(fetched_incidents) == 2


def test_list_detection_summaries_command_no_results(mocker):
    """
    Test cs-falcon-list-detection-summaries when no detections found

    Given:
     - There is no detection in the system
    When:
     - Searching for detections using cs-falcon-list-detection-summaries command
     - The server returns empty list
    Then:
     - The command not fails
    """
    from CrowdStrikeFalcon import list_detection_summaries_command

    response = {
        "meta": {
            "query_time": 0.028057688,
        },
        "resources": [],
        "errors": [],
    }
    mocker.patch("CrowdStrikeFalcon.http_request", return_value=response)
    res = list_detection_summaries_command()
    assert res.readable_output == "### CrowdStrike Detections\n**No entries.**\n"


def test_run_command_batch_id(requests_mock, mocker):
    """
    Test cs-falcon-run-command when batch_id is given as an argument.

    Given:
     - A batch_id host_ids, command_type, full_command.
    When:
     - Running the command cs-falcon-run-command
    Then:
     - Check that the batch_id is correct.
    """
    from CrowdStrikeFalcon import run_command

    args = {"host_ids": "host_id", "command_type": "ls", "full_command": "ls", "batch_id": "batch_id"}
    mocker.patch.object(demisto, "args", return_value=args)
    response = load_json("test_data/run_command/run_command_with_batch.json")
    requests_mock.post(f"{SERVER_URL}/real-time-response/combined/batch-command/v1", json=response, status_code=201)
    results = run_command()
    expected_results = {
        "CrowdStrike": {
            "Command": [
                {
                    "BaseCommand": "ls",
                    "BatchID": "batch_id",
                    "Command": "ls",
                    "HostID": "aid",
                    "SessionID": "session_id",
                    "Stderr": "",
                    "Stdout": "Directory listing for C:\\ -\n\n"
                    "Name                                     Type         Size (bytes)    Size (MB)       "
                    "Last Modified (UTC+2)     Created (UTC+2)          \n"
                    "----                                     ----         ------------    ---------       "
                    "---------------------     ---------------          \n"
                    "$Recycle.Bin                             <Directory>  --              --              "
                    "6/19/2023 4:11:43 PM      9/15/2018 10:19:00 AM    \n"
                    "Config.Msi                               <Directory>  --              --              "
                    "11/14/2023 1:56:25 AM     8/17/2023 1:49:07 AM     \n",
                }
            ]
        }
    }
    assert results["EntryContext"] == expected_results


def test_run_command_without_batch_id(requests_mock, mocker):
    """
    Test cs-falcon-run-command when batch_id isn't given as an argument.

    Given:
     - host_ids, command_type, full_command.
    When:
     - Running the command cs-falcon-run-command
    Then:
     - Check that the batch_id is correct.
    """
    from CrowdStrikeFalcon import run_command

    args = {
        "host_ids": "host_id",
        "command_type": "ls",
        "full_command": "ls",
    }
    mocker.patch.object(demisto, "args", return_value=args)
    requests_mock.post(
        f"{SERVER_URL}/real-time-response/combined/batch-init-session/v1", json={"batch_id": "new_batch_id"}, status_code=201
    )
    response = load_json("test_data/run_command/run_command_with_batch.json")
    requests_mock.post(f"{SERVER_URL}/real-time-response/combined/batch-command/v1", json=response, status_code=201)
    results = run_command()
    expected_results = {
        "CrowdStrike": {
            "Command": [
                {
                    "BaseCommand": "ls",
                    "BatchID": "new_batch_id",
                    "Command": "ls",
                    "HostID": "aid",
                    "SessionID": "session_id",
                    "Stderr": "",
                    "Stdout": "Directory listing for C:\\ -\n\n"
                    "Name                                     Type         Size (bytes)    Size (MB)       "
                    "Last Modified (UTC+2)     Created (UTC+2)          \n"
                    "----                                     ----         ------------    ---------       "
                    "---------------------     ---------------          \n"
                    "$Recycle.Bin                             <Directory>  --              --              "
                    "6/19/2023 4:11:43 PM      9/15/2018 10:19:00 AM    \n"
                    "Config.Msi                               <Directory>  --              --              "
                    "11/14/2023 1:56:25 AM     8/17/2023 1:49:07 AM     \n",
                }
            ]
        }
    }
    assert results["EntryContext"] == expected_results


def test_list_users_command(mocker):
    """
    Test cs-falcon-list-users command.

    Given:
     - No arguments.
    When:
     - Running the command cs-falcon-list-users
    Then:
     - Check that the command returns the correct results.
    """
    import CrowdStrikeFalcon

    entities_api_mock = load_json("test_data/list_users_command/entities_users_response.json")
    queries_api_mock = load_json("test_data/list_users_command/queries_users_response.json")
    mocker.patch.object(CrowdStrikeFalcon, "http_request", side_effect=[entities_api_mock, queries_api_mock])

    result = CrowdStrikeFalcon.cs_falcon_list_users_command(args={})
    assert result.outputs_prefix == "CrowdStrike.Users"
    assert result.outputs_key_field == "uuid"
    assert result.outputs == queries_api_mock["resources"]


def test_get_cve_command(mocker):
    """
    Given:
        - Raw response with duplicates
    When:
        - Running cve command
    Then:
        - Validate that the response doesn't contain duplicates
    """
    import CrowdStrikeFalcon

    raw1 = {
        "id": "CVE-2023-12345",
        "description": "A1",
        "published_date": "2023-12-10T10:15:00Z",
        "base_score": 10,
        "vector": "A1B2C3D4",
        "cisa_info": {"due_date": "2023-12-24T00:00:00Z", "is_cisa_kev": True},
        "actors": ["ALPHA", "BETA", "GAMMA"],
    }
    raw2 = {
        "id": "CVE-2023-12345",
        "description": "A1",
        "published_date": "2023-12-10T10:15:00Z",
        "base_score": 10,
        "vector": "A1B2C3D4",
        "cisa_info": {"due_date": "2023-12-24T00:00:00Z", "is_cisa_kev": False},
        "actors": ["ALPHA", "BETA", "GAMMA"],
    }
    http_response = {"resources": [{"cve": raw1}, {"cve": raw1}, {"cve": raw1}, {"cve": raw2}, {"cve": raw1}, {"cve": raw2}]}

    mocker.patch.object(CrowdStrikeFalcon, "http_request", return_value=http_response)

    results = CrowdStrikeFalcon.get_cve_command(args={"cve": "CVE-2023-12345"})
    assert len(results) == 2


def test_http_request(mocker):
    """
    Given:
        - arguments of a http_request
    When:
        - Running any command
    Then:
        - Validate that the in case of 429 error code, get_token() will be called again in order to create a new token and
            generic_http_request will be called again as well.
    """
    from CrowdStrikeFalcon import http_request
    from requests import Response

    res_429 = Response()
    res_429.status_code = 429
    res_200 = Response()
    res_200.status_code = 200
    mock_request_get_token = mocker.patch("CrowdStrikeFalcon.get_token", return_value="token")
    mock_request_generic_http_request = mocker.patch("CrowdStrikeFalcon.generic_http_request", side_effect=[res_429, res_200])
    http_request(url_suffix="url_suffix", method="method", headers={}, no_json=True)
    # validate that in a case of 429, we will try again
    assert mock_request_generic_http_request.call_count == 2
    assert mock_request_get_token.call_count == 2


def test_http_request_get_token_request(mocker):
    """
    Given:
        - arguments of a http_request send by get_token_request()
    When:
        - requesting a new token
    Then:
        - validate that the correct arguments were sent
    """
    from CrowdStrikeFalcon import http_request
    from requests import Response

    res_200 = Response()
    res_200.status_code = 200
    mock_request_generic_http_request = mocker.patch("CrowdStrikeFalcon.generic_http_request", side_effect=[res_200])
    mocker.patch.object(demisto, "params", return_value={"url": SERVER_URL, "proxy": True})
    body = {"client_id": "client_id", "client_secret": "client_secret"}
    retries = 5
    status_list_to_retry = [429]
    valid_status_codes = [200, 201, 202, 204]
    int_timeout = 60
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    method = "POST"
    url_suffix = "/oauth2/token"
    http_request(url_suffix=url_suffix, method=method, data=body, get_token_flag=False, headers=headers, no_json=True)
    headers["User-Agent"] = "PANW-XSOAR"
    assert mock_request_generic_http_request.call_count == 1
    mock_request_generic_http_request.assert_called_with(
        method=method,
        server_url=SERVER_URL,
        headers=headers,
        url_suffix=url_suffix,
        data=body,
        files=ANY,
        params=ANY,
        proxy=ANY,
        resp_type="response",
        verify=True,
        error_handler=ANY,
        json_data=ANY,
        timeout=int_timeout,
        ok_codes=valid_status_codes,
        retries=retries,
        status_list_to_retry=status_list_to_retry,
    )


def test_http_request_get_token_request_429(mocker, requests_mock):
    """
    Given:
        - arguments of a http_request send by get_token_request()
    When:
        - requesting a new token
    Then:
        - Validate that in case of 429 error code when trying to create a new token won't return None at the end of http_request,
            but raise an exception with the relevant error.
    """
    from CrowdStrikeFalcon import http_request

    requests_mock.post(
        f"{SERVER_URL}/oauth2/token",
        json={
            "meta": {"query_time": 0.000875986, "powered_by": "crowdstrike-api-gateway", "trace_id": "trace_id"},
            "errors": [{"code": 429, "message": "API rate limit exceeded."}],
        },
        status_code=429,
    )
    mocker.patch.object(demisto, "params", return_value={"url": SERVER_URL, "proxy": True})
    mock_request_generic_http_request = mocker.patch("CrowdStrikeFalcon.generic_http_request")
    mock_request_error_handler = mocker.patch("CrowdStrikeFalcon.error_handler")
    body = {"client_id": "client_id", "client_secret": "client_secret"}
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    method = "POST"
    url_suffix = "/oauth2/token"
    http_request(url_suffix=url_suffix, method=method, data=body, get_token_flag=False, headers=headers, no_json=True)
    assert mock_request_generic_http_request.call_count == 1
    assert mock_request_error_handler.call_count == 1


class ResMocker:
    def __init__(self, http_response, status_code, reason, text=None):
        self.http_response = http_response
        self.status_code = status_code
        self.reason = reason
        self.ok = False
        self.text = text

    def json(self):
        if self.http_response is None:
            raise ValueError("No JSON object could be decoded")
        return self.http_response


def test_error_handler():
    """
    Given:
        - A response with an error from the API.
    When:
        - Running any command
    Then:
        - Validate that the error message contains the correct info
    """
    from CrowdStrikeFalcon import error_handler

    status_code = 429
    reason = "Too Many Requests API rate limit exceeded."
    res_json = {
        "meta": {
            "query_time": 0.00571046,
            "pagination": {"offset": 0, "limit": 100, "total": 2},
            "powered_by": "legacy-detects",
            "trace_id": "11111111-1111-1111-1111-111111111111",
        },
        "errors": [],
    }

    arg_res = ResMocker(res_json, status_code, reason)
    try:
        error_handler(arg_res)
    except DemistoException as e:
        assert e.message == f"Error in API call to CrowdStrike Falcon: code: {status_code} - reason: {reason}"


def test_error_handler_non_json_response():
    """
    Given:
        - A response from the API that returns a non-JSON body (e.g., text/plain from NGSIEM errors).
    When:
        - Running error_handler
    Then:
        - Validate that the error message contains the status code, reason, and the plain-text body content.
        - Validate that the body is truncated to 4000 characters.
    """
    from CrowdStrikeFalcon import error_handler

    status_code = 500
    reason = "Internal Server Error"
    plain_text_body = "SearchQueryParsingError: Error parsing query at line 1:0"

    arg_res = ResMocker(None, status_code, reason, text=plain_text_body)
    with pytest.raises(DemistoException) as e:
        error_handler(arg_res)
    assert e.value.message == (
        f"Error in API call to CrowdStrike Falcon: code: {status_code} - reason: {reason}\n{plain_text_body}"
    )


@pytest.mark.parametrize(
    "url_suffix, expected_len, filter_args",
    [
        (
            "alerts/queries/alerts/v2?filter=product%3A%27ngsiem%27%2Bcreated_timestamp%3A%3E%272024-06-19T15%3A25%3A00Z%27",
            3,
            "product:'ngsiem'+created_timestamp:>'2024-06-19T15:25:00Z'",
        ),
        ("alerts/queries/alerts/v2?filter=", 3, None),
    ],
)
def test_get_detection___url_and_params(mocker, url_suffix, expected_len, filter_args):
    """
    Given:
        - Input arguments including `filter_args` and a mocked HTTP request.
    When:
        - Invoking `get_fetch_detections` with various input parameters
    Then:
        - Verify that the correct `url_suffix` is used

    Test Scenarios:
        1. When `Legacy_version` is False, the `url_suffix` should be:
           "alerts/queries/alerts/v2?filter=product%3A%27ngsiem%27%2Bcreated_timestamp%3A%3E%272024-06-19T15%3A25%3A00Z%27"
           since all parameters are part of the URL and are URL-encoded, aand the expected len is 3 since all the
           provided parameters are passed under 'parameters'
        2. When `Legacy_version` is False, the `url_suffix` should be:
           "alerts/queries/alerts/v2?filter=product%3A%27epp%27%2Btype%3A%27ldt%27%"
           filter
           since all parameters are part of the URL and are URL-encoded, and the expected len is 3 since all the
           provided parameters are passed under 'parameters'
        3. When `Legacy_version` is True, the `url_suffix` should be:
           "/detects/queries/detects/v1" and the expected len is 3 since all the
           provided parameters are passed under 'parameters'.
    """
    from CrowdStrikeFalcon import get_detections

    http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")

    get_detections(last_behavior_time="2024-06-19T15:25:00Z", behavior_id=123, filter_arg=filter_args)
    assert http_request_mocker.call_args_list[0][0][1] == url_suffix
    assert len(http_request_mocker.call_args_list[0][0]) == expected_len


@pytest.mark.parametrize(
    "tag, url_suffix, data",
    [
        (
            "test_tag",
            "/alerts/entities/alerts/v3",
            '{"action_parameters": [{"name": "show_in_ui", "value": "True"}, {"name": "assign_to_uuid", "value": "123"}, {"name": "update_status", "value": "resolved"}, {"name": "append_comment", "value": "comment"}, {"name": "add_tag", "value": "test_tag"}], "composite_ids": ["123"]}',  # noqa: E501
        ),
    ],
)
def test_resolve_detection(mocker, tag, url_suffix, data):
    """
    Given:
        - tag, url_suffix and data.
    When:
        - Running resolve_detection
    Then:
        Validate that the correct url_suffix is used
        the url_suffix should be alerts/entities/alerts/v3
    """
    from CrowdStrikeFalcon import resolve_detection

    http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")

    resolve_detection(
        ids=["123"], status="resolved", assigned_to_uuid="123", username=None, show_in_ui="True", comment="comment", tag=tag
    )
    assert http_request_mocker.call_args_list[0][0][1] == url_suffix
    assert http_request_mocker.call_args_list[0][1]["data"] == data


def test_resolve_detection_username_not_legacy(mocker):
    """
    Given:
        - A username is provided to assign detection to
    When:
        - Running resolve_detection not in legacy mode
    Then:
        - The username to uuid function should not be called and data containing the username should be sent
    """
    from CrowdStrikeFalcon import resolve_detection_command

    http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")
    mocker.patch("CrowdStrikeFalcon.demisto.args", return_value={"ids": ["123"], "username": "username"})

    expected_data = json.dumps(
        {"action_parameters": [{"name": "assign_to_user_id", "value": "username"}], "composite_ids": ["123"]}
    )

    resolve_detection_command()
    assert http_request_mocker.call_args_list[0][1]["data"] == expected_data


@pytest.mark.parametrize(
    "url_suffix, request_params",
    [
        (
            "/alerts/queries/alerts/v2?filter=product%3A%27epp%27%2Btype%3A%27ldt%27%2Bupdated_timestamp%3A%3E%272024-06-19T15%3A25%3A00Z%27",
            {"sort": "created_timestamp.asc", "offset": 5, "limit": 3},
        ),
    ],
)
def test_get_fetch_detections__url(mocker, url_suffix, request_params):
    """
    Given:
        - The `Legacy_version` flag
    When:
        - Invoking `get_fetch_detections` with various input parameters
    Then:
        - Verify that the correct `url_suffix` is used and the input parameters are correctly passed

    Test Scenarios:
        the `url_suffix` should be:
        '/alerts/queries/alerts/v2?filter=product%3A%27epp%27%2Btype%3A%27ldt%27%2Bupdated_timestamp%3A%3E%272024-06-19T15%3A25%3A00Z%27'
        All parameters (except 'limit') are part of the URL and are URL-encoded.
    """
    from CrowdStrikeFalcon import get_fetch_detections

    http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")

    get_fetch_detections(filter_arg=None, offset=5, last_updated_timestamp="2024-06-19T15:25:00Z", has_limit=True, limit=3)

    assert http_request_mocker.call_args_list[0][0][1] == url_suffix
    assert http_request_mocker.call_args_list[0][0][2] == request_params


def test_detections_to_human_readable(mocker):
    """
    Given:
        - The Legacy_version flag
    When:
        - Running detections_to_human_readable
    Then:
        - Validate that the correct output is returned based on the Legacy_version flag
    """
    from CrowdStrikeFalcon import detections_to_human_readable

    expected_output = [
        {"status": "open", "max_severity": "critical", "detection_id": "123", "created_time": "2022-01-01T00:00:00Z"}
    ]
    mock_table_to_markdown = mocker.patch("CrowdStrikeFalcon.tableToMarkdown")
    input = (
        {
            "status": "open",
            "max_severity_displayname": "high",
            "detection_id": "123",
            "created_timestamp": "2022-01-01T00:00:00Z",
            "severity_name": "critical",
        },
    )
    detections_to_human_readable(input)

    assert mock_table_to_markdown.call_args[0][1] == expected_output


def test_modify_detection_summaries_outputs():
    """
    Given:
        - A detection dictionary
    When:
        - Running modify_detection_summaries_outputs
    Then:
        - Validate that the output is correctly modified
    """
    from CrowdStrikeFalcon import modify_detection_summaries_outputs

    detection = {
        "pattern_disposition_details": "details",
        "timestamp": "time",
        "device": {"device_id": "device1", "hostinfo": "info"},
        "filename": "file",
        "cmdline": "cmd",
        "pattern_disposition": "disposition",
        "parent_details": {
            "sha256": "parent_sha256_test",
            "cmdline": "parent_cmd_test",
            "md5": "parent_md5_test",
            "process_graph_id": "parent_id_test",
        },
        "composite_id": "composite",
    }

    modified_detection = modify_detection_summaries_outputs(detection)

    assert modified_detection["behaviors"]["device_id"] == "device1"
    assert modified_detection["behaviors"]["filename"] == "file"
    assert modified_detection["hostinfo"] == "info"
    assert modified_detection["detection_id"] == "composite"
    assert modified_detection["behaviors"]["parent_details"]["parent_sha256"] == "parent_sha256_test"
    assert modified_detection["behaviors"]["parent_details"]["parent_cmdline"] == "parent_cmd_test"
    assert modified_detection["behaviors"]["parent_details"]["parent_md5"] == "parent_md5_test"
    assert modified_detection["behaviors"]["timestamp"] == "time"


def test_truncate_long_time_str():
    """
    Given:
        - A list of detections
    When:
        - Running truncate_long_time_str
    Then:
        - Validate that the time string is correctly truncated to 6 digits after the dot
    """
    from CrowdStrikeFalcon import truncate_long_time_str

    detections = [
        {"time": "2022-01-01T00:00:00.000000000000000000000000000Z"},
        {"time": "2022-01-01T00:00:00.000000Z"},
        {"time": "2022-01-01T00:00:00.000000000000000000000000000Z"},
        {"time": "2022-01-01T00:00:00Z"},
    ]
    time_key = "time"

    assert truncate_long_time_str(detections, time_key) == [
        {"time": "2022-01-01T00:00:00.000000Z"},
        {"time": "2022-01-01T00:00:00.000000Z"},
        {"time": "2022-01-01T00:00:00.000000Z"},
        {"time": "2022-01-01T00:00:00Z"},
    ]


def test_get_detections_entities__url(mocker):
    """
    When:
        - Running get_detections_entities
    Then:
        - Validate that the correct url is used based on the Legacy_version flag
    """
    from CrowdStrikeFalcon import get_detections_entities

    expected_url = "/alerts/entities/alerts/v2"
    http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")
    get_detections_entities(["123"])
    assert http_request_mocker.call_args_list[0][0][1] == expected_url


@pytest.mark.parametrize(
    "expected_url, expected_parameters",
    [
        (
            "/alerts/queries/alerts/v2?filter=created_timestamp%3A%3E%272024-06-19T15%3A25%3A00Z%27",
            {"sort": "created_timestamp.asc", "offset": 0, "limit": 2},
        ),
    ],
)
def test_get_detections_ids__url_and_params(mocker, expected_url, expected_parameters):
    """
    Given:
        - Input arguments including expected_url and expected_parameters.
    When:
        - Running get_detections_ids
    Then:
        Validate that the correct url and params are sent based on the Legacy_version flag
        the url_suffix should be:
        '/alerts/queries/alerts/v2?filter=created_timestamp%3A%3E%272024-06-19T15%3A25%3A00Z%27'
        and the parameters are passed under are:
        {'sort': 'created_timestamp.asc', 'offset': 0, 'limit': 2}


    """
    from CrowdStrikeFalcon import get_detections_ids

    http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")
    get_detections_ids(filter_arg="created_timestamp:>'2024-06-19T15:25:00Z'")
    assert http_request_mocker.call_args_list[0][0][1] == expected_url
    assert http_request_mocker.call_args_list[0][0][2] == expected_parameters


def test_modify_detection_outputs(mocker):
    """
    Given:
        - A detection dictionary
    When:
        - Running modify_detection_outputs
    Then:
        - Validate that the output is correctly modified
    """
    from CrowdStrikeFalcon import modify_detection_outputs

    mocker.patch("CrowdStrikeFalcon.DETECTIONS_BEHAVIORS_KEY_MAP", {"key1": "value1", "key2": "value2"})

    detection = {
        "key1": "value1",
        "key2": "value2",
        "key3": "value3",
        "parent_details": "details",
        "triggering_process_graph_id": "id",
        "testing": "test",
    }

    assert modify_detection_outputs(detection) == {
        "key3": "value3",
        "testing": "test",
        "behaviors": [{"key1": "value1", "key2": "value2", "parent_details": "details", "triggering_process_graph_id": "id"}],
    }


@pytest.mark.parametrize(
    "expected_results",
    [
        ({"action_parameters": [{"name": "key1", "value": "value1"}], "composite_ids": ["123"]}),
    ],
)
def test_resolve_detections_prepare_body_request(mocker, expected_results):
    """
    Given:
        - action_parameters
    When:
        - Running resolve_detections_prepare_body_request
    Then:
        - Validate that the correct body is returned based on the Legacy_version flag
    """
    from CrowdStrikeFalcon import resolve_detections_prepare_body_request

    assert resolve_detections_prepare_body_request(ids=["123"], action_params_values={"key1": "value1"}) == expected_results


def test_resolve_detections_request__url(mocker):
    """
    When:
        - Running resolve_detections_request
    Then:
        - Validate that the correct url is used based on the Legacy_version flag
    """
    from CrowdStrikeFalcon import resolve_detections_request

    expected_url = "/alerts/entities/alerts/v3"
    http_request_mocker = mocker.patch("CrowdStrikeFalcon.http_request")
    resolve_detections_request(ids=["123"])
    assert http_request_mocker.call_args_list[0][1]["url_suffix"] == expected_url


def test_get_status(mocker):
    """
    Given:
        - Raw response of get_status_request
    When:
        - Running get_status command
    Then:
        - Validate that the contains the ids and state
    """
    import CrowdStrikeFalcon

    device_ids = [
        "0bde2c4645294245aca522971ccc4567",
        "04a75a2d15b44a5995c9c17200ad1212",
        "046761c46ec84f40b27b6f79ce7c6543",
        "8ed44198a6f64f9fabd0479c30989876",
        "d4210a0957e640f18c237a2fa1141122",
    ]

    response = load_json("test_data/online_states_response.json")

    mocker.patch.object(CrowdStrikeFalcon, "http_request", return_value=response)

    results = CrowdStrikeFalcon.get_status(device_ids)
    assert len(results) == 5
    assert results == {
        "0bde2c4645294245aca522971ccc4567": "Online",
        "04a75a2d15b44a5995c9c17200ad1212": "Online",
        "046761c46ec84f40b27b6f79ce7c6543": "Online",
        "8ed44198a6f64f9fabd0479c30989876": "Online",
        "d4210a0957e640f18c237a2fa1141122": "Online",
    }


def test_fix_time_field():
    """
    Given:
        - A detection, a string representing the key of the time we want to fix.
    When:
        - Running fetch_incidents command
    Then:
        - Validate that the value of the given key in the detection was updated correctly.
    """
    from CrowdStrikeFalcon import fix_time_field

    detection_1 = {"created_timestamp": "2023-04-20T11:13:10.424647194Z"}
    detection_2 = {"created_timestamp": "2023-04-20T11:13:10.424647Z"}
    detection_3 = {"created_timestamp": "2023-04-20T11:13:10.424Z"}

    fix_time_field(detection_1, "created_timestamp")
    fix_time_field(detection_2, "created_timestamp")
    fix_time_field(detection_3, "created_timestamp")

    assert detection_1["created_timestamp"] == "2023-04-20T11:13:10.424647Z"
    assert detection_2["created_timestamp"] == "2023-04-20T11:13:10.424647Z"
    assert detection_3["created_timestamp"] == "2023-04-20T11:13:10.424Z"


def test_enrich_groups_no_resources(mocker):
    """
    Given:
        - A non exist group id.
    When:
        - Running enrich_groups.
    Then:
        - Validate that the result are empty and no exception raised.
    """
    import CrowdStrikeFalcon

    group_ids = "test_group_id"

    mocker.patch.object(CrowdStrikeFalcon, "http_request", return_value={"resources": None})

    assert CrowdStrikeFalcon.enrich_groups(group_ids) == {}


@pytest.mark.parametrize("command", ["fetch-events", "fetch-incidents"])
def test_fetch_items_reads_last_run_indexes_correctly(mocker, command):
    """
    Ensure that fetch_events correctly accesses each index of last_run based on its data type.

    Each index in last_run is mocked with the appropriate identifiers.
    The test verifies that these identifiers appear in the same order in the last_run output,
    indicating correct index usage within the function.
    """
    from CrowdStrikeFalcon import fetch_items, LastRunIndex, TOTAL_FETCH_TYPE_XSIAM, TOTAL_FETCH_TYPE_XSOAR, set_last_run_per_type

    last_run_size = TOTAL_FETCH_TYPE_XSIAM if command == "fetch-events" else TOTAL_FETCH_TYPE_XSOAR
    last_run_identifiers = create_empty_last_run(last_run_size)

    set_last_run_per_type(last_run_identifiers, index=LastRunIndex.DETECTIONS, data={"Detection ID:": 1})
    set_last_run_per_type(last_run_identifiers, index=LastRunIndex.IDP_DETECTIONS, data={"IDP ID:": 3})
    set_last_run_per_type(last_run_identifiers, index=LastRunIndex.MOBILE_DETECTIONS, data={"Mobile ID:": 4})
    set_last_run_per_type(last_run_identifiers, index=LastRunIndex.ON_DEMAND_DETECTIONS, data={"ODS ID:": 5})
    set_last_run_per_type(last_run_identifiers, index=LastRunIndex.OFP_DETECTION, data={"OFP ID:": 6})

    if command == "fetch-incidents":
        set_last_run_per_type(last_run_identifiers, index=LastRunIndex.IOM, data={"IOM ID:": 7})
        set_last_run_per_type(last_run_identifiers, index=LastRunIndex.IOA, data={"IOA ID:": 8})
        set_last_run_per_type(last_run_identifiers, index=LastRunIndex.THIRD_PARTY_DETECTIONS, data={"THIRD PARTY ID:": 9})
        set_last_run_per_type(last_run_identifiers, index=LastRunIndex.NGSIEM_DETECTIONS, data={"NGSIEM ID:": 10})
        set_last_run_per_type(last_run_identifiers, index=LastRunIndex.NGSIEM_INCIDENTS, data={"NGSIEM INCIDENT ID:": 11})
        set_last_run_per_type(last_run_identifiers, index=LastRunIndex.NGSIEM_AUTOMATED_LEADS, data={"AUTOMATED LEAD ID:": 12})
        set_last_run_per_type(last_run_identifiers, index=LastRunIndex.NGSIEM_CASES, data={"NGSIEM CASE ID:": 13})

    # Create a copy to avoid reference issues
    last_run_identifiers_copy = list(last_run_identifiers)

    mocker.patch("CrowdStrikeFalcon.demisto.getLastRun", return_value=last_run_identifiers_copy)
    mocker.patch("CrowdStrikeFalcon.demisto.params", return_value={})

    last_run_identifiers_result, _ = fetch_items(command=command)

    # Verify that fetch_events refers to the correctly indexes for each type by last_run object.
    assert last_run_identifiers_result == last_run_identifiers


def test_http_request_is_time_sensitive_timeout_and_retries(mocker):
    """
    Given:
        - is_time_sensitive() returns True or False
        - get_token_flag is True or False
    When:
        - Running http_request function
    Then:
        - Validate that when is_time_sensitive()=True: retries=0 and timeout=15
        - Validate that when is_time_sensitive()=False: retries=0 and timeout=60 (default)
        - Validate that when get_token_flag=False: retries=5 and timeout=60
    """
    from CrowdStrikeFalcon import http_request
    from requests import Response

    res_200 = Response()
    res_200.status_code = 200
    res_200._content = b'{"result": "success"}'

    mocker.patch.object(demisto, "params", return_value={"url": SERVER_URL, "proxy": True})
    mocker.patch("CrowdStrikeFalcon.get_token", return_value="test_token")

    # Test case 1: is_time_sensitive()=True should set retries=0 and timeout=15
    mock_generic_http_request = mocker.patch("CrowdStrikeFalcon.generic_http_request", side_effect=[res_200])
    mocker.patch("CrowdStrikeFalcon.is_time_sensitive", return_value=True)

    http_request(method="GET", url_suffix="/test", no_json=True)

    assert mock_generic_http_request.call_count == 1
    call_args = mock_generic_http_request.call_args[1]  # kwargs
    assert call_args["retries"] == 0, f"Expected retries=0 when is_time_sensitive=True, got {call_args['retries']}"
    assert call_args["timeout"] == 15, f"Expected timeout=15 when is_time_sensitive=True, got {call_args['timeout']}"

    # Test case 2: is_time_sensitive()=False should set retries=0 and timeout=60
    mock_generic_http_request = mocker.patch("CrowdStrikeFalcon.generic_http_request", side_effect=[res_200])
    mocker.patch("CrowdStrikeFalcon.is_time_sensitive", return_value=False)

    http_request(method="GET", url_suffix="/test", no_json=True)

    assert mock_generic_http_request.call_count == 1
    call_args = mock_generic_http_request.call_args[1]  # kwargs
    assert call_args["retries"] == 0, f"Expected retries=0 when is_time_sensitive=False, got {call_args['retries']}"
    assert call_args["timeout"] == 60, f"Expected timeout=60 when is_time_sensitive=False, got {call_args['timeout']}"

    # Test case 3: get_token_flag=False should set retries=5 and timeout=60
    mock_generic_http_request = mocker.patch("CrowdStrikeFalcon.generic_http_request", side_effect=[res_200])
    mocker.patch("CrowdStrikeFalcon.is_time_sensitive", return_value=False)

    http_request(method="GET", url_suffix="/test", get_token_flag=False, no_json=True)

    assert mock_generic_http_request.call_count == 1
    call_args = mock_generic_http_request.call_args[1]  # kwargs
    assert call_args["retries"] == 5, f"Expected retries=5 when get_token_flag=False, got {call_args['retries']}"
    assert call_args["timeout"] == 60, f"Expected timeout=60 when get_token_flag=False, got {call_args['timeout']}"


class TestFetchAssetsFlow:
    """Tests for the fetch-assets flow."""

    def generate_mock_alerts(self, count, start_id=1):
        """Generates a list of mock CNAPP alerts."""
        alerts = []
        for i in range(count):
            alerts.append(
                {
                    "severity": "Critical",
                    "first_seen_timestamp": "2025-11-24T11:04:03Z",
                    "last_seen_timestamp": "2025-11-26T10:18:35Z",
                    "detection_name": f"PotentialKernelTampering-{start_id + i}",
                    "detection_event_simple_name": "BPFCommandIssued",
                    "detection_description": "The eBPF feature has been invoked from within a container. This is a highly"
                    "unusual activity from within the container and can be used to load a kernel root kit or manipulate kernel"
                    "behavior or settings effecting the entire host system where the container is running.",
                    "containers_impacted_count": "1",
                    "containers_impacted_ids": ["test"],
                }
            )
        return alerts

    @pytest.mark.parametrize(
        "total, expected_items_count, expected_snapshot_id, expected_last_run",
        [
            (
                100,
                100,
                "123123",
                {
                    "offset": 0,
                    "total_fetched_until_now": 0,
                },
            ),
            (
                200,
                1,
                "123123",
                {"offset": 100, "total_fetched_until_now": 100, "snapshot_id": "123123", "nextTrigger": "0", "type": 1},
            ),
        ],
    )
    def test_get_cnapp_assets(self, mocker, requests_mock, total, expected_items_count, expected_snapshot_id, expected_last_run):
        """
        Given:
            - a mock response for /container-security/combined/container-alerts/v1 endpoint.
            - case 1: mock where the response is the last one (no further calls should be done)
            - case 2: mock where the response is not the last one (further calls should be done)
        When:
            - Calling get_cnapp_assets.
        Then:
            - Verify that http_request is called with correct pagination parameters.
            - Verify that all alerts are collected.
            - Verify that the last_run object is updated correctly for pagination.
            - case 1: items_count should be set to 1, no nextTrigger/type/snapshot_id in the last run
            - case 2: items_count should be set to 100, nextTrigger/type/snapshot_id in the last run
        """
        from CrowdStrikeFalcon import get_cnapp_assets
        import time

        initial_last_run = {"offset": 0, "total_fetched_until_now": 0}

        mocker.patch.object(
            demisto,
            "params",
            return_value={
                "url": SERVER_URL,
            },
        )
        mocker.patch("CrowdStrikeFalcon.demisto.getAssetsLastRun", return_value=initial_last_run)
        mocker.patch.object(time, "time", return_value=123.123)
        mock_alerts_page1 = self.generate_mock_alerts(100, 1)
        request_mock_obj = requests_mock.get(
            f"{SERVER_URL}/container-security/combined/container-alerts/v1?offset=0&limit=100",
            json={"resources": mock_alerts_page1, "meta": {"pagination": {"offset": 0, "limit": 100, "total": total}}},
        )

        new_last_run, cnapp_alerts, items_count, snapshot_id = get_cnapp_assets()

        assert request_mock_obj.call_count == 1
        assert len(cnapp_alerts) == 100
        assert items_count == expected_items_count
        assert snapshot_id == expected_snapshot_id
        assert new_last_run == expected_last_run

    def test_fetch_assets_command_two_calls_flow(self, mocker, requests_mock):
        """
        Given:
            - A scenario where fetch-assets needs two API calls to retrieve all assets.
            - First call returns 100 assets, indicating a total of 150.
            - Second call returns the remaining 50 assets.
        When:
            - Calling fetch_assets_command twice to simulate the full flow.
        Then:
            - Verify that http_request is called with correct pagination parameters for both calls.
            - Verify that send_data_to_xsiam is called twice with the correct assets and snapshot_id.
            - Verify that setAssetsLastRun is called twice with the correct last_run objects.
            - Verify that updateModuleHealth is called twice with the correct assetsPulled count.
        """
        from CrowdStrikeFalcon import fetch_assets_command, CNAPP_PRODUCT, VENDOR
        import time

        # Mocks
        mock_get_assets_last_run = mocker.patch("CrowdStrikeFalcon.demisto.getAssetsLastRun")
        mock_set_assets_last_run = mocker.patch("CrowdStrikeFalcon.demisto.setAssetsLastRun")
        mock_send_data_to_xsiam = mocker.patch("CrowdStrikeFalcon.send_data_to_xsiam")
        mock_update_module_health = mocker.patch("CrowdStrikeFalcon.demisto.updateModuleHealth")
        mocker.patch.object(time, "time", return_value=123.123)
        mocker.patch.object(demisto, "params", return_value={"fetch_assets_type": "CNAPP Alerts"})

        # --- First Call ---
        # Initial last_run for the first call
        mock_get_assets_last_run.return_value = {"offset": 0, "total_fetched_until_now": 0}

        # Mock API response for the first call (100 assets out of 150)
        mock_alerts_page1 = self.generate_mock_alerts(100, 1)
        requests_mock.get(
            f"{SERVER_URL}/container-security/combined/container-alerts/v1?offset=0&limit=100",
            json={"resources": mock_alerts_page1, "meta": {"pagination": {"offset": 0, "limit": 100, "total": 150}}},
        )

        fetch_assets_command()

        # Assertions for the first call
        assert mock_send_data_to_xsiam.call_count == 1
        mock_send_data_to_xsiam.assert_called_with(
            data=mock_alerts_page1,
            vendor=VENDOR,
            product=CNAPP_PRODUCT,
            data_type="assets",
            snapshot_id="123123",
            items_count=1,
            should_update_health_module=False,
        )
        assert mock_set_assets_last_run.call_count == 1
        mock_set_assets_last_run.assert_called_with(
            {"offset": 100, "total_fetched_until_now": 100, "snapshot_id": "123123", "nextTrigger": "0", "type": 1}
        )
        assert mock_update_module_health.call_count == 1
        mock_update_module_health.assert_called_with({"assetsPulled": 100})

        # --- Second Call ---
        # last_run for the second call (as it would be after the first call)
        mock_get_assets_last_run.return_value = {
            "offset": 100,
            "total_fetched_until_now": 100,
            "snapshot_id": "123123",
            "nextTrigger": "0",
            "type": 1,
        }

        # Mock API response for the second call (remaining 50 assets)
        mock_alerts_page2 = self.generate_mock_alerts(50, 101)
        requests_mock.get(
            f"{SERVER_URL}/container-security/combined/container-alerts/v1?offset=100&limit=100",
            json={"resources": mock_alerts_page2, "meta": {"pagination": {"offset": 100, "limit": 100, "total": 150}}},
        )

        fetch_assets_command()

        # Assertions for the second call
        assert mock_send_data_to_xsiam.call_count == 2
        mock_send_data_to_xsiam.assert_called_with(
            data=mock_alerts_page2,
            vendor=VENDOR,
            product=CNAPP_PRODUCT,
            data_type="assets",
            snapshot_id="123123",
            items_count=150,
            should_update_health_module=False,
        )
        assert mock_set_assets_last_run.call_count == 2
        mock_set_assets_last_run.assert_called_with({"offset": 0, "total_fetched_until_now": 0})
        assert mock_update_module_health.call_count == 2
        mock_update_module_health.assert_called_with({"assetsPulled": 50})

    def test_list_cnapp_alerts_command(self, requests_mock):
        """
        Given:
            - A mock for get cnapp alerts request.
        When:
            - Calling list_cnapp_alerts_command twice.
        Then:
            Ensures the data was parsed correctly into a CommandResults object
        """
        from CrowdStrikeFalcon import list_cnapp_alerts_command

        mock_alerts_page1 = self.generate_mock_alerts(1, 1)
        requests_mock.get(
            f"{SERVER_URL}/container-security/combined/container-alerts/v1",
            json={"resources": mock_alerts_page1, "meta": {"pagination": {"offset": 0, "limit": 100, "total": 1}}},
        )
        command_results_to_assert = list_cnapp_alerts_command(args={})
        expected_outputs = mock_alerts_page1
        expected_readable_outputs = (
            "### CrowdStrike CNAPP alerts\n|severity|first_seen_timestamp|last_seen_timestamp|"
            "detection_name|detection_event_simple_name|detection_description|containers_impacted_count|containers_impacted_ids|"
            "\n|---|---|---|---|---|---|---|---|\n| Critical | 2025-11-24T11:04:03Z | 2025-11-26T10:18:35Z |"
            " PotentialKernelTampering-1 | BPFCommandIssued | The eBPF feature has been invoked from within a container. This is"
            " a highlyunusual activity from within the container and can be used to load a kernel root kit or manipulate"
            " kernelbehavior or settings effecting the entire host system where the container is running. | 1 | test |\n"
        )
        assert command_results_to_assert.outputs == expected_outputs
        assert command_results_to_assert.readable_output == expected_readable_outputs


def test_fetch_endpoint_detections_builds_grouped_filter_arg(mocker: MockerFixture):
    """
    Given:
        - A known start_fetch_time returned from get_fetch_run_time_range.
        - A user fetch_query configured via demisto.params()["fetch_query"].
    When:
        - Calling fetch_endpoint_detections.
    Then:
        - Validate that we call get_fetch_detections with:
          (created_timestamp:>'<start_fetch_time>')+(<fetch_query>)
          so the timestamp constraint is applied to the full user query.
    """
    from CrowdStrikeFalcon import fetch_endpoint_detections

    mocker.patch("CrowdStrikeFalcon.get_fetch_run_time_range", return_value=("2025-12-18T11:44:17Z", "end"))
    mocker.patch.object(demisto, "params", return_value={"fetch_query": 'severity_name:"Critical", severity_name:"High"'})

    get_fetch_detections_mocker = mocker.patch(
        "CrowdStrikeFalcon.get_fetch_detections",
        return_value={"resources": [], "meta": {"pagination": {"total": 0}}},
    )

    # stop later logic from doing real work
    mocker.patch("CrowdStrikeFalcon.get_detections_entities", return_value={"resources": []})
    mocker.patch(
        "CrowdStrikeFalcon.filter_incidents_by_duplicates_and_limit", side_effect=lambda incidents_res, **_: incidents_res
    )
    mocker.patch("CrowdStrikeFalcon.update_last_run_object", side_effect=lambda **kwargs: kwargs["last_run"])

    fetch_endpoint_detections(current_fetch_info_detections={"offset": 0}, look_back=2, is_fetch_events=False)

    assert get_fetch_detections_mocker.call_args[1]["filter_arg"] == (
        '(created_timestamp:>\'2025-12-18T11:44:17Z\')+(severity_name:"Critical", severity_name:"High")'
    )


@pytest.mark.parametrize(
    "product_type, fetch_query, expected_filter",
    [
        (
            "idp",
            'severity_name:"Critical", severity_name:"High"',
            "(product:'idp'+created_timestamp:>'2025-12-18T11:44:17Z')+(severity_name:\"Critical\", severity_name:\"High\")",
        ),
        (
            "ods",
            "status:'new'",
            "(type:'ods'+created_timestamp:>'2025-12-18T11:44:17Z')+(status:'new')",
        ),
        (
            "idp",
            "",
            "product:'idp'+created_timestamp:>'2025-12-18T11:44:17Z'",
        ),
    ],
)
def test_fetch_detections_by_product_type_builds_grouped_filter(
    mocker: MockerFixture, product_type, fetch_query, expected_filter
):
    """
    Given:
        - A known start_fetch_time returned from get_fetch_run_time_range.
        - A product_type and an optional user fetch_query.
    When:
        - Calling fetch_detections_by_product_type.
    Then:
        - Validate that we build a base filter and, when fetch_query exists, group it as:
          (<base_filter>)+(<fetch_query>)
        - Validate that for ODS/OFP we replace 'product:' with 'type:'.
    """
    from CrowdStrikeFalcon import fetch_detections_by_product_type

    mocker.patch("CrowdStrikeFalcon.get_fetch_run_time_range", return_value=("2025-12-18T11:44:17Z", "end"))
    get_detections_ids_mocker = mocker.patch(
        "CrowdStrikeFalcon.get_detections_ids",
        return_value={"resources": [], "meta": {"pagination": {"total": 0}}},
    )

    mocker.patch("CrowdStrikeFalcon.get_detection_entities", return_value={"resources": []})
    mocker.patch(
        "CrowdStrikeFalcon.filter_incidents_by_duplicates_and_limit", side_effect=lambda incidents_res, **_: incidents_res
    )
    mocker.patch("CrowdStrikeFalcon.update_last_run_object", side_effect=lambda **kwargs: kwargs["last_run"])
    mocker.patch("CrowdStrikeFalcon.truncate_long_time_str", side_effect=lambda x, *_: x)

    fetch_detections_by_product_type(
        current_fetch_info={"offset": 0},
        look_back=2,
        product_type=product_type,
        fetch_query=fetch_query,
        detections_type="dummy",
        detection_name_prefix="dummy",
        start_time_key="created_timestamp",
        is_fetch_events=False,
    )

    assert get_detections_ids_mocker.call_args[1]["filter_arg"] == expected_filter


def test_get_remote_recon_data_success(mocker):
    """
    Given:
        - A remote incident ID for a Recon notification.
    When:
        - Calling get_remote_recon_data.
    Then:
        - Ensure get_recon_notifications_detailed is called with the correct ID.
        - Ensure the returned data matches the expected structure.
    """
    from CrowdStrikeFalcon import get_remote_recon_data, IncidentType, RECON_NOTIFICATION

    remote_id = "12345"
    remote_incident_id = f"{IncidentType.RECON.value}{remote_id}"
    mock_notification = {"id": remote_id, "notification": {"status": "new"}}

    mocker.patch("CrowdStrikeFalcon.get_recon_notifications_detailed", return_value=[mock_notification])

    mirrored_data, updated_object, incident_type = get_remote_recon_data(remote_incident_id)

    assert mirrored_data == mock_notification
    assert updated_object == {"incident_type": RECON_NOTIFICATION, "notification.status": "new", "status": "new"}
    assert incident_type == RECON_NOTIFICATION


def test_get_remote_recon_data_not_found(mocker):
    """
    Given:
        - A remote incident ID for a Recon notification that does not exist.
    When:
        - Calling get_remote_recon_data.
    Then:
        - Ensure a DemistoException is raised.
    """
    from CrowdStrikeFalcon import get_remote_recon_data, IncidentType, DemistoException

    remote_id = "12345"
    remote_incident_id = f"{IncidentType.RECON.value}{remote_id}"

    mocker.patch("CrowdStrikeFalcon.get_recon_notifications_detailed", return_value=[])

    with pytest.raises(DemistoException) as e:
        get_remote_recon_data(remote_incident_id)
    assert f"No Recon notification found for ID: {remote_incident_id}" in str(e.value)


@pytest.mark.parametrize(
    "delta, inc_status, close_in_cs, expected_status, expected_call",
    [
        ({"status": 2, "closeReason": "True Positive"}, IncidentStatus.DONE, True, "closed-true-positive", True),
        ({"status": 2, "closeReason": "False Positive"}, IncidentStatus.DONE, True, "closed-false-positive", True),
        ({"status": "in-progress"}, IncidentStatus.ACTIVE, False, "in-progress", True),
        ({"other_field": 1}, IncidentStatus.DONE, False, None, False),
    ],
)
def test_update_remote_recon_notification(mocker, delta, inc_status, close_in_cs, expected_status, expected_call):
    """
    Given:
        - Delta, incident status, and close_in_cs_falcon setting.
    When:
        - Calling update_remote_recon_notification.
    Then:
        - Ensure http_request is called with the correct parameters if an update is expected,
          or not called when no relevant change is found.
    """
    from CrowdStrikeFalcon import update_remote_recon_notification, IncidentType

    remote_id = "12345"
    remote_incident_id = f"{IncidentType.RECON.value}{remote_id}"

    mocker.patch("CrowdStrikeFalcon.close_in_cs_falcon", return_value=close_in_cs)
    mock_http_request = mocker.patch("CrowdStrikeFalcon.http_request")

    update_remote_recon_notification(delta, inc_status, remote_incident_id)

    if expected_call:
        mock_http_request.assert_called_once_with(
            "PATCH", "/recon/entities/notifications/v1", json=[{"id": remote_id, "status": expected_status}]
        )
    else:
        mock_http_request.assert_not_called()


def test_get_recon_notification_ids_for_fetch(mocker):
    """
    Given:
        - Filter, offset, and limit.
    When:
        - Calling get_recon_notification_ids_for_fetch.
    Then:
        - Ensure http_request is called with correct params.
        - Ensure return values match response.
    """
    from CrowdStrikeFalcon import get_recon_notification_ids_for_fetch

    mock_response = {"resources": ["id1", "id2"], "meta": {"pagination": {"total": 10, "offset": 0}}}
    mock_http_request = mocker.patch("CrowdStrikeFalcon.http_request", return_value=mock_response)

    ids, offset, total = get_recon_notification_ids_for_fetch(filter="filter", recon_offset=0, limit=2)

    mock_http_request.assert_called_once()
    assert ids == ["id1", "id2"]
    assert offset == 0
    assert total == 10


def test_recon_notifications_pagination(mocker):
    """
    Given:
        - Filter, offset, limits.
    When:
        - Calling recon_notifications_pagination.
    Then:
        - Ensure get_recon_notification_ids_for_fetch is called.
        - Ensure get_recon_notifications_detailed is called (since is_fetch=True).
        - Ensure next_offset is calculated correctly.
    """
    from CrowdStrikeFalcon import recon_notifications_pagination

    mocker.patch("CrowdStrikeFalcon.get_recon_notification_ids_for_fetch", return_value=(["id1"], 0, 10))
    mocker.patch("CrowdStrikeFalcon.get_recon_notifications_detailed", return_value=[{"id": "id1"}])

    ids, details, next_offset = recon_notifications_pagination(filter="filter", recon_offset=0, fetch_limit=5)

    assert ids == ["id1"]
    assert details == [{"id": "id1"}]
    assert next_offset == 1  # 0 + 1 &lt; 10


def test_recon_notification_to_incident(mocker):
    """
    Given:
        - A Recon notification dictionary.
    When:
        - Calling recon_notification_to_incident.
    Then:
        - Ensure the returned incident dictionary is structured correctly.
    """
    from CrowdStrikeFalcon import recon_notification_to_incident, IncidentSeverity
    import json

    notification = {"id": "12345", "notification": {"created_date": "2023-01-01T00:00:00Z", "rule_priority": "high"}}
    incident_type = "Recon Notification"

    # Mock globals used in function
    mocker.patch("CrowdStrikeFalcon.MIRROR_DIRECTION", "Both")
    mocker.patch("CrowdStrikeFalcon.INTEGRATION_INSTANCE", "instance")

    incident = recon_notification_to_incident(notification, incident_type)

    assert incident["name"] == "12345"
    assert incident["occurred"] == "2023-01-01T00:00:00Z"
    assert incident["severity"] == IncidentSeverity.HIGH

    raw_json = json.loads(incident["rawJSON"])
    assert raw_json["id"] == "12345"
    assert raw_json["mirror_direction"] == "Both"
    assert raw_json["mirror_instance"] == "instance"
    assert raw_json["incident_type"] == incident_type


def test_get_cases_data(mocker):
    """
    Given:
        - Filter, limit and offset.
    When:
        - Running get_cases_data.
    Then:
        - Verify that the http_request is called with the correct arguments.
        - Verify that the function returns the correct total cases and ids.
        - Verify that the filter is passed in the params dict for proper URL encoding.
    """
    from CrowdStrikeFalcon import get_cases_data

    http_request_mock = mocker.patch(
        "CrowdStrikeFalcon.http_request", return_value={"meta": {"pagination": {"total": 10}}, "resources": ["case1", "case2"]}
    )

    total, ids = get_cases_data("some_filter", 10, 0)

    assert total == 10
    assert ids == ["case1", "case2"]
    http_request_mock.assert_called_with(
        "GET", "/cases/queries/cases/v1", {"sort": "created_timestamp.asc", "offset": 0, "limit": 10, "filter": "some_filter"}
    )


def test_get_cases_data_with_special_characters(mocker):
    """
    Given:
        - Filter with special characters (e.g., '+' for AND operator in FQL).
    When:
        - Running get_cases_data with a filter containing special characters.
    Then:
        - Verify that the filter is passed in the params dict (not embedded in URL).
        - Verify that http_request receives the filter as a parameter for proper encoding.
    """
    from CrowdStrikeFalcon import get_cases_data

    http_request_mock = mocker.patch(
        "CrowdStrikeFalcon.http_request", return_value={"meta": {"pagination": {"total": 5}}, "resources": ["case3", "case4"]}
    )

    # Test with a filter that includes the '+' operator (FQL AND)
    filter_with_plus = "created_timestamp:>'2026-02-21T16:26:49.079836Z'+severity:>40"
    total, ids = get_cases_data(filter_with_plus, 10, 0)

    assert total == 5
    assert ids == ["case3", "case4"]
    # Verify the filter is in params dict, not in the URL path
    http_request_mock.assert_called_with(
        "GET",
        "/cases/queries/cases/v1",
        {"sort": "created_timestamp.asc", "offset": 0, "limit": 10, "filter": filter_with_plus},
    )


def test_get_cases_data_without_filter(mocker):
    """
    Given:
        - No filter (empty string).
    When:
        - Running get_cases_data without a filter.
    Then:
        - Verify that the http_request is called without a filter parameter.
    """
    from CrowdStrikeFalcon import get_cases_data

    http_request_mock = mocker.patch(
        "CrowdStrikeFalcon.http_request", return_value={"meta": {"pagination": {"total": 3}}, "resources": ["case5"]}
    )

    total, ids = get_cases_data("", 10, 0)

    assert total == 3
    assert ids == ["case5"]
    # Verify no filter parameter is passed when filter is empty
    http_request_mock.assert_called_with(
        "GET", "/cases/queries/cases/v1", {"sort": "created_timestamp.asc", "offset": 0, "limit": 10}
    )


def test_get_cases_details(mocker):
    """
    Given:
        - List of case IDs.
    When:
        - Running get_cases_details.
    Then:
        - Verify that the http_request is called with the correct arguments.
        - Verify that the function returns the correct case details.
    """
    from CrowdStrikeFalcon import get_cases_details

    http_request_mock = mocker.patch(
        "CrowdStrikeFalcon.http_request", return_value={"resources": [{"id": "case1", "status": "new"}]}
    )

    details = get_cases_details(["case1"])

    assert details == [{"id": "case1", "status": "new"}]
    http_request_mock.assert_called_with("POST", "/cases/entities/cases/v2", data=json.dumps({"ids": ["case1"]}))


def test_get_remote_ngsiem_case_data(mocker):
    """
    Given:
        - Remote case ID.
    When:
        - Running get_remote_ngsiem_case_data.
    Then:
        - Verify that get_cases_details is called with the correct ID.
        - Verify that the function returns the correct mirrored case and updated object.
    """
    from CrowdStrikeFalcon import get_remote_ngsiem_case_data, IncidentType

    mocker.patch("CrowdStrikeFalcon.get_cases_details", return_value=[{"id": "case1", "status": "new", "state": "open"}])

    remote_id = f"{IncidentType.NGSIEM_CASE.value}:case1"
    mirrored_case, updated_object = get_remote_ngsiem_case_data(remote_id)

    assert mirrored_case == {"id": "case1", "status": "new", "state": "open"}
    assert updated_object == {"incident_type": "ngsiem_case", "status": "new", "state": "open"}


@pytest.mark.parametrize(
    "delta, inc_status, close_in_cs_falcon_param, expected_status",
    [({"status": "in_progress"}, IncidentStatus.ACTIVE, False, "in_progress"), ({}, IncidentStatus.ACTIVE, False, None)],
)
def test_update_remote_ngsiem_case(mocker, delta, inc_status, close_in_cs_falcon_param, expected_status):
    """
    Given:
        - Delta, incident status, and close_in_cs_falcon parameter.
    When:
        - Running update_remote_ngsiem_case.
    Then:
        - Verify that update_remote_ngsiem_case is called with the correct status if applicable.
    """
    from CrowdStrikeFalcon import update_remote_ngsiem_case, IncidentType

    mocker.patch.object(demisto, "params", return_value={"close_in_cs_falcon": close_in_cs_falcon_param})
    update_mock = mocker.patch("CrowdStrikeFalcon.patch_remote_entity", return_value="success")

    remote_id = f"{IncidentType.NGSIEM_CASE.value}:case1"
    result = update_remote_ngsiem_case(delta, inc_status, remote_id)

    if expected_status:
        update_mock.assert_called_with("case1", status=expected_status)
        assert result == "success"
    else:
        update_mock.assert_not_called()
        assert result == ""


def test_fetch_ngsiem_cases(mocker):
    """
    Given:
        - Last run object, look back, and fetch query.
    When:
        - Running fetch_ngsiem_cases.
    Then:
        - Verify that the function calls dependencies correctly.
        - Verify that it returns the correct cases and last run object.
    """
    from CrowdStrikeFalcon import fetch_ngsiem_cases

    mocker.patch("CrowdStrikeFalcon.get_fetch_run_time_range", return_value=("start_time", "end_time"))
    mocker.patch("CrowdStrikeFalcon.get_cases_data", return_value=(1, ["case1"]))
    mocker.patch(
        "CrowdStrikeFalcon.get_cases_details",
        return_value=[{"id": "case1", "created_timestamp": "2023-01-01T00:00:00.000Z", "severity": "High"}],
    )
    mocker.patch(
        "CrowdStrikeFalcon.filter_incidents_by_duplicates_and_limit", side_effect=lambda incidents_res, **kwargs: incidents_res
    )
    mocker.patch("CrowdStrikeFalcon.update_last_run_object", return_value={"offset": 1})

    cases, last_run = fetch_ngsiem_cases({}, 3, "some_query")

    assert len(cases) == 1
    assert cases[0]["name"] == "ngsiem_case ID: case1"
    assert cases[0]["severity"] == "High"
    assert last_run == {"offset": 1}


def test_list_case_summaries_command_no_given_ids(mocker):
    """
    Test list_case_summaries_command without ids arg
    Given
     - No arguments given, as is
    When
     - The user is running list_case_summaries_command with no ids
    Then
     - Function is executed properly and returns no cases found message
    """
    from CrowdStrikeFalcon import list_case_summaries_command

    mocker.patch.object(demisto, "args", return_value={})

    # Mock get_cases_data to return IDs
    mocker.patch("CrowdStrikeFalcon.get_cases_data", return_value=(1, ["case_id_1"]))

    entity_response = [
        {
            "assigned_to_name": "Test no ids",
            "cid": "string",
            "created_time": "2022-02-21T16:36:57.759Z",
            "description": "string",
            "id": "case_id_1",
            "status": "new",
            "severity": "High",
            "tags": ["tag1"],
            "version": "1",
        }
    ]
    # Mock get_cases_entities to return case details
    mocker.patch("CrowdStrikeFalcon.get_cases_entities", return_value=entity_response)

    result = list_case_summaries_command()
    assert result.outputs[0]["assigned_to_name"] == "Test no ids"
    assert result.outputs[0]["id"] == "case_id_1"


def test_list_case_summaries_command_with_given_ids(requests_mock, mocker):
    """
    Test list_case_summaries_command with ids arg
    Given
     - ids
    When
     - The user is running list_case_summaries_command with ids
    Then
     - Function is executed properly and get_cases_entities func was called
    """
    from CrowdStrikeFalcon import list_case_summaries_command

    entity_response = {
        "errors": [],
        "meta": {"pagination": {"limit": 0, "offset": 0, "total": 0}, "powered_by": "string"},
        "resources": [
            {
                "assigned_to_name": "Test with ids",
                "cid": "string",
                "created_time": "2022-02-21T16:36:57.759Z",
                "description": "string",
                "id": "case_id_1",
                "status": "new",
                "severity": "High",
                "tags": ["tag1"],
                "version": "1",
            }
        ],
    }

    requests_mock.post(
        f"{SERVER_URL}/cases/entities/cases/v2",
        json=entity_response,
        status_code=200,
    )
    mocker.patch.object(demisto, "args", return_value={"ids": "case_id_1"})

    outputs = list_case_summaries_command().outputs

    assert outputs[0]["assigned_to_name"] == "Test with ids"
    assert outputs[0]["id"] == "case_id_1"


def test_add_case_tags_command(requests_mock):
    """
    Given:
        - Case ID and tags.
    When:
        - Running add_case_tags_command.
    Then:
        - Verify that the http_request is called with the correct arguments.
        - Verify that the function returns the correct readable output.
    """
    from CrowdStrikeFalcon import add_case_tags_command

    requests_mock.post(f"{SERVER_URL}/cases/entities/case-tags/v1", json={})

    result = add_case_tags_command({"id": "case1", "tags": "tag1,tag2"})

    assert result.readable_output == "Tags were added successfully."
    assert requests_mock.last_request.json() == {"id": "case1", "tags": ["tag1", "tag2"]}


def test_delete_case_tags_command(requests_mock):
    """
    Given:
        - Case ID and tag.
    When:
        - Running delete_case_tags_command.
    Then:
        - Verify that the http_request is called with the correct arguments.
        - Verify that the function returns the correct readable output.
    """
    from CrowdStrikeFalcon import delete_case_tags_command

    requests_mock.delete(f"{SERVER_URL}/cases/entities/case-tags/v1?id=case1&tag=tag1", json={})

    result = delete_case_tags_command({"id": "case1", "tag": "tag1"})

    assert result.readable_output == "Tags were deleted successfully."
    assert requests_mock.last_request.qs["id"] == ["case1"]
    assert requests_mock.last_request.qs["tag"] == ["tag1"]


def test_get_evidence_for_case_command(requests_mock, mocker):
    """
    Given:
        - Case ID.
    When:
        - Running get_evidence_for_case_command.
    Then:
        - Verify that the http_request is called with the correct arguments.
        - Verify that the function returns the correct readable output.
    """
    from CrowdStrikeFalcon import get_evidence_for_case_command

    response_data = load_json("test_data/get_evidence_for_case_response.json")
    requests_mock.post(f"{SERVER_URL}/cases/entities/cases/v2", json=response_data)
    mocker.patch.object(demisto, "args", return_value={"id": "case_id_1"})
    result = get_evidence_for_case_command({"id": "case_id_1"})

    assert result.outputs == response_data["resources"][0]["evidence"]


def test_resolve_case_command(requests_mock):
    """
    Given:
        - Case ID and fields to update.
    When:
        - Running resolve_case_command.
    Then:
        - Verify that the http_request is called with the correct arguments.
        - Verify that the function returns the correct readable output.
    """
    from CrowdStrikeFalcon import resolve_case_command

    requests_mock.patch(f"{SERVER_URL}/cases/entities/cases/v2", json={})

    # Test case 1: Update status and description
    args = {"id": "case1", "status": "in_progress", "description": "investigating"}
    result = resolve_case_command(args)
    assert "Case case1 was changed successfully" in result.readable_output
    assert "Status" in result.readable_output
    assert "Description" in result.readable_output
    assert requests_mock.last_request.json() == {
        "id": "case1",
        "fields": {"status": "in_progress", "description": "investigating"},
    }

    # Test case 2: Remove user assignment
    args = {"id": "case1", "remove_user_assignment": "true"}
    result = resolve_case_command(args)
    assert "Assigned To Uuid" in result.readable_output
    assert "Unassigned" in result.readable_output
    assert requests_mock.last_request.json() == {
        "id": "case1",
        "fields": {"remove_user_assignment": True},
    }

    # Test case 3: Invalid severity
    with pytest.raises(ValueError, match="Severity must be an integer between 10 and 100"):
        resolve_case_command({"id": "case1", "severity": "5"})

    # Test case 4: Missing ID
    with pytest.raises(ValueError, match="The 'id' argument is required"):
        resolve_case_command({"status": "new"})


def test_patch_remote_entity_recon_type(requests_mock):
    """
    Given:
        - A case ID, status, and is_recon_type=True.
    When:
        - Calling patch_remote_entity with is_recon_type=True.
    Then:
        - Verify the request is sent to the recon endpoint with the correct payload format (list).
    """
    from CrowdStrikeFalcon import patch_remote_entity

    requests_mock.patch(f"{SERVER_URL}/recon/entities/notifications/v1", json={"resources": []})

    patch_remote_entity(case_id="recon-case-1", status="closed-true-positive", is_recon_type=True)

    assert requests_mock.last_request.json() == [{"id": "recon-case-1", "status": "closed-true-positive"}]


def test_patch_remote_entity_recon_type_with_multiple_fields(requests_mock):
    """
    Given:
        - A case ID with status and severity, and is_recon_type=True.
    When:
        - Calling patch_remote_entity with is_recon_type=True and multiple fields.
    Then:
        - Verify the recon payload includes all fields flattened (not nested under 'fields').
    """
    from CrowdStrikeFalcon import patch_remote_entity

    requests_mock.patch(f"{SERVER_URL}/recon/entities/notifications/v1", json={"resources": []})

    patch_remote_entity(case_id="recon-case-2", status="in_progress", severity=50, is_recon_type=True)

    sent_payload = requests_mock.last_request.json()
    assert isinstance(sent_payload, list)
    assert len(sent_payload) == 1
    assert sent_payload[0] == {"id": "recon-case-2", "status": "in_progress", "severity": 50}


def test_patch_remote_entity_with_template_id(requests_mock):
    """
    Given:
        - A case ID and a template_id.
    When:
        - Calling patch_remote_entity with template_id.
    Then:
        - Verify the template field is formatted as {"id": template_id} in the payload.
    """
    from CrowdStrikeFalcon import patch_remote_entity

    requests_mock.patch(f"{SERVER_URL}/cases/entities/cases/v2", json={"resources": []})

    patch_remote_entity(case_id="case-tmpl-1", template_id="tmpl-abc-123")

    assert requests_mock.last_request.json() == {
        "id": "case-tmpl-1",
        "fields": {"template": {"id": "tmpl-abc-123"}},
    }


def test_patch_remote_entity_with_name_and_assigned_to(requests_mock):
    """
    Given:
        - A case ID with name and assigned_to_uuid.
    When:
        - Calling patch_remote_entity with name and assigned_to_uuid.
    Then:
        - Verify the payload includes the correct field mappings.
    """
    from CrowdStrikeFalcon import patch_remote_entity

    requests_mock.patch(f"{SERVER_URL}/cases/entities/cases/v2", json={"resources": []})

    patch_remote_entity(case_id="case-name-1", name="Updated Case", assigned_to_uuid="user-uuid-123")

    assert requests_mock.last_request.json() == {
        "id": "case-name-1",
        "fields": {"name": "Updated Case", "assigned_to_user_uuid": "user-uuid-123"},
    }


def test_resolve_case_command_with_name_and_template(requests_mock):
    """
    Given:
        - A case ID with name and template_id arguments.
    When:
        - Running resolve_case_command with name and template_id.
    Then:
        - Verify the command returns success and the payload is correct.
    """
    from CrowdStrikeFalcon import resolve_case_command

    requests_mock.patch(f"{SERVER_URL}/cases/entities/cases/v2", json={})

    args = {"id": "case1", "name": "New Name", "template_id": "tmpl-1"}
    result = resolve_case_command(args)
    assert "Case case1 was changed successfully" in result.readable_output
    assert "Name" in result.readable_output
    assert requests_mock.last_request.json() == {
        "id": "case1",
        "fields": {"name": "New Name", "template": {"id": "tmpl-1"}},
    }


def test_resolve_case_command_with_valid_severity(requests_mock):
    """
    Given:
        - A case ID with a valid severity value (between 10 and 100).
    When:
        - Running resolve_case_command with severity=50.
    Then:
        - Verify the command returns success and severity is in the payload.
    """
    from CrowdStrikeFalcon import resolve_case_command

    requests_mock.patch(f"{SERVER_URL}/cases/entities/cases/v2", json={})

    args = {"id": "case1", "severity": "50"}
    result = resolve_case_command(args)
    assert "Case case1 was changed successfully" in result.readable_output
    assert requests_mock.last_request.json() == {
        "id": "case1",
        "fields": {"severity": 50},
    }


# ============== NGSIEM Search Events Tests ==============
@pytest.mark.parametrize(
    "events, expected_rawstring",
    [
        # \& in string is replaced with &
        ([{"@rawstring": "foo\\&bar"}], "foo&bar"),
        # Multiple \& occurrences are all replaced
        ([{"@rawstring": "a\\&b\\&c"}], "a&b&c"),
        # String without \& is left unchanged
        ([{"@rawstring": "plain text no escape"}], "plain text no escape"),
        # Empty string stays empty
        ([{"@rawstring": ""}], ""),
        # Non-string @rawstring (dict) is left unchanged
        ([{"@rawstring": {"already": "parsed"}}], {"already": "parsed"}),
        # Event without @rawstring is left unchanged
        ([{"some_field": "value"}], None),
    ],
)
def test_clean_ngsiem_rawstring_field(events, expected_rawstring):
    """
    Given:
        - A list of events with various @rawstring values.
    When:
        - Running clean_ngsiem_rawstring_field.
    Then:
        - Literal '\\&' sequences in string @rawstring values are replaced with '&'.
        - Non-string or missing @rawstring values are left unchanged.
    """
    from CrowdStrikeFalcon import clean_ngsiem_rawstring_field

    result = clean_ngsiem_rawstring_field(events)
    if expected_rawstring is None:
        assert "@rawstring" not in result[0]
    else:
        assert result[0]["@rawstring"] == expected_rawstring


@pytest.mark.parametrize(
    "query, limit, expected",
    [
        ("event_simpleName=ProcessRollup2", 100, "event_simpleName=ProcessRollup2 | tail(100)"),
        ("event_simpleName=ProcessRollup2 | tail(50)", 100, "event_simpleName=ProcessRollup2 | tail(50)"),
    ],
)
def test_build_ngsiem_query_with_limit(query, limit, expected):
    """
    Given:
        - A query string and a limit.
    When:
        - Running build_ngsiem_query_with_limit.
    Then:
        - tail(limit) is appended only if not already present (case-insensitive).
    """
    from CrowdStrikeFalcon import build_ngsiem_query_with_limit

    assert build_ngsiem_query_with_limit(query, limit) == expected


@pytest.mark.parametrize(
    "val, expected",
    [
        (None, None),
        ("", None),
        (1700000000, 1700000000000),
        (1700000000000, 1700000000000),
        ("1700000000", 1700000000000),
        ("2023-11-14T00:00:00Z", 1699920000000),
    ],
)
def test_arg_to_timestamp(val, expected):
    """
    Given:
        - A raw time argument in one of the supported formats (None,
          epoch seconds/milliseconds, or ISO-8601 string).
    When:
        - Converting it via arg_to_timestamp.
    Then:
        - Returns None for None.
        - Returns the expected epoch-milliseconds int for all other valid inputs.
    """
    from CrowdStrikeFalcon import arg_to_timestamp

    assert arg_to_timestamp(val) == expected


@freeze_time("2023-11-15T00:00:00Z")
def test_arg_to_timestamp_relative():
    """
    Given:
        - A relative time expression ("24 hours").
    When:
        - Converting it via arg_to_timestamp with time frozen at 2023-11-15T00:00:00Z.
    Then:
        - Returns the epoch-milliseconds timestamp for 24 hours before the frozen time.
    """
    from CrowdStrikeFalcon import arg_to_timestamp

    assert arg_to_timestamp("24 hours") == 1699920000000


@pytest.mark.parametrize(
    "events, expected_count, expected_hr_contains",
    [
        (
            [{"timestamp": "2023-01-01T00:00:00Z", "id": "evt1"}, {"timestamp": "2023-01-01T00:01:00Z", "id": "evt2"}],
            2,
            "NGSIEM Events (Total: 2)",
        ),
        ([], 0, "No events found"),
    ],
)
def test_process_ngsiem_search_completion(events, expected_count, expected_hr_contains):
    """
    Given:
        - A response with events or no events.
    When:
        - Running process_ngsiem_search_completion.
    Then:
        - Returns a PollResult with continue_to_poll=False and correct outputs/HR.
    """
    from CrowdStrikeFalcon import process_ngsiem_search_completion

    response = {"events": events, "done": True}
    args = {"wait_for_result": True}
    result = process_ngsiem_search_completion(response, args)

    assert result.continue_to_poll is False
    assert expected_hr_contains in result.response.readable_output
    if expected_count > 0:
        assert len(result.response.outputs) == expected_count
        assert result.response.outputs_prefix == "CrowdStrike.NGSiemEvent"


def test_initiate_ngsiem_search_request(requests_mock):
    """
    Given:
        - A repository and search body.
    When:
        - Running initiate_ngsiem_search_request.
    Then:
        - The correct API endpoint is called and the response is returned.
    """
    from CrowdStrikeFalcon import initiate_ngsiem_search_request

    requests_mock.post(
        f"{SERVER_URL}/humio/api/v1/repositories/search-all/queryjobs",
        json={"id": "job123"},
        status_code=200,
    )
    result = initiate_ngsiem_search_request("search-all", {"queryString": "test"})
    assert result["id"] == "job123"
    assert requests_mock.last_request.json() == {"queryString": "test"}


def test_get_ngsiem_search_results_request(requests_mock):
    """
    Given:
        - A repository and job ID.
    When:
        - Running get_ngsiem_search_results_request.
    Then:
        - The correct API endpoint is called and the response is returned.
    """
    from CrowdStrikeFalcon import get_ngsiem_search_results_request

    requests_mock.get(
        f"{SERVER_URL}/humio/api/v1/repositories/search-all/queryjobs/job123",
        json={"done": True, "events": [{"id": "evt1"}]},
        status_code=200,
    )
    result = get_ngsiem_search_results_request("search-all", "job123")
    assert result["done"] is True
    assert len(result["events"]) == 1


@pytest.mark.parametrize(
    "args, initiate_ret, poll_ret, expect_raise, raise_match, expect_continue, expect_hr, expect_job_id",
    [
        # 1) First call: should initiate, set job_id, and continue polling
        (
            {"query": "test", "repository": "search-all", "wait_for_result": True},
            {"id": "job123"},
            {"done": False, "cancelled": False},
            False,
            None,
            True,
            None,
            "job123",
        ),
        # 2) First call but initiate returns no id: should raise
        (
            {"query": "test", "repository": "search-all", "wait_for_result": True},
            {},
            None,
            True,
            "Failed to initiate",
            None,
            None,
            None,
        ),
        # 3) Poll: in progress
        (
            {"query": "test", "job_id": "job123", "repository": "search-all", "wait_for_result": True},
            None,
            {"done": False, "cancelled": False},
            False,
            None,
            True,
            "Searching NGSIEM events:",
            None,
        ),
        # 4) Poll: done with events
        (
            {"query": "test", "job_id": "job123", "repository": "search-all", "wait_for_result": True},
            None,
            {"done": True, "events": [{"id": "evt1", "timestamp": "2023-01-01T00:00:00Z"}]},
            False,
            None,
            False,
            "NGSIEM Events",
            None,
        ),
        # 5) Poll: done no events
        (
            {"query": "test", "job_id": "job123", "repository": "search-all", "wait_for_result": True},
            None,
            {"done": True, "events": []},
            False,
            None,
            False,
            "No events found",
            None,
        ),
        # 6) Poll: cancelled -> raise
        (
            {"query": "test", "job_id": "job123", "repository": "search-all", "wait_for_result": True},
            None,
            {"done": False, "cancelled": True},
            True,
            "cancelled",
            None,
            None,
            None,
        ),
    ],
)
def test_cs_falcon_search_ngsiem_events_command_merged(
    mocker,
    args,
    initiate_ret,
    poll_ret,
    expect_raise,
    raise_match,
    expect_continue,
    expect_hr,
    expect_job_id,
):
    """
    Given:
        - Args that represent either:
          * First call (no job_id) -> build body + initiate search
          * Polling call (job_id exists) -> only fetch results
        - Mocked initiate/poll responses (including error cases)
    When:
        - Running cs_falcon_search_ngsiem_events_command(args)
    Then:
        - Raises DemistoException when expected (match raise_match)
        - Otherwise returns a PollResult with expected continue_to_poll and HR content
        - Sets args["job_id"] on first-call successful initiation when expected
    """
    from CrowdStrikeFalcon import cs_falcon_search_ngsiem_events_command

    mocker.patch.object(ScheduledCommand, "raise_error_if_not_supported")

    # If this is the "first call" flow, cs_falcon_search_ngsiem_events_command will build body + initiate.
    if "job_id" not in args:
        mocker.patch("CrowdStrikeFalcon.build_ngsiem_search_body", return_value={"queryString": "test | tail(50)"})
        mocker.patch("CrowdStrikeFalcon.initiate_ngsiem_search_request", return_value=initiate_ret)

        # After initiate, the command typically does a first "get results" call too (for polling).
        if poll_ret is not None:
            mocker.patch("CrowdStrikeFalcon.get_ngsiem_search_results_request", return_value=poll_ret)
    else:
        # Polling flow (job_id exists): only get results is used.
        mocker.patch("CrowdStrikeFalcon.get_ngsiem_search_results_request", return_value=poll_ret)

    if expect_raise:
        with pytest.raises(DemistoException, match=raise_match):
            cs_falcon_search_ngsiem_events_command(args)
        return

    result = cs_falcon_search_ngsiem_events_command(args)

    if expect_continue:
        # When polling should continue, the decorator wraps the result with a ScheduledCommand
        assert result.scheduled_command is not None
    else:
        # When polling is done, the decorator returns the final CommandResults directly
        assert result.scheduled_command is None
    if expect_hr is not None:
        assert expect_hr in result.readable_output
    if expect_job_id is not None:
        assert args["job_id"] == expect_job_id


@pytest.mark.parametrize(
    "events, hr_keys, expected_rows",
    [
        # Bare key preferred over @key and #key
        ([{"id": "bare", "@id": "at", "#id": "hash"}], ["id"], [{"id": "bare"}]),
        # Falls back to @key when bare key missing
        ([{"@id": "at_val"}], ["id"], [{"id": "at_val"}]),
        # Missing key entirely → None
        ([{"other": "x"}], ["id"], [{"id": None}]),
        # Timestamp int epoch-ms → date string
        ([{"timestamp": 1700000000000}], ["timestamp"], "date_str"),
        # Timestamp numeric string → date string
        ([{"timestamp": "1700000000000"}], ["timestamp"], "date_str"),
        # Timestamp float epoch-ms → date string
        ([{"#timestamp": 1700000000000.0}], ["timestamp"], "date_str"),
        # Timestamp non-numeric string → passthrough
        ([{"@timestamp": "2023-01-01T00:00:00Z"}], ["timestamp"], [{"timestamp": "2023-01-01T00:00:00Z"}]),
    ],
)
def test_build_ngsiem_hr_rows(events, hr_keys, expected_rows):
    """
    Given:
        - Events with various key patterns (bare, @-prefixed, #-prefixed, missing)
          and timestamp values (int, float, numeric string, non-numeric string).
    When:
        - Running build_ngsiem_hr_rows.
    Then:
        - Key resolution: bare key > @key > #key > None.
        - Timestamp conversion: int/float/numeric-string epoch-ms → ISO date string;
          non-numeric string passes through unchanged.
    """
    from CrowdStrikeFalcon import build_ngsiem_hr_rows

    rows = build_ngsiem_hr_rows(events, hr_keys)
    if expected_rows == "date_str":
        # For epoch-ms inputs, just verify it was converted to a date string containing the year
        assert isinstance(rows[0]["timestamp"], str)
        assert "2023" in rows[0]["timestamp"]
    else:
        assert rows == expected_rows


def test_build_ngsiem_hr_rows_multiple_events_and_keys():
    """
    Given:
        - Multiple events with mixed key resolution and timestamp formats.
    When:
        - Running build_ngsiem_hr_rows with several hr_keys.
    Then:
        - Each event row resolves keys correctly and timestamps are handled per type.
    """
    from CrowdStrikeFalcon import build_ngsiem_hr_rows

    events = [
        {"id": "evt1", "@timestamp": 1700000000000, "#event_simpleName": "DNS"},
        {"#id": "evt2", "timestamp": "not-a-number", "event_simpleName": "HTTP"},
    ]
    rows = build_ngsiem_hr_rows(events, ["id", "timestamp", "event_simpleName"])
    assert len(rows) == 2
    assert rows[0]["id"] == "evt1"
    assert isinstance(rows[0]["timestamp"], str)
    assert "2023" in rows[0]["timestamp"]
    assert rows[0]["event_simpleName"] == "DNS"
    assert rows[1]["id"] == "evt2"
    assert rows[1]["timestamp"] == "not-a-number"
    assert rows[1]["event_simpleName"] == "HTTP"


""" Fetch Assets Spotlight """


class TestSpotlightSeverityBasedFetch:
    """
    Tests for the new severity-based parallel fetching implementation.
    Tests fetch_vulnerabilities_by_severity() and fetch_spotlight_by_severity_parallel().
    """

    @pytest.mark.asyncio
    async def test_fetch_vulnerabilities_by_severity_single_page(self, mocker):
        """
        Tests fetching vulnerabilities for a single severity with one page of results.

        Given:
            - API returns vulnerabilities for CRITICAL severity in a single page.
        When:
            - fetch_vulnerabilities_by_severity is called for CRITICAL.
        Then:
            - Correct filter is applied (severity=CRITICAL, status=open/reopen).
            - Vulnerabilities are returned.
            - AIDs are extracted and sent to asset handler.
            - Background task is created to send data to XSIAM.
        """
        from CrowdStrikeFalcon import fetch_vulnerabilities_by_severity, SPOTLIGHT_VULN_PRODUCT

        # Setup
        mock_client = mocker.AsyncMock()
        mock_context_store = mocker.Mock()
        mock_state = mocker.Mock()
        mock_state.metadata = {}
        snapshot_id = "snap123"

        # Mock asset handler
        mock_handler = mocker.Mock()
        mock_handler.receive_new_aids = mocker.AsyncMock()

        # Mock API response
        vulnerabilities = [
            {"id": "vuln1", "aid": "aid1", "cve": {"severity": "CRITICAL"}},
            {"id": "vuln2", "aid": "aid2", "cve": {"severity": "CRITICAL"}},
        ]
        mock_response = mocker.Mock()
        mock_response.json.return_value = {
            "resources": vulnerabilities,
            "meta": {"pagination": {"after": None}},  # No more pages
        }
        mock_client._request.return_value = mock_response

        # Mock task creation
        def create_task_side_effect(*args, **kwargs):
            f = asyncio.Future()
            f.set_result(1)
            return f

        mock_create_task = mocker.patch(
            "CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context",
            side_effect=create_task_side_effect,
        )

        # Execute
        total, aids, tasks, withheld = await fetch_vulnerabilities_by_severity(
            client=mock_client,
            severity="CRITICAL",
            context_store=mock_context_store,
            spotlight_state=mock_state,
            snapshot_id=snapshot_id,
            asset_handler=mock_handler,
        )

        # Verify
        assert total == 2
        assert aids == {"aid1", "aid2"}
        assert len(tasks) == 1
        # First record is withheld for the seal
        assert withheld == [vulnerabilities[0]]

        # Verify API was called with correct filter
        mock_client._request.assert_awaited_once()
        call_kwargs = mock_client._request.call_args.kwargs
        assert "params" in call_kwargs
        assert "filter" in call_kwargs["params"]
        assert "CRITICAL" in call_kwargs["params"]["filter"]
        assert "status:['open','reopen']" in call_kwargs["params"]["filter"]
        # Lookback window filter is applied to bound the dataset size for large tenants
        assert "updated_timestamp:>'now-100d'" in call_kwargs["params"]["filter"]

        # Verify AIDs sent to handler
        mock_handler.receive_new_aids.assert_awaited_once_with({"aid1", "aid2"})

        # Verify XSIAM task created
        mock_create_task.assert_called_once()
        task_kwargs = mock_create_task.call_args.kwargs
        assert task_kwargs["product"] == SPOTLIGHT_VULN_PRODUCT
        # The first record is withheld for the seal, so the data batch holds the remaining records
        assert task_kwargs["data"] == vulnerabilities[1:]

    @pytest.mark.asyncio
    async def test_fetch_vulnerabilities_by_severity_multiple_pages(self, mocker):
        """
        Tests fetching vulnerabilities with pagination across multiple pages.

        Given:
            - API returns 2 pages of HIGH severity vulnerabilities.
        When:
            - fetch_vulnerabilities_by_severity is called for HIGH.
        Then:
            - Both pages are fetched sequentially.
            - Total count and AIDs are aggregated correctly.
            - Two background tasks are created (one per page).
        """
        from CrowdStrikeFalcon import fetch_vulnerabilities_by_severity

        # Setup
        mock_client = mocker.AsyncMock()
        mock_handler = mocker.Mock()
        mock_handler.receive_new_aids = mocker.AsyncMock()

        # Mock API responses - 2 pages
        page1_vulns = [{"id": "v1", "aid": "aid1"}, {"id": "v2", "aid": "aid2"}]
        page2_vulns = [{"id": "v3", "aid": "aid3"}]

        mock_response1 = mocker.Mock()
        mock_response1.json.return_value = {
            "resources": page1_vulns,
            "meta": {"pagination": {"after": "token_page2"}},
        }

        mock_response2 = mocker.Mock()
        mock_response2.json.return_value = {
            "resources": page2_vulns,
            "meta": {"pagination": {"after": None}},  # Last page
        }

        mock_client._request.side_effect = [mock_response1, mock_response2]

        # Mock task creation
        def create_task_side_effect(*args, **kwargs):
            f = asyncio.Future()
            f.set_result(1)
            return f

        mocker.patch(
            "CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context",
            side_effect=create_task_side_effect,
        )

        # Execute
        total, aids, tasks, withheld = await fetch_vulnerabilities_by_severity(
            client=mock_client,
            severity="HIGH",
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap123",
            asset_handler=mock_handler,
        )

        # Verify
        assert total == 3  # 2 from page1 + 1 from page2
        assert aids == {"aid1", "aid2", "aid3"}
        assert len(tasks) == 2  # One task per page
        assert withheld == [page1_vulns[0]]
        assert mock_client._request.call_count == 2

    @pytest.mark.asyncio
    async def test_fetch_spotlight_by_severity_parallel_success(self, mocker):
        """
        Tests parallel fetching across all severity levels.

        Given:
            - Multiple severities with different vulnerability counts.
            - No previously completed severities (clean start).
        When:
            - fetch_spotlight_by_severity_parallel is called.
        Then:
            - All 6 severity tasks are created and executed in parallel.
            - Results are aggregated correctly.
            - Asset handler flushes remaining AIDs.
            - Final sealing batch is sent.
            - completed_severities is cleared after all complete.
        """
        from CrowdStrikeFalcon import fetch_spotlight_by_severity_parallel

        # Setup
        mock_client = mocker.AsyncMock()
        mock_context_store = mocker.Mock()
        mock_state = mocker.Mock()
        mock_state.metadata = {}
        snapshot_id = "snap123"

        # Mock fetch_vulnerabilities_by_severity to return different results per severity
        async def mock_fetch_by_severity(client, severity, **kwargs):
            severity_data = {
                "CRITICAL": (10, {"aid1", "aid2"}, set(), [{"id": "c1", "aid": "aid1"}]),
                "HIGH": (20, {"aid3", "aid4"}, set(), [{"id": "h1", "aid": "aid3"}]),
                "MEDIUM": (15, {"aid5"}, set(), [{"id": "m1", "aid": "aid5"}]),
                "LOW": (5, {"aid6"}, set(), [{"id": "l1", "aid": "aid6"}]),
                "NONE": (0, set(), set(), []),
                "UNKNOWN": (0, set(), set(), []),
            }
            return severity_data.get(severity, (0, set(), set(), []))

        mocker.patch(
            "CrowdStrikeFalcon.fetch_vulnerabilities_by_severity",
            side_effect=mock_fetch_by_severity,
        )

        # Mock asset handler
        mock_handler_cls = mocker.patch("CrowdStrikeFalcon.AssetsDeviceHandler")
        mock_handler = mock_handler_cls.return_value
        mock_handler.flush_remaining = mocker.AsyncMock()
        mock_handler.processed_aids = {"aid1", "aid2", "aid3", "aid4", "aid5", "aid6"}

        # Mock background task waiter
        mocker.patch("CrowdStrikeFalcon.wait_for_background_tasks", new_callable=mocker.AsyncMock)

        # Mock final sealing task
        def create_task_side_effect(*args, **kwargs):
            f = asyncio.Future()
            f.set_result(1)
            return f

        mocker.patch(
            "CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context",
            side_effect=create_task_side_effect,
        )

        # Mock save functions
        mock_update_state = mocker.patch("CrowdStrikeFalcon.update_spotlight_state_and_metadata")
        mocker.patch("CrowdStrikeFalcon.save_spotlight_state")

        # Execute - no previously completed severities
        total_vulns, unique_aids = await fetch_spotlight_by_severity_parallel(
            client=mock_client,
            context_store=mock_context_store,
            spotlight_state=mock_state,
            snapshot_id=snapshot_id,
            completed_severities=[],  # Clean start
        )

        # Verify
        assert total_vulns == 50  # 10+20+15+5+0+0
        assert unique_aids == {"aid1", "aid2", "aid3", "aid4", "aid5", "aid6"}

        # Verify asset handler was created and flushed
        mock_handler_cls.assert_called_once()
        mock_handler.flush_remaining.assert_awaited_once()

        # Verify state was saved (completed_severities will be cleared by fetch_spotlight_assets)
        assert mock_update_state.called

    @pytest.mark.asyncio
    async def test_fetch_spotlight_by_severity_parallel_one_severity_fails(self, mocker, capfd):
        """
        Tests that if one severity fails, others continue processing.

        Given:
            - HIGH severity fetch raises an exception.
            - Other severities succeed.
        When:
            - fetch_spotlight_by_severity_parallel is called.
        Then:
            - Exception is logged but not propagated.
            - Other severities complete successfully.
            - Results include only successful severities.
            - HIGH is NOT marked as completed (will retry next cycle).
            - Snapshot is NOT sealed (not all severities completed).
        """
        from CrowdStrikeFalcon import fetch_spotlight_by_severity_parallel

        # Setup
        mock_client = mocker.AsyncMock()
        mock_state = mocker.Mock()
        mock_state.metadata = {}

        # Mock fetch_vulnerabilities_by_severity - HIGH fails, others succeed
        async def mock_fetch_by_severity(client, severity, **kwargs):
            if severity == "HIGH":
                raise Exception("HIGH severity API error")
            severity_data = {
                "CRITICAL": (10, {"aid1"}, set(), [{"id": "c1", "aid": "aid1"}]),
                "MEDIUM": (5, {"aid2"}, set(), [{"id": "m1", "aid": "aid2"}]),
                "LOW": (3, {"aid3"}, set(), [{"id": "l1", "aid": "aid3"}]),
                "NONE": (0, set(), set(), []),
                "UNKNOWN": (0, set(), set(), []),
            }
            return severity_data.get(severity, (0, set(), set(), []))

        mocker.patch(
            "CrowdStrikeFalcon.fetch_vulnerabilities_by_severity",
            side_effect=mock_fetch_by_severity,
        )

        # Mock asset handler
        mock_handler_cls = mocker.patch("CrowdStrikeFalcon.AssetsDeviceHandler")
        mock_handler = mock_handler_cls.return_value
        mock_handler.flush_remaining = mocker.AsyncMock()
        mock_handler.processed_aids = set()

        mocker.patch("CrowdStrikeFalcon.wait_for_background_tasks", new_callable=mocker.AsyncMock)

        def create_task_side_effect(*args, **kwargs):
            f = asyncio.Future()
            f.set_result(1)
            return f

        mocker.patch(
            "CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context",
            side_effect=create_task_side_effect,
        )

        # Mock save functions
        mock_update_state = mocker.patch("CrowdStrikeFalcon.update_spotlight_state_and_metadata")
        mocker.patch("CrowdStrikeFalcon.save_spotlight_state")

        # Execute - should not raise exception
        # Disable stdout capture since this test intentionally triggers error logging
        with capfd.disabled():
            total_vulns, unique_aids = await fetch_spotlight_by_severity_parallel(
                client=mock_client,
                context_store=mocker.Mock(),
                spotlight_state=mock_state,
                snapshot_id="snap123",
                completed_severities=[],  # Clean start
            )

        # Verify - only successful severities counted
        assert total_vulns == 18  # 10+5+3 (HIGH excluded)
        assert unique_aids == {"aid1", "aid2", "aid3"}

        # Verify sealing batch was NOT sent (not all severities completed)
        # The create_task mock is called for each severity's batches, but NOT for final sealing
        # We can't easily verify this without inspecting call args, so we check state instead

        # Verify completed_severities does NOT include HIGH (it failed)
        # Check the final state update call
        final_call = mock_update_state.call_args_list[-1]
        completed = final_call.kwargs["completed_severities"]
        assert "HIGH" not in completed
        assert "CRITICAL" in completed
        assert "MEDIUM" in completed
        assert "LOW" in completed

    @pytest.mark.asyncio
    async def test_fetch_vulnerabilities_by_severity_asset_enrichment(self, mocker):
        """
        Tests that AIDs are correctly sent to asset handler for enrichment.

        Given:
            - Vulnerabilities with various AIDs.
        When:
            - fetch_vulnerabilities_by_severity is called.
        Then:
            - Unique AIDs are extracted from vulnerabilities.
            - AIDs are sent to asset handler for enrichment.
            - Asset handler receives correct set of AIDs.
        """
        from CrowdStrikeFalcon import fetch_vulnerabilities_by_severity

        # Setup
        mock_client = mocker.AsyncMock()
        mock_handler = mocker.Mock()
        mock_handler.receive_new_aids = mocker.AsyncMock()

        # Mock API response with duplicate AIDs
        vulnerabilities = [
            {"id": "v1", "aid": "aid1"},
            {"id": "v2", "aid": "aid1"},  # Duplicate
            {"id": "v3", "aid": "aid2"},
            {"id": "v4", "aid": "aid3"},
        ]
        mock_response = mocker.Mock()
        mock_response.json.return_value = {
            "resources": vulnerabilities,
            "meta": {"pagination": {"after": None}},
        }
        mock_client._request.return_value = mock_response

        def create_task_side_effect(*args, **kwargs):
            f = asyncio.Future()
            f.set_result(1)
            return f

        mocker.patch(
            "CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context",
            side_effect=create_task_side_effect,
        )

        # Execute
        total, aids, tasks, _withheld = await fetch_vulnerabilities_by_severity(
            client=mock_client,
            severity="MEDIUM",
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap123",
            asset_handler=mock_handler,
        )

        # Verify
        assert total == 4
        assert aids == {"aid1", "aid2", "aid3"}  # Deduplicated

        # Verify handler received AIDs
        mock_handler.receive_new_aids.assert_awaited_once_with({"aid1", "aid2", "aid3"})

    @pytest.mark.asyncio
    async def test_fetch_vulnerabilities_by_severity_empty_response(self, mocker):
        """
        Tests handling of empty response for a severity.

        Given:
            - API returns no vulnerabilities for LOW severity.
        When:
            - fetch_vulnerabilities_by_severity is called for LOW.
        Then:
            - Function completes without error.
            - Returns zero count and empty AID set.
            - Still creates background task (with empty data).
        """
        from CrowdStrikeFalcon import fetch_vulnerabilities_by_severity

        # Setup
        mock_client = mocker.AsyncMock()
        mock_handler = mocker.Mock()
        mock_handler.receive_new_aids = mocker.AsyncMock()

        # Mock empty API response
        mock_response = mocker.Mock()
        mock_response.json.return_value = {
            "resources": [],
            "meta": {"pagination": {"after": None}},
        }
        mock_client._request.return_value = mock_response

        def create_task_side_effect(*args, **kwargs):
            f = asyncio.Future()
            f.set_result(1)
            return f

        mocker.patch(
            "CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context",
            side_effect=create_task_side_effect,
        )

        # Execute
        total, aids, tasks, withheld = await fetch_vulnerabilities_by_severity(
            client=mock_client,
            severity="LOW",
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap123",
            asset_handler=mock_handler,
        )

        # Verify
        assert total == 0
        assert aids == set()
        assert len(tasks) == 1  # Task still created (empty data batch)
        assert withheld == []  # Nothing to withhold from an empty severity

        # Verify handler received empty set
        mock_handler.receive_new_aids.assert_awaited_once_with(set())

    @pytest.mark.asyncio
    async def test_fetch_vulnerabilities_by_severity_401_error(self, mocker):
        """
        Tests that HTTP 401 authentication errors fail fast without retrying.

        Given:
            - API returns HTTP 401 Unauthorized error.
        When:
            - fetch_vulnerabilities_by_severity is called.
        Then:
            - ContentClientError is raised immediately with clear authentication error message.
            - Error message indicates authentication failure.
            - No retry attempts are made.
        """
        from CrowdStrikeFalcon import fetch_vulnerabilities_by_severity
        from ContentClientApiModule import ContentClientError

        # Setup
        mock_client = mocker.AsyncMock()
        mock_handler = mocker.Mock()
        mock_handler.receive_new_aids = mocker.AsyncMock()

        # Create a mock response with 401 status code
        mock_response = mocker.Mock()
        mock_response.status_code = 401
        mock_response.text = "Unauthorized"

        # Create ContentClientError with 401 response
        error_401 = ContentClientError("Unauthorized", response=mock_response)

        # Mock fetch_spotlight_vulnerabilities_page to raise 401 error
        mocker.patch(
            "CrowdStrikeFalcon.fetch_spotlight_vulnerabilities_page",
            side_effect=error_401,
        )

        # Mock logging
        mock_log = mocker.patch("CrowdStrikeFalcon.log_falcon_assets")

        # Execute and verify exception is raised
        with pytest.raises(ContentClientError) as exc_info:
            await fetch_vulnerabilities_by_severity(
                client=mock_client,
                severity="CRITICAL",
                context_store=mocker.Mock(),
                spotlight_state=mocker.Mock(),
                snapshot_id="snap123",
                asset_handler=mock_handler,
            )

        # Verify error message contains authentication failure details
        error_message = str(exc_info.value)
        assert "Authentication failed" in error_message
        assert "HTTP 401" in error_message
        assert "CRITICAL" in error_message
        assert "Invalid or expired credentials" in error_message

        # Verify error was logged
        assert any("Authentication failed" in str(call) and "error" in str(call) for call in mock_log.call_args_list)

    @pytest.mark.asyncio
    async def test_fetch_vulnerabilities_by_severity_401_error_string_match(self, mocker):
        """
        Tests that 401 errors are detected via string matching when response object is not available.

        Given:
            - ContentClientError with "Unauthorized" or "401" in error message.
        When:
            - fetch_vulnerabilities_by_severity is called.
        Then:
            - ContentClientError is raised with clear authentication error message.
        """
        from CrowdStrikeFalcon import fetch_vulnerabilities_by_severity
        from ContentClientApiModule import ContentClientError

        # Setup
        mock_client = mocker.AsyncMock()
        mock_handler = mocker.Mock()
        mock_handler.receive_new_aids = mocker.AsyncMock()

        # Create ContentClientError with "Unauthorized" in message but no response object
        error_unauthorized = ContentClientError("Request failed: Unauthorized access")

        # Mock fetch_spotlight_vulnerabilities_page to raise error
        mocker.patch(
            "CrowdStrikeFalcon.fetch_spotlight_vulnerabilities_page",
            side_effect=error_unauthorized,
        )

        # Mock logging
        mock_log = mocker.patch("CrowdStrikeFalcon.log_falcon_assets")

        # Execute and verify exception is raised
        with pytest.raises(ContentClientError) as exc_info:
            await fetch_vulnerabilities_by_severity(
                client=mock_client,
                severity="HIGH",
                context_store=mocker.Mock(),
                spotlight_state=mocker.Mock(),
                snapshot_id="snap123",
                asset_handler=mock_handler,
            )

        # Verify error message contains authentication failure details
        error_message = str(exc_info.value)
        assert "Authentication failed" in error_message
        assert "HIGH" in error_message
        assert "Invalid or expired credentials" in error_message

        # Verify error was logged
        assert any("Authentication failed" in str(call) and "error" in str(call) for call in mock_log.call_args_list)

    @pytest.mark.asyncio
    async def test_fetch_vulnerabilities_by_severity_withholds_first_record(self, mocker):
        """
        Tests that the first fetched record is withheld from the data batches (Option F seal fix).

        Given:
            - API returns 3 vulnerabilities for CRITICAL severity in a single page.
        When:
            - fetch_vulnerabilities_by_severity is called.
        Then:
            - The first record is withheld and returned as the 4th tuple element.
            - The data batch sent to XSIAM contains only the remaining records (no duplicate of withheld).
            - The withheld record is still counted in the total and its AID is still enriched.
        """
        from CrowdStrikeFalcon import fetch_vulnerabilities_by_severity

        mock_client = mocker.AsyncMock()
        mock_handler = mocker.Mock()
        mock_handler.receive_new_aids = mocker.AsyncMock()

        vulnerabilities = [
            {"id": "v1", "aid": "aid1"},
            {"id": "v2", "aid": "aid2"},
            {"id": "v3", "aid": "aid3"},
        ]
        mock_response = mocker.Mock()
        mock_response.json.return_value = {
            "resources": vulnerabilities,
            "meta": {"pagination": {"after": None}},
        }
        mock_client._request.return_value = mock_response

        def create_task_side_effect(*args, **kwargs):
            f = asyncio.Future()
            f.set_result(1)
            return f

        mock_create_task = mocker.patch(
            "CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context",
            side_effect=create_task_side_effect,
        )

        total, aids, tasks, withheld = await fetch_vulnerabilities_by_severity(
            client=mock_client,
            severity="CRITICAL",
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap123",
            asset_handler=mock_handler,
        )

        # Total still counts every fetched record (withholding is at send-time, not count-time)
        assert total == 3
        # AID enrichment still covers the withheld record's host
        assert aids == {"aid1", "aid2", "aid3"}
        mock_handler.receive_new_aids.assert_awaited_once_with({"aid1", "aid2", "aid3"})

        # Exactly one record withheld for the seal
        assert withheld == [{"id": "v1", "aid": "aid1"}]

        # The data batch sent during fetch must NOT include the withheld record
        sent_data = mock_create_task.call_args.kwargs["data"]
        assert sent_data == [{"id": "v2", "aid": "aid2"}, {"id": "v3", "aid": "aid3"}]

    @pytest.mark.asyncio
    async def test_fetch_vulnerabilities_by_severity_withholds_only_once_across_pages(self, mocker):
        """
        Tests that only the very first record (first page) is withheld, not one per page.

        Given:
            - API returns 2 pages of HIGH severity vulnerabilities.
        When:
            - fetch_vulnerabilities_by_severity is called.
        Then:
            - Only the first record of the first page is withheld.
            - Page 2 is sent in full.
        """
        from CrowdStrikeFalcon import fetch_vulnerabilities_by_severity

        mock_client = mocker.AsyncMock()
        mock_handler = mocker.Mock()
        mock_handler.receive_new_aids = mocker.AsyncMock()

        page1_vulns = [{"id": "v1", "aid": "aid1"}, {"id": "v2", "aid": "aid2"}]
        page2_vulns = [{"id": "v3", "aid": "aid3"}]

        mock_response1 = mocker.Mock()
        mock_response1.json.return_value = {
            "resources": page1_vulns,
            "meta": {"pagination": {"after": "token_page2"}},
        }
        mock_response2 = mocker.Mock()
        mock_response2.json.return_value = {
            "resources": page2_vulns,
            "meta": {"pagination": {"after": None}},
        }
        mock_client._request.side_effect = [mock_response1, mock_response2]

        sent_batches = []

        def create_task_side_effect(*args, **kwargs):
            sent_batches.append(kwargs["data"])
            f = asyncio.Future()
            f.set_result(1)
            return f

        mocker.patch(
            "CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context",
            side_effect=create_task_side_effect,
        )

        total, aids, tasks, withheld = await fetch_vulnerabilities_by_severity(
            client=mock_client,
            severity="HIGH",
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap123",
            asset_handler=mock_handler,
        )

        assert total == 3
        assert withheld == [{"id": "v1", "aid": "aid1"}]
        # First page batch had its first record withheld; second page sent in full
        assert sent_batches[0] == [{"id": "v2", "aid": "aid2"}]
        assert sent_batches[1] == [{"id": "v3", "aid": "aid3"}]

    @pytest.mark.asyncio
    async def test_fetch_vulnerabilities_by_severity_empty_withholds_nothing(self, mocker):
        """
        Tests that a severity with no records withholds nothing.

        Given:
            - API returns no vulnerabilities for LOW severity.
        When:
            - fetch_vulnerabilities_by_severity is called.
        Then:
            - withheld_records is empty.
        """
        from CrowdStrikeFalcon import fetch_vulnerabilities_by_severity

        mock_client = mocker.AsyncMock()
        mock_handler = mocker.Mock()
        mock_handler.receive_new_aids = mocker.AsyncMock()

        mock_response = mocker.Mock()
        mock_response.json.return_value = {
            "resources": [],
            "meta": {"pagination": {"after": None}},
        }
        mock_client._request.return_value = mock_response

        def create_task_side_effect(*args, **kwargs):
            f = asyncio.Future()
            f.set_result(1)
            return f

        mocker.patch(
            "CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context",
            side_effect=create_task_side_effect,
        )

        total, aids, tasks, withheld = await fetch_vulnerabilities_by_severity(
            client=mock_client,
            severity="LOW",
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap123",
            asset_handler=mock_handler,
        )

        assert total == 0
        assert withheld == []

    @pytest.mark.asyncio
    async def test_finalize_seals_with_real_withheld_records(self, mocker):
        """
        Tests that the final sealing batch carries real withheld records and the true total count.

        Given:
            - All severities completed.
            - Withheld records collected from severities.
        When:
            - finalize_severity_fetch is called.
        Then:
            - The seal batch is sent with the withheld records as data (NOT empty).
            - items_count equals the true total_vulnerabilities.
            - batch_number is the high sealing number (999999).
        """
        from CrowdStrikeFalcon import finalize_severity_fetch, SPOTLIGHT_SEVERITIES

        mocker.patch("CrowdStrikeFalcon.wait_for_background_tasks", new_callable=mocker.AsyncMock)

        seal_call = {}

        def create_task_side_effect(*args, **kwargs):
            seal_call.update(kwargs)
            f = asyncio.Future()
            f.set_result(1)
            return f

        mock_create_task = mocker.patch(
            "CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context",
            side_effect=create_task_side_effect,
        )

        mock_handler = mocker.Mock()
        mock_handler.flush_remaining = mocker.AsyncMock()
        mock_handler.processed_aids = set()

        mocker.patch("CrowdStrikeFalcon.update_spotlight_state_and_metadata")
        mocker.patch("CrowdStrikeFalcon.save_spotlight_state")

        withheld_records = [{"id": "v1", "aid": "aid1"}, {"id": "v2", "aid": "aid2"}]

        await finalize_severity_fetch(
            all_pending_tasks=set(),
            current_completed_severities=list(SPOTLIGHT_SEVERITIES),
            total_vulnerabilities=1000,
            all_unique_aids={"aid1", "aid2"},
            asset_handler=mock_handler,
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap123",
            withheld_records=withheld_records,
        )

        # The seal must carry real data, not an empty list
        assert mock_create_task.called
        assert seal_call["data"] == withheld_records
        assert seal_call["data"] != []
        assert seal_call["items_count"] == 1000
        assert seal_call["batch_number"] == 999999

    @pytest.mark.asyncio
    async def test_finalize_skips_seal_when_no_withheld_records(self, mocker):
        """
        Tests that no seal request is emitted when the grand total is zero (no withheld records).

        Given:
            - All severities completed but there are zero records overall.
        When:
            - finalize_severity_fetch is called with empty withheld_records.
        Then:
            - No sealing batch is sent (legitimately empty snapshot, no empty request emitted).
        """
        from CrowdStrikeFalcon import finalize_severity_fetch, SPOTLIGHT_SEVERITIES

        mocker.patch("CrowdStrikeFalcon.wait_for_background_tasks", new_callable=mocker.AsyncMock)

        mock_create_task = mocker.patch(
            "CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context",
        )

        mock_handler = mocker.Mock()
        mock_handler.flush_remaining = mocker.AsyncMock()
        mock_handler.processed_aids = set()

        mocker.patch("CrowdStrikeFalcon.update_spotlight_state_and_metadata")
        mocker.patch("CrowdStrikeFalcon.save_spotlight_state")

        await finalize_severity_fetch(
            all_pending_tasks=set(),
            current_completed_severities=list(SPOTLIGHT_SEVERITIES),
            total_vulnerabilities=0,
            all_unique_aids=set(),
            asset_handler=mock_handler,
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap123",
            withheld_records=[],
        )

        # No empty seal request should be emitted
        mock_create_task.assert_not_called()

    @pytest.mark.asyncio
    async def test_finalize_does_not_seal_when_severities_incomplete(self, mocker, capfd):
        """
        Tests that the seal is not sent if not all severities completed (resume next cycle).

        Given:
            - Only some severities completed.
            - Withheld records present.
        When:
            - finalize_severity_fetch is called.
        Then:
            - No seal is emitted; the snapshot is not finalized this cycle.
        """
        from CrowdStrikeFalcon import finalize_severity_fetch

        mocker.patch("CrowdStrikeFalcon.wait_for_background_tasks", new_callable=mocker.AsyncMock)
        mock_create_task = mocker.patch(
            "CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context",
        )
        mock_handler = mocker.Mock()
        mock_handler.flush_remaining = mocker.AsyncMock()
        mock_handler.processed_aids = set()

        # This path intentionally logs a "NOT sealed" warning to stderr; disable capture.
        with capfd.disabled():
            await finalize_severity_fetch(
                all_pending_tasks=set(),
                current_completed_severities=["CRITICAL", "HIGH"],
                total_vulnerabilities=30,
                all_unique_aids={"aid1"},
                asset_handler=mock_handler,
                context_store=mocker.Mock(),
                spotlight_state=mocker.Mock(),
                snapshot_id="snap123",
                withheld_records=[{"id": "v1", "aid": "aid1"}],
            )

        mock_create_task.assert_not_called()

    @pytest.mark.asyncio
    async def test_await_and_aggregate_collects_withheld_records(self, mocker):
        """
        Tests that withheld records are aggregated across severities.

        Given:
            - Several severity tasks each returning a withheld record.
        When:
            - await_and_aggregate_severity_results is called.
        Then:
            - All withheld records are aggregated and returned.
        """
        from CrowdStrikeFalcon import await_and_aggregate_severity_results

        mocker.patch("CrowdStrikeFalcon.update_spotlight_state_and_metadata")
        mocker.patch("CrowdStrikeFalcon.save_spotlight_state")

        def make_task(result):
            f = asyncio.Future()
            f.set_result(result)
            return f

        severity_tasks = [
            ("CRITICAL", make_task((10, {"aid1"}, set(), [{"id": "c1", "aid": "aid1"}]))),
            ("HIGH", make_task((20, {"aid2"}, set(), [{"id": "h1", "aid": "aid2"}]))),
            ("LOW", make_task((0, set(), set(), []))),
        ]

        (
            total,
            all_aids,
            all_tasks,
            completed,
            withheld,
        ) = await await_and_aggregate_severity_results(
            severity_tasks=severity_tasks,
            current_completed_severities=[],
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap123",
        )

        assert total == 30
        assert all_aids == {"aid1", "aid2"}
        assert {"id": "c1", "aid": "aid1"} in withheld
        assert {"id": "h1", "aid": "aid2"} in withheld
        assert len(withheld) == 2

    @pytest.mark.asyncio
    async def test_await_and_aggregate_merges_prior_withheld_records(self, mocker):
        """
        Tests that withheld records from previous cycles are merged and re-persisted (XSUP-70815).

        Given:
            - prior_withheld_records loaded from context (from severities completed earlier).
            - A new severity completing this cycle with its own withheld record.
        When:
            - await_and_aggregate_severity_results is called.
        Then:
            - The returned withheld records include both the prior and the new records.
            - The merged list is persisted to context via update_spotlight_state_and_metadata
              so it survives a later resume / container restart.
        """
        from CrowdStrikeFalcon import await_and_aggregate_severity_results

        mock_update_state = mocker.patch("CrowdStrikeFalcon.update_spotlight_state_and_metadata")
        mocker.patch("CrowdStrikeFalcon.save_spotlight_state")

        def make_task(result):
            f = asyncio.Future()
            f.set_result(result)
            return f

        prior = [{"id": "c1", "aid": "aid1"}, {"id": "h1", "aid": "aid2"}]
        severity_tasks = [
            ("MEDIUM", make_task((15, {"aid3"}, set(), [{"id": "m1", "aid": "aid3"}]))),
        ]

        (
            total,
            all_aids,
            all_tasks,
            completed,
            withheld,
        ) = await await_and_aggregate_severity_results(
            severity_tasks=severity_tasks,
            current_completed_severities=["CRITICAL", "HIGH"],
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap123",
            prior_withheld_records=prior,
        )

        # Returned list carries prior + new
        assert {"id": "c1", "aid": "aid1"} in withheld
        assert {"id": "h1", "aid": "aid2"} in withheld
        assert {"id": "m1", "aid": "aid3"} in withheld
        assert len(withheld) == 3

        # The merged withheld records were persisted to context
        assert mock_update_state.called
        saved_withheld = mock_update_state.call_args.kwargs["withheld_records"]
        assert {"id": "c1", "aid": "aid1"} in saved_withheld
        assert {"id": "m1", "aid": "aid3"} in saved_withheld
        assert len(saved_withheld) == 3

    def test_update_spotlight_state_persists_withheld_records(self, mocker):
        """
        Tests that withheld_records are written into the spotlight metadata.

        Given:
            - A spotlight state object and a list of withheld records.
        When:
            - update_spotlight_state_and_metadata is called with withheld_records.
        Then:
            - The metadata contains the withheld_records so they are persisted to context.
        """
        from CrowdStrikeFalcon import update_spotlight_state_and_metadata, ContentClientState

        state = ContentClientState.from_dict({})
        withheld = [{"id": "c1", "aid": "aid1"}]

        update_spotlight_state_and_metadata(
            spotlight_state=state,
            cursor=None,
            snapshot_id="snap123",
            total_fetched=0,
            unique_aids=set(),
            processed_aids=set(),
            completed_severities=["CRITICAL"],
            withheld_records=withheld,
        )

        assert state.metadata["withheld_records"] == withheld
        assert state.metadata["completed_severities"] == ["CRITICAL"]


class TestAssetsDeviceHandler:
    @pytest.mark.asyncio
    async def test_handler_trigger_enrichment(self, mocker):
        """
        Tests that the AssetsDeviceHandler triggers enrichment when the batch limit is exceeded.

        Given:
            - A handler with a batch limit of 5.
            - 7 unique AIDs are received.
        When:
            - receive_aids is called.
        Then:
            - Enrichment is triggered once for the first 5 AIDs.
            - 2 AIDs remain in the pending buffer.
        """
        from CrowdStrikeFalcon import AssetsDeviceHandler

        # Setup
        mock_client = mocker.AsyncMock()
        handler = AssetsDeviceHandler(
            client=mock_client,
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap1",
            processed_aids=set(),
            batch_limit=5,
        )

        handler.enrich_and_ingest_batch = mocker.AsyncMock()

        # Execute
        await handler.receive_new_aids({f"aid{i}" for i in range(7)})

        # Verify
        assert len(handler.pending_buffer) == 2
        handler.enrich_and_ingest_batch.assert_called_once()
        args, _ = handler.enrich_and_ingest_batch.call_args
        assert len(args[0]) == 5

    @pytest.mark.asyncio
    async def test_handler_deduplication(self, mocker):
        """
        Tests that the AssetsDeviceHandler correctly deduplicates AIDs.

        Given:
            - A handler with "processed_1" in processed_aids and "buffer_1" in the buffer.
        When:
            - receive_aids is called with duplicate and new AIDs.
        Then:
            - Only the new AIDs are added to the buffer.
            - Existing processed and buffered AIDs are ignored.
        """
        from CrowdStrikeFalcon import AssetsDeviceHandler

        # Setup
        handler = AssetsDeviceHandler(
            client=mocker.AsyncMock(),
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap1",
            processed_aids={"processed_1"},
            batch_limit=10,
        )

        # Pre-fill buffer
        handler.pending_buffer = {"buffer_1"}

        # Execute
        await handler.receive_new_aids({"processed_1", "buffer_1", "new_1"})

        # Verify
        assert handler.pending_buffer == {"buffer_1", "new_1"}

    @pytest.mark.asyncio
    async def test_handler_flush_remaining(self, mocker):
        """
        Tests that flush_remaining processes any items left in the buffer.

        Given:
            - A handler with AIDs in the pending buffer.
        When:
            - flush_remaining is called.
        Then:
            - Enrichment is triggered for the remaining items.
            - The buffer is cleared.
        """
        from CrowdStrikeFalcon import AssetsDeviceHandler

        # Setup
        handler = AssetsDeviceHandler(
            client=mocker.AsyncMock(),
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(),
            snapshot_id="snap1",
            processed_aids=set(),
            batch_limit=10,
        )
        handler.pending_buffer = {"aid1", "aid2"}
        handler.enrich_and_ingest_batch = mocker.AsyncMock()

        # Execute
        await handler.flush_remaining(total_items_count=100)

        # Verify
        handler.enrich_and_ingest_batch.assert_called_once()
        args, kwargs = handler.enrich_and_ingest_batch.call_args
        assert set(args[0]) == {"aid1", "aid2"}
        assert kwargs["final_items_count"] == 100
        assert len(handler.pending_buffer) == 0

    @pytest.mark.asyncio
    async def test_handler_enrichment_empty_response(self, mocker):
        """
        Tests behavior when the Device enrichment API returns no results.

        Given:
            - The Device API returns an empty list for the requested AIDs.
        When:
            - enrich_and_ingest_batch is called.
        Then:
            - No XSIAM task is created (preventing ingestion of empty data).
        """
        from CrowdStrikeFalcon import AssetsDeviceHandler

        # Setup
        mock_client = mocker.AsyncMock()
        mock_response = mocker.Mock()
        mock_response.json.return_value = {"resources": []}
        mock_client._request.return_value = mock_response

        handler = AssetsDeviceHandler(
            client=mock_client,
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(metadata={}),
            snapshot_id="snap1",
            processed_aids=set(),
            batch_limit=10,
        )

        mock_create_task = mocker.patch("CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context")

        # Execute
        await handler.enrich_and_ingest_batch(["d1"])

        # Verify
        mock_client._request.assert_awaited_once()
        mock_create_task.assert_not_called()
        # The batch is marked processed even when nothing resolves, so invalid IDs are not retried.
        assert "d1" in handler.processed_aids

    @pytest.mark.asyncio
    async def test_handler_enrichment_partial_success(self, mocker):
        """
        Tests that a CrowdStrike partial-success response (HTTP 400 with valid resources and
        invalid device IDs in 'errors') ingests the valid devices instead of failing the batch.

        Given:
            - The Device API returns valid resources AND an 'errors' array of invalid device IDs.
        When:
            - enrich_and_ingest_batch is called.
        Then:
            - The request is made with ok_codes=(200, 400) so the 400 is not raised.
            - The valid devices are sent to XSIAM (a send task is created).
            - The invalid device IDs are logged (warning) and skipped; no exception is raised.
        """
        from CrowdStrikeFalcon import AssetsDeviceHandler

        # Setup: response with both resolved resources and an errors array (partial success).
        mock_client = mocker.AsyncMock()
        mock_response = mocker.Mock()
        mock_response.json.return_value = {
            "resources": [{"device_id": "valid1"}],
            "errors": [{"code": 400, "message": "invalid device id [bad_id_1]"}],
        }
        mock_client._request.return_value = mock_response

        handler = AssetsDeviceHandler(
            client=mock_client,
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(metadata={}),
            snapshot_id="snap1",
            processed_aids=set(),
            batch_limit=10,
        )
        mocker.patch.object(handler, "_filter_asset_fields", side_effect=lambda d: d)
        mock_create_task = mocker.patch("CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context")
        mock_log = mocker.patch("CrowdStrikeFalcon.log_falcon_assets")

        # Execute
        await handler.enrich_and_ingest_batch(["valid1", "bad_id_1"])

        # Verify: 400 accepted, valid devices ingested, invalid IDs logged as a warning.
        mock_client._request.assert_awaited_once()
        _, request_kwargs = mock_client._request.call_args
        assert request_kwargs.get("ok_codes") == (200, 400)
        mock_create_task.assert_called_once()
        assert any(
            "invalid device id" in str(call.args[0]) and (len(call.args) > 1 and call.args[1] == "warning")
            for call in mock_log.call_args_list
        )

    @pytest.mark.asyncio
    async def test_handler_enrichment_real_failure_raises(self, mocker):
        """
        Tests that a genuine failure (no 'resources' in the response) still propagates.

        Given:
            - The Device enrichment call raises an exception (e.g. a real error, not partial success).
        When:
            - enrich_and_ingest_batch is called.
        Then:
            - The exception is re-raised and no XSIAM send task is created.
        """
        from CrowdStrikeFalcon import AssetsDeviceHandler

        # The error path logs via demisto.error(), which writes to stdout under demistomock and
        # would trip the conftest no-stdout/stderr guard at teardown. Patch the demisto reference
        # used inside the integration module so the expected error log from this deliberate
        # failure path is captured rather than leaked to stdout.
        mocker.patch("CrowdStrikeFalcon.demisto.error")

        mock_client = mocker.AsyncMock()
        mock_client._request.side_effect = Exception("Request failed: real server error")

        handler = AssetsDeviceHandler(
            client=mock_client,
            context_store=mocker.Mock(),
            spotlight_state=mocker.Mock(metadata={}),
            snapshot_id="snap1",
            processed_aids=set(),
            batch_limit=10,
        )
        mock_create_task = mocker.patch("CrowdStrikeFalcon.create_task_send_batch_to_xsiam_and_save_context")

        # Execute / Verify
        with pytest.raises(Exception, match="real server error"):
            await handler.enrich_and_ingest_batch(["d1"])
        mock_create_task.assert_not_called()

    @pytest.mark.asyncio
    async def test_send_batch_out_of_order_completion(self, mocker):
        """
        Tests that state is only saved if the completing batch is newer than the last saved batch.

        Given:
            - Two concurrent batches where Batch 2 finishes before Batch 1.
        When:
            - The callback runs for Batch 2, then for Batch 1.
        Then:
            - State is saved for Batch 2.
            - State is NOT saved for Batch 1 (avoiding regression).
        """
        from CrowdStrikeFalcon import send_batch_to_xsiam_and_save_context

        # Setup
        mock_save_callback = mocker.Mock()
        mocker.patch("CrowdStrikeFalcon.send_data_to_xsiam_async", return_value=[])
        mocker.patch("asyncio.gather", new_callable=mocker.AsyncMock)

        # Scenario: Batch 2 finishes first.
        res2 = await send_batch_to_xsiam_and_save_context(
            data=[],
            vendor="v",
            product="p",
            snapshot_id="s",
            items_count=1,
            batch_number=2,
            last_saved_batch_number=0,
            context_store=mocker.Mock(),
            state=mocker.Mock(),
            save_state_callback=mock_save_callback,
            data_type="assets",
        )

        assert res2 == 2
        mock_save_callback.assert_called()
        mock_save_callback.reset_mock()

        # Scenario: Batch 1 finishes later.
        res1 = await send_batch_to_xsiam_and_save_context(
            data=[],
            vendor="v",
            product="p",
            snapshot_id="s",
            items_count=1,
            batch_number=1,
            last_saved_batch_number=2,
            context_store=mocker.Mock(),
            state=mocker.Mock(),
            save_state_callback=mock_save_callback,
            data_type="assets",
        )

        assert res1 == 2
        mock_save_callback.assert_not_called()

    def test_state_persistence_structure(self, mocker):
        """
        Tests that the state is serialized into the correct JSON structure for the Context Store.

        Given:
            - A populated ContentClientState object.
        When:
            - save_spotlight_state is called.
        Then:
            - The integration context is updated with a 'spotlight_assets' key.
            - Existing context data is preserved.
        """
        from CrowdStrikeFalcon import save_spotlight_state, ContentClientState

        # Setup
        mock_context_store = mocker.Mock()
        integration_context = {"existing_key": "val"}
        mock_context_store.read.return_value = integration_context
        state = ContentClientState()
        state.cursor = "token123"
        state.metadata = {"snapshot_id": "snap1", "processed_aids": ["a1", "a2"]}

        # Execute
        save_spotlight_state(mock_context_store, state)

        # Verify
        mock_context_store.write.assert_called_once()
        saved_context = mock_context_store.write.call_args[0][0]

        assert "spotlight_assets" in saved_context
        assert saved_context["spotlight_assets"]["cursor"] == "token123"
        assert saved_context["spotlight_assets"]["metadata"]["snapshot_id"] == "snap1"
        assert "existing_key" in saved_context


def test_list_workflow_definitions_command(mocker):
    """
    Test cs-falcon-list-workflow-definitions command.

    Given:
        - Arguments with name filter.
    When:
        - Running list_workflow_definitions_command.
    Then:
        - Verify the command returns expected workflow definitions.
    """
    from CrowdStrikeFalcon import list_workflow_definitions_command

    mock_response = {
        "resources": [
            {
                "id": "def-123",
                "name": "Test Workflow",
                "description": "A test workflow",
                "trigger": {
                    "event": "on_demand",
                    "name": "Manual Trigger",
                    "schedule": "",
                    "type": "on_demand",
                },
            }
        ]
    }
    mocker.patch("CrowdStrikeFalcon.http_request", return_value=mock_response)

    args = {"name": "Test", "limit": "10"}
    result = list_workflow_definitions_command(args)

    assert result.outputs_prefix == "CrowdStrike.WorkflowDefinition"
    assert result.outputs_key_field == "id"
    assert len(result.outputs) == 1
    assert result.outputs[0]["id"] == "def-123"
    assert result.outputs[0]["name"] == "Test Workflow"
    assert "Test Workflow" in result.readable_output


def test_workflow_execute_command(mocker):
    """
    Test cs-falcon-workflow-execute command.

    Given:
        - A definition_id and body.
    When:
        - Running workflow_execute_command.
    Then:
        - Verify the command returns expected execution result.
    """
    from CrowdStrikeFalcon import workflow_execute_command

    mock_response = {
        "meta": {"trace_id": "trace-789"},
        "resources": ["exec-456"],
    }
    mocker.patch("CrowdStrikeFalcon.http_request", return_value=mock_response)

    args = {"definition_id": "def-123", "body": "{}"}
    result = workflow_execute_command(args)

    assert result.outputs_prefix == "CrowdStrike.Workflow"
    assert result.outputs == ["exec-456"]
    assert "exec-456" in result.readable_output


def test_workflow_execute_command_missing_args(mocker):
    """
    Test cs-falcon-workflow-execute command with missing required args.

    Given:
        - No definition_id or name provided.
    When:
        - Running workflow_execute_command.
    Then:
        - Verify DemistoException is raised.
    """
    from CrowdStrikeFalcon import workflow_execute_command

    args = {"body": "{}"}
    with pytest.raises(DemistoException, match="Either 'definition_id' or 'name' must be provided."):
        workflow_execute_command(args)


def test_list_workflow_executions_command(mocker):
    """
    Test cs-falcon-list-workflow-executions command.

    Given:
        - Arguments with definition_id filter.
    When:
        - Running list_workflow_executions_command.
    Then:
        - Verify the command returns expected workflow executions.
    """
    from CrowdStrikeFalcon import list_workflow_executions_command

    mock_response = {
        "resources": [
            {
                "id": "exec-001",
                "execution_id": "exec-001",
                "status": "completed",
                "activities": [
                    {
                        "node_id": "node-1",
                        "start_timestamp": "2024-01-01T00:00:00Z",
                        "end_timestamp": "2024-01-01T00:01:00Z",
                        "status": "completed",
                        "name": "Step 1",
                        "type": "action",
                    }
                ],
            }
        ]
    }
    mocker.patch("CrowdStrikeFalcon.http_request", return_value=mock_response)

    args = {"definition_id": "def-123", "limit": "10"}
    result = list_workflow_executions_command(args)

    assert result.outputs_prefix == "CrowdStrike.Workflows.Execution"
    assert len(result.outputs) == 1
    assert result.outputs[0]["execution_id"] == "exec-001"
    assert "Step 1" in result.readable_output


def test_list_workflow_execution_results_command(mocker):
    """
    Test cs-falcon-list-workflow-execution-results command.
    Given: A list of execution IDs.
    When: Running list_workflow_execution_results_command.
    Then: Verify the command returns expected execution results with nested activities.
    """
    from CrowdStrikeFalcon import list_workflow_execution_results_command

    mock_response = {
        "resources": [
            {
                "execution_id": "ae6b9021abff1dc526093dbcb5e66bd2",
                "activities": [
                    {
                        "node_id": "GetCaseDetails",
                        "start_timestamp": "2026-03-10T15:10:24.024Z",
                        "end_timestamp": "2026-03-10T15:10:24.374Z",
                        "status": "Completed",
                        "id": "3dc4a68cf25bf32ceec6588c6d5c8989",
                        "name": "Get case details",
                        "type": "cases",
                    }
                ],
            }
        ]
    }
    mocker.patch("CrowdStrikeFalcon.http_request", return_value=mock_response)

    args = {"ids": "exec-001,exec-002"}
    result = list_workflow_execution_results_command(args)

    assert result.outputs_prefix == "CrowdStrike.Workflows.ExecutionResult"
    assert result.outputs_key_field == "execution_id"
    assert len(result.outputs) == 1
    assert result.outputs[0]["execution_id"] == "ae6b9021abff1dc526093dbcb5e66bd2"
    assert result.outputs[0]["activities"][0]["status"] == "Completed"
    assert "GetCaseDetails" in result.readable_output
    assert "Get case details" in result.readable_output


def test_list_workflow_execution_results_command_partial_errors(mocker):
    """
    Test cs-falcon-list-workflow-execution-results command with partial errors.
    Given: Two execution IDs, one valid and one not found.
    When: Running list_workflow_execution_results_command.
    Then: Verify the command returns results for the valid ID and shows errors for the invalid one.
    """
    from CrowdStrikeFalcon import list_workflow_execution_results_command

    mock_response = {
        "meta": {"query_time": 8.1e-8, "powered_by": "workflow-api", "trace_id": "01193598-4a4b-41e2-970a-cb7135629d3f"},
        "errors": [{"code": 404, "message": "execution ID 'ae6b9021abff1dc526093dbcb5e66bda' not found"}],
        "resources": [
            {
                "execution_id": "ae6b9021abff1dc526093dbcb5e66bd2",
                "activities": [
                    {
                        "node_id": "GetCaseDetails",
                        "start_timestamp": "2026-03-10T15:10:24.024Z",
                        "end_timestamp": "2026-03-10T15:10:24.374Z",
                        "status": "Completed",
                        "id": "3dc4a68cf25bf32ceec6588c6d5c8989",
                        "name": "Get case details",
                        "type": "cases",
                    }
                ],
            }
        ],
    }
    mocker.patch("CrowdStrikeFalcon.http_request", return_value=mock_response)

    args = {"ids": "ae6b9021abff1dc526093dbcb5e66bd2,ae6b9021abff1dc526093dbcb5e66bda"}
    result = list_workflow_execution_results_command(args)

    assert result.outputs_prefix == "CrowdStrike.Workflows.ExecutionResult"
    assert len(result.outputs) == 1
    assert result.outputs[0]["execution_id"] == "ae6b9021abff1dc526093dbcb5e66bd2"
    assert "GetCaseDetails" in result.readable_output
    assert "Errors" in result.readable_output
    assert "404" in result.readable_output
    assert "ae6b9021abff1dc526093dbcb5e66bda" in result.readable_output


def test_workflow_execution_action_command(mocker):
    """
    Test cs-falcon-workflow-execution-action command.
    Given: Execution IDs and action_name=cancel, all IDs valid.
    When: Running workflow_execution_action_command.
    Then: Verify the command returns expected readable output with resources_affected count.
    """
    from CrowdStrikeFalcon import workflow_execution_action_command

    mock_response = {"meta": {"writes": {"resources_affected": 1}}, "errors": [], "resources": []}
    mocker.patch("CrowdStrikeFalcon.http_request", return_value=mock_response)

    args = {"ids": "exec-001", "action_name": "cancel"}
    result = workflow_execution_action_command(args)

    assert "1 workflow execution(s) cancelled" in result.readable_output
    assert "exec-001" in result.readable_output
    assert "Errors" not in result.readable_output
    assert result.outputs is None


def test_workflow_execution_action_command_invalid_action(mocker):
    """
    Test cs-falcon-workflow-execution-action command with invalid action.

    Given:
        - An invalid action_name.
    When:
        - Running workflow_execution_action_command.
    Then:
        - Verify DemistoException is raised.
    """
    from CrowdStrikeFalcon import workflow_execution_action_command

    args = {"ids": "exec-001", "action_name": "invalid"}
    with pytest.raises(DemistoException, match="Invalid action_name"):
        workflow_execution_action_command(args)


def test_workflow_execution_action_command_partial_success(mocker):
    """
    Test cs-falcon-workflow-execution-action command with partial success.
    Given: Two execution IDs, one valid and one invalid (fake_id).
    When: Running workflow_execution_action_command.
    Then: Verify the command shows resources_affected count and error details for the failed ID.
    """
    from CrowdStrikeFalcon import workflow_execution_action_command

    mock_response = {
        "meta": {
            "query_time": 6.3e-8,
            "pagination": {"offset": 0, "limit": 0, "total": 0},
            "writes": {"resources_affected": 1},
            "powered_by": "workflow-api",
            "trace_id": "b7b085fc-5203-4d7e-a53d-6e6820a47123",
        },
        "errors": [{"code": 404, "message": "Not Found", "id": "fake_id"}],
    }
    mocker.patch("CrowdStrikeFalcon.http_request", return_value=mock_response)

    args = {"ids": "proper_id,fake_id", "action_name": "cancel"}
    result = workflow_execution_action_command(args)

    assert "1 workflow execution(s) cancelled" in result.readable_output
    assert "proper_id" in result.readable_output
    assert "Errors" in result.readable_output
    assert "fake_id" in result.readable_output
    assert "404" in result.readable_output
    assert "Not Found" in result.readable_output
    assert result.outputs is None


def test_list_workflow_definitions_command_filter_priority(mocker):
    """
    Test that when filter arg is provided, convenience args are ignored.
    Given: Both filter and name args provided.
    When: Running list_workflow_definitions_command.
    Then: Verify only the filter arg is used, name is ignored.
    """
    from CrowdStrikeFalcon import list_workflow_definitions_command

    mock_response = {"resources": []}
    http_mock = mocker.patch("CrowdStrikeFalcon.http_request", return_value=mock_response)

    args = {"filter": "enabled:True", "name": "ShouldBeIgnored", "limit": "10"}
    list_workflow_definitions_command(args)

    call_args = http_mock.call_args
    params = call_args.kwargs.get("params") or call_args[1].get("params") or call_args[0][2] if len(call_args[0]) > 2 else None
    # The filter should be exactly "enabled:True", not containing "name:~'ShouldBeIgnored'"
    assert "ShouldBeIgnored" not in str(params)


def test_list_workflow_executions_command_filter_priority(mocker):
    """
    Test that when filter arg is provided, convenience args are ignored.
    Given: Both filter and definition_id args provided.
    When: Running list_workflow_executions_command.
    Then: Verify only the filter arg is used, definition_id is ignored.
    """
    from CrowdStrikeFalcon import list_workflow_executions_command

    mock_response = {"resources": []}
    http_mock = mocker.patch("CrowdStrikeFalcon.http_request", return_value=mock_response)

    args = {"filter": "status:'completed'", "definition_id": "ShouldBeIgnored", "limit": "10"}
    list_workflow_executions_command(args)

    call_args = http_mock.call_args
    params = call_args.kwargs.get("params") or call_args[1].get("params") or call_args[0][2] if len(call_args[0]) > 2 else None
    assert "ShouldBeIgnored" not in str(params)


class TestSynchronousCompression:
    """Tests for synchronous compression in send_data_to_xsiam_async (CIAC-16811 memory optimization)."""

    @pytest.mark.asyncio
    async def test_send_data_to_xsiam_async_compresses_synchronously(self, mocker):
        """
        Tests that send_data_to_xsiam_async compresses data synchronously before creating async tasks.

        Given:
            - A list of vulnerability dicts to send to XSIAM.
        When:
            - send_data_to_xsiam_async is called.
        Then:
            - Data is compressed synchronously (gzip.compress called during function execution, not in async task).
            - Async tasks receive pre-compressed bytes, not raw data.
            - The returned tasks are valid asyncio tasks.
        """
        import gzip
        import json

        from CrowdStrikeFalcon import send_data_to_xsiam_async

        # Setup - mock XSIAM credentials and params
        mocker.patch("CrowdStrikeFalcon.demisto.params", return_value={"url": "mock_url"})
        mocker.patch(
            "CrowdStrikeFalcon.demisto.callingContext", {"context": {"IntegrationInstance": "test", "IntegrationBrand": "CSF"}}
        )
        mocker.patch("CrowdStrikeFalcon.demisto.getLicenseCustomField", return_value="mock-token")

        # Track gzip.compress calls to verify synchronous compression
        original_compress = gzip.compress
        compress_calls: list[bytes] = []

        def tracking_compress(data, *args, **kwargs):
            result = original_compress(data, *args, **kwargs)
            compress_calls.append(result)
            return result

        mocker.patch("CrowdStrikeFalcon.gzip.compress", side_effect=tracking_compress)

        # Mock xsiam_api_call_async to avoid actual HTTP calls
        mock_api_call = mocker.AsyncMock()
        mocker.patch("CrowdStrikeFalcon.xsiam_api_call_async", mock_api_call)

        # Test data - 3 vulnerability dicts
        test_data = [
            {"id": "vuln1", "aid": "aid1", "status": "open", "cve": {"id": "CVE-2024-0001", "severity": "HIGH"}},
            {"id": "vuln2", "aid": "aid2", "status": "open", "cve": {"id": "CVE-2024-0002", "severity": "MEDIUM"}},
            {"id": "vuln3", "aid": "aid3", "status": "open", "cve": {"id": "CVE-2024-0003", "severity": "LOW"}},
        ]

        # Execute
        tasks = send_data_to_xsiam_async(
            data=test_data,
            vendor="CrowdStrike",
            product="Falcon_Spotlight",
            data_type="assets",
            snapshot_id="snap123",
        )

        # Verify compression happened synchronously (before tasks are awaited)
        assert len(compress_calls) > 0, "gzip.compress should have been called synchronously"

        # Verify compressed data is valid gzip that decompresses to the original data
        for compressed in compress_calls:
            decompressed = gzip.decompress(compressed).decode("utf-8")
            # Each line should be a valid JSON object from our test data
            for line in decompressed.strip().split("\n"):
                parsed = json.loads(line)
                assert "id" in parsed
                assert "aid" in parsed

        # Verify tasks were created
        assert len(tasks) > 0, "Should have created at least one async task"

    @pytest.mark.asyncio
    async def test_send_data_to_xsiam_async_empty_data_assets(self, mocker):
        """
        Tests that send_data_to_xsiam_async handles empty data for asset seal correctly.

        Given:
            - Empty data list with data_type="assets" (seal batch).
        When:
            - send_data_to_xsiam_async is called.
        Then:
            - A task is still created (for the seal).
            - No crash occurs.
        """
        from CrowdStrikeFalcon import send_data_to_xsiam_async

        mocker.patch("CrowdStrikeFalcon.demisto.params", return_value={"url": "mock_url"})
        mocker.patch(
            "CrowdStrikeFalcon.demisto.callingContext", {"context": {"IntegrationInstance": "test", "IntegrationBrand": "CSF"}}
        )
        mocker.patch("CrowdStrikeFalcon.demisto.getLicenseCustomField", return_value="mock-token")
        mocker.patch("CrowdStrikeFalcon.xsiam_api_call_async", mocker.AsyncMock())

        tasks = send_data_to_xsiam_async(
            data=[],
            vendor="CrowdStrike",
            product="Falcon_Spotlight",
            data_type="assets",
            snapshot_id="snap123",
        )

        # Seal batch should still create a task
        assert len(tasks) == 1, "Empty assets data should create one seal task"

    def test_send_data_to_xsiam_async_empty_data_non_assets(self, mocker):
        """
        Tests that send_data_to_xsiam_async returns empty list for non-asset empty data.

        Given:
            - Empty data list with data_type="events".
        When:
            - send_data_to_xsiam_async is called.
        Then:
            - Returns empty list (no tasks created).
        """
        from CrowdStrikeFalcon import send_data_to_xsiam_async

        mocker.patch("CrowdStrikeFalcon.demisto.params", return_value={"url": "mock_url"})
        mocker.patch(
            "CrowdStrikeFalcon.demisto.callingContext", {"context": {"IntegrationInstance": "test", "IntegrationBrand": "CSF"}}
        )
        mocker.patch("CrowdStrikeFalcon.demisto.getLicenseCustomField", return_value="mock-token")

        tasks = send_data_to_xsiam_async(
            data=[],
            vendor="CrowdStrike",
            product="Falcon_Spotlight",
            data_type="events",
        )

        assert tasks == [], "Empty non-asset data should return no tasks"


class TestModuleTestConnectionErrors:
    """Tests for module_test() error handling (CRTX-269894)."""

    # The message CommonServerPython raises for a requests ConnectionError (see CRTX-269894).
    DNS_FAILURE_MESSAGE = (
        "Verify that the server URL parameter is correct and that you have access to the server from your host."
        "\nHTTPSConnectionPool(host='api.crowdstrike.cominvalid-domain-000', port=443): Max retries exceeded with url:"
        " /oauth2/token (Caused by NameResolutionError: Failed to resolve 'api.crowdstrike.cominvalid-domain-000')"
    )

    def test_connection_error_returns_friendly_message(self, mocker):
        """
        Given:
            - A Server URL whose host cannot be resolved.
        When:
            - Running the test-module command.
        Then:
            - A friendly message is returned, without the raw connection internals,
              and the full traceback is written to the debug log.
        """
        from CrowdStrikeFalcon import module_test

        debug_mock = mocker.patch.object(demisto, "debug")
        mocker.patch("CrowdStrikeFalcon.get_token", side_effect=DemistoException(self.DNS_FAILURE_MESSAGE))

        result = module_test()

        assert "Server URL" in result
        assert "HTTPSConnectionPool" not in result
        assert "Traceback" in "".join(str(call) for call in debug_mock.call_args_list)

    def test_returns_ok_on_success(self, mocker):
        """
        Given:
            - Valid credentials and a reachable server.
        When:
            - Running the test-module command.
        Then:
            - "ok" is returned.
        """
        from CrowdStrikeFalcon import module_test

        mocker.patch("CrowdStrikeFalcon.get_token", return_value="token")

        assert module_test() == "ok"