Cortex XDR - IR

Cortex XDR is the world's first detection and response app that natively integrates network, endpoint, and cloud data to stop sophisticated attacks.

Endpoint · Cortex XDR by Palo Alto Networks

Details

IDCortex XDR - IR
ProviderPalo Alto Networks
CategoryEndpoint
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix

README

Cortex XDR is the world’s first detection and response app that natively integrates network, endpoint, and cloud data to stop sophisticated attacks.
This integration was integrated and tested with version 2.6.5 of Cortex XDR - IR.

Configure Palo Alto Networks Cortex XDR - Investigation and Response on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Palo Alto Networks Cortex XDR - Investigation and Response.
  3. Click Add instance to create and configure a new integration instance.
Parameter Description Required
Fetch incidents   False
Incident type   False
Server URL (copy URL from XDR)   True
API Key ID   False
API Key   False
Remove legacy incident fields Not selected for backwards compatibility. Recommended to select. This will remove duplicated incident fields under file_artifacts, network_artifacts, and alerts (like client_id, clientid.) False
Incident Mirroring Direction Not supported on Cortex platform. False
Close Mirrored XSOAR Incident When selected, closing the Cortex XDR incident is mirrored in Cortex XSOAR. False
Close Mirrored Cortex XDR Incident When selected, closing the Cortex XSOAR incident is mirrored in Cortex XDR. Notice: If not selected, but ‘Close all related alerts in XDR’ is selected, the incident will automatically be closed in Cortex XDR. False
XDR mirroring delay in minutes In the event of a delay in mirroring incoming changes from XDR, use the xdr_delay parameter to extend the look-back period. However, be aware that this may result in increased latency when updating incidents. False
Custom close-reason mapping (XSOAR -> XDR mirrored incident. Overwrites default close-reason mapping defined by Cortex XSOAR) Define how to close the mirrored incidents from Cortex XSOAR into Cortex XDR with a custom close reason mapping. Enter a comma-separated list of close reasons (acceptable format {Cortex XSOAR close reason}={Cortex XDR close reason}) to override the default close reason mapping defined by Cortex XSOAR. Note that the mapping must be configured accordingly with the existing close reasons in Cortex XSOAR and Cortex XDR. Not following this format will result in closing the incident with a default close reason. Example: “Resolved=Other,Duplicate=Other”. Refer to ../README.md for possible close-reasons - `XDR Incident Mirroring`. False
Custom close-reason mapping (XDR -> XSOAR mirrored incident. Overwrites default close-reason mapping defined by Cortex XSOAR) Define how to close the mirrored incidents from Cortex XDR into Cortex XSOAR with a custom close reason mapping. Enter a comma-separated list of close reasons (acceptable format {Cortex XDR close reason}={Cortex XSOAR close reason}) to override the default close reason mapping defined by Cortex XSOAR. Note that the mapping must be configured accordingly with the existing close reasons in Cortex XSOAR and Cortex XDR. Not following this format will result in closing the incident with a default close reason. Example: “Known Issue=Resolved, Duplicate Incident=Other”. Refer to ../README.md for possible close-reasons - `XDR Incident Mirroring`. False
HTTP Timeout The timeout of the HTTP requests sent to Cortex XDR API (in seconds). False
Maximum number of incidents per fetch The maximum number of incidents per fetch. Cannot exceed 100. False
Only fetch starred incidents   False
Starred incidents fetch window Starred fetch window timestamp (<number> <time unit>, e.g., 12 hours, 7 days). Fetches only starred incidents within the specified time range. False
First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)   False
Sync Incident Owners For Cortex XSOAR version 6.0.0 and above. If selected, for every incident fetched from Cortex XDR to Cortex XSOAR, the incident owners will be synced. Note that once this value is changed and synchronized between the systems, additional changes will not be reflected. For example, if you change the owner in Cortex XSOAR, the new owner will also be changed in Cortex XDR. However, if you now change the owner back in Cortex XDR, this additional change will not be reflected in Cortex XSOAR. In addition, for this change to be reflected, the owners must exist in both Cortex XSOAR and Cortex XDR. False
Trust any certificate (not secure)   False
Use system proxy settings   False
Prevent Only Mode Whether the XDR tenant mode is prevent only False
Incident Statuses to Fetch The statuses of the incidents that will be fetched. If no status is provided then incidents of all the statuses will be fetched. Note: An incident whose status was changed to a filtered status after its creation time will not be fetched. False
Minimize Incident Information Whether to fetch only the essential incident’s fields - without Network Artifacts and File Artifacts to minimize the incident’s information. False
Minimize Alert Information Whether to fetch only the essential alert fields in order to minimize the incident’s information. Possible values: null_values to remove all null values from alerts data, or any other field of an alert. False
Close all related alerts in XDR Close all related alerts in Cortex XDR once an incident has been closed in Cortex XSOAR. False
  1. Click Test to validate the URLs, token, and connection.

Configuration


To configure the integration on Cortex XSOAR, you need to generate an API Key and obtain your Cortex XDR URL.

Generate an API Key and API Key ID

  1. In Cortex XDR, navigate to Settings > Configurations > API Keys.
  2. Click +New Key and generate an Advanced key.
  3. Copy the generated API Key and the Key ID from the table.

Note 1: You can assign a custom or built-in role to the API Key. Instance Admin provides the highest privileges, while Privileged Responder offers maximum command execution with fewer overall permissions. In order for the integration test to pass, the API Key must have View permissions for Cases and Issues.

Note 2: If you experience missing updates when mirroring incoming changes from XDR, increase the XDR mirroring delay in minutes parameter. Note that this may increase latency when updating incidents.

URL

  1. In Cortex XDR, navigate to Settings > Configurations > API Keys.
  2. Click Copy URL in the top right corner.

XDR & XSOAR

Playbooks


Cortex XDR Incident Handling

The playbook syncs and updates new Cortex XDR alerts that construct the incident.
It enriches indicators using Threat Intelligence integrations and Palo Alto Networks
AutoFocus. The incident’s severity is then updated based on the indicators reputation
and an analyst is assigned for manual investigation. If chosen, automated remediation
with Palo Alto Networks FireWall is initiated. After a manual review by the
SOC analyst, the Cortex XDR incident is closed automatically.

To utilize this playbook for handling Cortex XDR incidents, the classifier that should be selected is Cortex XDR - Classifier.
The selected Mapper (incoming) should be XDR - Incoming Mapper, and the selected Mapper (outgoing) should be Cortex XDR - Outgoing Mapper.

Cortex XDR Lite - Incident Handling

This playbook is a lite default playbook to handle Cortex XDR incidents, and it doesn’t require additional integrations to run. The playbook is triggered by fetching a Palo Alto Networks Cortex XDR incident. First, the playbook performs enrichment on the incident’s indicators. Then, the playbook performs investigation and analysis on the command line and searches for related Cortex XDR alerts by Mitre tactics to identify malicious activity performed on the endpoint and by the user. Based on the enrichment and the investigation results, the playbooks sets the verdict of the incident. If malicious indicators are found, the playbook takes action to block these indicators and isolate the affected endpoint to prevent further damage or the spread of threats. If the verdict is not determined, it lets the analyst decide whether to continue to the remediation stage or close the investigation as benign. As part of this playbook, you’ll receive a comprehensive layout that presents incident details, analysis, investigation findings, and the final verdict. Additionally, the layout offers convenient remediation buttons for quicker manual actions.

To utilize this playbook for handling XDR incidents, the classifier should be empty, and the selected incident type should be Cortex XDR - Lite.
The selected Mapper (incoming) should be XDR - Incoming Mapper, and the selected Mapper (outgoing) should be Cortex XDR - Outgoing Mapper.

Use Cases


  • Fetch incidents from Cortex XDR
  • Enrich incident with alerts and incident from Cortex XDR
  • Update incident in Cortex XDR
  • Search for endpoints
  • Isolate/unisolate endpoints
  • Insert parsed alerts into Cortex XDR
  • Insert CEF alerts into Cortex XDR
  • Query for agent audit reports
  • Query for audit management logs
  • Create distribution
  • Get distribution download URL
  • Get distribution versions

Automation


To sync incidents between Cortex XSOAR and Cortex XDR, you should use the XDRSyncScript script, which you can find in the automation page.

Fetched Incidents Data


incident_id:31
creation_time:1564594008755
modification_time:1566339537617
detection_time:null
status:new
severity:low
description:6 'Microsoft Windows RPC Fragment Evasion Attempt' alerts detected by PAN NGFW on 6 hosts
assigned_user_mail:null
assigned_user_pretty_name:null
alert_count:6
low_severity_alert_count:0
med_severity_alert_count:6
high_severity_alert_count:0
user_count:1
host_count:6
notes:null
resolve_comment:null
manual_severity:low
manual_description:null
xdr_url:https://1111.paloaltonetworks.com/incident-view/31

XDR Incident Mirroring

Note this feature is available from Cortex XSOAR version 6.0.0

You can enable incident mirroring between Cortex XSOAR incidents and Cortex XDR incidents.
To setup the mirroring follow these instructions:

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Cortex XDR - IR and select your integration instance.
  3. Enable Fetches incidents.
  4. Under Mapper (incoming), select XDR - Incoming Mapper.
  5. Under Mapper (outgoing), select Cortex XDR - Outgoing Mapper.
  6. In the Incident Mirroring Direction integration parameter, select in which direction the incidents should be mirrored:
    • Incoming - Any changes in Cortex XDR incidents will be reflected in Cortex XSOAR incidents.
    • Outgoing - Any changes in Cortex XSOAR incidents will be reflected in Cortex XDR incidents.
    • Both - Changes in Cortex XSOAR and Cortex XDR incidents will be reflected in both directions.
    • None - Choose this to turn off incident mirroring.
  7. Optional: Provide a custom close-reason mapping for mirrored XDR <-> XSOAR incidents. Please use only possible close-reasons to map:

    Possible Closure Reasons for Cortex XSOAR Incident
    Resolved
    False Positive
    Duplicate
    Security Testing
    Other
    Possible Closure Reasons for Cortex Cortex XDR Incident
    True Positive
    False Positive
    Duplicate Incident
    Security Testing
    Known Issue
    Other
    Auto

    Failing to use only available values will result in using default mapping of closure reasons within the mirroring process.

Close-reason default mapping XSOAR -> XDR: Other=Other, Duplicate=Duplicate Incident, False Positive=False Positive, Resolved=True Positive

Close-reason default mapping XDR -> XSOAR: Known Issue=Other, Duplicate Incident=Duplicate, False Positive=False Positive, True Positive=Resolved, Other=Other, Auto=Resolved

  1. Optional: Check the Sync Incident Owners integration parameter to sync the incident owners in both Cortex XDR and Cortex XSOAR.

    • Note: This feature will only work if the same users are registered in both Cortex XSOAR and Cortex XDR.
  2. Newly fetched incidents will be mirrored in the chosen direction.

    • Note: This will not effect existing incidents.

XDR Mirroring Notes, limitations and Troubleshooting

  • While you can mirror changes in incident fields both in and out in each incident, you can only mirror in a single direction at a time. For example:
    If we have an incident with two fields (A and B) in Cortex XDR and Cortex XSOAR while Incoming And Outgoing mirroring is selected:
    • I can mirror field A from Cortex XDR to Cortex XSOAR and field B from Cortex XSOAR to Cortex XDR.
    • I cannot mirror changes from field A in both directions.

    Initially all fields are mirrored in from Cortex XDR to Cortex XSOAR. Once they are changed in Cortex XSOAR, they can only be mirrored out.

  • Do not use the XDRSyncScript automation nor any playbook that uses this automation
    (e.g Cortex XDR Incident Sync or Cortex XDR incident handling v2), as it impairs the mirroring functionality.

  • When migrating an existing instance to the mirroring feature, or in case the mirroring does not work as expected, make sure that:
    • The default playbook of the Cortex XDR Incident incident type is not Cortex XDR Incident Sync, change it to a
      different playbook that does not use XDRSyncScript.
    • The Cortex XDR integration instance incoming mapper is set to Cortex XDR - Incoming Mapper and the outgoing mapper is set to Cortex XDR - Outgoing Mapper.
    • Mirroring impacts only incidents that were fetched after the mirroring was enabled for this instance. If incidents were fetched with the incorrect mapper, changing the mapper will not affect them. This can be resolved by resetting the last fetch run and re-fetching the incidents. New incidents will be created and the old ones will no longer be relevant.
  • The API includes a limit rate of 10 API requests per minute. Therefore, in a case of a limit rate exception, the sync loop will stop and will resume from the last incident.

  • Owner and closeReason mappings are done using the integration code, therefore they are not part of the out-of-the-box mapper and should not be specified in any future mapper.

Fetch Behavior vs Mirroring

Note: All incidents, including those with a “resolved” status, will be fetched into Cortex XSOAR as “active” incidents to enable the execution of our automations. However, the original resolved status of the incidents will be preserved in the incident details. If you prefer to keep certain incidents closed, you can utilize the “Incident Statuses to Fetch” filter during the configuration stage and choose not to import those specific incidents. Alternatively, you can utilize pre-processing rules to define specific types of incidents to be imported as closed.

Regarding mirroring, if you have already imported an incident and the mirroring feature is enabled, changing the incident’s status to resolved on the Cortex XDR platform will trigger the mirroring process, resulting in the closure of the incident in Cortex XSOAR.

Commands

You can execute these commands from the Cortex XSOAR 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.

xdr-get-incidents (Deprecated)


This command is deprecated please use xdr-case-list instead. Returns a list of incidents, which you can filter by a list of incident IDs (max. 100), the time the incident was last modified, and the time the incident was created.
If you pass multiple filtering arguments, they will be concatenated using the AND condition. The OR condition is not supported.

Required Permissions

Required Permissions For API call:
Alerts And Incidents –> View
Builtin Roles with this permission includes: “Investigator”, “Responder”, “Privileged Investigator”, “Privileged Responder”, “Viewer”, and “Instance Admin”.

Base Command

xdr-get-incidents

Input

Argument Name Description Required
lte_creation_time A date in the format 2019-12-31T23:59:00 in UTC. Only incidents that were created on or before the specified date/time will be retrieved. Optional
gte_creation_time A date in the format 2019-12-31T23:59:00 in UTC. Only incidents that were created on or after the specified date/time will be retrieved. Optional
lte_modification_time Filters returned incidents that were created on or before the specified date/time, in the format 2019-12-31T23:59:00. Optional
gte_modification_time Filters returned incidents that were modified on or after the specified date/time, in the format 2019-12-31T23:59:00. Optional
incident_id_list An array or CSV string of incident IDs. Optional
since_creation_time Filters returned incidents that were created on or after the specified date/time range, for example, 1 month, 2 days, 1 hour, and so on. Optional
since_modification_time Filters returned incidents that were modified on or after the specified date/time range, for example, 1 month, 2 days, 1 hour, and so on. Optional
sort_by_modification_time Sorts returned incidents by the date/time that the incident was last modified (“asc” - ascending, “desc” - descending). Possible values are: asc, desc. Optional
sort_by_creation_time Sorts returned incidents by the date/time that the incident was created (“asc” - ascending, “desc” - descending). Possible values are: asc, desc. Optional
page Page number (for pagination). The default is 0 (the first page). Default is 0. Optional
limit Maximum number of incidents to return per page. The default and maximum is 100. Default is 100. Optional
status Filters only incidents in the specified status. The options are: new, under_investigation, resolved_known_issue, resolved_false_positive, resolved_true_positive resolved_security_testing, resolved_other, resolved_auto, resolved_auto_resolve. Optional
starred Whether the incident is starred (Boolean value: true or false). Possible values are: true, false. Optional
starred_incidents_fetch_window Starred fetch window timestamp (<number> <time unit>, e.g., 12 hours, 7 days). Default is 3 days. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.Incident.incident_id String Unique ID assigned to each returned incident.
PaloAltoNetworksXDR.Incident.manual_severity String Incident severity assigned by the user. This does not affect the calculated severity. Can be “low”, “medium”, “high”
PaloAltoNetworksXDR.Incident.manual_description String Incident description provided by the user.
PaloAltoNetworksXDR.Incident.assigned_user_mail String Email address of the assigned user.
PaloAltoNetworksXDR.Incident.high_severity_alert_count String Number of alerts with the severity HIGH.
PaloAltoNetworksXDR.Incident.host_count number Number of hosts involved in the incident.
PaloAltoNetworksXDR.Incident.xdr_url String A link to the incident view on Cortex XDR.
PaloAltoNetworksXDR.Incident.assigned_user_pretty_name String Full name of the user assigned to the incident.
PaloAltoNetworksXDR.Incident.alert_count number Total number of alerts in the incident.
PaloAltoNetworksXDR.Incident.med_severity_alert_count number Number of alerts with the severity MEDIUM.
PaloAltoNetworksXDR.Incident.user_count number Number of users involved in the incident.
PaloAltoNetworksXDR.Incident.severity String Calculated severity of the incident. Valid values are: “low”,”medium”,”high”
PaloAltoNetworksXDR.Incident.low_severity_alert_count String Number of alerts with the severity LOW.
PaloAltoNetworksXDR.Incident.status String Current status of the incident. Valid values are: “new”,”under_investigation”,”resolved_known_issue”,”resolved_duplicate”,”resolved_false_positive”,”resolved_true_positive”,”resolved_security_testing” or “resolved_other”.
PaloAltoNetworksXDR.Incident.description String Dynamic calculated description of the incident.
PaloAltoNetworksXDR.Incident.resolve_comment String Comments entered by the user when the incident was resolved.
PaloAltoNetworksXDR.Incident.notes String Comments entered by the user regarding the incident.
PaloAltoNetworksXDR.Incident.creation_time date Date and time the incident was created on Cortex XDR.
PaloAltoNetworksXDR.Incident.detection_time date Date and time that the first alert occurred in the incident.
PaloAltoNetworksXDR.Incident.modification_time date Date and time that the incident was last modified.
Command Example

!xdr-get-incidents gte_creation_time=2010-10-10T00:00:00 limit=3 sort_by_creation_time=desc

Context Example
{
    "PaloAltoNetworksXDR.Incident": [
        {
            "host_count": 1, 
            "incident_id": "4", 
            "manual_severity": "medium", 
            "description": "5 'This alert from content  TestXDRPlaybook' alerts detected by Checkpoint - SandBlast  ", 
            "severity": "medium", 
            "modification_time": 1579290004178, 
            "assigned_user_pretty_name": null, 
            "notes": null, 
            "creation_time": 1577276587937, 
            "alert_count": 5, 
            "med_severity_alert_count": 1, 
            "detection_time": null, 
            "assigned_user_mail": null, 
            "resolve_comment": "This issue was solved in Incident number 192304", 
            "status": "new", 
            "user_count": 1, 
            "xdr_url": "https://some.xdr.url.com/incident-view/4", 
            "starred": false, 
            "low_severity_alert_count": 0, 
            "high_severity_alert_count": 4, 
            "manual_description": null
        }, 
        {
            "host_count": 1, 
            "incident_id": "3", 
            "manual_severity": "medium", 
            "description": "'test 1' generated by Virus Total - Firewall", 
            "severity": "medium", 
            "modification_time": 1579237974014, 
            "assigned_user_pretty_name": "woo@test.com", 
            "notes": null, 
            "creation_time": 1576100096594, 
            "alert_count": 1, 
            "med_severity_alert_count": 0, 
            "detection_time": null, 
            "assigned_user_mail": "woo@test.com", 
            "resolve_comment": null, 
            "status": "new", 
            "user_count": 1, 
            "xdr_url": "https://some.xdr.url.com/incident-view/3", 
            "starred": false, 
            "low_severity_alert_count": 0, 
            "high_severity_alert_count": 1, 
            "manual_description": null
        }, 
        {
            "host_count": 1, 
            "incident_id": "2", 
            "manual_severity": "high", 
            "description": "'Alert Name Example 333' along with 1 other alert generated by Virus Total - VPN & Firewall-3 and Checkpoint - SandBlast", 
            "severity": "high", 
            "modification_time": 1579288790259, 
            "assigned_user_pretty_name": null, 
            "notes": null, 
            "creation_time": 1576062816474, 
            "alert_count": 2, 
            "med_severity_alert_count": 0, 
            "detection_time": null, 
            "assigned_user_mail": null, 
            "resolve_comment": null, 
            "status": "under_investigation", 
            "user_count": 1, 
            "xdr_url": "https://some.xdr.url.com/incident-view/2", 
            "starred": false, 
            "low_severity_alert_count": 0, 
            "high_severity_alert_count": 2, 
            "manual_description": null
        }
    ]
}
Human Readable Output

Incidents

alert_count assigned_user_mail assigned_user_pretty_name creation_time description detection_time high_severity_alert_count host_count incident_id low_severity_alert_count manual_description manual_severity med_severity_alert_count modification_time notes resolve_comment severity starred status user_count xdr_url
5     1577276587937 5 ‘This alert from content TestXDRPlaybook’ alerts detected by Checkpoint - SandBlast   4 1 4 0   medium 1 1579290004178   This issue was solved in Incident number 192304 medium false new 1 https://some.xdr.url.com/incident-view/4
1 woo@test.com woo@test.com 1576100096594 ‘test 1’ generated by Virus Total - Firewall   1 1 3 0   medium 0 1579237974014     medium false new 1 https://some.xdr.url.com/incident-view/3
2     1576062816474 ‘Alert Name Example 333’ along with 1 other alert generated by Virus Total - VPN & Firewall-3 and Checkpoint - SandBlast   2 1 2 0   high 0 1579288790259     high false under_investigation 1 https://some.xdr.url.com/incident-view/2

xdr-get-incident-extra-data (Deprecated)


This command is deprecated. Returns additional data for the specified incident, for example, related alerts, file artifacts, network artifacts, and so on.

Required Permissions

Required Permissions For API call:
Alerts And Incidents –> View

Builtin Roles with this permission includes: “Investigator”, “Responder”, “Privileged Investigator”, “Privileged Responder”, “Viewer”, and “Instance Admin”.

Base Command

xdr-get-incident-extra-data

Input

Argument Name Description Required
incident_id The ID of the incident for which to get additional data. Required
alerts_limit Maximum number of alerts to return. Default is 1000. Optional
return_only_updated_incident Return data only if the incident was changed since the last time it was mirrored into Cortex XSOAR. This flag should be used only from within a Cortex XDR incident. Default is False. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.Incident.incident_id String Unique ID assigned to each returned incident.
PaloAltoNetworksXDR.Incident.creation_time Date Date and time the incident was created on Cortex XDR.
PaloAltoNetworksXDR.Incident.modification_time Date Date and time that the incident was last modified.
PaloAltoNetworksXDR.Incident.detection_time Date Date and time that the first alert occurred in the incident.
PaloAltoNetworksXDR.Incident.status String Current status of the incident. Valid values are:
“new”,”under_investigation”,”resolved_known_issue”,”resolved_duplicate”,”resolved_false_positive”,”resolved_true_positive”,”resolved_security_testing”,”resolved_other”    
PaloAltoNetworksXDR.Incident.severity String Calculated severity of the incident. Valid values are: “low”,”medium”,”high”
PaloAltoNetworksXDR.Incident.description String Dynamic calculated description of the incident.
PaloAltoNetworksXDR.Incident.assigned_user_mail String Email address of the assigned user.
PaloAltoNetworksXDR.Incident.assigned_user_pretty_name String Full name of the user assigned to the incident.
PaloAltoNetworksXDR.Incident.alert_count Number Total number of alerts in the incident.
PaloAltoNetworksXDR.Incident.low_severity_alert_count Number Number of alerts with the severity LOW.
PaloAltoNetworksXDR.Incident.med_severity_alert_count Number Number of alerts with the severity MEDIUM.
PaloAltoNetworksXDR.Incident.high_severity_alert_count Number Number of alerts with the severity HIGH.
PaloAltoNetworksXDR.Incident.user_count Number Number of users involved in the incident.
PaloAltoNetworksXDR.Incident.host_count Number Number of hosts involved in the incident
PaloAltoNetworksXDR.Incident.notes Unknown Comments entered by the user regarding the incident.
PaloAltoNetworksXDR.Incident.resolve_comment String Comments entered by the user when the incident was resolved.
PaloAltoNetworksXDR.Incident.manual_severity String Incident severity assigned by the user. This does not affect the calculated severity of low, medium, or high.
PaloAltoNetworksXDR.Incident.manual_description String Incident description provided by the user.
PaloAltoNetworksXDR.Incident.xdr_url String A link to the incident view on Cortex XDR.
PaloAltoNetworksXDR.Incident.starred Boolean Incident starred.
PaloAltoNetworksXDR.Incident.wildfire_hits.mitre_techniques_ids_and_names String Incident Mitre techniques IDs and names.
PaloAltoNetworksXDR.Incident.wildfire_hits.mitre_tactics_ids_and_names String Incident Mitre tactics ids and names.
PaloAltoNetworksXDR.Incident.alerts.alert_id String Unique ID for each alert.
PaloAltoNetworksXDR.Incident.alerts.detection_timestamp Date Date and time that the alert occurred.
PaloAltoNetworksXDR.Incident.alerts.source String Source of the alert. The product/vendor this alert came from.
PaloAltoNetworksXDR.Incident.alerts.severity String Severity of the alert.Valid values are: “low”,”medium”,”high”””
PaloAltoNetworksXDR.Incident.alerts.name String Calculated name of the alert.
PaloAltoNetworksXDR.Incident.alerts.category String Category of the alert, for example, Spyware Detected via Anti-Spyware profile.
PaloAltoNetworksXDR.Incident.alerts.description String Textual description of the alert.
PaloAltoNetworksXDR.Incident.alerts.host_ip_list Unknown Host IP involved in the alert.
PaloAltoNetworksXDR.Incident.alerts.host_name String Host name involved in the alert.
PaloAltoNetworksXDR.Incident.alerts.user_name String User name involved with the alert.
PaloAltoNetworksXDR.Incident.alerts.event_type String Event type. Valid values are: “Process Execution”,”Network Event”,”File Event”,”Registry Event”,”Injection Event”,”Load Image Event”,”Windows Event Log”
PaloAltoNetworksXDR.Incident.alerts.action String The action that triggered the alert. Valid values are: “REPORTED”, “BLOCKED”, “POST_DETECTED”, “SCANNED”, “DOWNLOAD”, “PROMPT_ALLOW”, “PROMPT_BLOCK”, “DETECTED”, “BLOCKED_1”, “BLOCKED_2”, “BLOCKED_3”, “BLOCKED_5”, “BLOCKED_6”, “BLOCKED_7”, “BLOCKED_8”, “BLOCKED_9”, “BLOCKED_10”, “BLOCKED_11”, “BLOCKED_13”, “BLOCKED_14”, “BLOCKED_15”, “BLOCKED_16”, “BLOCKED_17”, “BLOCKED_24”, “BLOCKED_25”, “DETECTED_0”, “DETECTED_4”, “DETECTED_18”, “DETECTED_19”, “DETECTED_20”, “DETECTED_21”, “DETECTED_22”, “DETECTED_23”
PaloAltoNetworksXDR.Incident.alerts.action_pretty String The action that triggered the alert. Valid values are: “Detected (Reported)” “Prevented (Blocked)” “Detected (Post Detected)” “Detected (Scanned)” “Detected (Download)” “Detected (Prompt Allow)” “Prevented (Prompt Block)” “Detected” “Prevented (Denied The Session)” “Prevented (Dropped The Session)” “Prevented (Dropped The Session And Sent a TCP Reset)” “Prevented (Blocked The URL)” “Prevented (Blocked The IP)” “Prevented (Dropped The Packet)” “Prevented (Dropped All Packets)” “Prevented (Terminated The Session And Sent a TCP Reset To Both Sides Of The Connection)” “Prevented (Terminated The Session And Sent a TCP Reset To The Client)” “Prevented (Terminated The Session And Sent a TCP Reset To The Server)” “Prevented (Continue)” “Prevented (Block-Override)” “Prevented (Override-Lockout)” “Prevented (Override)” “Prevented (Random-Drop)” “Prevented (Silently Dropped The Session With An ICMP Unreachable Message To The Host Or Application)” “Prevented (Block)” “Detected (Allowed The Session)” “Detected (Raised An Alert)” “Detected (Syncookie Sent)” “Detected (Forward)” “Detected (Wildfire Upload Success)” “Detected (Wildfire Upload Failure)” “Detected (Wildfire Upload Skip)” “Detected (Sinkhole)”
PaloAltoNetworksXDR.Incident.alerts.actor_process_image_name String Image name.
PaloAltoNetworksXDR.Incident.alerts.actor_process_command_line String Command line.
PaloAltoNetworksXDR.Incident.alerts.actor_process_signature_status String Signature status. Valid values are: “Signed” “Invalid Signature” “Unsigned” “Revoked” “Signature Fail” “N/A” “Weak Hash”.
PaloAltoNetworksXDR.Incident.alerts.actor_process_signature_vendor String Signature vendor name.
PaloAltoNetworksXDR.Incident.alerts.causality_actor_process_image_name String Image name.
PaloAltoNetworksXDR.Incident.alerts.causality_actor_process_command_line String Command line.
PaloAltoNetworksXDR.Incident.alerts.causality_actor_process_signature_status String Signature status. Valid values are: “Signed” “Invalid Signature” “Unsigned” “Revoked” “Signature Fail” “N/A” “Weak Hash”
PaloAltoNetworksXDR.Incident.alerts.causality_actor_process_signature_vendor String Signature vendor.
PaloAltoNetworksXDR.Incident.alerts.causality_actor_causality_id Unknown Causality ID.
PaloAltoNetworksXDR.Incident.alerts.action_process_image_name String Image name.
PaloAltoNetworksXDR.Incident.alerts.action_process_image_command_line String Command line.
PaloAltoNetworksXDR.Incident.alerts.action_process_image_sha256 String Image SHA256.
PaloAltoNetworksXDR.Incident.alerts.action_process_signature_status String Signature status. Valid values are: “Signed” “Invalid Signature” “Unsigned” “Revoked” “Signature Fail” “N/A” “Weak Hash”
PaloAltoNetworksXDR.Incident.alerts.action_process_signature_vendor String Signature vendor name.
PaloAltoNetworksXDR.Incident.alerts.action_file_path String File path.
PaloAltoNetworksXDR.Incident.alerts.action_file_md5 String File MD5.
PaloAltoNetworksXDR.Incident.alerts.action_file_sha256 String File SHA256.
PaloAltoNetworksXDR.Incident.alerts.action_registry_data String Registry data.
PaloAltoNetworksXDR.Incident.alerts.action_registry_full_key String Registry full key.
PaloAltoNetworksXDR.Incident.alerts.action_local_ip String Local IP.
PaloAltoNetworksXDR.Incident.alerts.action_local_port Number Local port.
PaloAltoNetworksXDR.Incident.alerts.action_remote_ip String Remote IP.
PaloAltoNetworksXDR.Incident.alerts.action_remote_port Number Remote port.
PaloAltoNetworksXDR.Incident.alerts.action_external_hostname String External hostname.
PaloAltoNetworksXDR.Incident.alerts.fw_app_id Unknown Firewall app id.
PaloAltoNetworksXDR.Incident.alerts.is_whitelisted String Is the alert on allow list. Valid values are: “Yes” “No”
PaloAltoNetworksXDR.Incident.alerts.starred Boolean Alert starred.
PaloAltoNetworksXDR.Incident.network_artifacts.type String Network artifact type.
PaloAltoNetworksXDR.Incident.network_artifacts.network_remote_port number The remote port related to the artifact.
PaloAltoNetworksXDR.Incident.network_artifacts.alert_count number Number of alerts related to the artifact.
PaloAltoNetworksXDR.Incident.network_artifacts.network_remote_ip String The remote IP related to the artifact.
PaloAltoNetworksXDR.Incident.network_artifacts.is_manual boolean Whether the artifact was created by the user (manually).
PaloAltoNetworksXDR.Incident.network_artifacts.network_domain String The domain related to the artifact.
PaloAltoNetworksXDR.Incident.network_artifacts.type String The artifact type. Valid values are: “META”, “GID”, “CID”, “HASH”, “IP”, “DOMAIN”, “REGISTRY”, “HOSTNAME”
PaloAltoNetworksXDR.Incident.network_artifacts.network_country String The country related to the artifact.
PaloAltoNetworksXDR.Incident.file_artifacts.file_signature_status String Digital signature status of the file. Valid values are: “SIGNATURE_UNAVAILABLE” “SIGNATURE_SIGNED” “SIGNATURE_INVALID” “SIGNATURE_UNSIGNED” “SIGNATURE_WEAK_HASH”
PaloAltoNetworksXDR.Incident.file_artifacts.is_process boolean Whether the file artifact is related to a process execution.
PaloAltoNetworksXDR.Incident.file_artifacts.file_name String Name of the file.
PaloAltoNetworksXDR.Incident.file_artifacts.file_wildfire_verdict String The file verdict, calculated by Wildfire. Valid values are: “BENIGN” “MALWARE” “GRAYWARE” “PHISHING” “UNKNOWN”.
PaloAltoNetworksXDR.Incident.file_artifacts.alert_count number Number of alerts related to the artifact.
PaloAltoNetworksXDR.Incident.file_artifacts.is_malicious boolean Whether the artifact is malicious, as decided by the Wildfire verdict.
PaloAltoNetworksXDR.Incident.file_artifacts.is_manual boolean Whether the artifact was created by the user (manually).
PaloAltoNetworksXDR.Incident.file_artifacts.type String The artifact type. Valid values are: “META” “GID” “CID” “HASH” “IP” “DOMAIN” “REGISTRY” “HOSTNAME”
PaloAltoNetworksXDR.Incident.file_artifacts.file_sha256 String SHA256 hash of the file.
PaloAltoNetworksXDR.Incident.file_artifacts.file_signature_vendor_name String File signature vendor name.
Account.Username String The username in the relevant system.
Endpoint.Hostname String The hostname that is mapped to this endpoint.
Endpoint.ID String The agent ID of the endpoint.
File.Path String The path where the file is located.
File.MD5 String The MD5 hash of the file.
File.SHA256 String The SHA256 hash of the file.
File.Name String The full file name (including file extension).
Process.Name String The name of the process.
Process.MD5 String The MD5 hash of the process.
Process.SHA256 String The SHA256 hash of the process.
Process.PID String The PID of the process.
Process.Path String The file system path to the binary file.
Process.Start Time String The timestamp of the process start time.
Process.CommandLine String The full command line (including arguments).
Process.is_malicious boolean Whether the artifact is malicious, as decided by the Wildfire verdict.
IP.Address String IP address.
IP.Geo.Country String The country in which the IP address is located.
Domain.Name String The domain name, for example: “google.com”.
Human Readable Output

Incident 4

alert_count assigned_user_mail assigned_user_pretty_name creation_time description detection_time high_severity_alert_count host_count incident_id low_severity_alert_count manual_description manual_severity med_severity_alert_count modification_time notes resolve_comment severity starred status user_count xdr_url
5     1577276587937 5 ‘This alert from content TestXDRPlaybook’ alerts detected by Checkpoint - SandBlast   4 1 4 0   medium 1 1579290004178   This issue was solved in Incident number 192304 medium false new 1 https://some.xdr.url.com/incident-view/4

Alerts

action action_external_hostname action_file_md5 action_file_path action_file_sha256 action_local_ip action_local_port action_pretty action_process_image_command_line action_process_image_name action_process_image_sha256 action_process_signature_status action_process_signature_vendor action_registry_data action_registry_full_key action_remote_ip action_remote_port actor_process_command_line actor_process_image_name actor_process_signature_status actor_process_signature_vendor alert_id category causality_actor_causality_id causality_actor_process_command_line causality_actor_process_image_name causality_actor_process_signature_status causality_actor_process_signature_vendor description detection_timestamp event_type fw_app_id host_ip_list host_name is_whitelisted name severity source starred user_name
VALUE_NA,
N/A
        196.168.0.1 7000 VALUE_NA,
N/A
      N/A N/A     2.2.2.2 8000     N/A N/A 6         N/A N/A Test - alert generated by Test XDR Playbook 1577276586921 Network Event       No Test - alert generated by Test XDR Playbook medium Cisco - Sandblast false  
VALUE_NA,
N/A
        196.168.0.111 2000 VALUE_NA,
N/A
      N/A N/A     2.2.2.2 6000     N/A N/A 7         N/A N/A This alert from content TestXDRPlaybook description 1577776701589 Network Event       No This alert from content TestXDRPlaybook high Checkpoint - SandBlast false  
VALUE_NA,
N/A
        196.168.0.111 2000 VALUE_NA,
N/A
      N/A N/A     2.2.2.2 6000     N/A N/A 8         N/A N/A This alert from content TestXDRPlaybook description 1577958479843 Network Event       No This alert from content TestXDRPlaybook high Checkpoint - SandBlast false  
VALUE_NA,
N/A
        196.168.0.111 2000 VALUE_NA,
N/A
      N/A N/A     2.2.2.2 6000     N/A N/A 9         N/A N/A This alert from content TestXDRPlaybook description 1578123895414 Network Event       No This alert from content TestXDRPlaybook high Checkpoint - SandBlast false  
VALUE_NA,
N/A
        196.168.0.111 2000 VALUE_NA,
N/A
      N/A N/A     2.2.2.2 6000     N/A N/A 10         N/A N/A This alert from content TestXDRPlaybook description 1578927443615 Network Event       No This alert from content TestXDRPlaybook high Checkpoint - SandBlast false  

Network Artifacts

alert_count is_manual network_country network_domain network_remote_ip network_remote_port type
5 false     2.2.2.2 8000 IP

File Artifacts

No entries.

xdr-update-incident (Deprecated)


This command is deprecated please use xdr-case-update instead. Updates one or more fields of a specified incident. Missing fields will be ignored. To remove the assignment for an incident, pass a null value in the assignee email argument.

Required Permissions

Required Permissions For API call:
Alerts And Incidents –> View / Edit

Builtin Roles with this permission includes: “Investigator”, “Privileged Investigator”, “Privileged Responder”, and “Instance Admin”.

Base Command

xdr-update-incident

Input

Argument Name Description Required
incident_id XDR incident ID. You can get the incident ID from the output of the ‘xdr-get-incidents’ command or the ‘xdr-get-incident-extra-details’ command. Required
manual_severity Severity to assign to the incident (LOW, MEDIUM, or HIGH). Possible values are: HIGH, MEDIUM, LOW. Optional
assigned_user_mail Email address of the user to assign to the incident. Optional
assigned_user_pretty_name Full name of the user assigned to the incident. To supply a new value in this field, you must also provide a value for the ‘assigned_user_mail’ argument. Optional
status Status of the incident. Valid values are: NEW, UNDER_INVESTIGATION, RESOLVED_KNOWN_ISSUE, RESOLVED_DUPLICATE, RESOLVED_FALSE_POSITIVE, RESOLVED_TRUE_POSITIVE, RESOLVED_SECURITY_TESTING, RESOLVED_OTHER. Possible values are: NEW, UNDER_INVESTIGATION, RESOLVED_KNOWN_ISSUE, RESOLVED_DUPLICATE, RESOLVED_FALSE_POSITIVE, RESOLVED_TRUE_POSITIVE, RESOLVED_SECURITY_TESTING, RESOLVED_OTHER. Optional
resolve_comment Comment explaining why the incident was resolved. This should be set when the incident is resolved. Optional
unassign_user If true, will remove all assigned users from the incident. Possible values are: true. Optional
Command Example

!xdr-update-incident incident_id=4

Context Output

There is no context output for this command.

Human Readable Output

Incident 4 has been updated

xdr-insert-parsed-alert (Deprecated)


This command is deprecated. Uploads an alert from external alert sources in Cortex XDR format. Cortex XDR displays alerts that are parsed
successfully in related incidents and views. You can send 600 alerts per minute. Each request can contain a
maximum of 60 alerts.

Required Permissions

Required Permissions For API call:
External Alerts Mapping–> View

Builtin Roles with this permission includes: “Instance Admin”.

Base Command

xdr-insert-parsed-alert

Input

Argument Name Description Required
product String value that defines the product. Required
vendor String value that defines the vendor. Required
local_ip String value for the source IP address. Optional
local_port Integer value for the source port. Required
remote_ip String value of the destination IP
address.
Required
remote_port Integer value for the destination
port.
Required
event_timestamp Integer value representing the time the alert occurred in milliseconds, or a string value in date format 2019-10-23T10:00:00. If not set, the event time will be defined as now. Optional
severity String value of alert severity. Valid values are:
Informational, Low, Medium or High. Possible values are: Informational, Low, Medium, High. Default is Medium.
Optional
alert_name String defining the alert name. Required
alert_description String defining the alert description. Optional

Context Output

There is no context output for this command.

xdr-insert-cef-alerts (Deprecated)


This command is deprecated. Upload alerts in CEF format from external alert sources. After you map CEF alert fields to Cortex XDR fields, Cortex XDR displays the alerts in related incidents and views. You can send 600 requests per minute. Each request can contain a maximum of 60 alerts.

Required Permissions

Required Permissions For API call:
External Alerts Mapping–> View

Builtin Roles with this permission includes: “Instance Admin”.

Base Command

xdr-insert-cef-alerts

Input

Argument Name Description Required
cef_alerts List of alerts in CEF format. Required

Context Output

There is no context output for this command.

xdr-endpoint-isolate


Isolates the specified endpoint.

Required Permissions

Required Permissions For API call:
Endpoint Administrations –> View
Action Center –> View/ Edit
Action Center –> Isolate

Builtin Roles with this permission includes: “Privileged Responder” and “Instance Admin”.

Base Command

xdr-endpoint-isolate

Input

Argument Name Description Required
incident_id Allows linking the response action to the incident that triggered it. Optional
endpoint_id The endpoint ID (string) to isolate. You can retrieve the string from the xdr-get-endpoints command. Required
suppress_disconnected_endpoint_error Whether to suppress an error when trying to isolate a disconnected endpoint. When sets to false, an error will be returned. Possible values are: true, false. Default is false. Optional
interval_in_seconds Interval in seconds between each poll. Optional
timeout_in_seconds Polling timeout in seconds. Optional
action_id For polling use. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.Isolation.endpoint_id String The endpoint ID.

xdr-endpoint-unisolate


Reverses the isolation of an endpoint.

Required Permissions

Required Permissions For API call:
Endpoint Administrations –> View
Action Center –> View/ Edit
Action Center –> Isolate

Builtin Roles with this permission includes: “Privileged Responder” and “Instance Admin”.

Base Command

xdr-endpoint-unisolate

Input

Argument Name Description Required
incident_id Allows linking the response action to the incident that triggered it. Optional
endpoint_id The endpoint ID (string) for which to reverse the isolation. You can retrieve it from the xdr-get-endpoints command. Required
suppress_disconnected_endpoint_error Whether to suppress an error when trying to unisolate a disconnected endpoint. When sets to false, an error will be returned. Possible values are: true, false. Default is false. Optional
interval_in_seconds Interval in seconds between each poll. Optional
timeout_in_seconds Polling timeout in seconds. Optional
action_id For polling use. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.UnIsolation.endpoint_id String Isolates the specified endpoint.

xdr-get-endpoints


Gets a list of endpoints, according to the passed filters. If there are no filters, all endpoints are returned. Filtering by multiple fields will be concatenated using AND condition (OR is not supported). Maximum result set size is 100. Offset is the zero-based number of endpoint from the start of the result set (start by counting from 0).

Required Permissions

Required Permissions For API call:
Endpoint Administrations –> View

Builtin Roles with this permission includes: “Privileged Responder”, “Viewer” and “Instance Admin”.

Base Command

xdr-get-endpoints

Input

Argument Name Description Required
status A comma-separated list of endpoints statuses to filter. Valid values are: connected, disconnected, lost, uninstalled, windows, linux, macos, android, isolated, unisolated. Optional
endpoint_id_list A comma-separated list of endpoint IDs. Optional
dist_name A comma-separated list of distribution package names or installation package names.
Example: dist_name1,dist_name2.
Optional
ip_list A comma-separated list of private IP addresses.
Example: Example: 10.1.1.1,192.168.1.1.
Optional
public_ip_list A comma-separated list of public IP addresses that correlate to the last IPv4 address from which the Cortex XDR agent connected (know as Last Origin IP).
Example: 8.8.8.8,1.1.1.1.
Optional
group_name The group name to which the agent belongs.
Example: group_name1,group_name2.
Optional
platform The endpoint platform. Valid values are: “windows”, “linux”, “macos”, or “android”. . Possible values are: windows, linux, macos, android. Optional
alias_name A comma-separated list of alias names.
Examples: alias_name1,alias_name2.
Optional
isolate Specifies whether the endpoint was isolated or unisolated. Possible values are: isolated, unisolated. Optional
hostname Hostname
Example: hostname1,hostname2.
Optional
first_seen_gte All the agents that were first seen after {first_seen_gte}.
Supported values:
1579039377301 (time in milliseconds)
“3 days” (relative date)
“2019-10-21T23:45:00” (date).
Optional
first_seen_lte All the agents that were first seen before {first_seen_lte}.
Supported values:
1579039377301 (time in milliseconds)
“3 days” (relative date)
“2019-10-21T23:45:00” (date).
Optional
last_seen_gte All the agents that were last seen before {last_seen_gte}.
Supported values:
1579039377301 (time in milliseconds)
“3 days” (relative date)
“2019-10-21T23:45:00” (date).
Optional
last_seen_lte All the agents that were last seen before {last_seen_lte}.
Supported values:
1579039377301 (time in milliseconds)
“3 days” (relative date)
“2019-10-21T23:45:00” (date).
Optional
page Page number (for pagination). The default is 0 (the first page). Default is 0. Optional
limit Maximum number of endpoints to return per page. The default and maximum is 30. Default is 30. Optional
sort_by Specifies whether to sort endpoints by the first time or last time they were seen. Can be “first_seen” or “last_seen”. Possible values are: first_seen, last_seen. Optional
sort_order The order by which to sort results. Can be “asc” (ascending) or “desc” ( descending). Default set to asc. Possible values are: asc, desc. Default is asc. Optional
username The usernames to query for, accepts a single user, or comma-separated list of usernames. Optional
all_results Whether to return all endpoints. If true, will override the ‘limit’ and ‘page’ arguments. Possible values are: false, true. Default is false. Optional
use_hr_timestamps Whether to return timestamp values in human-readable format as opposed to Unix epoch timestamp format. Possible values are: false, true. Default is false. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.Endpoint.endpoint_id String The endpoint ID.
PaloAltoNetworksXDR.Endpoint.endpoint_name String The endpoint name.
PaloAltoNetworksXDR.Endpoint.endpoint_type String The endpoint type.
PaloAltoNetworksXDR.Endpoint.endpoint_status String The status of the endpoint.
PaloAltoNetworksXDR.Endpoint.os_type String The endpoint OS type.
PaloAltoNetworksXDR.Endpoint.ip Unknown A list of IP addresses.
PaloAltoNetworksXDR.Endpoint.users Unknown A list of users.
PaloAltoNetworksXDR.Endpoint.domain String The endpoint domain.
PaloAltoNetworksXDR.Endpoint.alias String The endpoint’s aliases.
PaloAltoNetworksXDR.Endpoint.first_seen Unknown First seen date/time in Epoch (milliseconds).
PaloAltoNetworksXDR.Endpoint.last_seen Date Last seen date/time in Epoch (milliseconds).
PaloAltoNetworksXDR.Endpoint.content_version String Content version.
PaloAltoNetworksXDR.Endpoint.installation_package String Installation package.
PaloAltoNetworksXDR.Endpoint.active_directory String Active directory.
PaloAltoNetworksXDR.Endpoint.install_date Date Install date in Epoch (milliseconds).
PaloAltoNetworksXDR.Endpoint.endpoint_version String Endpoint version.
PaloAltoNetworksXDR.Endpoint.is_isolated String Whether the endpoint is isolated.
PaloAltoNetworksXDR.Endpoint.group_name String The name of the group to which the endpoint belongs.
PaloAltoNetworksXDR.Endpoint.count String Number of endpoints returned.
Endpoint.Hostname String The hostname that is mapped to this endpoint.
Endpoint.ID String The unique ID within the tool retrieving the endpoint.
Endpoint.IPAddress String The IP address of the endpoint.
Endpoint.Domain String The domain of the endpoint.
Endpoint.OS String The endpoint’s operation system.
Account.Username String The username in the relevant system.
Account.Domain String The domain of the account.
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.
Command Example

!xdr-get-endpoints isolate="unisolated" first_seen_gte="3 month" page="0" limit="30" sort_order="asc"

Context Example
{
    "Endpoint": [
        {
            "Domain": "WORKGROUP",
            "Hostname": "aaaaa.compute.internal",
            "ID": "ea303670c76e4ad09600c8b346f7c804",
            "IPAddress": [
                "172.31.11.11"
            ],
            "OS": "Windows",
            "Status" : "Online",
            "IsIsolated" : "No",
            "Vendor": "Cortex XDR - IR"
        },
        {
            "Domain": "WORKGROUP",
            "Hostname": "EC2AMAZ-P7PPOI4",
            "ID": "f8a2f58846b542579c12090652e79f3d",
            "IPAddress": [
                "2.2.2.2"
            ],
            "OS": "Windows",
            "Status" : "Online",
            "IsIsolated" : "No",
            "Vendor": "Cortex XDR - IR"
        }
    ],
    "PaloAltoNetworksXDR.Endpoint": [
        {
            "domain": "", 
            "users": [
                "ec2-user"
            ], 
            "endpoint_name": "aaaaa.compute.internal", 
            "ip": [
                "172.31.11.11"
            ], 
            "install_date": 1575795969644, 
            "endpoint_version": "7.0.0.1915", 
            "group_name": null, 
            "installation_package": "linux", 
            "alias": "", 
            "active_directory": null, 
            "endpoint_status": "CONNECTED", 
            "os_type": "AGENT_OS_LINUX", 
            "endpoint_id": "ea303670c76e4ad09600c8b346f7c804", 
            "content_version": "111-17757", 
            "first_seen": 1575795969644, 
            "endpoint_type": "AGENT_TYPE_SERVER", 
            "is_isolated": "AGENT_UNISOLATED", 
            "last_seen": 1579290023629
        }, 
        {
            "domain": "WORKGROUP", 
            "users": [
                "Administrator"
            ], 
            "endpoint_name": "EC2AMAZ-P7PPOI4", 
            "ip": [
                "2.2.2.2"
            ], 
            "install_date": 1575796381739, 
            "endpoint_version": "7.0.0.27797", 
            "group_name": null, 
            "installation_package": "Windows Server 2016", 
            "alias": "", 
            "active_directory": null, 
            "endpoint_status": "CONNECTED", 
            "os_type": "AGENT_OS_WINDOWS", 
            "endpoint_id": "f8a2f58846b542579c12090652e79f3d", 
            "content_version": "111-17757", 
            "first_seen": 1575796381739, 
            "endpoint_type": "AGENT_TYPE_SERVER", 
            "is_isolated": "AGENT_UNISOLATED", 
            "last_seen": 1579289957412
        }
    ]
}
Human Readable Output

Endpoints

active_directory alias content_version domain endpoint_id endpoint_name endpoint_status endpoint_type endpoint_version first_seen group_name install_date installation_package ip is_isolated last_seen os_type users
    111-17757   ea303670c76e4ad09600c8b346f7c804 aaaaa.compute.internal CONNECTED AGENT_TYPE_SERVER 7.0.0.1915 1575795969644   1575795969644 linux 172.31.11.11 AGENT_UNISOLATED 1579290023629 AGENT_OS_LINUX ec2-user
    111-17757 WORKGROUP f8a2f58846b542579c12090652e79f3d EC2AMAZ-P7PPOI4 CONNECTED AGENT_TYPE_SERVER 7.0.0.27797 1575796381739   1575796381739 Windows Server 2016 2.2.2.2 AGENT_UNISOLATED 1579289957412 AGENT_OS_WINDOWS Administrator

xdr-get-distribution-versions


Gets a list of all the agent versions to use for creating a distribution list.

Required Permissions

Required Permissions For API call:
Endpoint Installations –> View

Builtin Roles with this permission includes: “Viewer” and “Instance Admin”.

Base Command

xdr-get-distribution-versions

Input

There are no input arguments for this command.

Context Output

Path Type Description
PaloAltoNetworksXDR.DistributionVersions.windows Unknown A list of Windows agent versions.
PaloAltoNetworksXDR.DistributionVersions.linux Unknown A list of Linux agent versions.
PaloAltoNetworksXDR.DistributionVersions.macos Unknown A list of Mac agent versions.
Command Example

##### Context Example

{
“PaloAltoNetworksXDR.DistributionVersions”: {
“windows”: [
“5.0.8.29673”,
“5.0.9.30963”,
“6.1.4.28751”,
“7.0.0.28644”
],
“macos”: [
“6.1.4.1681”,
“7.0.0.1914”
],
“linux”: [
“6.1.4.1680”,
“7.0.0.1916”
]
}
}


##### Human Readable Output

>### windows

>|versions|
>|---|
>| 5.0.8.29673 |
>| 5.0.9.30963 |
>| 6.1.4.28751 |
>| 7.0.0.28644 |
>
>
>### linux

>|versions|
>|---|
>| 6.1.4.1680 |
>| 7.0.0.1916 |
>
>
>### macos

>|versions|
>|---|
>| 6.1.4.1681 |
>| 7.0.0.1914 |

### xdr-create-distribution

***
Creates an installation package. This is an asynchronous call that returns the distribution ID. This does not mean that the creation succeeded. To confirm that the package has been created, check the status of the distribution by running the Get Distribution Status API.

##### Required Permissions

Required Permissions For API call:
`Endpoint Installations` --> `View/ Edit`

Builtin Roles with this permission includes: "Instance Admin".

#### Base Command

`xdr-create-distribution`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | A string representing the name of the installation package. | Required |
| platform | String, valid values are:<br/>• windows <br/>• linux<br/>• macos <br/>• android. Possible values are: windows, linux, macos, android. | Required |
| package_type | A string representing the type of package to create.<br/>standalone - An installation for a new agent<br/>upgrade - An upgrade of an agent from ESM. Possible values are: standalone, upgrade. | Required |
| agent_version | agent_version returned from xdr-get-distribution-versions. Not required for Android platform. | Required |
| description | Information about the package. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.Distribution.id | String | The installation package ID. |
| PaloAltoNetworksXDR.Distribution.name | String | The name of the installation package. |
| PaloAltoNetworksXDR.Distribution.platform | String | The installation OS. |
| PaloAltoNetworksXDR.Distribution.agent_version | String | Agent version. |
| PaloAltoNetworksXDR.Distribution.description | String | Information about the package. |

##### Command Example

```!xdr-create-distribution agent_version=6.1.4.1680 name="dist_1" package_type=standalone platform=linux description="some description"```

##### Context Example

{
“PaloAltoNetworksXDR.Distribution”: {
“description”: “some description”,
“package_type”: “standalone”,
“platform”: “linux”,
“agent_version”: “6.1.4.1680”,
“id”: “43aede7f846846fa92b50149663fbb25”,
“name”: “dist_1”
}
}


##### Human Readable Output

Distribution 43aede7f846846fa92b50149663fbb25 created successfully

### xdr-get-distribution-url

***
Gets the distribution URL for downloading the installation package.

##### Required Permissions

Required Permissions For API call:
`Endpoint Installations` --> `View`

Builtin Roles with this permission includes: "Viewer" and "Instance Admin".

#### Base Command

`xdr-get-distribution-url`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| distribution_id | The ID of the installation package.<br/>Copy the distribution_id from the "id" field on Endpoints &gt; Agent Installation page. | Required |
| package_type | The installation package type. Valid<br/>values are:<br/>• upgrade<br/>• sh - For Linux<br/>• rpm - For Linux<br/>• deb - For Linux<br/>• pkg - For Mac<br/>• x86 - For Windows<br/>• x64 - For Windows. Possible values are: upgrade, sh, rpm, deb, pkg, x86, x64. | Required |
| download_package | Supported only for package_type x64 or x86. Whether to download the installation package file. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.Distribution.id | String | Distribution ID. |
| PaloAltoNetworksXDR.Distribution.url | String | URL for downloading the installation package. |

##### Command Example

```!xdr-get-distribution-url distribution_id=2c74c11b63074653aa01d575a82bf52a package_type=sh```

### xdr-get-create-distribution-status

***
Gets the status of the installation package.

##### Required Permissions

Required Permissions For API call:
`Endpoint Installations` --> `View`

Builtin Roles with this permission includes: "Viewer" and "Instance Admin".

#### Base Command

`xdr-get-create-distribution-status`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| distribution_ids | A comma-separated list of distribution IDs to get the status for. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.Distribution.id | String | Distribution ID. |
| PaloAltoNetworksXDR.Distribution.status | String | The status of installation package. |

##### Command Example

```!xdr-get-create-distribution-status distribution_ids=2c74c11b63074653aa01d575a82bf52a```

### xdr-get-audit-management-logs

***
Gets management logs. You can filter by multiple fields, which will be concatenated using the AND condition (OR is not supported). Maximum result set size is 100. Offset is the zero-based number of management logs from the start of the result set (start by counting from 0).

##### Required Permissions

Required Permissions For API call:
`Auditing` --> `View`

Builtin Roles with this permission includes: "Viewer" and "Instance Admin".

##### Context Example

{
“PaloAltoNetworksXDR.Distribution”: [
{
“status”: “Completed”,
“id”: “2c74c11b63074653aa01d575a82bf52a”
}
]
}


##### Human Readable Output

>### Distribution Status

>|id|status|
>|---|---|
>| 2c74c11b63074653aa01d575a82bf52a | Completed |

#### Base Command

`xdr-get-audit-management-logs`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| email | User’s email address. | Optional |
| type | The audit log type. Possible values are: LIVE_TERMINAL, RULES, AUTH, RESPONSE, INCIDENT_MANAGEMENT, ENDPOINT_MANAGEMENT, ALERT_WHITELIST, PUBLIC_API, DISTRIBUTIONS, STARRED_INCIDENTS, POLICY_PROFILES, DEVICE_CONTROL_PROFILE, HOST_FIREWALL_PROFILE, POLICY_RULES, PROTECTION_POLICY, DEVICE_CONTROL_TEMP_EXCEPTIONS, DEVICE_CONTROL_GLOBAL_EXCEPTIONS, GLOBAL_EXCEPTIONS, MSSP, REPORTING, DASHBOARD, BROKER_VM. | Optional |
| sub_type | The audit log subtype. | Optional |
| result | Result type. Possible values are: SUCCESS, FAIL, PARTIAL. | Optional |
| timestamp_gte | Return logs for which the timestamp is after 'log_time_after'.<br/>Supported values:<br/>1579039377301 (time in milliseconds)<br/>"3 days" (relative date)<br/>"2019-10-21T23:45:00" (date). | Optional |
| timestamp_lte | Return logs for which the timestamp is before the 'log_time_after'.<br/>Supported values:<br/>1579039377301 (time in milliseconds)<br/>"3 days" (relative date)<br/>"2019-10-21T23:45:00" (date). | Optional |
| page | Page number (for pagination). The default is 0 (the first page). Default is 0. | Optional |
| limit | Maximum number of audit logs to return per page. The default and maximum is 30. Default is 30. | Optional |
| sort_by | Specifies the field by which to sort the results. By default the sort is defined as creation-time and DESC. Can be "type", "sub_type", "result", or "timestamp". Possible values are: type, sub_type, result, timestamp. | Optional |
| sort_order | The sort order. Can be "asc" (ascending) or "desc" (descending). Default set to "desc". Possible values are: asc, desc. Default is desc. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_ID | Number | Audit log ID. |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_OWNER_NAME | String | Audit owner name. |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_OWNER_EMAIL | String | Audit owner email address. |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_ASSET_JSON | String | Asset JSON. |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_ASSET_NAMES | String | Audit asset names. |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_HOSTNAME | String | Host name. |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_RESULT | String | Audit result. |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_REASON | String | Audit reason. |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_DESCRIPTION | String | Description of the audit. |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_ENTITY | String | Audit entity (e.g., AUTH, DISTRIBUTIONS). |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_ENTITY_SUBTYPE | String | Entity subtype (e.g., Login, Create). |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_CASE_ID | Number | Audit case ID. |
| PaloAltoNetworksXDR.AuditManagementLogs.AUDIT_INSERT_TIME | Date | Log's insert time. |

### xdr-get-audit-agent-reports

***
Gets agent event reports. You can filter by multiple fields, which will be concatenated using the AND condition (OR is not supported). Maximum result set size is 100. Offset is the zero-based number of reports from the start of the result set (start by counting from 0).

##### Required Permissions

Required Permissions For API call:
`Auditing` --> `View`

Builtin Roles with this permission includes: "Viewer" and "Instance Admin".

#### Base Command

`xdr-get-audit-agent-reports`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| endpoint_ids | A comma-separated list of endpoint IDs. | Optional |
| endpoint_names | A comma-separated list of endpoint names. | Optional |
| type | The report type. Can be "Installation", "Policy", "Action", "Agent Service", "Agent Modules", or "Agent Status". Possible values are: Installation, Policy, Action, Agent Service, Agent Modules, Agent Status. | Optional |
| sub_type | The report subtype. Possible values are: Install, Uninstall, Upgrade, Local Configuration, Content Update, Policy Update, Process Exception, Hash Exception, Scan, File Retrieval, File Scan, Terminate Process, Isolate, Cancel Isolation, Payload Execution, Quarantine, Restore, Stop, Start, Module Initialization, Local Analysis Model, Local Analysis Feature Extraction, Fully Protected, OS Incompatible, Software Incompatible, Kernel Driver Initialization, Kernel Extension Initialization, Proxy Communication, Quota Exceeded, Minimal Content, Reboot Required, Missing Disc Access. | Optional |
| result | The result type. Can be "Success" or "Fail". If not passed, returns all event reports. Possible values are: Success, Fail. | Optional |
| timestamp_gte | Return logs that their timestamp is greater than 'log_time_after'.<br/>Supported values:<br/>1579039377301 (time in milliseconds)<br/>"3 days" (relative date)<br/>"2019-10-21T23:45:00" (date). | Optional |
| timestamp_lte | Return logs for which the timestamp is before the 'timestamp_lte'.<br/><br/>Supported values:<br/>1579039377301 (time in milliseconds)<br/>"3 days" (relative date)<br/>"2019-10-21T23:45:00" (date). | Optional |
| page | Page number (for pagination). The default is 0 (the first page). Default is 0. | Optional |
| limit | The maximum number of reports to return. Default and maximum is 30. Default is 30. | Optional |
| sort_by | The field by which to sort results. Can be "type", "category", "trapsversion", "timestamp", or "domain"). Possible values are: type, category, trapsversion, timestamp, domain. | Optional |
| sort_order | The sort order. Can be "asc" (ascending) or "desc" (descending). Default is "asc". Possible values are: asc, desc. Default is asc. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.AuditAgentReports.ENDPOINTID | String | Endpoint ID. |
| PaloAltoNetworksXDR.AuditAgentReports.ENDPOINTNAME | String | Endpoint name. |
| PaloAltoNetworksXDR.AuditAgentReports.DOMAIN | String | Agent domain. |
| PaloAltoNetworksXDR.AuditAgentReports.TRAPSVERSION | String | Traps version. |
| PaloAltoNetworksXDR.AuditAgentReports.RECEIVEDTIME | Date | Received time in Epoch time. |
| PaloAltoNetworksXDR.AuditAgentReports.TIMESTAMP | Date | Timestamp in Epoch time. |
| PaloAltoNetworksXDR.AuditAgentReports.CATEGORY | String | Report category (e.g., Audit). |
| PaloAltoNetworksXDR.AuditAgentReports.TYPE | String | Report type (e.g., Action, Policy). |
| PaloAltoNetworksXDR.AuditAgentReports.SUBTYPE | String | Report subtype (e.g., Fully Protected,Policy Update,Cancel Isolation\). |
| PaloAltoNetworksXDR.AuditAgentReports.RESULT | String | Report result. |
| PaloAltoNetworksXDR.AuditAgentReports.REASON | String | Report reason. |
| PaloAltoNetworksXDR.AuditAgentReports.DESCRIPTION | String | Agent report description. |
| Endpoint.ID | String | The unique ID within the tool retrieving the endpoint. |
| Endpoint.Hostname | String | The hostname that is mapped to this endpoint. |
| Endpoint.Domain | String | The domain of the endpoint. |

### xdr-blocklist-files

***
Block lists requested files which have not already been block listed or added to allow lists.

##### Required Permissions

Required Permissions For API call:
`Action Center` --> `View/ Edit`
`Action Center` --> `Allow List/Block List`

Builtin Roles with this permission includes: "Responder", "Privileged Responder" and "Instance Admin".

#### Base Command

`xdr-blocklist-files`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Links the response action to the triggered incident. | Optional |
| hash_list | String that represents a list of hashed files you want to block list. Must be a valid SHA256 hash. | Required |
| comment | String that represents additional information regarding the action. | Optional |
| detailed_response | Choose either regular response or detailed response. Default value = false, regular response. Possible values are: true, false. Default is false. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.blocklist.added_hashes | Number | Number of file hashes added to block list. |
| PaloAltoNetworksXDR.blocklist.excluded_hashes | Number | Number of file hashes excluded from block list. |

### xdr-allowlist-files

***
Adds requested files to allow list if they are not already on block list or allow list.

##### Required Permissions

Required Permissions For API call:
`Action Center` --> `View/ Edit`
`Action Center` --> `Allow List/Block List`

Builtin Roles with this permission includes: "Responder", "Privileged Responder" and "Instance Admin".

#### Base Command

`xdr-allowlist-files`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Links the response action to the triggered incident. | Optional |
| hash_list | String that represents a list of hashed files you want to add to allow lists. Must be a valid SHA256 hash. | Required |
| comment | String that represents additional information regarding the action. | Optional |
| detailed_response | Choose either regular response or detailed response. Default value = false, regular response. Possible values are: true, false. Default is false. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.allowlist.added_hashes | Number | Number of added file hashes to allowlist. |
| PaloAltoNetworksXDR.allowlist.excluded_hashes | Number | Number of excluded file hashes from allowlist. |

### xdr-file-quarantine

***
Quarantines a file on selected endpoints. You can select up to 1000 endpoints.

##### Required Permissions

Required Permissions For API call:
`Action Center` --> `View/ Edit`
`Action Center` --> `Quarantine`

Builtin Roles with this permission includes: "Responder", "Privileged Responder" and "Instance Admin".

#### Base Command

`xdr-file-quarantine`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Allows linking the response action to the incident that triggered it. | Optional |
| endpoint_id_list | List of endpoint IDs. | Required |
| file_path | String that represents the path of the file you want to quarantine. | Required |
| file_hash | String that represents the file's hash. Must be a valid SHA256 hash. | Required |
| interval_in_seconds | Interval in seconds between each poll. | Optional |
| timeout_in_seconds | Polling timeout in seconds. | Optional |
| action_id | The action IDs for polling use. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.bucket | String | The bucket in which the error occurred. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_name | String | The name of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_path | String | The path of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_size | Number | The size of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.missing_files | Unknown | The missing files that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorData | String | The error reason data. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminated_by | String | The instance ID which terminated the action and caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorDescription | String | The error reason description. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminate_result | Unknown | The error reason terminate result. |

### xdr-get-quarantine-status

***
Retrieves the quarantine status for a selected file.

##### Required Permissions

Required Permissions For API call:
`Action Center` --> `View/ Edit`
`Action Center` --> `Quarantine`

Builtin Roles with this permission includes: "Responder", "Privileged Responder" and "Instance Admin".

#### Base Command

`xdr-get-quarantine-status`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| endpoint_id | String the represents the endpoint ID. | Required |
| file_hash | String that represents the file hash. Must be a valid SHA256 hash. | Required |
| file_path | String that represents the file path. | Required |

#### Context Output

There is no context output for this command.

### xdr-file-restore

***
Restores a quarantined file on requested endpoints.

##### Required Permissions

Required Permissions For API call:
`Action Center` --> `View/ Edit`
`Action Center` --> `Quarantine`

Builtin Roles with this permission includes: "Responder", "Privileged Responder" and "Instance Admin".

#### Base Command

`xdr-file-restore`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Allows linking the response action to the incident that triggered it. | Optional |
| file_hash | The hash code of the file. Must be a valid SHA256 hash. | Required |
| endpoint_id | String that represents the endpoint ID. If you do not enter a specific endpoint ID, the request will run restore on all endpoints that relate to the quarantined file you defined. | Optional |
| interval_in_seconds | Interval in seconds between each poll. | Optional |
| timeout_in_seconds | Polling timeout in seconds. | Optional |
| action_id | The action IDs for polling use. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.bucket | String | The bucket in which the error occurred. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_name | String | The name of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_path | String | The path of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_size | Number | The size of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.missing_files | Unknown | The missing files that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorData | String | The error reason data. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminated_by | String | The instance ID which terminated the action and caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorDescription | String | The error reason description. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminate_result | Unknown | The error reason terminate result. |

### xdr-endpoint-scan-execute

***
Runs a scan on a selected endpoint. To scan all endpoints, run this command with argument all=true. Note: scanning all the endpoints may cause performance issues and latency.

##### Required Permissions

Required Permissions For API call:
`Action Center` --> `View`
`Endpoint Administrations` --> `View/ Edit`
`Endpoint Administrations` --> `Endpoint Scan`

Builtin Roles with this permission includes: "Privileged Responder" and "Instance Admin".

#### Base Command

`xdr-endpoint-scan-execute`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Allows linking the response action to the incident that triggered it. | Optional |
| endpoint_id_list | List of endpoint IDs. | Optional |
| dist_name | Name of the distribution list. | Optional |
| gte_first_seen | Greater than or equal to first seen timestamp in milliseconds. | Optional |
| gte_last_seen | Greater than or equal to last seen timestamp in milliseconds. | Optional |
| lte_first_seen | Less than or equal to first seen timestamp in milliseconds. | Optional |
| lte_last_seen | Less than or equal to last seen timestamp in milliseconds. | Optional |
| ip_list | List of IP addresses. | Optional |
| group_name | Name of the endpoint group. | Optional |
| platform | Type of operating system. Possible values are: windows, linux, macos, android. | Optional |
| alias | Endpoint alias name. | Optional |
| isolate | Whether an endpoint has been isolated. Possible values are: isolated, unisolated. | Optional |
| hostname | Name of the host. | Optional |
| all | Whether to scan all of the endpoints. Scanning all of the endpoints may cause performance issues and latency. Possible values are: true, false. Default is false. | Optional |
| action_id | The action IDs for polling use. | Optional |
| interval_in_seconds | Interval in seconds between each poll. | Optional |
| timeout_in_seconds | Polling timeout in seconds. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.endpointScan.actionId | Number | The action ID of the scan request. |
| PaloAltoNetworksXDR.endpointScan.aborted | Boolean | Was the scan aborted? |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.bucket | String | The bucket in which the error occurred. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_name | String | The name of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_path | String | The path of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_size | Number | The size of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.missing_files | Unknown | The missing files that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorData | String | The error reason data. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminated_by | String | The instance ID which terminated the action and caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorDescription | String | The error reason description. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminate_result | Unknown | The error reason terminate result. |

### xdr-endpoint-scan-abort

***
Cancels the scan of selected endpoints. A scan can only be aborted if the selected endpoints are Pending or In Progress. To scan all endpoints, run the command with the argument all=true. Note that scanning all of the endpoints may cause performance issues and latency.

##### Required Permissions

Required Permissions For API call:
`Endpoint Administrations` --> `View/ Edit`
`Endpoint Administrations` --> `Endpoint Scan`

Builtin Roles with this permission includes: "Privileged Responder" and "Instance Admin".

#### Base Command

`xdr-endpoint-scan-abort`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Allows linking the response action to the incident that triggered it. | Optional |
| endpoint_id_list | List of endpoint IDs. | Optional |
| dist_name | Name of the distribution list. | Optional |
| gte_first_seen | GTE first seen timestamp in milliseconds. | Optional |
| gte_last_seen | GTE last seen timestamp in milliseconds. | Optional |
| lte_first_seen | LTE first seen timestamp in milliseconds. | Optional |
| lte_last_seen | LTE last seen timestamp in milliseconds. | Optional |
| ip_list | List of IP addresses. | Optional |
| group_name | Name of the endpoint group. | Optional |
| platform | Type of operating system. Possible values are: windows, linux, macos, android. | Optional |
| alias | Endpoint alias name. | Optional |
| isolate | Whether an endpoint has been isolated. Can be "isolated" or "unisolated". Possible values are: isolated, unisolated. | Optional |
| hostname | Name of the host. | Optional |
| all | Whether to scan all of the endpoints. Note: scanning all of the endpoints may cause performance issues and latency. Possible values are: true, false. Default is false. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.endpointScan.actionId | Unknown | The action ID of the abort scan request. |
| PaloAltoNetworksXDR.endpointScan.aborted | Boolean | Was the scan aborted? |

### get-mapping-fields

***
Gets mapping fields from remote incident. Note: This method will not update the current incident, it's here for debugging purposes.

#### 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. Note: This method will not update the current incident, it's here for debugging purposes.

#### Base Command

`get-remote-data`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| id | The remote incident ID. | Required |
| lastUpdate | UTC timestamp in seconds. The incident 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 that were modified since the last update. Note: This method is here for debugging purposes. get-modified-remote-data is used as part of a Mirroring feature, which is available since version 6.1.

#### Base Command

`get-modified-remote-data`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| lastUpdate | Date string representing the local time.The incident is only returned if it was modified after the last update time. | Optional |

#### Context Output

There is no context output for this command.

### xdr-get-policy

***
Gets the policy name for a specific endpoint.

##### Required Permissions

Required Permissions For API call:
`Endpoint Prevention Policies` --> `View`

Builtin Roles with this permission includes: "Privileged Investigator", "Privileged Responder", "Viewer", and "Instance Admin".

#### Base Command

`xdr-get-policy`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| endpoint_id | The endpoint ID. Can be retrieved by running the xdr-get-endpoints command. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.Policy | string | The policy allocated with the endpoint. |
| PaloAltoNetworksXDR.Policy.policy_name | string | Name of the policy allocated with the endpoint. |
| PaloAltoNetworksXDR.Policy.endpoint_id | string | Endpoint ID. |

### xdr-get-scripts

***
Gets a list of scripts available in the scripts library.

##### Required Permissions

Required Permissions For API call:
`Agent Scripts library` --> `View`
`Endpoint Administrations` --> `View/ Edit`
`Endpoint Administrations` --> `Endpoint Scan`

Builtin Roles with this permission includes: "Privileged Responder", "Viewer" and "Instance Admin".

#### Base Command

`xdr-get-scripts`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| script_name | A comma-separated list of the script names. | Optional |
| description | A comma-separated list of the script descriptions. | Optional |
| created_by | A comma-separated list of the users who created the script. | Optional |
| limit | The maximum number of scripts returned to the War Room. | Optional |
| offset | (Int) Offset in the data set. | Optional |
| windows_supported | Whether the script can be executed on a Windows operating system. Possible values are: true, false. | Optional |
| linux_supported | Whether the script can be executed on a Linux operating system. Possible values are: true, false. | Optional |
| macos_supported | Whether the script can be executed on a Mac operating system. Possible values are: true, false. | Optional |
| is_high_risk | Whether the script has a high-risk outcome. Possible values are: true, false. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.Scripts | Unknown | The scripts command results. |
| PaloAltoNetworksXDR.Scripts.script_id | Unknown | Script ID. |
| PaloAltoNetworksXDR.Scripts.name | string | Name of the script. |
| PaloAltoNetworksXDR.Scripts.description | string | Description of the script. |
| PaloAltoNetworksXDR.Scripts.modification_date | Unknown | Timestamp of when the script was last modified. |
| PaloAltoNetworksXDR.Scripts.created_by | string | Name of the user who created the script. |
| PaloAltoNetworksXDR.Scripts.windows_supported | boolean | Whether the script can be executed on a Windows operating system. |
| PaloAltoNetworksXDR.Scripts.linux_supported | boolean | Whether the script can be executed on a Linux operating system. |
| PaloAltoNetworksXDR.Scripts.macos_supported | boolean | Whether the script can be executed on Mac operating system. |
| PaloAltoNetworksXDR.Scripts.is_high_risk | boolean | Whether the script has a high-risk outcome. |
| PaloAltoNetworksXDR.Scripts.script_uid | string | Globally Unique Identifier of the script, used to identify the script when executing. |

### xdr-delete-endpoints

***
Deletes selected endpoints in the Cortex XDR app. You can delete up to 1000 endpoints.

##### Required Permissions

Required Permissions For API call:
`Endpoint Administrations` --> `View/ Edit`

Builtin Roles with this permission includes: "Privileged Responder" and "Instance Admin".

#### Base Command

`xdr-delete-endpoints`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| endpoint_ids | A comma-separated list of endpoint IDs. You can retrieve the endpoint IDs from the xdr-get-endpoints command. | Required |

#### Context Output

There is no context output for this command.

### xdr-get-endpoint-device-control-violations

***
Gets a list of device control violations filtered by selected fields. You can retrieve up to 100 violations.

##### Required Permissions

Required Permissions For API call:
`Device Control` --> `View`

Builtin Roles with this permission includes: "Privileged Investigator", "Privileged Responder", "Viewer", and "Instance Admin".

#### Base Command

`xdr-get-endpoint-device-control-violations`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| endpoint_ids | A comma-separated list of endpoint IDs. You can retrieve the endpoint IDs from the xdr-get-endpoints command. | Optional |
| type | Type of violation. Possible values are: "cd-rom", "disk drive", "floppy disk", and "portable device". Possible values are: cd-rom, disk drive, floppy disk, portable device. | Optional |
| timestamp_gte | Timestamp of the violation. Violations that are greater than or equal to this timestamp will be returned. Values can be in either ISO date format, relative time, or epoch timestamp. For example:  "2019-10-21T23:45:00" (ISO date format), "3 days ago" (relative time) 1579039377301 (epoch time). | Optional |
| timestamp_lte | Timestamp of the violation. Violations that are less than or equal to this timestamp will be returned. Values can be in either ISO date format, relative time, or epoch timestamp. For example:  "2019-10-21T23:45:00" (ISO date format), "3 days ago" (relative time) 1579039377301 (epoch time). | Optional |
| ip_list | A comma-separated list of IP addresses. | Optional |
| vendor | Name of the vendor. | Optional |
| vendor_id | Vendor ID. | Optional |
| product | Name of the product. | Optional |
| product_id | Product ID. | Optional |
| serial | Serial number. | Optional |
| hostname | Hostname. | Optional |
| violation_id_list | A comma-separated list of violation IDs. | Optional |
| username | Username. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.EndpointViolations | Unknown | Endpoint violations command results. |
| PaloAltoNetworksXDR.EndpointViolations.violations | Unknown | A list of violations. |
| PaloAltoNetworksXDR.EndpointViolations.violations.os_type | string | Type of the operating system. |
| PaloAltoNetworksXDR.EndpointViolations.violations.hostname | string | Host name of the violation. |
| PaloAltoNetworksXDR.EndpointViolations.violations.username | string | Username of the violation. |
| PaloAltoNetworksXDR.EndpointViolations.violations.ip | string | IP address of the violation. |
| PaloAltoNetworksXDR.EndpointViolations.violations.timestamp | number | Timestamp of the violation. |
| PaloAltoNetworksXDR.EndpointViolations.violations.violation_id | number | Violation ID. |
| PaloAltoNetworksXDR.EndpointViolations.violations.type | string | Type of violation. |
| PaloAltoNetworksXDR.EndpointViolations.violations.vendor_id | string | Vendor ID of the violation. |
| PaloAltoNetworksXDR.EndpointViolations.violations.vendor | string | Name of the vendor of the violation. |
| PaloAltoNetworksXDR.EndpointViolations.violations.product_id | string | Product ID of the violation. |
| PaloAltoNetworksXDR.EndpointViolations.violations.product | string | Name of the product of the violation. |
| PaloAltoNetworksXDR.EndpointViolations.violations.serial | string | Serial number of the violation. |
| PaloAltoNetworksXDR.EndpointViolations.violations.endpoint_id | string | Endpoint ID of the violation. |

### xdr-file-retrieve

***
Retrieves files from selected endpoints. You can retrieve up to 20 files, from no more than 10 endpoints. At least one endpoint ID and one file path are necessary in order to run the command. After running this command, you can use the xdr-action-status-get command with returned action_id, to check the action status.

##### Required Permissions

Required Permissions For API call:
`Action Center` --> `View/ Edit`
`Action Center` --> `File Retrieval`

Builtin Roles with this permission includes: "Privileged Responder" and "Instance Admin".

#### Base Command

`xdr-file-retrieve`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Allows to link the response action to the incident that triggered it. | Optional |
| endpoint_ids | A comma-separated list of endpoint IDs. | Required |
| windows_file_paths | A comma-separated list of file paths on the Windows platform. | Optional |
| linux_file_paths | A comma-separated list of file paths on the Linux platform. | Optional |
| mac_file_paths | A comma-separated list of file paths on the Mac platform. | Optional |
| generic_file_path | A comma-separated list of file paths in any platform. Can be used instead of the mac/windows/linux file paths. The order of the files path list must be parallel to the endpoints list order, so the first file path in the list is related to the first endpoint and so on. | Optional |
| paths_separator | The delimiter used to separate multiple file paths. Set to a different character (for example, ";") when a file path contains a comma. Default is ,. | Optional |
| interval_in_seconds | Interval in seconds between each poll. | Optional |
| timeout_in_seconds | Polling timeout in seconds. | Optional |
| action_id | The action IDs for polling use. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.RetrievedFiles.action_id | string | ID of the action to retrieve files from selected endpoints. |
| PaloAltoNetworksXDR.RetrievedFiles.endpoint_id | string | Endpoint ID. Added only when the operation is successful. |
| PaloAltoNetworksXDR.RetrievedFiles.file_link | string | Link to the file. Added only when the operation is successful. |
| PaloAltoNetworksXDR.RetrievedFiles.status | string | The action status. Added only when the operation is unsuccessful. |
| PaloAltoNetworksXDR.RetrievedFiles.ErrorReasons.bucket | String | The bucket in which the error occurred. |
| PaloAltoNetworksXDR.RetrievedFiles.ErrorReasons.file_name | String | The name of the file that caused the error. |
| PaloAltoNetworksXDR.RetrievedFiles.ErrorReasons.file_path | String | The path of the file that caused the error. |
| PaloAltoNetworksXDR.RetrievedFiles.ErrorReasons.file_size | Number | The size of the file that caused the error. |
| PaloAltoNetworksXDR.RetrievedFiles.ErrorReasons.missing_files | Unknown | The missing files that caused the error. |
| PaloAltoNetworksXDR.RetrievedFiles.ErrorReasons.errorData | String | The error reason data. |
| PaloAltoNetworksXDR.RetrievedFiles.ErrorReasons.terminated_by | String | The instance ID which terminated the action and caused the error. |
| PaloAltoNetworksXDR.RetrievedFiles.ErrorReasons.errorDescription | String | The error reason description. |
| PaloAltoNetworksXDR.RetrievedFiles.ErrorReasons.terminate_result | Unknown | The error reason terminate result. |

### xdr-retrieve-file-details

***
View the file retrieved by the xdr-retrieve-files command according to the action ID. Before running this command, you can use the xdr-action-status-get command to check if this action completed successfully.

##### Required Permissions

Required Permissions For API call:
`Action Center` --> `View/ Edit`
`Action Center` --> `File Retrieval`

Builtin Roles with this permission includes: "Privileged Responder" and "Instance Admin".

#### Base Command

`xdr-retrieve-file-details`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| action_id | Action ID retrieved from the xdr-retrieve-files command. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| File | Unknown | The file details command results. |
| File.Name | String | The full file name \(including the file extension\). |
| File.EntryID | String | The ID for locating the file in the War Room. |
| File.Size | Number | The size of the file in bytes. |
| File.MD5 | String | The MD5 hash 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.Extension | String | The file extension. For example: "xls". |
| File.Type | String | The file type, as determined by libmagic \(same as displayed in file entries\). |

### xdr-get-script-metadata

***
Gets the full definition of a specific script in the scripts library.

##### Required Permissions

Required Permissions For API call:
`Agent Scripts library` --> `View`

Builtin Roles with this permission includes: "Privileged Responder", "Viewer" and "Instance Admin".

#### Base Command

`xdr-get-script-metadata`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| script_uid | Unique identifier of the script, returned by the xdr-get-scripts command. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.ScriptMetadata | Unknown | The script metadata command results. |
| PaloAltoNetworksXDR.ScriptMetadata.script_id | number | Script ID. |
| PaloAltoNetworksXDR.ScriptMetadata.name | string | Script name. |
| PaloAltoNetworksXDR.ScriptMetadata.description | string | Script description. |
| PaloAltoNetworksXDR.ScriptMetadata.modification_date | unknown | Timestamp of when the script was last modified. |
| PaloAltoNetworksXDR.ScriptMetadata.created_by | string | Name of the user who created the script. |
| PaloAltoNetworksXDR.ScriptMetadata.is_high_risk | boolean | Whether the script has a high-risk outcome. |
| PaloAltoNetworksXDR.ScriptMetadata.windows_supported | boolean | Whether the script can be executed on a Windows operating system. |
| PaloAltoNetworksXDR.ScriptMetadata.linux_supported | boolean | Whether the script can be executed on a Linux operating system. |
| PaloAltoNetworksXDR.ScriptMetadata.macos_supported | boolean | Whether the script can be executed on a Mac operating system. |
| PaloAltoNetworksXDR.ScriptMetadata.entry_point | string | Name of the entry point selected for the script. An empty string indicates  the script defined as just run. |
| PaloAltoNetworksXDR.ScriptMetadata.script_input | string | Name and type for the specified entry point. |
| PaloAltoNetworksXDR.ScriptMetadata.script_output_type | string | Type of the output. |
| PaloAltoNetworksXDR.ScriptMetadata.script_output_dictionary_definitions | Unknown | If the script_output_type is a dictionary, an array with friendly name, name, and type for each output. |

### xdr-get-script-code

***
Gets the code of a specific script in the script library.

##### Required Permissions

Required Permissions For API call:
`Agent Scripts library` --> `View`

Builtin Roles with this permission includes: "Privileged Responder", "Viewer" and "Instance Admin".

#### Base Command

`xdr-get-script-code`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| script_uid | Unique identifier of the script, returned by the xdr-get-scripts command. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.ScriptCode | Unknown | The script code command results. |
| PaloAltoNetworksXDR.ScriptCode.code | string | The code of a specific script in the script library. |
| PaloAltoNetworksXDR.ScriptCode.script_uid | string | Unique identifier of the script. |

### xdr-action-status-get

***
Retrieves the status of the requested actions according to the action ID.

##### Required Permissions

Required Permissions For API call:
`Action Center` --> `View`

Builtin Roles with this permission includes: "Responder", "Privileged Investigator", "Privileged Responder", "Viewer", and "Instance Admin".

#### Base Command

`xdr-action-status-get`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| action_id | The action ID of the selected request. After performing an action, you will receive an action ID. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.GetActionStatus | Unknown | The action status command results. |
| PaloAltoNetworksXDR.GetActionStatus.endpoint_id | string | Endpoint ID. |
| PaloAltoNetworksXDR.GetActionStatus.status | string | The status of the specific endpoint ID. |
| PaloAltoNetworksXDR.GetActionStatus.action_id | number | The specified action ID. |

### xdr-run-script

***
Deprecated. Use the `xdr-script-run` command instead. Initiates a new endpoint script execution action using a script from the script library.

#### Base Command

`xdr-run-script`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Allows linking the response action to the incident that triggered it. | Optional |
| endpoint_ids | A comma-separated list of endpoint IDs. Can be retrieved by running the xdr-get-endpoints command. | Required |
| script_uid | Unique identifier of the script. Can be retrieved by running the xdr-get-scripts command. | Required |
| parameters | Dictionary containing the parameter name as key and its value for this execution as the value. For example, {"param1":"param1_value","param2":"param2_value"}. | Optional |
| timeout | The timeout in seconds for this execution. Default is 600. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.ScriptRun.action_id | Number | ID of the action initiated. |
| PaloAltoNetworksXDR.ScriptRun.endpoints_count | Number | Number of endpoints the action was initiated on. |

### xdr-snippet-code-script-execute

***
Initiates a new endpoint script execution action using the provided snippet code.

##### Required Permissions

Required Permissions For API call:
`Action Center` --> `View`

Builtin Roles with this permission includes: "Responder", "Privileged Investigator", "Privileged Responder", "Viewer", and "Instance Admin".

#### Base Command

`xdr-snippet-code-script-execute`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Allows linking the response action to the incident that triggered it. | Optional |
| endpoint_ids | A comma-separated list of endpoint IDs. Can be retrieved by running the xdr-get-endpoints command. | Required |
| snippet_code | Section of a script to initiate on an endpoint (e.g., print("7")). | Required |
| interval_in_seconds | Interval in seconds between each poll. | Optional |
| timeout_in_seconds | Polling timeout in seconds. | Optional |
| action_id | Action IDs for polling use. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.ScriptRun.action_id | Number | ID of the action initiated. |
| PaloAltoNetworksXDR.ScriptRun.endpoints_count | Number | Number of endpoints the action was initiated on. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.bucket | String | The bucket in which the error occurred. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_name | String | The name of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_path | String | The path of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_size | Number | The size of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.missing_files | Unknown | The missing files that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorData | String | The error reason data. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminated_by | String | The instance ID which terminated the action and caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorDescription | String | The error reason description. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminate_result | Unknown | The error reason terminate result. |

### xdr-get-script-execution-status

***
Retrieves the status of a script execution action.

##### Required Permissions

Required Permissions For API call:
`Agent Scripts library` --> `View`

Builtin Roles with this permission includes: "Privileged Responder", "Viewer" and "Instance Admin".

#### Base Command

`xdr-get-script-execution-status`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| action_id | Action IDs retrieved from the xdr-run-script command. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.ScriptStatus.general_status | String | General status of the action, considering the status of all the endpoints. |
| PaloAltoNetworksXDR.ScriptStatus.error_message | String | Error message regarding permissions for running APIs or the action doesn’t exist. |
| PaloAltoNetworksXDR.ScriptStatus.endpoints_timeout | Number | Number of endpoints in "timeout" status. |
| PaloAltoNetworksXDR.ScriptStatus.action_id | Number | ID of the action initiated. |
| PaloAltoNetworksXDR.ScriptStatus.endpoints_pending_abort | Number | Number of endpoints in "pending abort" status. |
| PaloAltoNetworksXDR.ScriptStatus.endpoints_pending | Number | Number of endpoints in "pending" status. |
| PaloAltoNetworksXDR.ScriptStatus.endpoints_in_progress | Number | Number of endpoints in "in progress" status. |
| PaloAltoNetworksXDR.ScriptStatus.endpoints_failed | Number | Number of endpoints in "failed" status. |
| PaloAltoNetworksXDR.ScriptStatus.endpoints_expired | Number | Number of endpoints in "expired" status. |
| PaloAltoNetworksXDR.ScriptStatus.endpoints_completed_successfully | Number | Number of endpoints in "completed successfully" status. |
| PaloAltoNetworksXDR.ScriptStatus.endpoints_canceled | Number | Number of endpoints in "canceled" status. |
| PaloAltoNetworksXDR.ScriptStatus.endpoints_aborted | Number | Number of endpoints in "aborted" status. |

### xdr-get-script-execution-results

***
Retrieve the results of a script execution action.

##### Required Permissions

Required Permissions For API call:
`Agent Scripts library` --> `View`

Builtin Roles with this permission includes: "Privileged Responder", "Viewer" and "Instance Admin".

#### Base Command

`xdr-get-script-execution-results`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| action_id | Action IDs retrieved from the xdr-run-script command. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.ScriptResult.action_id | Number | ID of the action initiated. |
| PaloAltoNetworksXDR.ScriptResult.results.retrieved_files | Number | Number of successfully retrieved files. |
| PaloAltoNetworksXDR.ScriptResult.results.endpoint_ip_address | String | Endpoint IP address. |
| PaloAltoNetworksXDR.ScriptResult.results.endpoint_name | String | Name of successfully retrieved files. |
| PaloAltoNetworksXDR.ScriptResult.results.failed_files | Number | Number of files failed to retrieve. |
| PaloAltoNetworksXDR.ScriptResult.results.endpoint_status | String | Endpoint status. |
| PaloAltoNetworksXDR.ScriptResult.results.domain | String | Domain to which the endpoint belongs. |
| PaloAltoNetworksXDR.ScriptResult.results.endpoint_id | String | Endpoint ID. |
| PaloAltoNetworksXDR.ScriptResult.results.execution_status | String | Execution status of this endpoint. |
| PaloAltoNetworksXDR.ScriptResult.results.return_value | String | Value returned by the script in case the type is not a dictionary. |
| PaloAltoNetworksXDR.ScriptResult.results.standard_output | String | The STDOUT and the STDERR logged by the script during the execution. |
| PaloAltoNetworksXDR.ScriptResult.results.retention_date | Date | Timestamp in which the retrieved files will be deleted from the server. |

### xdr-get-script-execution-result-files

***
Gets the files retrieved from a specific endpoint during a script execution.

##### Required Permissions

Required Permissions For API call:
`Agent Scripts library` --> `View`

Builtin Roles with this permission includes: "Privileged Responder", "Viewer" and "Instance Admin".

#### Base Command

`xdr-get-script-execution-result-files`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| action_id | Action ID retrieved from the xdr-run-script command. | Required |
| endpoint_id | Endpoint ID. Can be retrieved by running the xdr-get-endpoints command. | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| File.Size | String | 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 | EntryID 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. |

### xdr-script-commands-execute

***
Initiates a new endpoint script execution of shell commands.

#### Base Command

`xdr-script-commands-execute`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Allows linking the response action to the incident that triggered it. | Optional |
| endpoint_ids | A comma-separated list of endpoint IDs. Can be retrieved by running the xdr-get-endpoints command. | Required |
| commands | A comma-separated list of shell commands to execute. Set the `is_raw_command` argument to `true` to prevent splitting by commas. (Useful when using `\|\|`, `&amp;&amp;`, `;` separators for controlling the flow of multiple commands). | Required |
| is_raw_command | Whether to pass the command as-is. When false, the command is split by commas and sent as a list of commands, that are run independently. | Optional |
| command_type | Type of shell command. Possible values are: powershell, native. | Optional |
| timeout | The timeout in seconds for this execution. Default is 600. | Optional |
| interval_in_seconds | Interval in seconds between each poll. | Optional |
| timeout_in_seconds | Polling timeout in seconds. | Optional |
| action_id | The action IDs for polling use. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.ScriptRun.action_id | Number | ID of the action initiated. |
| PaloAltoNetworksXDR.ScriptRun.endpoints_count | Number | Number of endpoints the action was initiated on. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.bucket | String | The bucket in which the error occurred. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_name | String | The name of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_path | String | The path of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_size | Number | The size of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.missing_files | Unknown | The missing files that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorData | String | The error reason data. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminated_by | String | The instance ID which terminated the action and caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorDescription | String | The error reason description. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminate_result | Unknown | The error reason terminate result. |

### xdr-file-delete-script-execute

***
Initiates a new endpoint script execution to delete the specified file.

#### Base Command

`xdr-file-delete-script-execute`

#### Input

| **Argument Name** | **Description**                                                                                                       | **Required** |
| --- |-----------------------------------------------------------------------------------------------------------------------| --- |
| incident_id | Allows linking the response action to the incident that triggered it.                                                 | Optional |
| endpoint_ids | A comma-separated list of endpoint IDs. Can be retrieved by running the xdr-get-endpoints command.                    | Required |
| file_path | A comma-separated list of paths of the files to delete. All of the given file paths will run on all of the endpoints. | Required |
| timeout | The timeout in seconds for this execution. Default is 600.                                                            | Optional |
| interval_in_seconds | Interval in seconds between each poll.                                                                                | Optional |
| timeout_in_seconds | Polling timeout in seconds.                                                                                           | Optional |
| action_id | The action IDs for polling use.                                                                                       | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.ScriptRun.action_id | Number | ID of the action initiated. |
| PaloAltoNetworksXDR.ScriptRun.endpoints_count | Number | Number of endpoints the action was initiated on. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.bucket | String | The bucket in which the error occurred. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_name | String | The name of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_path | String | The path of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_size | Number | The size of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.missing_files | Unknown | The missing files that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorData | String | The error reason data. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminated_by | String | The instance ID which terminated the action and caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorDescription | String | The error reason description. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminate_result | Unknown | The error reason terminate result. |

### xdr-file-exist-script-execute

***
Initiates a new endpoint script execution to check if the file exists.

#### Base Command

`xdr-file-exist-script-execute`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Allows linking the response action to the incident that triggered it. | Optional |
| endpoint_ids | A comma-separated list of endpoint IDs. Can be retrieved by running the xdr-get-endpoints command. | Required |
| file_path | A comma-separated list of paths of the files to check for existence. All of the given file paths will run on all of the endpoints. | Required |
| timeout | The timeout in seconds for this execution. Default is 600. | Optional |
| interval_in_seconds | Interval in seconds between each poll. | Optional |
| timeout_in_seconds | Polling timeout in seconds. | Optional |
| action_id | The action IDs for polling use. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.ScriptRun.action_id | Number | ID of the action initiated. |
| PaloAltoNetworksXDR.ScriptRun.endpoints_count | Number | Number of endpoints the action was initiated on. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.bucket | String | The bucket in which the error occurred. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_name | String | The name of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_path | String | The path of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_size | Number | The size of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.missing_files | Unknown | The missing files that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorData | String | The error reason data. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminated_by | String | The instance ID which terminated the action and caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorDescription | String | The error reason description. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminate_result | Unknown | The error reason terminate result. |

### xdr-kill-process-script-execute

***
Initiates a new endpoint script execution kill process.

#### Base Command

`xdr-kill-process-script-execute`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Allows to link the response action to the incident that triggered it. | Optional |
| endpoint_ids | A comma-separated list of endpoint IDs. Can be retrieved by running the xdr-get-endpoints command. | Required |
| process_name | Names of processes to kill. Will kill all of the given processes on all of the endpoints. | Required |
| timeout | The timeout in seconds for this execution. Default is 600. | Optional |
| interval_in_seconds | Interval in seconds between each poll. | Optional |
| timeout_in_seconds | Polling timeout in seconds. | Optional |
| action_id | The action IDs for polling use. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.ScriptRun.action_id | Number | ID of the action initiated. |
| PaloAltoNetworksXDR.ScriptRun.endpoints_count | Number | Number of endpoints the action was initiated on. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.bucket | String | The bucket in which the error occurred. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_name | String | The name of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_path | String | The path of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.file_size | Number | The size of the file that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.missing_files | Unknown | The missing files that caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorData | String | The error reason data. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminated_by | String | The instance ID which terminated the action and caused the error. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.errorDescription | String | The error reason description. |
| PaloAltoNetworksXDR.GetActionStatus.ErrorReasons.terminate_result | Unknown | The error reason terminate result. |

### endpoint

***
Returns information about an endpoint.

##### Required Permissions

Required Permissions For API call:
`Endpoint Administrations` --> `View`

Builtin Roles with this permission includes: "Privileged Responder", "Viewer" and "Instance Admin".

#### Base Command

`endpoint`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| id | The endpoint ID. | Optional |
| ip | The endpoint IP address. | Optional |
| hostname | The endpoint host name. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| 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. |

### xdr-get-endpoints-by-status

***
Returns the number of the connected\disconnected endpoints.

##### Required Permissions

Required Permissions For API call:
`Endpoint Administrations` --> `View`

Builtin Roles with this permission includes: "Privileged Responder", "Viewer" and "Instance Admin".

#### Base Command

`xdr-get-endpoints-by-status`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| status | The status of the endpoint to filter. Possible values are: connected, disconnected, lost, uninstalled. | Required |
| last_seen_gte | All the agents that were last seen before {last_seen_gte}. Supported<br/>        values: 1579039377301 (time in milliseconds) "3 days" (relative date) "2019-10-21T23:45:00"<br/>        (date). | Optional |
| last_seen_lte | All the agents that were last seen before {last_seen_lte}. Supported<br/>        values: 1579039377301 (time in milliseconds) "3 days" (relative date) "2019-10-21T23:45:00"<br/>        (date). | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.EndpointsStatus.status | String | The endpoint status. |
| PaloAltoNetworksXDR.EndpointsStatus.count | Number | The number of endpoints with this status. |

### xdr-get-cloud-original-alerts (Deprecated)

***
This command is deprecated. Returns information about each alert ID.

##### Required Permissions

Required Permissions For API call:
`Alerts & Incidents` --> `View`

Builtin Roles with this permission includes: "Investigator", "Responder", "Privileged Investigator", "Privileged Responder", "Viewer", and "Instance Admin".

#### Base Command

`xdr-get-cloud-original-alerts`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| alert_ids | A comma-separated list of alert IDs. | Required |
| events_from_decider_format | Whether to return events_from_decider context output as a dictionary (the raw API response) or as a list (improved for playbook automation) - relevant only when filter_alert_fields is set to False. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.OriginalAlert.event._time | String | The timestamp of the occurrence of the event. |
| PaloAltoNetworksXDR.OriginalAlert.event.vendor | String | Vendor name. |
| PaloAltoNetworksXDR.OriginalAlert.event.event_timestamp | Number | Event timestamp. |
| PaloAltoNetworksXDR.OriginalAlert.event.event_type | Number | Event type \(static 500\). |
| PaloAltoNetworksXDR.OriginalAlert.event.cloud_provider | String | The cloud provider - GCP, AZURE, or AWS. |
| PaloAltoNetworksXDR.OriginalAlert.event.project | String | The project in which the event occurred. |
| PaloAltoNetworksXDR.OriginalAlert.event.cloud_provider_event_id | String | The ID given to the event by the cloud provider, if the ID exists. |
| PaloAltoNetworksXDR.OriginalAlert.event.cloud_correlation_id | String | The ID the cloud provider is using to aggregate events that are part of the same general event. |
| PaloAltoNetworksXDR.OriginalAlert.event.operation_name_orig | String | The name of the operation that occurred, as supplied by the cloud provider. |
| PaloAltoNetworksXDR.OriginalAlert.event.operation_name | String | The normalized name of the operation performed by the event. |
| PaloAltoNetworksXDR.OriginalAlert.event.identity_orig | String | Contains the original identity related fields as provided by the cloud provider. |
| PaloAltoNetworksXDR.OriginalAlert.event.identity_name | String | The name of the identity that initiated the action. |
| PaloAltoNetworksXDR.OriginalAlert.event.identity_uuid | String | Same as identity_name but also contains the UUID of the identity if it exists. |
| PaloAltoNetworksXDR.OriginalAlert.event.identity_type | String | An enum representing the type of the identity. |
| PaloAltoNetworksXDR.OriginalAlert.event.identity_sub_type | String | An enum representing the sub-type of the identity, respective to its identity_type. |
| PaloAltoNetworksXDR.OriginalAlert.event.identity_invoked_by_name | String | The name of the identity that invoked the action as it appears in the log. |
| PaloAltoNetworksXDR.OriginalAlert.event.identity_invoked_by_uuid | String | The UUID of the identity that invoked the action as it appears in the log. |
| PaloAltoNetworksXDR.OriginalAlert.event.identity_invoked_by_type | String | An enum that represents the type of identity event that invoked the action. |
| PaloAltoNetworksXDR.OriginalAlert.event.identity_invoked_by_sub_type | String | An enum that represents the respective sub_type of the type of identity \(identity_type\) that has invoked the action. |
| PaloAltoNetworksXDR.OriginalAlert.event.operation_status | String | Status of whether the operation has succeed or failed, if provided. |
| PaloAltoNetworksXDR.OriginalAlert.event.operation_status_orig | String | The operation status code as it appears in the log, including lookup from code number to code name. |
| PaloAltoNetworksXDR.OriginalAlert.event.operation_status_orig_code | String | The operation status code as it appears in the log. |
| PaloAltoNetworksXDR.OriginalAlert.event.operation_status_reason_provided | String | Description of the error, if the log record indicates an error and the cloud provider supplied the reason. |
| PaloAltoNetworksXDR.OriginalAlert.event.resource_type | String | The normalized type of the service that emitted the log row. |
| PaloAltoNetworksXDR.OriginalAlert.event.resource_type_orig | String | The type of the service that omitted the log as provided by the cloud provider. |
| PaloAltoNetworksXDR.OriginalAlert.event.resource_sub_type | String | The sub-type respective to the resource_type field, normalized across all cloud providers. |
| PaloAltoNetworksXDR.OriginalAlert.event.resource_sub_type_orig | String | The sub-type of the service that emitted this log row as provided by the cloud provider. |
| PaloAltoNetworksXDR.OriginalAlert.event.region | String | The cloud region of the resource that emitted the log. |
| PaloAltoNetworksXDR.OriginalAlert.event.zone | String | The availability zone of the resource that emitted the log. |
| PaloAltoNetworksXDR.OriginalAlert.event.referenced_resource | String | The cloud resource referenced in the audit log. |
| PaloAltoNetworksXDR.OriginalAlert.event.referenced_resource_name | String | Same as referenced_resource but provides only the substring that represents the resource name instead of the full asset ID. |
| PaloAltoNetworksXDR.OriginalAlert.event.referenced_resources_count | Number | The number of extracted resources referenced in this audit log. |
| PaloAltoNetworksXDR.OriginalAlert.event.user_agent | String | The user agent provided in the call to the API of the cloud provider. |
| PaloAltoNetworksXDR.OriginalAlert.event.caller_ip | String | The IP of the caller that performed the action in the log. |
| PaloAltoNetworksXDR.OriginalAlert.event.caller_ip_geolocation | String | The geolocation associated with the caller_ip's value. |
| PaloAltoNetworksXDR.OriginalAlert.event.caller_ip_asn | Number | The ASN of the caller_ip's value. |
| PaloAltoNetworksXDR.OriginalAlert.event.caller_project | String | The project of the caller entity. |
| PaloAltoNetworksXDR.OriginalAlert.event.raw_log | Unknown | The raw log that is being normalized. |
| PaloAltoNetworksXDR.OriginalAlert.event.log_name | String | The name of the log that contains the log row. |
| PaloAltoNetworksXDR.OriginalAlert.event.caller_ip_asn_org | String | The organization associated with the ASN of the caller_ip's value. |
| PaloAltoNetworksXDR.OriginalAlert.event.event_base_id | String | Event base ID. |
| PaloAltoNetworksXDR.OriginalAlert.event.ingestion_time | String | Ingestion time. |

### xdr-remove-allowlist-files

***
Removes requested files from allow list.

##### Required Permissions

Required Permissions For API call:
`Action Center` --> `View/ Edit`
`Action Center` --> `Allow List/Block List`

Builtin Roles with this permission includes: "Responder", "Privileged Responder" and "Instance Admin".

#### Base Command

`xdr-remove-allowlist-files`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Links the response action to the incident that triggered it. | Optional |
| hash_list | String that represents a list of hashed files you want to add to allow list. Must be a valid SHA256 hash. | Required |
| comment | String that represents additional information regarding the action. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.allowlist.removed_hashes | Number | Removed file hash |

### xdr-remove-blocklist-files

***
Removes requested files from block list.

##### Required Permissions

Required Permissions For API call:
`Action Center` --> `View/ Edit`
`Action Center` --> `Allow List/Block List`

Builtin Roles with this permission includes: "Responder", "Privileged Responder" and "Instance Admin".

#### Base Command

`xdr-remove-blocklist-files`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| incident_id | Links the response action to the incident that triggered it. | Optional |
| hash_list | String that represents a list of hashed files you want to add to allow list. Must be a valid SHA256 hash. | Required |
| comment | String that represents additional information regarding the action. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.blocklist.removed_hashes | Number | Removed fileHash from blocklist |

There is no context output for this command.

### xdr-get-alerts (Deprecated)

***
This command is deprecated, please use xdr-issue-list instead. Returns a list of alerts and their metadata, which you can filter by built-in arguments or use the custom_filter to input a JSON filter object.
Multiple filter arguments will be concatenated using the AND operator, while arguments that support a comma-separated list of values will use an OR operator between each value.

##### Required Permissions

Required Permissions For API call:
`Alerts & Incidents` --> `View`

Builtin Roles with this permission includes: "Investigator", "Responder", "Privileged Investigator", "Privileged Responder", "Viewer", and "Instance Admin".

#### Base Command

`xdr-get-alerts`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| alert_id | The unique ID of the alert. | Optional |
| severity | The severity of the alert. Possible values are: low, medium, high. | Optional |
| custom_filter | a custom filter, when using this argument, other filter arguments are not relevant. example: <br/>`{<br/>                "OR": [<br/>                    {<br/>                        "SEARCH_FIELD": "actor_process_command_line",<br/>                        "SEARCH_TYPE": "EQ",<br/>                        "SEARCH_VALUE": "path_to_file"<br/>                    }<br/>                ]<br/>            }`. | Optional |
| Identity_type | Account type. Possible values are: ANONYMOUS, APPLICATION, COMPUTE, FEDERATED_IDENTITY, SERVICE, SERVICE_ACCOUNT, TEMPORARY_CREDENTIALS, TOKEN, UNKNOWN, USER. | Optional |
| agent_id | A unique identifier per agent. | Optional |
| action_external_hostname | The host name to connect to. In case of a proxy connection, this value will differ from action_remote_ip. | Optional |
| rule_id | A string identifying the user rule. | Optional |
| rule_name | The name of the user rule. | Optional |
| alert_name | The alert name. | Optional |
| alert_source | The alert source. | Optional |
| time_frame | Supports relative times or “custom” time option. If you choose the "custom" option, you should use start_time and end_time arguments. Possible values are: 60 minutes, 3 hours, 12 hours, 24 hours, 2 days, 7 days, 14 days, 30 days, custom. | Optional |
| user_name | The name assigned to the user_id during agent runtime. | Optional |
| actor_process_image_name | The file name of the binary file. | Optional |
| causality_actor_process_image_command_line | CGO CMD. | Optional |
| actor_process_image_command_line | Trimmed to 128 unicode chars during event serialization.<br/>Full value reported as part of the original process event. | Optional |
| action_process_image_command_line | The command line of the process created. | Optional |
| actor_process_image_sha256 | SHA256 of the binary file. | Optional |
| causality_actor_process_image_sha256 | SHA256 of the binary file. | Optional |
| action_process_image_sha256 | SHA256 of the binary file. | Optional |
| action_file_image_sha256 | SHA256 of the file related to the event. | Optional |
| action_registry_name | The name of the registry. | Optional |
| action_registry_key_data | The key data of the registry. | Optional |
| host_ip | The host IP. | Optional |
| action_local_ip | The local IP address for the connection. | Optional |
| action_remote_ip | Remote IP address for the connection. | Optional |
| alert_action_status | Alert action status. Possible values are: detected, detected (allowed the session), detected (download), detected (forward), detected (post detected), detected (prompt allow), detected (raised an alert), detected (reported), detected (on write), detected (scanned), detected (sinkhole), detected (syncookie sent), detected (wildfire upload failure), detected (wildfire upload success), detected (wildfire upload skip), detected (xdr managed threat hunting), prevented (block), prevented (blocked), prevented (block-override), prevented (blocked the url), prevented (blocked the ip), prevented (continue), prevented (denied the session), prevented (dropped all packets), prevented (dropped the session), prevented (dropped the session and sent a tcp reset), prevented (dropped the packet), prevented (override), prevented (override-lockout), prevented (post detected), prevented (prompt block), prevented (random-drop), prevented (silently dropped the session with an icmp unreachable message to the host or application), prevented (terminated the session and sent a tcp reset to both sides of the connection), prevented (terminated the session and sent a tcp reset to the client), prevented (terminated the session and sent a tcp reset to the server), prevented (on write). | Optional |
| action_local_port | The local IP address for the connection. | Optional |
| action_remote_port | The remote port for the connection. | Optional |
| dst_action_external_hostname | The hostname we connect to. In case of a proxy connection, this value will differ from action_remote_ip. | Optional |
| sort_field | The field by which we sort the results. Default is source_insert_ts. | Optional |
| sort_order | The order in which we sort the results. Possible values are: DESC, ASC. | Optional |
| offset | The first page from which we bring the alerts. Default is 0. | Optional |
| limit | The last page from which we bring the alerts. Default is 50. | Optional |
| start_time | Relevant when "time_frame" argument is "custom". Supports Epoch timestamp and simplified extended ISO format (YYYY-MM-DDThh:mm:ss). | Optional |
| end_time | Relevant when "time_frame" argument is "custom". Supports Epoch timestamp and simplified extended ISO format (YYYY-MM-DDThh:mm:ss). | Optional |
| starred | Whether the alert is starred or not. Possible values are: true, false. | Optional |
| mitre_technique_id_and_name | The MITRE attack technique. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| PaloAltoNetworksXDR.Alert.internal_id | String | The unique ID of the alert. |
| PaloAltoNetworksXDR.Alert.source_insert_ts | Number | The detection timestamp |
| PaloAltoNetworksXDR.Alert.alert_name | String | The name of the alert. |
| PaloAltoNetworksXDR.Alert.severity | String | The severity of the alert. |
| PaloAltoNetworksXDR.Alert.alert_category | String | The category of the alert. |
| PaloAltoNetworksXDR.Alert.alert_action_status | String | The alert action. Possible values.

DETECTED: detected
DETECTED_0: detected \(allowed the session\)
DOWNLOAD: detected \(download\)
DETECTED_19: detected \(forward\)
POST_DETECTED: detected \(post detected\)
PROMPT_ALLOW: detected \(prompt allow\)
DETECTED_4: detected \(raised an alert\)
REPORTED: detected \(reported\)
REPORTED_TRIGGER_4: detected \(on write\)
SCANNED: detected \(scanned\)
DETECTED_23: detected \(sinkhole\)
DETECTED_18: detected \(syncookie sent\)
DETECTED_21: detected \(wildfire upload failure\)
DETECTED_20: detected \(wildfire upload success\)
DETECTED_22: detected \(wildfire upload skip\)
DETECTED_MTH: detected \(xdr managed threat hunting\)
BLOCKED_25: prevented \(block\)
BLOCKED: prevented \(blocked\)
BLOCKED_14: prevented \(block-override\)
BLOCKED_5: prevented \(blocked the url\)
BLOCKED_6: prevented \(blocked the ip\)
BLOCKED_13: prevented \(continue\)
BLOCKED_1: prevented \(denied the session\)
BLOCKED_8: prevented \(dropped all packets\)
BLOCKED_2: prevented \(dropped the session\)
BLOCKED_3: prevented \(dropped the session and sent a tcp reset\)
BLOCKED_7: prevented \(dropped the packet\)
BLOCKED_16: prevented \(override\)
BLOCKED_15: prevented \(override-lockout\)
BLOCKED_26: prevented \(post detected\)
PROMPT_BLOCK: prevented \(prompt block\)
BLOCKED_17: prevented \(random-drop\)
BLOCKED_24: prevented \(silently dropped the session with an icmp unreachable message to the host or application\)
BLOCKED_9: prevented \(terminated the session and sent a tcp reset to both sides of the connection\)
BLOCKED_10: prevented \(terminated the session and sent a tcp reset to the client\)
BLOCKED_11: prevented \(terminated the session and sent a tcp reset to the server\)
BLOCKED_TRIGGER_4: prevented \(on write\)
 |
| PaloAltoNetworksXDR.Alert.alert_action_status_readable | String | The alert action. |
| PaloAltoNetworksXDR.Alert.alert_name | String | The alert name. |
| PaloAltoNetworksXDR.Alert.alert_description | String | The alert description. |
| PaloAltoNetworksXDR.Alert.agent_ip_addresses | String | The host IP. |
| PaloAltoNetworksXDR.Alert.agent_hostname | String | The host name. |
| PaloAltoNetworksXDR.Alert.mitre_tactic_id_and_name | String | The MITRE attack tactic. |
| PaloAltoNetworksXDR.Alert.mitre_technique_id_and_name | String | The MITRE attack technique. |
| PaloAltoNetworksXDR.Alert.starred | Boolean | Whether the alert is starred or not. |

#### Command example

```!xdr-get-alerts severity="high" alert_action_status="detected (reported)" sort_field="source_insert_ts" offset="0" limit="1"```

#### Context Example

```json
{
    "PaloAltoNetworksXDR": {
        "Alert": {
            "action_country": [
                "UNKNOWN"
            ],
            "action_external_hostname": null,
            "action_file_macro_sha256": null,
            "action_file_md5": null,
            "action_file_name": null,
            "action_file_path": null,
            "action_file_sha256": null,
            "action_local_ip": null,
            "action_local_ip_v6": null,
            "action_local_port": null,
            "action_process_causality_id": null,
            "action_process_image_command_line": null,
            "action_process_image_md5": [
                "ddcd2be64212b10c3cf84496a879b098"
            ],
            "action_process_image_name": null,
            "action_process_image_path": [
                "C:\Users\administrator\Downloads\svchost.exe"
            ],
            "action_process_image_sha256": null,
            "action_process_instance_id": null,
            "action_process_os_pid": [
                5172
            ],
            "action_process_signature_status": [
                "SIGNATURE_UNAVAILABLE"
            ],
            "action_process_signature_vendor": null,
            "action_process_user_sid": null,
            "action_registry_data": null,
            "action_registry_full_key": null,
            "action_registry_key_name": null,
            "action_registry_value_name": null,
            "action_remote_ip": null,
            "action_remote_ip_v6": null,
            "action_remote_port": null,
            "activity_first_seen_at": null,
            "activity_last_seen_at": null,
            "actor_causality_id": null,
            "actor_effective_user_sid": null,
            "actor_effective_username": [
                "env1.local\administrator"
            ],
            "actor_process_causality_id": [
                "AdhDcc/XHpAAABQ0AAAAAA=="
            ],
            "actor_process_command_line": [
                "\"C:\Users\administrator\Downloads\svchost.exe\" "
            ],
            "actor_process_execution_time": [
                1648560911622
            ],
            "actor_process_image_md5": [
                "ddcd2be64212b10c3cf84496a879b098"
            ],
            "actor_process_image_name": [
                "svchost.exe"
            ],
            "actor_process_image_path": [
                "C:\Users\administrator\Downloads\svchost.exe"
            ],
            "actor_process_image_sha256": [
                "b013074d220d71877112b61e16927abbbb98ad29aa40609aca1b936332fbe4b7"
            ],
            "actor_process_instance_id": [
                "AdhDcc/XHpAAABQ0AAAAAA=="
            ],
            "actor_process_os_pid": [
                5172
            ],
            "actor_process_signature_status": [
                "SIGNATURE_UNSIGNED"
            ],
            "actor_process_signature_vendor": null,
            "actor_thread_thread_id": [
                2468
            ],
            "agent_data_collection_status": true,
            "agent_device_domain": "env1.local",
            "agent_fqdn": "DC1ENV1APC02.env1.local",
            "agent_host_boot_time": [
                0
            ],
            "agent_hostname": "DC1ENV1APC02",
            "agent_id": "63f88a9e797440ccac742a6adc926fb2",
            "agent_install_type": "STANDARD",
            "agent_ip_addresses": [
                "10.111.230.11"
            ],
            "agent_ip_addresses_v6": null,
            "agent_is_vdi": null,
            "agent_os_sub_type": "10.0.10240",
            "agent_os_type": "AGENT_OS_WINDOWS",
            "agent_version": "7.6.1.46600",
            "alert_action_status": "REPORTED",
            "alert_action_status_readable": "detected (reported)",
            "alert_category": "Malware",
            "alert_description": "Behavioral threat detected (rule: bioc.masquerade_svchost)",
            "alert_description_raw": "Behavioral threat detected (rule: bioc.masquerade_svchost)",
            "alert_is_fp": false,
            "alert_name": "Behavioral Threat",
            "alert_source": "TRAPS",
            "alert_sub_type": null,
            "alert_type": "Unclassified",
            "association_strength": [
                50
            ],
            "attack_techniques": null,
            "attempt_counter": 0,
            "audit_ids": null,
            "bioc_category_enum_key": null,
            "bioc_indicator": null,
            "caller_ip": null,
            "case_id": 48,
            "causality_actor_causality_id": [
                "AdhDcc/XHpAAABQ0AAAAAA=="
            ],
            "causality_actor_process_command_line": [
                "\"C:\Users\administrator\Downloads\svchost.exe\" "
            ],
            "causality_actor_process_execution_time": [
                1648560911622
            ],
            "causality_actor_process_image_md5": null,
            "causality_actor_process_image_name": [
                "svchost.exe"
            ],
            "causality_actor_process_image_path": [
                "C:\Users\administrator\Downloads\svchost.exe"
            ],
            "causality_actor_process_image_sha256": [
                "b013074d220d71877112b61e16927abbbb98ad29aa40609aca1b936332fbe4b7"
            ],
            "causality_actor_process_instance_id": [
                "AdhDcc/XHpAAABQ0AAAAAA=="
            ],
            "causality_actor_process_os_pid": [
                5172
            ],
            "causality_actor_process_signature_status": [
                "SIGNATURE_UNSIGNED"
            ],
            "causality_actor_process_signature_vendor": null,
            "cloud_provider": null,
            "cluster_name": null,
            "container_id": null,
            "contains_featured_host": [
                "NO"
            ],
            "contains_featured_ip": [
                "NO"
            ],
            "contains_featured_user": [
                "NO"
            ],
            "deduplicate_tokens": null,
            "detection_modules": null,
            "dns_query_name": null,
            "drilldown_max_ts": null,
            "drilldown_min_ts": null,
            "drilldown_query": null,
            "dss_country": null,
            "dss_department": null,
            "dss_groups": null,
            "dss_job_title": null,
            "dst_action_country": null,
            "dst_action_external_hostname": null,
            "dst_action_external_port": null,
            "dst_actor_process_image_name": null,
            "dst_actor_process_os_pid": null,
            "dst_agent_hostname": null,
            "dst_agent_id": null,
            "dst_agent_os_type": [
                "NO_HOST"
            ],
            "dst_association_strength": null,
            "dst_causality_actor_process_execution_time": null,
            "dst_os_actor_process_image_name": null,
            "dst_os_actor_process_os_pid": null,
            "dynamic_fields": {
                "action_country": [
                    "UNKNOWN"
                ],
                "action_process_signature_status": [
                    "SIGNATURE_UNAVAILABLE"
                ],
                "activated": "0001-01-01T00:00:00Z",
                "activatingingUserId": "",
                "actor_effective_username": [
                    "env1.local\administrator"
                ],
                "actor_process_command_line": [
                    "\"C:\Users\administrator\Downloads\svchost.exe\" "
                ],
                "actor_process_image_md5": [
                    "ddcd2be64212b10c3cf84496a879b098"
                ],
                "actor_process_image_name": [
                    "svchost.exe"
                ],
                "actor_process_image_path": [
                    "C:\Users\administrator\Downloads\svchost.exe"
                ],
                "actor_process_image_sha256": [
                    "b013074d220d71877112b61e16927abbbb98ad29aa40609aca1b936332fbe4b7"
                ],
                "actor_process_os_pid": [
                    5172
                ],
                "actor_process_signature_status": [
                    "SIGNATURE_UNSIGNED"
                ],
                "actor_thread_thread_id": [
                    2468
                ],
                "agent_device_domain": "env1.local",
                "agent_fqdn": "DC1ENV1APC02.env1.local",
                "agent_hostname": "DC1ENV1APC02",
                "agent_id": "63f88a9e797440ccac742a6adc926fb2",
                "agent_ip_addresses": [
                    "10.111.230.11"
                ],
                "agent_os_sub_type": "10.0.10240",
                "agent_os_type": "AGENT_OS_WINDOWS",
                "alert_action_status": "REPORTED",
                "alert_category": "Malware",
                "alert_description": "Behavioral threat detected (rule: bioc.masquerade_svchost)",
                "alert_name": "Behavioral Threat",
                "alert_source": "TRAPS",
                "alert_type": "Unclassified",
                "attachment": null,
                "category": "",
                "causality_actor_causality_id": [
                    "AdhDcc/XHpAAABQ0AAAAAA=="
                ],
                "causality_actor_process_command_line": [
                    "\"C:\Users\administrator\Downloads\svchost.exe\" "
                ],
                "causality_actor_process_image_name": [
                    "svchost.exe"
                ],
                "causality_actor_process_image_path": [
                    "C:\Users\administrator\Downloads\svchost.exe"
                ],
                "causality_actor_process_image_sha256": [
                    "b013074d220d71877112b61e16927abbbb98ad29aa40609aca1b936332fbe4b7"
                ],
                "causality_actor_process_signature_status": [
                    "SIGNATURE_UNSIGNED"
                ],
                "closeReason": "",
                "closed": "0001-01-01T00:00:00Z",
                "closingUserId": "",
                "contains_featured_host": [
                    "NO"
                ],
                "contains_featured_ip": [
                    "NO"
                ],
                "contains_featured_user": [
                    "NO"
                ],
                "dbotCurrentDirtyFields": null,
                "dbotDirtyFields": null,
                "dbotMirrorDirection": "",
                "dbotMirrorId": "",
                "dbotMirrorInstance": "",
                "dbotMirrorLastSync": "0001-01-01T00:00:00Z",
                "dbotMirrorTags": null,
                "droppedCount": 0,
                "dueDate": "0001-01-01T00:00:00Z",
                "event_type": [
                    1
                ],
                "feedBased": false,
                "fw_is_phishing": [
                    "NOT_AVAILABLE"
                ],
                "internal_id": 6887,
                "investigationId": "6887",
                "isDebug": false,
                "is_whitelisted": false,
                "labels": null,
                "lastJobRunTime": "0001-01-01T00:00:00Z",
                "lastOpen": "0001-01-01T00:00:00Z",
                "linkedCount": 0,
                "linkedIncidents": null,
                "mac": "00:50:56:89:8b:8e",
                "mitre_tactic_id_and_name": [
                    "TA0005 - Defense Evasion",
                    "TA0002 - Execution"
                ],
                "mitre_technique_id_and_name": [
                    "T1036.005 - Masquerading: Match Legitimate Name or Location"
                ],
                "module_id": [
                    "Behavioral Threat Protection"
                ],
                "notifyTime": "2022-09-21T06:45:17.746532863Z",
                "occurred": "0001-01-01T00:00:00Z",
                "openDuration": 0,
                "os_actor_process_signature_status": [
                    "SIGNATURE_UNAVAILABLE"
                ],
                "os_actor_thread_thread_id": [
                    2468
                ],
                "phase": "",
                "playbookId": "T1036 - Masquerading",
                "reason": "",
                "reminder": "0001-01-01T00:00:00Z",
                "resolution_comment": "",
                "resolution_status": "STATUS_020_UNDER_INVESTIGATION",
                "runStatus": "error",
                "severity": "SEV_040_HIGH",
                "sla": 0,
                "sourceInstance": "",
                "source_insert_ts": 1648560949000,
                "starred": false
            },
            "end_match_attempt_ts": null,
            "event_id": null,
            "event_sub_type": null,
            "event_timestamp": [
                1648560949290
            ],
            "event_type": [
                1
            ],
            "events_length": 1,
            "external_id": "d4c2983dfab74741b087dce1bbffd8d5",
            "family_tags": null,
            "filter_rule_id": null,
            "forensics_artifact_type": null,
            "from_dml": null,
            "fw_app_category": null,
            "fw_app_id": null,
            "fw_app_subcategory": null,
            "fw_app_technology": null,
            "fw_device_name": null,
            "fw_email_recipient": null,
            "fw_email_sender": null,
            "fw_email_subject": null,
            "fw_interface_from": null,
            "fw_interface_to": null,
            "fw_is_phishing": [
                "NOT_AVAILABLE"
            ],
            "fw_misc": null,
            "fw_rule": null,
            "fw_rule_id": null,
            "fw_serial_number": null,
            "fw_url_domain": null,
            "fw_vsys": null,
            "fw_xff": null,
            "identity_invoked_by_type": null,
            "identity_name": null,
            "identity_sub_type": null,
            "identity_type": null,
            "image_name": null,
            "internal_id": "6887",
            "iot_pivot_url": null,
            "is_disintegrated": null,
            "is_pcap": false,
            "is_whitelisted": false,
            "is_xsoar_alert": false,
            "last_modified_ts": 1663742717853,
            "local_insert_ts": 1648560958017,
            "mac": "00:50:56:89:8b:8e",
            "matching_service_rule_id": null,
            "matching_status": "MATCHED",
            "mitre_tactic_id_and_name": [
                "TA0005 - Defense Evasion",
                "TA0002 - Execution"
            ],
            "mitre_technique_id_and_name": [
                "T1036.005 - Masquerading: Match Legitimate Name or Location"
            ],
            "module_id": [
                "Behavioral Threat Protection"
            ],
            "module_name": [
                "COMPONENT_DSE"
            ],
            "operation_name": null,
            "original_severity": "SEV_040_HIGH",
            "os_actor_causality_id": null,
            "os_actor_effective_username": null,
            "os_actor_process_causality_id": null,
            "os_actor_process_command_line": null,
            "os_actor_process_execution_time": null,
            "os_actor_process_image_md5": null,
            "os_actor_process_image_name": null,
            "os_actor_process_image_path": null,
            "os_actor_process_image_sha256": null,
            "os_actor_process_instance_id": null,
            "os_actor_process_os_pid": null,
            "os_actor_process_signature_status": [
                "SIGNATURE_UNAVAILABLE"
            ],
            "os_actor_process_signature_vendor": null,
            "os_actor_thread_thread_id": [
                2468
            ],
            "phone_number": null,
            "pivot_url": null,
            "playbook_suggestion_rule_id": null,
            "policy_id": null,
            "project": null,
            "query_tables": null,
            "referenced_resource": null,
            "remote_cid": null,
            "resolution_comment": "",
            "resolution_status": "STATUS_020_UNDER_INVESTIGATION",
            "resource_sub_type": null,
            "resource_type": null,
            "severity": "SEV_040_HIGH",
            "source_insert_ts": 1648560949290,
            "starred": false,
            "story_id": null,
            "suggested_playbook_id": null,
            "tim_main_indicator": null,
            "user_agent": null,
            "xpanse_asset_id": null,
            "xpanse_asset_name": null,
            "xpanse_policy_id": null,
            "xpanse_primary_asset_id": null,
            "xpanse_service_id": null
        }
    }
}

Human Readable Output

Alerts

Action Alert ID Category Description Detection Timestamp Host IP Host Name Name Severity
detected (reported) 6887 Malware Behavioral threat detected (rule: bioc.masquerade_svchost) 2022-03-29T13:35:49.000Z 10.111.230.11 DC1ENV1APC02 Behavioral Threat SEV_040_HIGH

xdr-get-contributing-event


Retrieves contributing events for a specific correlation alert.
Known limitation: the command is compatible only with correlation alerts, otherwise an error will be raised.

Required Permissions

Required Permissions For API call:
Alerts & Incidents –> View

Builtin Roles with this permission includes: “Investigator”, “Responder”, “Privileged Investigator”, “Privileged Responder”, “Viewer”, and “Instance Admin”.

Base Command

xdr-get-contributing-event

Input

Argument Name Description Required
alert_ids The alert ID’s from where to retrieve the contributing events. Required
limit The maximum number of contributing events to retrieve. Default is 50. Optional
page_number The page number to retrieve. Minimum is 1. Default is 1. Optional
page_size The page size. Default is 50. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.ContributingEvent.alertID String The alert ID.
PaloAltoNetworksXDR.ContributingEvent.events Unknown Contributing events per alert.

Command example

!xdr-get-contributing-event alert_ids=`[123456 , 123457]`

Context Example

{
    "PaloAltoNetworksXDR": {
        "ContributingEvent": [
            {
                "alertID": "123456",
                "events": [
                    {
                        "Domain": "WIN10X64",
                        "Host_Name": "WIN10X64",
                        "Logon_Type": "7",
                        "Process_Name": "C:\\Windows\\System32\\svchost.exe",
                        "Raw_Message": "An account was successfully logged on.",
                        "Source_IP": "1.1.1.1",
                        "User_Name": "xsoar",
                        "111111": 15,
                        "222222": 165298280000,
                        "333333": "abcdef",
                        "444444": 1,
                        "555555": "ghijk",
                        "_is_cardable": true,
                        "_product": "XDR agent",
                        "_time": 165298280000,
                        "_vendor": "PANW",
                        "insert_timestamp": 165298280001
                    }
                ]
            },
            {
                "alert_id": "123457",
                "events": [
                    {
                        "Domain": "WIN10X64",
                        "Host_Name": "WIN10X64",
                        "Logon_Type": "7",
                        "Process_Name": "C:\\Windows\\System32\\svchost.exe",
                        "Raw_Message": "An account was successfully logged on",
                        "Source_IP": "1.1.1.1",
                        "User_Name": "xsoar",
                        "111111": 15,
                        "222222": 165298280000,
                        "333333": "abcdef",
                        "444444": 1,
                        "555555": "ghijk",
                        "_is_cardable": true,
                        "_product": "XDR agent",
                        "_time": 165298280000,
                        "_vendor": "PANW",
                        "insert_timestamp": 165298280001
                    }
                ]
            }
        ]
    }
}

Human Readable Output

Contributing events

Alert _ Id Events
123456 - Logon_Type: 7
User_Name: xsoar
Domain: WIN10X64
Source_IP: 1.1.1.1
Process_Name: C:\Windows\System32\svchost.exe
Host_Name: WIN10X64
Raw_Message: An account was successfully logged on. _time: 165298280000
555555: a1b2c3d4
222222: 165298280000
333333: abcdef
111111: 15
444444: 1
insert_timestamp: 165298280001
_vendor: PANW
_product: XDR agent
_is_cardable: true
123457 - Logon_Type: 7
User_Name: xsoar
Domain: WIN10X64
Source_IP: 1.1.1.1
Process_Name: C:\Windows\System32\svchost.exe
Host_Name: WIN10X64
Raw_Message: An account was successfully logged on. _time: 165298280000
555555: ghijk
222222: 165298280000
333333: abcdef
111111: 15
444444: 1
insert_timestamp: 165298280001
_vendor: PANW
_product: XDR agent
_is_cardable: true

xdr-replace-featured-field


Replace the featured hosts\users\IP addresses\active directory groups listed in your environment.

Required Permissions

Required Permissions For API call:
Alerts & Incidents –> View/ Edit

Builtin Roles with this permission includes: “Investigator”, “Privileged Investigator”, “Privileged Responder” and “Instance Admin”.

Base Command

xdr-replace-featured-field

Input

Argument Name Description Required
field_type The field type to change. Possible values are: hosts, users, ip_addresses, ad_groups. Required
values The string value, which defines the new field. Maximum length is 256 characters. Required
comments The string value, which represents additional information regarding the featured alert field. Optional
ad_type The string value to replace an active directory group or organizational unit. Possible values are: group, ou. Default is group. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.FeaturedField.fieldType String The field type that changed.
PaloAltoNetworksXDR.FeaturedField.fields String String value that defines the new field.

Command example

!xdr-replace-featured-field field_type=ip_addresses values=`["1.1.1.1"]` comments=`new ip address`

Context Example

{
    "PaloAltoNetworksXDR": {
        "FeaturedField": {
            "fieldType": "ip_addresses",
            "fields": [
                {
                    "comment": "new ip address",
                    "value": "1.1.1.1"
                }
            ]
        }
    }
}

Human Readable Output

Replaced featured: ip_addresses

Comment Value
new ip address 1.1.1.1

xdr-list-users


Retrieve a list of the current users in the environment.
Required license: Cortex XDR Pro per Endpoint, Cortex XDR Pro, or Cortex XDR Pro per GB.

Base Command

xdr-list-users

Input

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

Context Output

Path Type Description
PaloAltoNetworksXDR.User.user_email string Email address of the user
PaloAltoNetworksXDR.User.user_first_name string First name of the user
PaloAltoNetworksXDR.User.user_last_name string Last name of the user.
PaloAltoNetworksXDR.User.role_name string Role name associated with the user.
PaloAltoNetworksXDR.User.last_logged_in Number Timestamp of when the user last logged in.
PaloAltoNetworksXDR.User.user_type string Type of user.
PaloAltoNetworksXDR.User.groups array Name of user groups associated with the user, if applicable.
PaloAltoNetworksXDR.User.scope array Name of scope associated with the user, if applicable.

Command example


#### Context Example

```json
{
    "dummy": {
        "User": [
            {
                "groups": [],
                "last_logged_in": 1648158415051,
                "role_name": "dummy",
                "scope": [],
                "user_email": "dummy@dummy.com",
                "user_first_name": "dummy",
                "user_last_name": "dummy",
                "user_type": "dummy"
            },
             {
                "groups": [],
                "last_logged_in": null,
                "role_name": "dummy",
                "scope": [],
                "user_email": "dummy@dummy.com",
                "user_first_name": "dummy",
                "user_last_name": "dummy",
                "user_type": "dummy"
            }            
        ]
    }
}

Human Readable Output

Users

First Name Groups Last Name Role Type User email
dummy   dummy dummy dummy dummy
dummy   dummy dummy dummy dummy

xdr-list-risky-users


Retrieve the risk score of a specific user or list of users with the highest risk score in the environment along with the reason affecting each score.
Required license: Cortex XDR Pro per Endpoint, Cortex XDR Pro, or Cortex XDR Pro per GB.

Base Command

xdr-list-risky-users

Input

Argument Name Description Required
user_id Unique ID of a specific user.
User ID could be either of the foo/dummy format, or just dummy.
.
Optional
limit Limit the number of users that will appear in the list. (Use limit when no specific host is requested.). Default is 10. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.RiskyUser.type String Form of identification element.
PaloAltoNetworksXDR.RiskyUser.id String Identification value of the type field.
PaloAltoNetworksXDR.RiskyUser.score Number The score assigned to the user.
PaloAltoNetworksXDR.RiskyUser.reasons.date created String Date when the incident was created.
PaloAltoNetworksXDR.RiskyUser.reasons.description String Description of the incident.
PaloAltoNetworksXDR.RiskyUser.reasons.severity String The severity of the incident
PaloAltoNetworksXDR.RiskyUser.reasons.status String The incident status
PaloAltoNetworksXDR.RiskyUser.reasons.points Number The score.

Command example

!xdr-list-risky-users user_id=dummy

Context Example

{
    "PaloAltoNetworksXDR": {
        "RiskyUser": {
            "id": "dummy",
            "reasons": [],
            "score": 0,
            "type": "user"
        }
    }
}

Human Readable Output

Risky Users

User ID Score Description
dummy 0  

xdr-list-risky-hosts


Retrieve the risk score of a specific host or list of hosts with the highest risk score in the environment along with the reason affecting each score.
Required license: Cortex XDR Pro per Endpoint, Cortex XDR Pro, or Cortex XDR Pro per GB.

Base Command

xdr-list-risky-hosts

Input

Argument Name Description Required
host_id The host name of a specific host. Optional
limit Limit the number of hosts that will appear in the list. By default, the limit is 50 hosts.(Use limit when no specific host is requested.). Default is 10. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.RiskyHost.type String Form of identification element.
PaloAltoNetworksXDR.RiskyHost.id String Identification value of the type field.
PaloAltoNetworksXDR.RiskyHost.score Number The score assigned to the host.
PaloAltoNetworksXDR.RiskyHost.reasons.date created String Date when the incident was created.
PaloAltoNetworksXDR.RiskyHost.reasons.description String Description of the incident.
PaloAltoNetworksXDR.RiskyHost.reasons.severity String The severity of the incident
PaloAltoNetworksXDR.RiskyHost.reasons.status String The incident status
PaloAltoNetworksXDR.RiskyHost.reasons.points Number The score.

Command example

!xdr-list-risky-hosts host_id=dummy

Context Example

{
    "PaloAltoNetworksXDR": {
        "RiskyHost": {
            "id": "dummy",
            "reasons": [],
            "score": 0,
            "type": "dummy"
        }
    }
}

Human Readable Output

Risky Hosts

Host ID Score Description
dummy 0  

xdr-list-user-groups


Retrieve a list of the current user emails associated with one or more user groups in the environment.
Required license: Cortex XDR Pro per Endpoint, Cortex XDR Pro, or Cortex XDR Pro per GB.

Base Command

xdr-list-user-groups

Input

Argument Name Description Required
group_names A comma-separated list of one or more user group names for which you want the associated users. Required

Context Output

Path Type Description
PaloAltoNetworksXDR.UserGroup.group_name String Name of the user group.
PaloAltoNetworksXDR.UserGroup.description String Description of the user group, if available.
PaloAltoNetworksXDR.UserGroup.pretty_name String Name of the user group as it appears in the management console.
PaloAltoNetworksXDR.UserGroup.insert_time Number Timestamp of when the user group was created.
PaloAltoNetworksXDR.UserGroup.update_time Number Timestamp of when the user group was last updated.
PaloAltoNetworksXDR.UserGroup.user_email array List of email addresses belonging to the users associated with the user group.
PaloAltoNetworksXDR.UserGroup.source String Type of user group.

Command example

!xdr-list-user-groups group_names=test

Context Example

{
    "PaloAltoNetworksXDR": {
        "UserGroup": {
            "description": "test",
            "group_name": "test",
            "insert_time": 1684746187678,
            "pretty_name": null,
            "source": "Custom",
            "update_time": 1684746209062,
            "user_email": [
                null
            ]
        }
    }
}

Human Readable Output

Groups

Group Name Group Description User email
test test for demo  

xdr-list-roles


Retrieve information about one or more roles created in the environment.
Required license: Cortex XDR Pro per Endpoint, Cortex XDR Pro, or Cortex XDR Pro per GB.

Base Command

xdr-list-roles

Input

Argument Name Description Required
role_names A comma-separated list of one or more role names in your environment for which you want detailed information. Required

Context Output

Path Type Description
PaloAltoNetworksXDR.Role.pretty_name String Name of the role as it appears in the management console.
PaloAltoNetworksXDR.Role.permissions array List of permissions associated with this role.
PaloAltoNetworksXDR.Role.insert_time Number Timestamp of when the role was created.
PaloAltoNetworksXDR.Role.update_time Number Timestamp of when the role was last updated.
PaloAltoNetworksXDR.Role.created_by String Email of the user who created the role.
PaloAltoNetworksXDR.Role.description String Description of the role, if available.
PaloAltoNetworksXDR.Role.groups array Group names associated with the role.
PaloAltoNetworksXDR.Role.users array Email address of users associated with the role.

Command example

!xdr-list-roles role_names=dummy

Context Example

{
    "PaloAltoNetworksXDR": {
        "Role": [
            [
                {
                    "created_by": "dummy dummy",
                    "description": "The user(s) have full access.",
                    "groups": [],
                    "insert_time": null,
                    "permissions": [
                        "dummy"
                    ],
                    "pretty_name": "dummy",
                    "update_time": null,
                    "users": []
                }
            ]
        ]
    }
}

Human Readable Output

Roles

Role Name Description Permissions Users Groups
dummy The user(s) have full access. ADMIN    

xdr-set-user-role


Add one or more users to a role.
Required license: Cortex XDR Pro per Endpoint, Cortex XDR Pro, or Cortex XDR Pro per GB.

Base Command

xdr-set-user-role

Input

Argument Name Description Required
user_emails A comma-separated list of one or more user emails of users you want to add to a role. Required
role_name Name of the role you want to add a user to. Required

Context Output

There is no context output for this command.

Command example

!xdr-set-user-role role_name=dummy user_emails=dummy

Human Readable Output

Role was updated successfully for 1 user.

xdr-remove-user-role


Remove one or more users from a role.
Required license: Cortex XDR Pro per Endpoint, Cortex XDR Pro, or Cortex XDR Pro per GB.

Base Command

xdr-remove-user-role

Input

Argument Name Description Required
user_emails A comma-separate list of one or more user emails of users you want to remove from a role. Required

Context Output

There is no context output for this command.

Command example

!xdr-remove-user-role user_emails=dummy

Human Readable Output

Role was removed successfully for 1 user.

xdr-script-run


Initiates a new endpoint script execution action using a script from the script library and returns the results.

Required Permissions

Required Permissions For API call:
Agent Scripts library –> View

Builtin Roles with this permission includes: “Privileged Responder”, “Viewer” and “Instance Admin”.

Base Command

xdr-script-run

Input

Argument Name Description Required
incident_id Allows linking the response action to the incident that triggered it. Optional
endpoint_ids A comma-separated list of endpoint IDs. Can be retrieved by running the xdr-get-endpoints command. Required
script_uid Unique identifier of the script. Can be retrieved by running the xdr-get-scripts command. Required
parameters Dictionary containing the parameter name as key and its value for this execution as the value. For example, {“param1”:”param1_value”,”param2”:”param2_value”}. Optional
timeout The timeout in seconds for this execution. Default is 600. Optional
polling_interval_in_seconds Interval in seconds between each poll. Default is 10. Optional
polling_timeout_in_seconds Polling timeout in seconds. Default is 600. Optional
action_id action ID for polling. Optional
hide_polling_output whether to hide the polling result (automatically filled by polling). Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.ScriptResult.action_id Number ID of the action initiated.
PaloAltoNetworksXDR.ScriptResult.results.retrieved_files Number Number of successfully retrieved files.
PaloAltoNetworksXDR.ScriptResult.results.endpoint_ip_address String Endpoint IP address.
PaloAltoNetworksXDR.ScriptResult.results.endpoint_name String Number of successfully retrieved files.
PaloAltoNetworksXDR.ScriptResult.results.failed_files Number Number of files failed to retrieve.
PaloAltoNetworksXDR.ScriptResult.results.endpoint_status String Endpoint status.
PaloAltoNetworksXDR.ScriptResult.results.domain String Domain to which the endpoint belongs.
PaloAltoNetworksXDR.ScriptResult.results.endpoint_id String Endpoint ID.
PaloAltoNetworksXDR.ScriptResult.results.execution_status String Execution status of this endpoint.
PaloAltoNetworksXDR.ScriptResult.results.return_value String Value returned by the script in case the type is not a dictionary.
PaloAltoNetworksXDR.ScriptResult.results.standard_output String The STDOUT and the STDERR logged by the script during the execution.
PaloAltoNetworksXDR.ScriptResult.results.retention_date Date Timestamp in which the retrieved files will be deleted from the server.

Base Command

xdr-script-run

Input

Argument Name Description Required
incident_id Allows linking the response action to the incident that triggered it. Optional
endpoint_ids A comma-separated list of endpoint IDs. Can be retrieved by running the xdr-get-endpoints command. Required
script_uid Unique identifier of the script. Can be retrieved by running the xdr-get-scripts command. Required
parameters Dictionary containing the parameter name as key and its value for this execution as the value. For example, {“param1”:”param1_value”,”param2”:”param2_value”}. Optional
timeout The timeout in seconds for this execution. Default is 600. Optional
polling_interval_in_seconds Interval in seconds between each poll. Default is 10. Optional
polling_timeout_in_seconds Polling timeout in seconds. Default is 600. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.ScriptResult.action_id Number ID of the action initiated.
PaloAltoNetworksXDR.ScriptResult.results.retrieved_files Number Number of successfully retrieved files.
PaloAltoNetworksXDR.ScriptResult.results.endpoint_ip_address String Endpoint IP address.
PaloAltoNetworksXDR.ScriptResult.results.endpoint_name String Number of successfully retrieved files.
PaloAltoNetworksXDR.ScriptResult.results.failed_files Number Number of files failed to retrieve.
PaloAltoNetworksXDR.ScriptResult.results.endpoint_status String Endpoint status.
PaloAltoNetworksXDR.ScriptResult.results.domain String Domain to which the endpoint belongs.
PaloAltoNetworksXDR.ScriptResult.results.endpoint_id String Endpoint ID.
PaloAltoNetworksXDR.ScriptResult.results.execution_status String Execution status of this endpoint.
PaloAltoNetworksXDR.ScriptResult.results.return_value String Value returned by the script in case the type is not a dictionary.
PaloAltoNetworksXDR.ScriptResult.results.standard_output String The STDOUT and the STDERR logged by the script during the execution.
PaloAltoNetworksXDR.ScriptResult.results.retention_date Date Timestamp in which the retrieved files will be deleted from the server.

Command example

!xdr-script-run endpoint_ids=1 script_uid=123

Human Readable Output

Waiting for the script to finish running on the following endpoints: [‘1’]…

Script Execution Results - 10368

_return_value domain endpoint_id endpoint_ip_address endpoint_name endpoint_status execution_status failed_files retention_date retrieved_files standard_output
Name: return value WORKGROUP 1 1.1.1.1 WIN10X64 STATUS_010_CONNECTED COMPLETED_SUCCESSFULLY 0   0  

Context Example

{
  "PaloAltoNetworksXDR": {
    "ScriptResult": {
      "results": [
        {
          "domain": "WORKGROUP",
          "endpoint_name": "WIN10X64",
          "retrieved_files": 0,
          "failed_files": 0,
          "standard_output": "",
          "_return_value": [
            "return_value"
          ],
          "command_output": [
            "command_output"
          ],
          "endpoint_status": "STATUS_010_CONNECTED",
          "command": "_return_value",
          "endpoint_id": "1",
          "endpoint_ip_address": [
            "1.1.1.1"
          ],
          "execution_status": "COMPLETED_SUCCESSFULLY",
          "retention_date": null
        }
      ],
      "action_id": 4444
    }
  }
}

xdr-endpoint-tag-add


Adds a tag to specified endpoint_ids

Required Permissions

Required Permissions For API call:
Endpoint Administrations –> View/ Edit

Builtin Roles with this permission includes: “Privileged Responder” and “Instance Admin”.

Base Command

xdr-endpoint-tag-add

Input

Argument Name Description Required
endpoint_ids List of endpoint IDs. Supports comma-separated list. Optional
tag Tag to add. Optional

Context Output

There is no context output for this command.

xdr-endpoint-tag-remove


Removes a tag from specified endpoint_ids.

Required Permissions

Required Permissions For API call:
Endpoint Administrations –> View/ Edit

Builtin Roles with this permission includes: “Privileged Responder” and “Instance Admin”.

Base Command

xdr-endpoint-tag-remove

Input

Argument Name Description Required
endpoint_ids List of endpoint IDs. Supports comma separated list. Optional
tag Tag to remove from specified endpoint_ids. Optional

Context Output

There is no context output for this command.

xdr-get-tenant-info


Provides information about the tenant.

Base Command

xdr-get-tenant-info

Input

There are no arguments for this command.

Context Output

Path Type Description
PaloAltoNetworksXDR.TenantInformation.pro_per_endpoint_expiration Date Expiration time pro per endpoint.
PaloAltoNetworksXDR.TenantInformation.purchased_pro_per_endpoint.agents Number Number of endpoints agent purchased.
PaloAltoNetworksXDR.TenantInformation.data_enabled_pro_per_endpoint Number Enabled data per pro endpoint.
PaloAltoNetworksXDR.TenantInformation.prevent_expiration Number Number of prevent expirations.
PaloAltoNetworksXDR.TenantInformation.purchased_prevent Number Number of purchased prevents.
PaloAltoNetworksXDR.TenantInformation.installed_prevent Number Number of installed prevents.
PaloAltoNetworksXDR.TenantInformation.pro_tb_expiration Date pro_tb license expiration time.
PaloAltoNetworksXDR.TenantInformation.purchased_pro_tb.tb Number Number of pro_tbs purchased.
PaloAltoNetworksXDR.TenantInformation.installed_pro_tb Number Number of pro_tbs installed.
PaloAltoNetworksXDR.TenantInformation.compute_unit_expiration Date Compute unit expiration time.
PaloAltoNetworksXDR.TenantInformation.purchased_compute_unit Number Number of compute units purchased.
PaloAltoNetworksXDR.TenantInformation.compute_unit_is_trial Boolean Whether the compute unit is a trial.
PaloAltoNetworksXDR.TenantInformation.host_insights_expiration Date Host insight expiration time.
PaloAltoNetworksXDR.TenantInformation.enabled_host_insights Number Number of host insights enabled.
PaloAltoNetworksXDR.TenantInformation.purchased_host_insights Number Number of purchased host insights.
PaloAltoNetworksXDR.TenantInformation.forensics_expiration Date Forensic expiration time.
PaloAltoNetworksXDR.TenantInformation.purchased_forensics Number Number of forensics purchased.

Command example


#### Context Example

```json
{
    "PaloAltoNetworksXDR": {
        "TenantInformation": {
            "compute_unit_expiration": 0,
            "data_enabled_pro_per_endpoint": 2,
            "forensics_expiration": 0,
            "installed_prevent": 2,
            "installed_pro_tb": 0,
            "prevent_expiration": 0,
            "pro_per_endpoint_expiration": "May 7th 2025 06:59:59",
            "pro_tb_expiration": "May 7th 2025 06:59:59",
            "purchased_compute_unit": 2000,
            "purchased_prevent": 0,
            "purchased_pro_per_endpoint": {
                "agents": 300
            },
            "purchased_pro_tb": {
                "tb": 1
            }
        }
    }
}

Human Readable Output

Tenant Information

Compute Unit Expiration Data Enabled Pro Per Endpoint Forensics _ Expiration Installed _ Prevent Installed Pro Tb Prevent _ Expiration Pro Per Endpoint _ Expiration Pro Tb Expiration Purchased Compute Unit Purchased _ Prevent Purchased Pro Per _ Endpoint Purchased Pro Tb
            May 7th 2025 06:59:59 May 7th 2025 06:59:59     agents: 300 tb: 1

Incident Mirroring

You can enable incident mirroring between Cortex XSOAR incidents and Palo Alto Networks Cortex XDR - Investigation and Response 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 Mirroring Direction integration parameter, select in which direction the incidents should be mirrored:

    Option Description
    None Turns off incident mirroring.
    Incoming Any changes in Palo Alto Networks Cortex XDR - Investigation and Response events (mirroring incoming fields) will be reflected in Cortex XSOAR incidents.
    Outgoing Any changes in Cortex XSOAR incidents will be reflected in Palo Alto Networks Cortex XDR - Investigation and Response events (outgoing mirrored fields).
    Both  

Newly fetched incidents will be mirrored in the chosen direction. However, this selection does not affect existing incidents.
Important Note: To ensure the mirroring works as expected, mappers are required, both for incoming and outgoing, to map the expected fields in Cortex XSOAR and Palo Alto Networks Cortex XDR - Investigation and Response.

xdr-endpoint-alias-change


Gets a list of endpoints according to the passed filters, and changes their alias name. Filtering by multiple fields will be concatenated using the AND condition (OR is not supported).

Base Command

xdr-endpoint-alias-change

Input

Argument Name Description Required
status The status of the endpoint to use as a filter. Possible values are: connected, disconnected. Optional
endpoint_id_list A comma-separated list of endpoint IDs to use as a filter. Optional
dist_name A comma-separated list of distribution package names or installation package names to use as a filter.
Example: dist_name1,dist_name2.
Optional
ip_list A comma-separated list of IP addresses to use as a filter.
Example: 8.8.8.8,1.1.1.1.
Optional
group_name A comma-separated list of group names to which the agent belongs to use as a filter.
Example: group_name1,group_name2.
Optional
platform The endpoint platform to use as a filter. Possible values are: windows, linux, macos, android. Optional
alias_name A comma-separated list of alias names to use as a filter.
Examples: alias_name1,alias_name2.
Optional
isolate Specifies whether the endpoint was isolated or unisolated to use as a filter. Possible values are: isolated, unisolated. Note: This argument returns only the first endpoint that matches the filter. Optional
hostname A comma-separated list of hostnames to use as a filter.
Example: hostname1,hostname2.
Optional
first_seen_gte All the agents that were first seen after {first_seen_gte} to use as a filter.
Supported values:
1579039377301 (time in milliseconds)
“3 days” (relative date)
“2019-10-21T23:45:00” (date).
Optional
first_seen_lte All the agents that were first seen before {first_seen_lte} to use as a filter.
Supported values:
1579039377301 (time in milliseconds)
“3 days” (relative date)
“2019-10-21T23:45:00” (date).
Optional
last_seen_gte All the agents that were last seen after {last_seen_gte} to use as a filter.
Supported values:
1579039377301 (time in milliseconds)
“3 days” (relative date)
“2019-10-21T23:45:00” (date).
Optional
last_seen_lte All the agents that were last seen before {last_seen_lte} to use as a filter.
Supported values:
1579039377301 (time in milliseconds)
“3 days” (relative date)
“2019-10-21T23:45:00” (date).
Optional
username The usernames to query for to use as a filter. Accepts a single user, or comma-separated list of usernames. Optional
new_alias_name The alias name to change to. Note: If you send an empty field, (e.g new_alias_name="") the current alias name is deleted. Required
scan_status The scan status of the endpoint to use as a filter. Possible values are: none, pending, in_progress, canceled, aborted, pending_cancellation, success, error. Optional

Context Output

There is no context output for this command.

Command example

!xdr-endpoint-alias-change new_alias_name=test scan_status=success ip_list=1.1.1.1

Human Readable Output

The endpoint alias was changed successfully.
Note: If there is no error in the process, then this is the output even when the specific endpoint does not exist.

xdr-update-alert (Deprecated)


This command is deprecated, please use xdr-issue-update instead. Update one or more alerts. You can update up to 100 alerts per request. Missing fields are ignored. Required license: Cortex XDR Prevent, Cortex XDR Pro per Endpoint, or Cortex XDR Pro per GB.

Base Command

xdr-update-alert

Input

Argument Name Description Required
alert_ids Comma-separated list of alert IDs. Required
severity Severity of the incident which was closed. Possible values are: critical, high, medium, low. Optional
status New status for updated alerts. Possible values are: new, resolved_threat_handled, under_investigation, resolved_security_testing, resolved_auto, resolved_auto_resolve, resolved_known_issue, resolved_duplicate, resolved_other, resolved_false_positive, resolved_true_positive. Optional
comment Comment to append to updated alerts. Optional

Context Output

There is no context output for this command.

Command example

!xdr-update-alert alert_ids=35326 severity=low

Human Readable Output

Alerts with IDs 35326 have been updated successfully.

xdr-asset-group-update


Updates an asset group.

Base Command

xdr-asset-group-update

Input

Argument Name Description Required
group_id The ID of the asset group to update. Required
group_name The name of the asset group. Optional
group_type The type of the asset group. Possible values are: Static, Dynamic. Optional
group_description The description of the asset group. Optional
membership_predicate_json Define the filter conditions for selecting which assets to be included in a dynamic Asset Group. For example: “{"AND":[{"SEARCH_FIELD":"xdm.asset.type.class","SEARCH_TYPE":"NEQ","SEARCH_VALUE":"Other"}]}”. The SEARCH_FIELD needs to be in lower case. For more information, please refer to the documentation: https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Update-an-Asset-Group. Optional

Context Output

There is no context output for this command.

xdr-asset-group-create


Creates an asset group.

Base Command

xdr-asset-group-create

Input

Argument Name Description Required
group_name The name of the asset group. Required
group_type The type of the asset group. Possible values are: Static, Dynamic. Required
group_description The description of the asset group. Optional
membership_predicate_json Define the filter conditions for selecting which assets to be included in a dynamic Asset Group. For example: “{"AND":[{"SEARCH_FIELD":"xdm.asset.type.class","SEARCH_TYPE":"NEQ","SEARCH_VALUE":"Other"}]}”. The SEARCH_FIELD needs to be in lower case. For more information, please refer to the documentation: https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Create-an-Asset-Group. Optional

Context Output

There is no context output for this command.

xdr-asset-group-list


Returns a list of asset groups.

Base Command

xdr-asset-group-list

Input

Argument Name Description Required
sort_field The field by which to sort the results. For example: XDM.ASSET_GROUP.NAME. Use the documentation for finding more fields here. Optional
sort_order The order in which to sort the results. Possible values are: asc, desc. Optional
filter_json The JSON to use for the “filter” field in the API. For example: “{"AND": [{"SEARCH_FIELD": "XDM.ASSET_GROUP.TYPE", "SEARCH_TYPE": "EQ", "SEARCH_VALUE": "Dynamic"}]}”. For more information, please refer to the documentation: https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Get-all-or-filtered-asset-groups. Optional
limit Maximum number of asset groups to return. Default is 50. Default is 50. Optional
page_size Page size for pagination. Optional
page Page number for pagination. 0 is the first page. Default is 0. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.AssetGroup.XDM_ASSET_GROUP_ID String The unique identifier of the asset group.
PaloAltoNetworksXDR.AssetGroup.XDM_ASSET_GROUP_NAME String The name of the asset group.
PaloAltoNetworksXDR.AssetGroup.XDM_ASSET_GROUP_TYPE String The type of the asset group (Static or Dynamic).
PaloAltoNetworksXDR.AssetGroup.XDM_ASSET_GROUP_FILTER String The filter query string used to define the members of the XDM asset group.
PaloAltoNetworksXDR.AssetGroup.XDM_ASSET_GROUP_DESCRIPTION String The description of the asset group.
PaloAltoNetworksXDR.AssetGroup.XDM_ASSET_GROUP_CREATION_TIME Date The timestamp when the group was created (Epoch).
PaloAltoNetworksXDR.AssetGroup.XDM_ASSET_GROUP_LAST_UPDATE_TIME Date The timestamp when the group was last updated (Epoch).
PaloAltoNetworksXDR.AssetGroup.XDM_ASSET_GROUP_CREATED_BY_PRETTY String The user or API key that created the group.
PaloAltoNetworksXDR.AssetGroup.XDM_ASSET_GROUP_MODIFIED_BY_PRETTY String The user or API key that last modified the group.
PaloAltoNetworksXDR.AssetGroup.XDM_ASSET_GROUP_MEMBERSHIP_PREDICATE Unknown The JSON object defining the search criteria for dynamic groups.
PaloAltoNetworksXDR.AssetGroup.IS_USED_BY_SBAC Boolean Indicates whether this asset group is currently utilized to define Scope-Based Access Control (SBAC) rules.

xdr-asset-group-delete


Deletes an asset group.

Base Command

xdr-asset-group-delete

Input

Argument Name Description Required
group_id The ID of the asset group to delete. Required

Context Output

There is no context output for this command.

xdr-asset-schema-field-options-get


Gets the enum values of a specified field in the asset schema.

Base Command

xdr-asset-schema-field-options-get

Input

Argument Name Description Required
field_name The name of the field to get options for. You can use xdr-asset-schema-get in order to get the schema. The field must be of type ENUM, for example: xdm.asset.provider. Required

Context Output

Path Type Description
PaloAltoNetworksXDR.AssetSchema.field_name String The name of the field.
PaloAltoNetworksXDR.AssetSchema.options Unknown The enum values of the field.

xdr-asset-schema-get


Gets the schema of the asset inventory.

Base Command

xdr-asset-schema-get

Input

There are no input arguments for this command.

Context Output

Path Type Description
PaloAltoNetworksXDR.AssetSchema.field_name String The name of the field.
PaloAltoNetworksXDR.AssetSchema.field_type String The type of the field.
PaloAltoNetworksXDR.AssetSchema.field_pretty_name String The pretty name of the field.

xdr-asset-list


Returns a list of assets.

Base Command

xdr-asset-list

Input

Argument Name Description Required
asset_id A comma-separated list of asset IDs. For example: “1,2,3”. When using this argument, the other filters will not be used. Optional
sort_field The field by which to sort the results. For example: xdm.asset.first_observed. Use the documentation for finding more fields here. Optional
sort_order The order in which to sort the results. Possible values are: asc, desc. Optional
filter_json The JSON to use for the “filter” field in the API. You can use xdr-asset-schema-get to get the schema. For more information, please refer to the documentation: here. Optional
limit Maximum number of assets to return. Default is 50. Default is 50. Optional
page_size Page size for pagination. Default is 50. Optional
page Page number for pagination. Default is 0. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.Asset.xdm_asset_id String The unique identifier of the asset.
PaloAltoNetworksXDR.Asset.xdm_asset_name String The name of the asset (e.g., image SHA).
PaloAltoNetworksXDR.Asset.xdm_asset_strong_id String The strong identifier (SHA256) of the asset.
PaloAltoNetworksXDR.Asset.xdm_asset_provider String The provider of the asset.
PaloAltoNetworksXDR.Asset.xdm_asset_external_provider_id String External provider ID (if applicable).
PaloAltoNetworksXDR.Asset.xdm_asset_first_observed Date Timestamp when the asset was first observed.
PaloAltoNetworksXDR.Asset.xdm_asset_last_observed Date Timestamp when the asset was last observed.
PaloAltoNetworksXDR.Asset.xdm_asset_type_id String The ID of the asset type (e.g., CORE_IMAGE).
PaloAltoNetworksXDR.Asset.xdm_asset_type_name String The name of the asset type.
PaloAltoNetworksXDR.Asset.xdm_asset_type_category String The category of the asset (e.g., Container Image).
PaloAltoNetworksXDR.Asset.xdm_asset_type_class String The class of the asset (e.g., Compute).
PaloAltoNetworksXDR.Asset.xdm_development_cycle_stages String The development cycle stages (e.g., RUNTIME).
PaloAltoNetworksXDR.Asset.xdm_image_architecture String The architecture of the image (e.g., amd64).
PaloAltoNetworksXDR.Asset.xdm_image_digest String The image digest hash.
PaloAltoNetworksXDR.Asset.xdm_image_identifier String The image identifier.
PaloAltoNetworksXDR.Asset.xdm_image_names String List of names/tags associated with the image.
PaloAltoNetworksXDR.Asset.xdm_image_os String The OS version of the image.
PaloAltoNetworksXDR.Asset.xdm_image_os_distribution String The OS distribution (e.g., UBUNTU).
PaloAltoNetworksXDR.Asset.xdm_image_os_family String The OS family (e.g., Linux).
PaloAltoNetworksXDR.Asset.xdm_image_os_release String The OS release name (e.g., noble).
PaloAltoNetworksXDR.Asset.xdm_image_labels Unknown Labels associated with the image.
PaloAltoNetworksXDR.Asset.xdm_image_layers Unknown List of layers making up the image.
PaloAltoNetworksXDR.Asset.xdm_image_layers_created_at Number Timestamp of when the layer was created.
PaloAltoNetworksXDR.Asset.xdm_image_layers_instruction String The instruction used to create the layer (e.g., RUN, ADD).
PaloAltoNetworksXDR.Asset.xdm_image_layers_size Number The size of the layer in bytes.
PaloAltoNetworksXDR.Asset.xdm_asset_related_cases_cases_breakdown Unknown Breakdown of related cases by severity.
PaloAltoNetworksXDR.Asset.xdm_asset_related_cases_critical_cases Number Count of critical cases.
PaloAltoNetworksXDR.Asset.xdm_asset_related_issues_issues_breakdown Unknown Breakdown of related issues by severity.
PaloAltoNetworksXDR.Asset.xdm_asset_relations Unknown Relations to other assets.
PaloAltoNetworksXDR.Asset.xdm_asset_group_ids Number Group IDs associated with the asset.

xdr-api-key-delete


Deletes the specified API keys.

Base Command

xdr-api-key-delete

Input

Argument Name Description Required
api_id A comma-separated list of API key IDs to delete. Required

Context Output

There is no context output for this command.

xdr-api-key-list


Gets a list of existing API keys.

Base Command

xdr-api-key-list

Input

Argument Name Description Required
api_id A comma-separated list of API key IDs to retrieve. Optional
role A comma-separated list of API key roles to retrieve. Optional
expires_before Filters API keys that expire before this date. For example: “1 year”. Filters API keys that have an expiration date. Optional
expires_after Filters API keys that expire after this date. For example: “1 year”. Filters API keys that have an expiration date. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.APIKeyData.id String The API key ID.
PaloAltoNetworksXDR.APIKeyData.roles String The roles associated with the API key.
PaloAltoNetworksXDR.APIKeyData.expiration Date The expiration date of the API key.

xdr-bioc-list


Returns a list of BIOCs.

Base Command

xdr-bioc-list

Input

Argument Name Description Required
name The BIOC name to filter by. Can filter by only one name at a time. Optional
severity The BIOC severity to filter by. Possible values are: info, low, medium, high, critical. Optional
type The BIOC type to filter by. Possible values are: other, persistence, evasion, tampering, file_type_obfuscation, privilege_escalation, credential_access, lateral_movement, execution, collection, exfiltration, infiltration, dropper, file_privilege_manipulation, reconnaissance, discovery. Optional
is_xql Whether the BIOC is XQL. Possible values are: true, false. Optional
comment The BIOC comment to filter by. Optional
status The BIOC status to filter by. Possible values are: enabled, disabled. Optional
indicator The BIOC indicator to filter by. Optional
mitre_technique_id_and_name The MITRE technique ID and name. Must be in format ‘ID - Name’, for example: [‘T1566 - Phishing’]. Optional
mitre_tactic_id_and_name The MITRE tactic ID and name. Must be in format ‘ID - Name’, for example: [‘T1566 - Phishing’]. Optional
extra_data Whether to return extended data. Possible values are: true, false. Optional
limit Maximum number of results to return. Optional
page_size Page size. Optional
page Page number. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.BIOC.rule_id String BIOC rule ID.
PaloAltoNetworksXDR.BIOC.name String BIOC name.
PaloAltoNetworksXDR.BIOC.type String BIOC type.
PaloAltoNetworksXDR.BIOC.severity String BIOC severity.
PaloAltoNetworksXDR.BIOC.status String BIOC status.
PaloAltoNetworksXDR.BIOC.is_xql Boolean Whether the BIOC is XQL.
PaloAltoNetworksXDR.BIOC.comment String The BIOC comment.
PaloAltoNetworksXDR.BIOC.indicator String The BIOC indicator.

xdr-bioc-create


Creates a new BIOC.

Base Command

xdr-bioc-create

Input

Argument Name Description Required
name The BIOC name. Required
severity The BIOC severity. Possible values are: info, low, medium, high, critical. Required
type The BIOC type. Possible values are: other, persistence, evasion, tampering, file_type_obfuscation, privilege_escalation, credential_access, lateral_movement, execution, collection, exfiltration, infiltration, dropper, file_privilege_manipulation, reconnaissance, discovery. Optional
is_xql Whether the new BIOC is XQL. Possible values are: true, false. Optional
comment The BIOC comment. Optional
status The BIOC status. Possible values are: enabled, disabled. Optional
indicator The BIOC indicator,
for example: ‘{“runOnCGO”:true,”investigationType”:”FILE_EVENT”,”investigation”:{“FILE_EVENT”:{“filter”:{“AND”:[{“SEARCH_FIELD”:”action_file_name”,”SEARCH_TYPE”:”EQ”,”SEARCH_VALUE”:”testfile.exe”}]}}}}’.
For more information, refer to the documentation here.
Required
mitre_technique_id_and_name The MITRE technique ID and name. Must be in format ‘ID - Name’, for example: [‘T1566 - Phishing’]. Optional
mitre_tactic_id_and_name The MITRE tactic ID and name. Must be in format ‘ID - Name’, for example: [‘TA0001 - Initial Access’]. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.BIOC.rule_id String BIOC ID.

xdr-bioc-update


Updates an existing BIOC.

Base Command

xdr-bioc-update

Input

Argument Name Description Required
rule_id BIOC rule ID. Required
name BIOC name. Required
severity BIOC severity. Possible values are: info, low, medium, high, critical. Required
type BIOC type. Possible values are: other, persistence, evasion, tampering, file_type_obfuscation, privilege_escalation, credential_access, lateral_movement, execution, collection, exfiltration, infiltration, dropper, file_privilege_manipulation, reconnaissance, discovery. Optional
is_xql Whether the BIOC is XQL. Possible values are: true, false. Optional
comment BIOC comment. Optional
status BIOC status. Possible values are: enabled, disabled. Optional
indicator The BIOC indicator,
for example: ‘{“runOnCGO”:true,”investigationType”:”FILE_EVENT”,”investigation”:{“FILE_EVENT”:{“filter”:{“AND”:[{“SEARCH_FIELD”:”action_file_name”,”SEARCH_TYPE”:”EQ”,”SEARCH_VALUE”:”testfile.exe”}]}}}}’.
For more information, refer to the documentation here.
Required
mitre_technique_id_and_name The MITRE technique ID and name. Must be in format ‘ID - Name’, for example: [‘T1566 - Phishing’]. Optional
mitre_tactic_id_and_name The MITRE tactic ID and name. Must be in format ‘ID - Name’, for example: [‘TA0001 - Initial Access’]. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.BIOC.rule_id String BIOC ID.

xdr-bioc-delete


Deletes a BIOC.

Base Command

xdr-bioc-delete

Input

Argument Name Description Required
name BIOC name. Optional
severity BIOC severity. Possible values are: info, low, medium, high, critical. Optional
type BIOC type. Possible values are: other, persistence, evasion, tampering, file_type_obfuscation, privilege_escalation, credential_access, lateral_movement, execution, collection, exfiltration, infiltration, dropper, file_privilege_manipulation, reconnaissance, discovery. Optional
is_xql Whether the BIOC is XQL. Possible values are: true, false. Optional
comment BIOC comment. Optional
indicator BIOC indicator. Optional
mitre_technique_id_and_name The MITRE technique ID and name. Must be in format ‘ID - Name’, for example: [‘T1566 - Phishing’]. Optional
mitre_tactic_id_and_name The MITRE tactic ID and name. Must be in format ‘ID - Name’, for example: [‘TA0001 - Initial Access’]. Optional

Context Output

There is no context output for this command.

xdr-correlation-rule-list


Returns a list of correlation rules.

Base Command

xdr-correlation-rule-list

Input

Argument Name Description Required
name Correlation rule name. Optional
severity Correlation rule severity. Possible values are: info, low, medium, high, critical. Optional
xql_query Correlation rule XQL query. Optional
is_xql Whether the correlation rule is XQL. Possible values are: true, false. Optional
dataset Correlation rule dataset. Optional
alert_name Alert name. Optional
alert_category Alert category. Optional
alert_fields Alert fields. Can be a string or a dictionary. Optional
alert_domain Alert domain. Optional
filter_json Filter JSON. Optional
extra_data Whether to return extended view. Possible values are: true, false. Optional
limit Maximum number of results to return. Optional
page_size Page size. Optional
page Page number. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.CorrelationRule.suppression_duration String The duration for which alerts are suppressed.
PaloAltoNetworksXDR.CorrelationRule.alert_category String The category of the alert (e.g., DROPPER).
PaloAltoNetworksXDR.CorrelationRule.mitre_defs Unknown MITRE ATT&CK definitions associated with the rule.
PaloAltoNetworksXDR.CorrelationRule.simple_schedule String The frequency at which the rule runs in a human-readable format.
PaloAltoNetworksXDR.CorrelationRule.mapping_strategy String The strategy used for field mapping.
PaloAltoNetworksXDR.CorrelationRule.suppression_fields String Fields used to determine alert suppression.
PaloAltoNetworksXDR.CorrelationRule.alert_domain String The security domain the alert belongs to.
PaloAltoNetworksXDR.CorrelationRule.crontab String The cron expression for the rule execution schedule.
PaloAltoNetworksXDR.CorrelationRule.search_window String The time window the query searches across.
PaloAltoNetworksXDR.CorrelationRule.user_defined_severity String Custom severity level defined by the user.
PaloAltoNetworksXDR.CorrelationRule.drilldown_query_timeframe String The timeframe used for drilldown queries.
PaloAltoNetworksXDR.CorrelationRule.user_defined_category String Custom category defined by the user.
PaloAltoNetworksXDR.CorrelationRule.lookup_mapping String Mapping details for lookups.
PaloAltoNetworksXDR.CorrelationRule.execution_mode String How the rule is executed, for example: SCHEDULED.
PaloAltoNetworksXDR.CorrelationRule.name String The name of the correlation rule.
PaloAltoNetworksXDR.CorrelationRule.is_enabled Boolean Whether the correlation rule is currently active.
PaloAltoNetworksXDR.CorrelationRule.investigation_query_link String The query used for further investigation.
PaloAltoNetworksXDR.CorrelationRule.alert_name String The specific name assigned to the generated alert.
PaloAltoNetworksXDR.CorrelationRule.suppression_enabled Boolean Whether suppression is enabled for this rule.
PaloAltoNetworksXDR.CorrelationRule.action String The action taken when the rule triggers, for example: ALERTS.
PaloAltoNetworksXDR.CorrelationRule.dataset String The dataset the rule queries.
PaloAltoNetworksXDR.CorrelationRule.alert_description String Detailed description of the generated alert.
PaloAltoNetworksXDR.CorrelationRule.timezone String The timezone used for the rule schedule.
PaloAltoNetworksXDR.CorrelationRule.xql_query String The XQL query string executed by the rule.
PaloAltoNetworksXDR.CorrelationRule.alert_fields Unknown Specific fields included in the alert output.
PaloAltoNetworksXDR.CorrelationRule.description String General description of the correlation rule.
PaloAltoNetworksXDR.CorrelationRule.alert_type String The type of alert generated.
PaloAltoNetworksXDR.CorrelationRule.severity String The severity level of the rule.
PaloAltoNetworksXDR.CorrelationRule.rule_id Number The unique identifier for the correlation rule.

xdr-correlation-rule-create


Creates a new correlation rule.

Base Command

xdr-correlation-rule-create

Input

Argument Name Description Required
name The correlation rule name. Required
severity The correlation rule severity. Example: severity=low. Possible values are: info, low, medium, high, critical. Required
xql_query The correlation rule XQL query. Example: xql_query=”dataset = xdr_data | limit 1”. Required
is_enabled Whether the rule is enabled. Example: is_enabled=true. Possible values are: true, false. Required
alert_category The alert category. Example: alert_category=dropper. Possible values are: other, persistence, evasion, tampering, file_type_obfuscation, privilege_escalation, credential_access, lateral_movement, execution, collection, exfiltration, infiltration, dropper, file_privilege_manipulation, reconnaissance, discovery. Required
execution_mode The rule execution mode. Example: execution_mode=scheduled. Possible values are: scheduled, real_time. Required
timezone The correlation rule timezone. Example: timezone=”Asia/Jerusalem”. Required
mapping_strategy The rule mapping strategy. Example: mapping_strategy=auto. Possible values are: auto, custom. Required
description The correlation rule description. Optional
alert_name The alert name. Optional
alert_description The alert description. Optional
alert_fields Alert fields (string or dictionary). Optional
search_window The search window timeframe. Example: search_window=”1 hours”. Optional
schedule The correlation rule schedule. Example: schedule=”10 minutes”. Optional
schedule_linux Linux scheduling for the rule. Example: schedule_linux=”/10 ** *”. Optional
suppression_enabled Whether suppression is enabled. Possible values are: true, false. Optional
suppression_duration Duration of correlation rule suppression. Optional
suppression_fields Suppression fields. Optional
dataset The correlation rule dataset. Example: dataset=alerts. Required
user_defined_severity User-defined severity. Optional
user_defined_category User-defined category. Optional
mitre_defs_json MITRE definitions. For example: ‘{“TA0005 - Defense Evasion”: [“T1014 - Rootkit”]}’. Optional
investigation_query_link Investigation query link. Optional
drilldown_query_timeframe The drilldown query timeframe. Example: drilldown_query_timeframe=”ALERT”. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.CorrelationRule.rule_id String Correlation rule ID.

xdr-correlation-rule-update


Updates an existing correlation rule.

Base Command

xdr-correlation-rule-update

Input

Argument Name Description Required
rule_id Correlation rule ID. Required
name The correlation rule name. Required
severity The correlation rule severity. Example: severity=low. Possible values are: info, low, medium, high, critical. Required
xql_query The correlation rule XQL query. Example: xql_query=”dataset = xdr_data | limit 1”. Required
is_enabled Whether the rule is enabled. Example: is_enabled=true. Possible values are: true, false. Required
alert_category The alert category. Example: alert_category=dropper. Possible values are: other, persistence, evasion, tampering, file_type_obfuscation, privilege_escalation, credential_access, lateral_movement, execution, collection, exfiltration, infiltration, dropper, file_privilege_manipulation, reconnaissance, discovery. Required
execution_mode The rule execution mode. Example: execution_mode=scheduled. Possible values are: scheduled, real_time. Required
timezone The correlation rule timezone. Example: timezone=”Asia/Jerusalem”. Required
mapping_strategy The rule mapping strategy. Example: mapping_strategy=auto. Possible values are: auto, custom. Required
description The correlation rule description. Optional
alert_name The alert name. Optional
alert_description The alert description. Optional
alert_fields Alert fields (string or dictionary). Optional
search_window The search window timeframe. Example: search_window=”1 hours”. Optional
schedule The correlation rule schedule. Example: schedule=”10 minutes”. Optional
schedule_linux Linux scheduling for the rule. Example: schedule_linux=”/10 ** *”. Optional
suppression_enabled Whether suppression is enabled. Possible values are: true, false. Optional
suppression_duration Duration of correlation rule suppression. Optional
suppression_fields Suppression fields. Optional
dataset The correlation rule dataset. Example: dataset=alerts. Required
user_defined_severity User-defined severity. Optional
user_defined_category User-defined category. Optional
mitre_defs_json MITRE definitions. For example: ‘{“TA0005 - Defense Evasion”: [“T1014 - Rootkit”]}’. Optional
investigation_query_link Investigation query link. Optional
drilldown_query_timeframe The drilldown query timeframe. Example: drilldown_query_timeframe=”ALERT”. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.CorrelationRule.rule_id String Correlation rule ID.

xdr-correlation-rule-delete


Deletes correlation rules.

Base Command

xdr-correlation-rule-delete

Input

Argument Name Description Required
rule_id Correlation rule ID. Required

Context Output

There is no context output for this command.

xdr-case-list


Returns a list of cases based on the specified filters.

Base Command

xdr-case-list

Input

Argument Name Description Required
case_id A comma-separated list of case IDs to filter by. Optional
case_domain A comma-separated list of case domains to filter by. Note: this filter is not applied when extra_data is set to true. Optional
severity A comma-separated list of severities to filter by. Note: this filter is not applied when extra_data is set to true. Optional
created_before Filters cases that were created before this date. Supports natural language expressions such as “one year”. Optional
created_after Filters cases that were created after this date. Supports natural language expressions such as “one year”. Optional
status A comma-separated list of statuses to filter by. Optional
sort_field The field by which to sort the results. Note: sorting by severity is not supported when extra_data is set to true. Possible values are: case_id, severity, creation_time. Optional
sort_order The order in which to sort the results. Possible values are: asc, desc. Optional
limit Maximum number of cases to return. Optional
page_size Page size for pagination. Optional
page Page number for pagination. Optional
extra_data Whether to retrieve extra case data including the associated issues, file artifacts, and network artifacts for each case. Possible values are: true, false. Default is false. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.Case.case_id String The unique identifier of the case.
PaloAltoNetworksXDR.Case.case_name String The name of the case.
PaloAltoNetworksXDR.Case.severity String The severity of the case.
PaloAltoNetworksXDR.Case.creation_time String The creation time of the case.
PaloAltoNetworksXDR.Case.modification_time String The modification time of the case.
PaloAltoNetworksXDR.Case.status_progress String The progress status of the case (e.g., New, Under Investigation).
PaloAltoNetworksXDR.Case.description String A detailed description of the case and involved entities.
PaloAltoNetworksXDR.Case.low_severity_issue_count Number The number of low severity issues associated with the case.
PaloAltoNetworksXDR.Case.med_severity_issue_count Number The number of medium severity issues associated with the case.
PaloAltoNetworksXDR.Case.case_domain String The security domain of the case.
PaloAltoNetworksXDR.Case.xdr_url String The direct URL to the incident view in the XDR console.
PaloAltoNetworksXDR.Case.is_blocked Boolean Indicates if the threat was blocked.
PaloAltoNetworksXDR.Case.aggregated_score Number The overall risk score calculated for the case.
PaloAltoNetworksXDR.Case.host_count Number The number of hosts involved in the case.
PaloAltoNetworksXDR.Case.user_count Number The number of users involved in the case.
PaloAltoNetworksXDR.Case.wildfire_hits Number The number of WildFire malware hits associated with the case.
PaloAltoNetworksXDR.Case.tags String A list of tags associated with the case.
PaloAltoNetworksXDR.Case.starred Boolean Whether the case has been starred/flagged.
PaloAltoNetworksXDR.Case.high_severity_issue_count Number The number of high-severity issues associated with the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.critical_severity_issue_count Number The number of critical-severity issues associated with the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.resolved_timestamp String The time the case was resolved. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.assigned_user_mail String The email address of the user assigned to the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.assigned_user_pretty_name String The display name of the user assigned to the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.hosts String The hosts involved in the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.users String The users involved in the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.mitre_tactics_ids_and_names String The MITRE ATT&CK tactics associated with the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.mitre_techniques_ids_and_names String The MITRE ATT&CK techniques associated with the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.issue_categories String The categories of the issues associated with the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.resolve_comment String The comment provided when the case was resolved. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.notes String Notes associated with the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.manual_severity String The severity manually set for the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.manual_description String The description manually set for the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.rule_based_score Number The rule-based risk score of the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.predicted_score Number The predicted risk score of the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.issue_count Number The total number of issues associated with the case. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.issue_id String The unique identifier of the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.case_id String The identifier of the case the issue belongs to. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.detection_timestamp Number The detection time of the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.name String The name of the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.category String The category of the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.severity String The severity of the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.source String The source of the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.action String The action taken for the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.action_pretty String The human-readable action taken for the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.host_ip String The host IP addresses associated with the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.host_name String The host name associated with the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.user_name String The user name associated with the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.action_file_name String The file name involved in the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.action_file_path String The file path involved in the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.mitre_tactic_id_and_name String The MITRE ATT&CK tactic associated with the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.Issues.mitre_technique_id_and_name String The MITRE ATT&CK technique associated with the issue. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.FileArtifacts.case_id String The identifier of the case associated with the file artifact. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.FileArtifacts.type String The type of the file artifact. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.FileArtifacts.file_name String The name of the file. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.FileArtifacts.file_sha256 String The SHA256 hash of the file. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.FileArtifacts.file_wildfire_verdict String The WildFire verdict for the file. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.FileArtifacts.is_malicious Boolean Whether the file has been identified as malicious. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.FileArtifacts.is_process Boolean Whether the file was a running process. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.FileArtifacts.is_manual Boolean Whether the file artifact was added manually. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.FileArtifacts.file_signature_vendor_name String The vendor name from the file signature. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.FileArtifacts.file_signature_status String The status of the file digital signature. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.FileArtifacts.alert_count Number The number of issues associated with the file artifact. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.NetworkArtifacts.case_id String The identifier of the case associated with the network artifact. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.NetworkArtifacts.type String The type of the network artifact. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.NetworkArtifacts.network_remote_ip String The remote IP address associated with the network artifact. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.NetworkArtifacts.network_remote_port Number The remote port associated with the network artifact. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.NetworkArtifacts.network_domain String The domain associated with the network artifact. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.NetworkArtifacts.network_country String The country associated with the network artifact. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.NetworkArtifacts.alert_count Number The number of issues associated with the network artifact. Returned when extra_data is set to true.
PaloAltoNetworksXDR.Case.NetworkArtifacts.is_manual Boolean Whether the network artifact was added manually. Returned when extra_data is set to true.

xdr-case-update


Updates an existing case with the specified details.

Base Command

xdr-case-update

Input

Argument Name Description Required
case_id The ID of the case to update. Required
status The status to set for the case. When set to “resolved”, the resolve_reason argument is required. Possible values are: new, under_investigation, resolved. Optional
resolve_reason The reason for resolving the case. Can only be provided when the status is set to “resolved”, and is required when resolving. Possible values are: resolved_known_issue, resolved_duplicate, resolved_false_positive, resolved_true_positive, resolved_security_testing, resolved_other. Optional
resolve_comment A free-text comment explaining the resolution. Can only be provided when the status is set to “resolved”. Optional
user_severity The user-defined severity to set for the case. Provide an empty string to clear the severity. Possible values are: low, medium, high, critical. Optional
assigned_user A valid Cortex XDR user email address to assign the case to. Optional
notes A free-text note for the case, used for operational or intermediate comments (e.g., a ServiceNow Ticket ID). Optional
custom_fields A JSON object string of tenant-defined custom fields to update on the case. The keys must be the exact custom field names as configured in your Cortex XDR tenant, and the values must match each field’s configuration. Ensure the custom field names are valid for the tenant; the integration only validates that the input is valid JSON. Example: {"my_custom_field": "value", "another_field": 42}. Optional

Context Output

There is no context output for this command.

xdr-case-artifact-list


Retrieves a list of artifacts for a specific case.

Base Command

xdr-case-artifact-list

Input

Argument Name Description Required
case_id The ID of the case for which to retrieve artifacts. Required

Context Output

Path Type Description
PaloAltoNetworksXDR.CaseNetworkArtifact.case_id String The ID of the case associated with the network artifact.
PaloAltoNetworksXDR.CaseNetworkArtifact.network_remote_ip String The remote IP address associated with the artifact.
PaloAltoNetworksXDR.CaseNetworkArtifact.network_remote_port Number The remote port used in the network connection.
PaloAltoNetworksXDR.CaseNetworkArtifact.alert_count Number Number of alerts associated with this network artifact.
PaloAltoNetworksXDR.CaseNetworkArtifact.type String The type of network artifact (e.g., IP).
PaloAltoNetworksXDR.CaseNetworkArtifact.is_manual Boolean Indicates if the artifact was added manually.
PaloAltoNetworksXDR.CaseFileArtifact.case_id String The ID of the case associated with the file artifact.
PaloAltoNetworksXDR.CaseFileArtifact.file_name String The name of the file.
PaloAltoNetworksXDR.CaseFileArtifact.file_sha256 String The SHA256 hash of the file.
PaloAltoNetworksXDR.CaseFileArtifact.file_wildfire_verdict String The verdict from WildFire (e.g., MALWARE, BENIGN).
PaloAltoNetworksXDR.CaseFileArtifact.is_malicious Boolean Whether the file has been identified as malicious.
PaloAltoNetworksXDR.CaseFileArtifact.is_process Boolean Indicates if the file was a running process.
PaloAltoNetworksXDR.CaseFileArtifact.file_signature_vendor_name String The vendor name from the file signature.
PaloAltoNetworksXDR.CaseFileArtifact.file_signature_status String The status of the digital signature (e.g., SIGNATURE_SIGNED).
PaloAltoNetworksXDR.CaseFileArtifact.alert_count Number Number of alerts associated with this file artifact.
PaloAltoNetworksXDR.CaseFileArtifact.low_confidence Boolean Indicates if the detection confidence is low.

xdr-automation-playbook-create


Creates or updates an automation playbook.

Base Command

xdr-automation-playbook-create

Input

Argument Name Description Required
entry_id The entry ID of the file. The playbook should be a zipped YAML file. Required

Context Output

There is no context output for this command.

xdr-automation-playbook-get


Gets an automation playbook.

Base Command

xdr-automation-playbook-get

Input

Argument Name Description Required
field The field to search by. Possible values are: id, name. Required
value The field value to search for. Required

Context Output

There is no context output for this command.

xdr-automation-playbook-delete


Deletes an automation playbook.

Base Command

xdr-automation-playbook-delete

Input

Argument Name Description Required
field The field to search by. Possible values are: id, name. Required
value The field value to search for. Required

Context Output

There is no context output for this command.

xdr-automation-script-delete


Deletes an automation script.

Base Command

xdr-automation-script-delete

Input

Argument Name Description Required
field The field to search by. Possible values are: id, name. Required
value The field value to search for. Required

Context Output

There is no context output for this command.

xdr-automation-script-get


Gets an automation script.

Base Command

xdr-automation-script-get

Input

Argument Name Description Required
field The field to search by. Possible values are: id, name. Required
value The field value to search for. Required

Context Output

There is no context output for this command.

xdr-automation-script-create


Creates or updates an automation script.

Base Command

xdr-automation-script-create

Input

Argument Name Description Required
entry_id The entry ID of the file. The script should be a zipped YAML file. Required

Context Output

There is no context output for this command.

xdr-vulnerability-details-get


Gets vulnerability details by ID.

Base Command

xdr-vulnerability-details-get

Input

Argument Name Description Required
vulnerability_id The vulnerability ID to retrieve details for. Required

Context Output

Path Type Description
PaloAltoNetworksXDR.Vulnerability.vulnerabilityID String The unique identifier (CVE ID).
PaloAltoNetworksXDR.Vulnerability.description String Detailed description of the vulnerability.
PaloAltoNetworksXDR.Vulnerability.publishedDate Number The date the vulnerability was first published.
PaloAltoNetworksXDR.Vulnerability.epss_score Number The Probability of exploitability (0-1).
PaloAltoNetworksXDR.Vulnerability.exploitDetails.exploitMaturity String Current state of exploit availability (e.g., weaponized).
PaloAltoNetworksXDR.Vulnerability.exploitDetails.weaponizedExploitFound Boolean Whether a functional exploit exists in the wild.
PaloAltoNetworksXDR.Vulnerability.exploitDetails.reportedExploited Boolean Whether the vulnerability has been actively exploited.
PaloAltoNetworksXDR.Vulnerability.exploitDetails.reportedExploitedByThreatActors Boolean Whether known threat actors are exploiting this vulnerability.
PaloAltoNetworksXDR.Vulnerability.exploitDetails.reportedExploitedByRansomware Boolean Whether the vulnerability is used in ransomware campaigns.
PaloAltoNetworksXDR.Vulnerability.exploitDetails.reportedExploitedByBotnets Boolean Whether the vulnerability is being leveraged by botnet clusters.
PaloAltoNetworksXDR.Vulnerability.exploitDetails.firstReportedThreatActor Number The timestamp of the first recorded threat actor activity.
PaloAltoNetworksXDR.Vulnerability.exploitDetails.mostRecentReportedThreatActor Number The timestamp of the last recorded threat actor activity.
PaloAltoNetworksXDR.Vulnerability.cisaKevDateDue Number The deadline set by CISA for federal agency remediation.
PaloAltoNetworksXDR.Vulnerability.weaknesses.cweId String The Common Weakness Enumeration ID.
PaloAltoNetworksXDR.Vulnerability.vulncheck-threat-actors String A list of specific threat actor groups associated with the vulnerability.

xdr-healthcheck-run


Runs a system health check on the Cortex XDR environment.

Base Command

xdr-healthcheck-run

Input

There are no input arguments for this command.

Context Output

Path Type Description
PaloAltoNetworksXDR.HealthStatus.status String The health status of the Cortex XDR system.

xdr-endpoint-triage-preset-list


Gets a list of available triage presets.

Base Command

xdr-endpoint-triage-preset-list

Input

There are no input arguments for this command.

Context Output

Path Type Description
PaloAltoNetworksXDR.EndpointTriagePreset.created_by String The user or system entity that created the triage preset.
PaloAltoNetworksXDR.EndpointTriagePreset.description String A brief explanation of the preset’s purpose or contents.
PaloAltoNetworksXDR.EndpointTriagePreset.name String The unique display name of the triage preset.
PaloAltoNetworksXDR.EndpointTriagePreset.os String The operating system platform the preset targets (e.g., windows, linux, macos).
PaloAltoNetworksXDR.EndpointTriagePreset.type String The category or classification of the preset (e.g., standard, custom).
PaloAltoNetworksXDR.EndpointTriagePreset.uuid String The unique universal identifier (UUID) assigned to this specific preset.

xdr-endpoint-triage


Initiates a forensics triage on the specified endpoints. You must have Cortex XDR with the Forensics add-on. The agent also must have the Forensics License enabled.

Base Command

xdr-endpoint-triage

Input

Argument Name Description Required
endpoint_id A comma-separated list of endpoint IDs to initiate triage on. You can find the endpoint ID under the “Inventory” section, under “All Endpoints”. Required
collector_uuid The collector UUID to use for the triage. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.EndpointTriage.TRIAGE_ID String The unique identifier assigned to the created triage action.
PaloAltoNetworksXDR.EndpointTriage.SUCCESSFUL_AGENT_IDS Array A list of agent IDs where the triage action was successfully initiated.
PaloAltoNetworksXDR.EndpointTriage.UNSUCCESSFUL_AGENT_IDS Array A list of agent IDs where the triage action failed to initiate.

xdr-issue-list


Retrieve issues based on filters. Issues with an ‘INFO’ severity level are filtered out.

Base Command

xdr-issue-list

Input

Argument Name Description Required
issue_id A comma-separated list of issue IDs. Optional
external_id A comma-separated list of external IDs. Optional
detection_method A comma-separated list of methods used to detect the activity. Possible values: ‘BIOC’, ‘Correlation’, ‘Analytics’, ‘System’, ‘IOC’. Possible values are: BIOC, Correlation, Analytics, System, IOC. Optional
domain A comma-separated list of domains. Optional
severity A comma-separated list of severities. Possible values: ‘critical’, ‘high’, ‘medium’, ‘low’, ‘info’. Possible values are: info, low, medium, high, critical. Optional
insert_time Filter by issues inserted after this time. Optional
status A comma-separated list of statuses. Possible values: ‘New’, ‘In Progress’, ‘Resolved’. Possible values are: New, In Progress, Resolved. Optional
sort_field The field to sort by. Possible values are: issue_id, severity, observation_time. Optional
sort_order The sort order. Possible values are: asc, desc. Optional
limit The maximum number of results to return. Optional
page_size The page size. Optional
page The page number. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.Issue.observation_time Number The timestamp when the observation occurred.
PaloAltoNetworksXDR.Issue.domain String The security domain associated with the observation.
PaloAltoNetworksXDR.Issue.remediation String Suggested remediation steps.
PaloAltoNetworksXDR.Issue.impact String The potential impact of the observed activity.
PaloAltoNetworksXDR.Issue.status String The current progress status (e.g., New).
PaloAltoNetworksXDR.Issue.status_resolution_comment String Comments regarding the resolution of the status.
PaloAltoNetworksXDR.Issue.status_progress String The current progress status (e.g., New).
PaloAltoNetworksXDR.Issue.asset_ids String List of asset IDs involved.
PaloAltoNetworksXDR.Issue.assigned_to_pretty String The display name of the user assigned to this item.
PaloAltoNetworksXDR.Issue.last_update_timestamp Number The last time this record was updated.
PaloAltoNetworksXDR.Issue.detection_method String The method used to detect the activity (e.g., BIOC).
PaloAltoNetworksXDR.Issue.is_starred Boolean Whether the item has been starred/flagged.
PaloAltoNetworksXDR.Issue.name String The name of the observation or alert.
PaloAltoNetworksXDR.Issue.external_id String The unique UUID for external reference.
PaloAltoNetworksXDR.Issue.insert_time Number The time the record was inserted into the database.
PaloAltoNetworksXDR.Issue.detection_rule_id String The ID of the specific detection rule triggered.
PaloAltoNetworksXDR.Issue.tags String A list of tags associated with the observation.
PaloAltoNetworksXDR.Issue.type String The classification type of the observation.
PaloAltoNetworksXDR.Issue.id Number The numeric ID of the observation.
PaloAltoNetworksXDR.Issue.description String Detailed description of the observed activity.
PaloAltoNetworksXDR.Issue.severity String The severity level (e.g., LOW).
PaloAltoNetworksXDR.Issue.category String The category classification.
PaloAltoNetworksXDR.Issue.is_excluded Boolean Whether this observation is excluded from standard views.

xdr-issue-create


Creates a new issue. Issues with an ‘INFO’ severity level are not displayed in the UI.

Base Command

xdr-issue-create

Input

Argument Name Description Required
name The issue name. Required
description The issue description. Required
observation_time The observation time. Should support english expressions like “one year”. Required
domain The domain. Required
category The category. Required
asset_id A comma-separated list of asset IDs. Optional
mitre_tactic A comma-separated list of MITRE tactics. Optional
mitre_technique A comma-separated list of MITRE techniques. Optional
type The issue type. Optional
extended_description The extended description. Optional
impact The impact. Optional
tags A comma-separated list of tags. Optional
is_excluded Whether the issue is excluded. Possible values are: true, false. Optional
is_starred Whether the issue is starred. Possible values are: true, false. Optional
assigned_to The user assigned to the issue. Optional
assigned_to_pretty The pretty name of the user assigned to the issue. Optional
severity The severity. Possible values are: critical, info, low, medium, high. Required
normalized_fields_json The JSON to add to the “normalized_fields” field in the API. Optional
custom_fields_json The JSON to add to the “custom_fields” field in the API. Optional

Context Output

Path Type Description
PaloAltoNetworksXDR.Issue.external_id String The issue external ID.
PaloAltoNetworksXDR.Issue.detection_method String The issue detection method.

xdr-issue-update


Updates an existing issue.

Base Command

xdr-issue-update

Input

Argument Name Description Required
issue_id The issue ID. Required
severity The severity. Possible values are: critical, info, low, medium, high. Optional
status The status. Possible values are: new, in_progress, resolved. Optional
resolve_reason The resolve reason. Possible values are: resolved_threat_handled, resolved_known_issue, resolved_duplicate, resolved_false_positive, resolved_other, resolved_true_positive, resolved_security_testing. Optional
resolve_comment The resolve comment. Optional

Context Output

There is no context output for this command.

Configuration parameters

  • isFetch — Fetch incidents
  • incidentFetchInterval — Incidents Fetch Interval
  • incidentType — Incident type
  • url — Server URL (copy URL from XDR) (required)
  • apikey_id_creds
  • apikey_creds
  • dont_format_sublists — Remove legacy incident fields
  • mirror_direction — Incident Mirroring Direction
  • close_xsoar_incident — Close Mirrored XSOAR Incident
  • close_xdr_incident — Close Mirrored Cortex XDR Incident
  • xdr_delay — XDR mirroring delay in minutes
  • custom_xsoar_to_xdr_close_reason_mapping — Custom close-reason mapping (XSOAR -> XDR mirrored incident. Overwrites default close-reason mapping defined by Cortex XSOAR)
  • custom_xdr_to_xsoar_close_reason_mapping — Custom close-reason mapping (XDR -> XSOAR mirrored incident. Overwrites default close-reason mapping defined by Cortex XSOAR)
  • apikey_id — API Key ID
  • apikey — API Key
  • timeout — HTTP Timeout
  • max_fetch — Maximum number of incidents per fetch
  • starred — Only fetch starred incidents
  • starred_incidents_fetch_window — Starred incidents fetch window
  • fetch_time — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)
  • sync_owners — Sync Incident Owners
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • prevent_only — Prevent Only Mode
  • status — Incident Statuses to Fetch
  • exclude_fields — Minimize Incident Information
  • excluded_alert_fields — Minimize Alert Information
  • close_alerts_in_xdr — Close all related alerts in XDR

Commands (109)

  • endpoint

    Returns information about an endpoint.

  • get-mapping-fields

    Gets mapping fields from remote incident. Note: This method will not update the current incident. It's here for debugging purposes.

  • get-modified-remote-data

    Gets the list of incidents that were modified since the last update. Note: This method is here for debugging purposes. get-modified-remote-data is used as part of a Mirroring feature, which is available since version 6.1.

  • get-remote-data

    Gets remote data from a remote incident. Note: This method will not update the current incident. It's here for debugging purposes.

  • xdr-action-status-get

    Retrieves the status of the requested actions according to the action ID.

  • xdr-allowlist-files

    Adds requested files to the allow list if they are not already on the block list or allow list.

  • xdr-api-key-delete

    Deletes the specified API keys.

  • xdr-api-key-list

    Gets a list of existing API keys.

  • xdr-asset-group-create

    Creates an asset group.

  • xdr-asset-group-delete

    Deletes an asset group.

  • xdr-asset-group-list

    Returns a list of asset groups.

  • xdr-asset-group-update

    Updates an asset group.

  • xdr-asset-list

    Returns a list of assets.

  • xdr-asset-schema-field-options-get

    Gets the enum values of a specified field in the asset schema.

  • xdr-asset-schema-get

    Gets the schema of the asset inventory.

  • xdr-automation-playbook-create

    Creates or updates an automation playbook.

  • xdr-automation-playbook-delete

    Deletes an automation playbook.

  • xdr-automation-playbook-get

    Gets an automation playbook.

  • xdr-automation-script-create

    Creates or updates an automation script.

  • xdr-automation-script-delete

    Deletes an automation script.

  • xdr-automation-script-get

    Gets an automation script.

  • xdr-bioc-create

    Creates a new BIOC.

  • xdr-bioc-delete

    Deletes a BIOC.

  • xdr-bioc-list

    Returns a list of BIOCs.

  • xdr-bioc-update

    Updates an existing BIOC.

  • xdr-blacklist-files Deprecated

    Deprecated. Use `xdr-blocklist-files` instead.

  • xdr-blocklist-files

    Block lists requested files which have not already been block listed or added to allow lists.

  • xdr-case-artifact-list

    Retrieves a list of artifacts for a specific case.

  • xdr-case-list

    Returns a list of cases based on the specified filters.

  • xdr-case-update

    Updates an existing case with the specified details.

  • xdr-correlation-rule-create

    Creates a new correlation rule.

  • xdr-correlation-rule-delete

    Deletes correlation rules.

  • xdr-correlation-rule-list

    Returns a list of correlation rules.

  • xdr-correlation-rule-update

    Updates an existing correlation rule.

  • xdr-create-distribution

    Creates an installation package. This is an asynchronous call that returns the distribution ID. This does not mean that the creation succeeded. To confirm that the package has been created, check the status of the distribution by running the Get Distribution Status API.

  • xdr-delete-endpoints

    Deletes selected endpoints in the Cortex XDR app. You can delete up to 1000 endpoints.

  • xdr-endpoint-alias-change

    Gets a list of endpoints according to the passed filters, and changes their alias name. Filtering by multiple fields will be concatenated using the AND condition (OR is not supported).

  • xdr-endpoint-isolate

    Isolates the specified endpoint.

  • xdr-endpoint-scan Deprecated

    Deprecated. Use `xdr-endpoint-scan-execute` instead.

  • xdr-endpoint-scan-abort

    Cancels the scan of selected endpoints. A scan can only be aborted if the selected endpoints are Pending or In Progress. To scan all endpoints, run the command with the argument all=true. Note that scanning all of the endpoints may cause performance issues and latency.

  • xdr-endpoint-scan-execute

    Runs a scan on a selected endpoint. To scan all endpoints, run this command with argument all=true. Note: Scanning all the endpoints may cause performance issues and latency.

  • xdr-endpoint-tag-add

    Adds a tag to specified endpoint_ids.

  • xdr-endpoint-tag-remove

    Removes a tag from specified endpoint_ids.

  • xdr-endpoint-triage

    Initiates a forensics triage on the specified endpoints. You must have Cortex XDR with the Forensics add-on. The agent also must have the Forensics License enabled.

  • xdr-endpoint-triage-preset-list

    Gets a list of available triage presets.

  • xdr-endpoint-unisolate

    Reverses the isolation of an endpoint.

  • xdr-file-delete-script-execute

    Initiates a new endpoint script execution to delete the specified file.

  • xdr-file-exist-script-execute

    Initiates a new endpoint script execution to check if the file exists.

  • xdr-file-quarantine

    Quarantines a file on selected endpoints. You can select up to 1000 endpoints.

  • xdr-file-restore

    Restores a quarantined file on requested endpoints.

  • xdr-file-retrieve

    Retrieves files from selected endpoints. You can retrieve up to 20 files, from no more than 10 endpoints. At least one endpoint ID and one file path are necessary in order to run the command. After running this command, you can use the xdr-action-status-get command with returned action_id, to check the action status.

  • xdr-get-alerts Deprecated

    This command is deprecated, please use xdr-issue-list instead. Returns a list of alerts and their metadata, which you can filter by built-in arguments or use the custom_filter to input a JSON filter object. Multiple filter arguments will be concatenated using the AND operator, while arguments that support a comma-separated list of values will use an OR operator between each value.

  • xdr-get-audit-agent-reports

    Gets agent event reports. You can filter by multiple fields, which will be concatenated using the AND condition (OR is not supported). Maximum result set size is 100. Offset is the zero-based number of reports from the start of the result set (start by counting from 0).

  • xdr-get-audit-management-logs

    Gets management logs. You can filter by multiple fields, which will be concatenated using the AND condition (OR is not supported). Maximum result set size is 100. Offset is the zero-based number of management logs from the start of the result set (start by counting from 0).

  • xdr-get-cloud-original-alerts Deprecated

    This command is deprecated. Returns information about each alert ID.

  • xdr-get-contributing-event

    Retrieves contributing events for a specific correlation alert.

  • xdr-get-create-distribution-status

    Gets the status of the installation package.

  • xdr-get-distribution-url

    Gets the distribution URL for downloading the installation package.

  • xdr-get-distribution-versions

    Gets a list of all the agent versions to use for creating a distribution list.

  • xdr-get-endpoint-device-control-violations

    Gets a list of device control violations filtered by selected fields. You can retrieve up to 100 violations.

  • xdr-get-endpoints

    Gets a list of endpoints, according to the passed filters. If there are no filters, all endpoints are returned. Filtering by multiple fields will be concatenated using AND condition (OR is not supported). Maximum result set size is 100. Offset is the zero-based number of endpoint from the start of the result set. (Start by counting from 0).

  • xdr-get-endpoints-by-status

    Returns the number of the connected\disconnected endpoints.

  • xdr-get-incident-extra-data Deprecated

    This command is deprecated. Use "xdr-case-list extra_data=true" instead. Returns additional data for the specified incident, for example, related alerts, file artifacts, network artifacts, and so on.

  • xdr-get-incidents Deprecated

    This command is deprecated please use xdr-case-list instead. Returns a list of incidents, which you can filter by a list of incident IDs (max. 100), the time the incident was last modified, and the time the incident was created. If you pass multiple filtering arguments, they will be concatenated using the AND condition. The OR condition is not supported.

  • xdr-get-policy

    Gets the policy name for a specific endpoint.

  • xdr-get-quarantine-status

    Retrieves the quarantine status for a selected file.

  • xdr-get-script-code

    Gets the code of a specific script in the script library.

  • xdr-get-script-execution-result-files

    Gets the files retrieved from a specific endpoint during a script execution.

  • xdr-get-script-execution-results

    Retrieve the results of a script execution action.

  • xdr-get-script-execution-status

    Retrieves the status of a script execution action.

  • xdr-get-script-metadata

    Gets the full definition of a specific script in the scripts library.

  • xdr-get-scripts

    Gets a list of scripts available in the scripts library.

  • xdr-get-tenant-info

    Provides information about the tenant.

  • xdr-healthcheck-run

    Runs a system health check on the Cortex XDR environment.

  • xdr-insert-cef-alerts Deprecated

    This command is deprecated. Upload alerts in CEF format from external alert sources. After you map CEF alert fields to Cortex XDR fields, Cortex XDR displays the alerts in related incidents and views. You can send 600 requests per minute. Each request can contain a maximum of 60 alerts.

  • xdr-insert-parsed-alert Deprecated

    This command is deprecated. Uploads an alert from external alert sources in Cortex XDR format. Cortex XDR displays alerts that are parsed successfully in related incidents and views. You can send 600 alerts per minute. Each request can contain a maximum of 60 alerts.

  • xdr-isolate-endpoint Deprecated

    Deprecated. Use `xdr-endpoint-isolate` instead.

  • xdr-issue-create

    Creates a new issue. Issues with an 'INFO' severity level are not displayed in the UI.

  • xdr-issue-list

    Retrieve issues based on filters. Issues with an 'INFO' severity level are filtered out.

  • xdr-issue-update

    Updates an existing issue.

  • xdr-kill-process-script-execute

    Initiates a new endpoint script execution kill process.

  • xdr-list-risky-hosts

    Retrieve the risk score of a specific host or list of hosts with the highest risk score in the environment along with the reason affecting each score.

  • xdr-list-risky-users

    Retrieve the risk score of a specific user or list of users with the highest risk score in the environment along with the reason affecting each score.

  • xdr-list-roles

    Retrieve information about one or more roles created in the environment.

  • xdr-list-user-groups

    Retrieve a list of the current user emails associated with one or more user groups in the environment.

  • xdr-list-users

    Retrieve a list of the current users in the environment.

  • xdr-quarantine-files Deprecated

    Deprecated. Use `xdr-file-quarantine` instead.

  • xdr-remove-allowlist-files

    Removes requested files from allow list.

  • xdr-remove-blocklist-files

    Removes requested files from the block list.

  • xdr-remove-user-role

    Remove one or more users from a role.

  • xdr-replace-featured-field

    Replace the featured hosts\users\IP addresses\active directory groups listed in your environment.

  • xdr-restore-file Deprecated

    Deprecated. Use `xdr-file-restore` instead.

  • xdr-retrieve-file-details

    View the file retrieved by the xdr-retrieve-files command according to the action ID. Before running this command, you can use the xdr-action-status-get command to check if this action completed successfully.

  • xdr-retrieve-files Deprecated

    Deprecated. Use `xdr-file-retrieve` instead.

  • xdr-run-script Deprecated

    Deprecated. Run xdr-script-run instead.

  • xdr-run-script-delete-file Deprecated

    Deprecated. Use `xdr-file-delete-script-execute` instead.

  • xdr-run-script-execute-commands Deprecated

    Deprecated. Use `xdr-script-commands-execute` instead.

  • xdr-run-script-file-exists Deprecated

    Deprecated. Use `xdr-file-exist-script-execute` instead.

  • xdr-run-script-kill-process Deprecated

    Deprecated. Use `xdr-kill-process-script-execute` instead.

  • xdr-run-snippet-code-script Deprecated

    Deprecated. Use `xdr-snippet-code-script-execute` instead.

  • xdr-script-commands-execute

    Initiates a new endpoint script execution of shell commands.

  • xdr-script-run

    Initiates a new endpoint script execution action using a script from the script library and returns the results.

  • xdr-set-user-role

    Add one or more users to a role.

  • xdr-snippet-code-script-execute

    Initiates a new endpoint script execution action using the provided snippet code.

  • xdr-unisolate-endpoint Deprecated

    Deprecated. Use `xdr-endpoint-unisolate` instead.

  • xdr-update-alert Deprecated

    This command is deprecated, please use xdr-issue-update instead. Update one or more alerts with the provided arguments. Required license: Cortex XDR Prevent, Cortex XDR Pro per Endpoint, or Cortex XDR Pro per GB.

  • xdr-update-incident Deprecated

    This command is deprecated please use xdr-case-update instead. Updates one or more fields of a specified incident. Missing fields will be ignored. To remove the assignment for an incident, pass a null value in the assignee email argument.

  • xdr-vulnerability-details-get

    Gets vulnerability details by ID.

  • xdr-whitelist-files Deprecated

    Deprecated. Use `xdr-allowlist-files` instead.

import copy
import json
from unittest.mock import MagicMock, patch

import demistomock as demisto
import pytest
from CommonServerPython import CommandResults, DemistoException, urljoin
from CoreIRApiModule import XDR_RESOLVED_STATUS_TO_XSOAR, XSOAR_RESOLVED_STATUS_TO_XDR
from CortexXDRIR import XDR_OPEN_STATUS_TO_XSOAR, XDR_TO_XSOAR, XSOAR_TO_XDR, get_xsoar_close_reasons
from freezegun import freeze_time

XDR_URL = "https://api.xdrurl.com"

""" HELPER FUNCTIONS """


def load_test_data(json_path):
    with open(json_path) as f:
        return json.load(f)


def get_incident_by_status(
    incident_id_list=None,
    lte_modification_time=None,
    gte_modification_time=None,
    lte_creation_time=None,
    gte_creation_time=None,
    starred=None,
    starred_incidents_fetch_window=None,
    statuses=None,
    sort_by_modification_time=None,
    sort_by_creation_time=None,
    page_number=0,
    limit=100,
    gte_creation_time_milliseconds=0,
):
    """
    The function simulate the client.get_incidents method for the test_fetch_incidents_filtered_by_status
    and for the test_get_incident_list_by_status.
    The function got the status as a string, and return from the json file only the incidents
    that are in the given status.
    """
    incidents_list = load_test_data("./test_data/get_incidents_list.json")["reply"]["incidents"]
    return [incident for incident in incidents_list if incident["status"] in statuses]


def get_incident_extra_data_by_status(incident_id, alerts_limit):
    """
    The function simulate the client.get_incident_extra_data method for the test_fetch_incidents_filtered_by_status.
    The function got the incident_id, and return the json file by the incident id.
    """
    if incident_id == "1":
        incident_extra_data = load_test_data("./test_data/get_incident_extra_data.json")
    else:
        incident_extra_data = load_test_data("./test_data/get_incident_extra_data_new_status.json")
    return incident_extra_data["reply"]


""" TESTS FUNCTIONS """


@freeze_time("1993-06-17 11:00:00 GMT")
def test_fetch_incidents(requests_mock, mocker):
    from CortexXDRIR import Client, fetch_incidents, sort_all_list_incident_fields

    raw_incident = load_test_data("./test_data/get_multiple_incidents_extra_data.json").get("reply", {}).get("incidents")[0]
    modified_raw_incident = raw_incident["incident"]
    modified_raw_incident["alerts"] = raw_incident.get("alerts").get("data")
    modified_raw_incident["file_artifacts"] = raw_incident.get("file_artifacts").get("data")
    modified_raw_incident["network_artifacts"] = raw_incident.get("network_artifacts").get("data")
    modified_raw_incident["mirror_direction"] = "In"
    modified_raw_incident["mirror_instance"] = "MyInstance"
    modified_raw_incident["last_mirrored_in"] = 740314800000

    mocker.patch.object(Client, "get_multiple_incidents_extra_data", return_value=[raw_incident])
    mocker.patch.object(demisto, "params", return_value={"exclude_fields": False, "mirror_direction": "Incoming"})

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)

    modified_raw_incident.get("alerts")[0]["host_ip_list"] = modified_raw_incident.get("alerts")[0].get("host_ip").split(",")
    mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=50)
    mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
    next_run, incidents = fetch_incidents(client, "3 month", "MyInstance", exclude_artifacts=False, last_run={})
    sort_all_list_incident_fields(modified_raw_incident)
    assert len(incidents) == 1
    assert incidents[0]["name"] == "XDR Incident 1 - desc1"
    assert "network_artifacts" in json.loads(incidents[0]["rawJSON"])
    assert json.loads(incidents[0]["rawJSON"]).pop("last_mirrored_in")
    assert incidents[0]["rawJSON"] == json.dumps(modified_raw_incident)


@freeze_time("1993-06-17 11:00:00 GMT")
def test_fetch_incidents_filtered_by_status(requests_mock, mocker):
    """
    Given:
        - List of fetched incidents
    When
        - Running fetch_incident with a given list of statuses to fetch
    Then
        - Verify the returned result is as we expected
    """
    from CortexXDRIR import Client, fetch_incidents

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    incident_extra_data_under_investigation = load_test_data("./test_data/get_incident_extra_data_host_id_array.json")["reply"][
        "incidents"
    ]
    incident_extra_data_new = load_test_data("./test_data/get_incident_extra_data_new_status.json")["reply"]["incidents"]
    mocker.patch.object(
        Client,
        "get_multiple_incidents_extra_data",
        return_value=(incident_extra_data_under_investigation + incident_extra_data_new),
    )
    mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=50)
    mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
    statuses_to_fetch = ["under_investigation", "new"]

    next_run, incidents = fetch_incidents(
        client, "3 month", "MyInstance", exclude_artifacts=False, statuses=statuses_to_fetch, last_run={}
    )

    assert len(incidents) == 2
    assert (
        incidents[0]["name"] == "XDR Incident 1 - 'Local Analysis Malware' generated by XDR Agent detected on host AAAAAA "
        "involving user Administrator"
    )
    assert (
        incidents[1]["name"] == "XDR Incident 2 - 'Local Analysis Malware' generated by XDR Agent detected on host "
        "BBBBB involving user Administrator"
    )

    raw_json = json.loads(incidents[0]["rawJSON"])
    assert raw_json["status"] == "under_investigation"

    raw_json = json.loads(incidents[1]["rawJSON"])
    assert raw_json["status"] == "new"


def return_extra_data_result(*args, **kwargs):
    raise Exception("Rate limit exceeded")


@freeze_time("1993-06-17 11:00:00 GMT")
def test_fetch_incidents_with_rate_limit_error(requests_mock, mocker):
    """
    Given:
        - a Rate limit error occurs in the second call for 'get_extra_data_command'
    When
        - running fetch_incidents command
    Then
        - the first successful incident is being created
        - the second incident is saved for the next run
    """
    from CortexXDRIR import Client, fetch_incidents

    mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=50)
    mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
    mocker.patch.object(Client, "get_multiple_incidents_extra_data", side_effect=return_extra_data_result)
    mocker.patch.object(demisto, "params", return_value={"extra_data": True, "mirror_direction": "Incoming"})

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    with pytest.raises(Exception) as e:
        next_run, incidents = fetch_incidents(client, "3 month", "MyInstance", exclude_artifacts=False, last_run={})
    assert str(e.value) == "Rate limit exceeded"


class TestFetchStarredIncident:
    def test_get_starred_incident_list_with_limit(self, mocker):
        """
        Given:
            - List of two starred incidents to fetch
        When
            - Running starred fetch_incident with limit of 1
        Then
            - Verify the returned result is as we expected and the fetch incident is getting new incident in each call.
        """
        from CortexXDRIR import Client, get_incidents_command

        get_incidents_list_response = load_test_data("./test_data/get_starred_incidents_list.json")
        request_side_effect = [
            {"reply": {"incidents": [get_incidents_list_response["reply"]["incidents"][0]]}},
            {
                "reply": {
                    "incidents": [
                        get_incidents_list_response["reply"]["incidents"][0],
                        get_incidents_list_response["reply"]["incidents"][1],
                    ]
                }
            },
        ]
        mocker.patch.object(Client, "_http_request", side_effect=request_side_effect)
        mocker.patch.object(demisto, "command", return_value="fetch-incidents")
        getLastRun_side_effect = [{"fetched_starred_incidents": {}}, {"fetched_starred_incidents": {"3": True}}]
        mocker.patch.object(demisto, "getLastRun", side_effect=getLastRun_side_effect)
        mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=50)
        mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
        client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
        args = {
            "incident_id_list": "1 day",
            "starred": True,
            "limit": 1,
            "starred_incidents_fetch_window": "3 days",
            "integration_context_brand": "PaloAltoNetworksXDR",
        }
        _, outputs, _ = get_incidents_command(client, args)
        res = outputs["PaloAltoNetworksXDR.Incident(val.incident_id==obj.incident_id)"]
        assert len(res) == 1
        assert res[0]["incident_id"] == "3"

        _, outputs, _ = get_incidents_command(client, args)
        res = outputs["PaloAltoNetworksXDR.Incident(val.incident_id==obj.incident_id)"]
        assert len(res) == 1
        assert res[0]["incident_id"] == "4"

    def test_fetch_only_starred_incidents(self, mocker):
        """
        Given:
            - List of fetched incidents
        When
            - Running fetch_incident with "only fetch starred incidents" flag
        Then
            - Verify the returned result is as we expected. First fetch two starred incidents are fetched and on second fetch,
            (same fetch window) incidents are filtered out since they have been fetched already.
        """
        from CortexXDRIR import Client, fetch_incidents

        get_incidents_list_response = load_test_data("./test_data/get_starred_incidents_list.json")["reply"]["incidents"]
        last_run_obj = {}
        mocker.patch.object(demisto, "params", return_value={"extra_data": True, "mirror_direction": "Incoming"})
        mocker.patch.object(demisto, "command", return_value="fetch-incidents")
        mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
        mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
        mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=50)
        mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
        mocker.patch.object(Client, "get_multiple_incidents_extra_data", return_value=get_incidents_list_response)
        client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
        next_run, incidents = fetch_incidents(
            client,
            "3 month",
            "MyInstance",
            exclude_artifacts=False,
            last_run=last_run_obj.get("next_run", {}),
            starred=True,
            starred_incidents_fetch_window="3 days",
        )
        assert len(incidents) == 2
        assert (
            incidents[0]["name"] == "XDR Incident 3 - 'Local Analysis Malware' generated by XDR Agent detected"
            " on host AAAAA involving user Administrator"
        )

        last_run_obj = {"next_run": next_run, "fetched_starred_incidents": {"3": True, "4": True}}
        mocker.patch.object(demisto, "getLastRun", return_value=last_run_obj)


def test_get_tenant_info(requests_mock):
    from CortexXDRIR import Client, get_tenant_info_command

    tenant_info_response = load_test_data("./test_data/get_tenant_info.json")
    requests_mock.post(f"{XDR_URL}/public_api/v1/system/get_tenant_info/", json=tenant_info_response)

    client = Client(base_url=f"{XDR_URL}/public_api/v1/", verify=False, timeout=120, proxy=False)
    expected_output = tenant_info_response.get("reply")
    response = get_tenant_info_command(client)
    assert response.outputs == expected_output


def test_insert_parsed_alert(requests_mock):
    from CortexXDRIR import Client, insert_parsed_alert_command

    insert_alerts_response = load_test_data("./test_data/create_alerts.json")
    requests_mock.post(f"{XDR_URL}/public_api/v1/alerts/insert_parsed_alerts/", json=insert_alerts_response)

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {
        "product": "VPN & Firewall-1",
        "vendor": "Check Point",
        "local_ip": "192.168.1.254",
        "local_port": "35398",
        "remote_ip": "0.0.0.0",
        "remote_port": "0",
        "event_timestamp": "1543270652000",
        "severity": "Low",
        "alert_name": "Alert Name Example",
        "alert_description": "Alert Description",
    }

    readable_output, outputs, _ = insert_parsed_alert_command(client, args)
    assert outputs is None
    assert readable_output == "Alert inserted successfully"


def test_insert_cef_alerts(requests_mock):
    from CortexXDRIR import Client, insert_cef_alerts_command

    insert_cef_alerts_response = load_test_data("./test_data/insert_cef_alerts.json")
    requests_mock.post(f"{XDR_URL}/public_api/v1/alerts/insert_cef_alerts/", json=insert_cef_alerts_response)

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)

    args = {
        "cef_alerts": [
            "CEF:0|Check Point|VPN-1 & FireWall-1|Check Point|Log|microsoft-ds|Unknown|act=AcceptdeviceDirection=0 "
            "rt=1569477512000 spt=56957 dpt=445 cs2Label=Rule Name cs2=ADPrimery "
            "layer_name=FW_Device_blackened Securitylayer_uuid=07693fc7-1a5c-4f31-8afe-77ae96c71b8c match_id=1806 "
            "parent_rule=0rule_action=Accept rule_uid=8e45f36b-d106-4d81-a1f0-9d1ed9a6be5c ifname=bond2logid=0 "
            "loguid={0x5d8c5388,0x61,0x29321fac,0xc0000022} origin=1.1.1.1originsicname=CN=DWdeviceBlackend,"
            "O=Blackend sequencenum=363 version=5dst=1.1.1.1 inzone=External outzone=Internal product=VPN-1 & "
            "FireWall-1 proto=6service_id=microsoft-ds src=1.1.1.1",
            "CEF:0|Check Point|VPN-1 & FireWall-1|Check Point|Log|Log|Unknown|act=AcceptdeviceDirection=0 "
            "rt=1569477501000 spt=63088 dpt=5985 cs2Label=Rule Namelayer_name=FW_Device_blackened Securitylayer_"
            "uuid=07693fc7-1a5c-4f31-8afe-77ae96c71b8c match_id=8899 parent_rule=0rule_action=Accept rule_"
            "uid=ae987933-82c0-470f-ab1c-1ad552c82369conn_direction=Internal ifname=bond1.12 "
            "logid=0loguid={0x5d8c537d,0xbb,0x29321fac,0xc0000014} origin=1.1.1.1originsicname=CN=DWdeviceBlackend,"
            "O=Blackend sequencenum=899 version=5dst=1.1.1.1 product=VPN-1 & FireWall-1 proto=6 src=1.1.1.1",
        ]
    }

    readable_output, _, _ = insert_cef_alerts_command(client, args)

    assert readable_output == "Alerts inserted successfully"


def test_sort_all_list_incident_fields():
    """
    Given:
        -  A raw incident
    When
        - running sort_all_list_incident_fields on it
    Then
        - the list fields (alerts for example) are sorted
    """
    from CortexXDRIR import sort_all_list_incident_fields

    raw_incident = load_test_data("test_data/raw_fetched_incident.json")
    sort_all_list_incident_fields(raw_incident)
    assert raw_incident.get("alerts")[0].get("alertid") == "42"
    assert raw_incident.get("alerts")[1].get("alertid") == "55"
    assert raw_incident.get("alerts")[2].get("alertid") == "60"

    assert raw_incident.get("hosts")[0] == "HOST1"
    assert raw_incident.get("hosts")[1] == "HOST2"

    assert raw_incident.get("file_artifacts")[0].get("filename") == "file.exe"
    assert raw_incident.get("file_artifacts")[1].get("filename") == "file2.exe"


@pytest.mark.parametrize("dont_format_sublists", [False, True])
def test_format_sublists_param(dont_format_sublists, mocker):
    """
    Given:
        -  A raw incident
    When
        - running sort_all_list_incident_fields on it with dont_format_sublists
    Then
        - if dont_format_sublists is False, should be formatted, so should have underscore
        - if dont_format_sublists is True, should not be formatted, so should not have underscore
        - Underscre value should always be present
    """
    from CortexXDRIR import sort_all_list_incident_fields

    raw_incident = load_test_data("test_data/raw_fetched_incident.json")
    mocker.patch.object(demisto, "params", return_value={"dont_format_sublists": dont_format_sublists})

    sort_all_list_incident_fields(raw_incident)
    assert bool(raw_incident.get("alerts")[0].get("alertid")) == (not dont_format_sublists)
    assert raw_incident.get("alerts")[0].get("alert_id")


def test_get_mapping_fields_command():
    """
    Given:
        -  nothing
    When
        - running get_mapping_fields_command
    Then
        - the result fits the expected mapping.
    """
    from CortexXDRIR import get_mapping_fields_command

    expected_mapping = {
        "Cortex XDR Incident Schema": {
            "status": 'Current status of the incident: "new","under_'
            'investigation","resolved_known_issue",'
            '"resolved_duplicate","resolved_false_positive",'
            '"resolved_true_positive","resolved_security_testing","resolved_other"',
            "assigned_user_mail": "Email address of the assigned user.",
            "assigned_user_pretty_name": "Full name of the user assigned to the incident.",
            "resolve_comment": "Comments entered by the user when the incident was resolved.",
            "manual_severity": "Incident severity assigned by the user. This does not "
            "affect the calculated severity low medium high",
            "close_reason": "The close reason of the XSOAR incident",
        }
    }
    res = get_mapping_fields_command()
    assert expected_mapping == res.extract_mapping()


def test_get_remote_data_command_should_update(requests_mock, mocker):
    """
    Given:
        -  an XDR client
        - arguments (id and lastUpdate time set to a lower than incident modification time)
        - a raw incident (get-extra-data results)
    When
        - running get_remote_data_command
    Then
        - the mirrored_object in the GetRemoteDataResponse is the same as the modified raw incident
        - the entries in the GetRemoteDataResponse in empty
    """
    from CortexXDRIR import Client, get_remote_data_command, sort_all_list_incident_fields

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"id": 1, "lastUpdate": 0}
    raw_incident = load_test_data("./test_data/get_incident_extra_data.json")
    expected_modified_incident = raw_incident["reply"]["incident"].copy()
    expected_modified_incident["alerts"] = copy.deepcopy(raw_incident["reply"].get("alerts").get("data"))
    expected_modified_incident["network_artifacts"] = raw_incident["reply"].get("network_artifacts").get("data")
    expected_modified_incident["file_artifacts"] = raw_incident["reply"].get("file_artifacts").get("data")
    expected_modified_incident["id"] = expected_modified_incident.get("incident_id")
    expected_modified_incident["assigned_user_mail"] = ""
    expected_modified_incident["assigned_user_pretty_name"] = ""
    expected_modified_incident["in_mirror_error"] = ""
    del expected_modified_incident["creation_time"]

    expected_modified_incident.get("alerts")[0]["host_ip_list"] = (
        expected_modified_incident.get("alerts")[0].get("host_ip").split(",")
    )

    # make sure get-extra-data is returning an incident
    mocker.patch("CortexXDRIR.get_last_mirrored_in_time", return_value=0)
    mocker.patch("CortexXDRIR.check_if_incident_was_modified_in_xdr", return_value=True)
    mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=50)
    mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
    mocker.patch.object(Client, "get_multiple_incidents_extra_data", return_value=raw_incident["reply"])
    response = get_remote_data_command(client, args)
    sort_all_list_incident_fields(expected_modified_incident)

    assert response.mirrored_object == expected_modified_incident
    assert response.entries[0].get("Contents") == {"dbotIncidentReopen": True}


def test_get_remote_data_command_with_rate_limit_exception(mocker):
    """
    Given:
        - an XDR client
        - arguments (id and lastUpdate time set to a lower than incident modification time)
        - a Rate limit exception is raises from get_extra_data_command method
    When
        - running get_remote_data_command
    Then
        - an "API rate limit" error is thrown so that the server will stop the sync loop and will resume from the last
        incident.
    """
    import sys

    from CortexXDRIR import Client, get_remote_data_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"id": 1, "lastUpdate": 0}

    mocker.patch("CortexXDRIR.return_error", side_effect=sys.exit)
    mocker.patch("CortexXDRIR.get_incident_extra_data_command", side_effect=Exception("Rate limit exceeded"))
    with pytest.raises(SystemExit):
        _ = get_remote_data_command(client, args)


def test_get_remote_data_command_should_not_update(requests_mock, mocker):
    """
    Given:
        -  an XDR client
        - arguments (id and lastUpdate time set to a higher than incident modification time)
        - a raw incident (get-extra-data results)
    When
        - running get_remote_data_command
    Then
        - returns an empty dict
    """
    from CortexXDRIR import Client, get_remote_data_command, sort_all_list_incident_fields

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"id": 1, "lastUpdate": "2020-07-31T00:00:00Z"}
    raw_incident = load_test_data("./test_data/get_incident_extra_data.json")
    expected_modified_incident = {"id": 1, "in_mirror_error": ""}
    sort_all_list_incident_fields(expected_modified_incident)

    # make sure get-extra-data is returning an incident
    mocker.patch("CortexXDRIR.get_last_mirrored_in_time", return_value=0)
    mocker.patch("CortexXDRIR.check_if_incident_was_modified_in_xdr", return_value=False)
    mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=50)
    mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
    requests_mock.post(f"{XDR_URL}/public_api/v1/incidents/get_incident_extra_data/", json=raw_incident)

    response = get_remote_data_command(client, args)
    assert response.mirrored_object == expected_modified_incident
    assert response.entries == []


@pytest.mark.parametrize(
    argnames="incident_status, close_xsoar_incident",
    argvalues=[(status, close_flag) for status in XDR_RESOLVED_STATUS_TO_XSOAR for close_flag in [True, False]],
)
def test_get_remote_data_command_should_close_issue(capfd, requests_mock, mocker, incident_status, close_xsoar_incident):
    """
    Given:
        - an XDR client
        - arguments (id and lastUpdate time set to a lower than incident modification time)
        - a raw incident (get-extra-data results) indicating the incident was closed on XDR side
    When
        - running get_remote_data_command
    Then
        - If close_xsoar_incident is True, the mirrored_object in the GetRemoteDataResponse holds the closing entry.
        - If close_xsoar_incident is False, the mirrored_object in the GetRemoteDataResponse does not hold the closing entry.
    """
    import copy

    from CortexXDRIR import Client, get_remote_data_command, sort_all_list_incident_fields

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    client._params["close_xsoar_incident"] = close_xsoar_incident
    args = {"id": 1, "lastUpdate": 0}
    raw_incident = load_test_data("./test_data/get_incident_extra_data.json")
    raw_incident["reply"]["incident"]["status"] = incident_status
    raw_incident["reply"]["incident"]["resolve_comment"] = "Handled"

    close_notes_prefix = "Known Issue.\n" if incident_status == "resolved_known_issue" else ""
    close_notes = f"{close_notes_prefix}Handled"

    expected_modified_incident = raw_incident["reply"]["incident"].copy()
    expected_modified_incident["alerts"] = copy.deepcopy(raw_incident["reply"].get("alerts").get("data"))
    expected_modified_incident["network_artifacts"] = raw_incident["reply"].get("network_artifacts").get("data")
    expected_modified_incident["file_artifacts"] = raw_incident["reply"].get("file_artifacts").get("data")
    expected_modified_incident["id"] = expected_modified_incident.get("incident_id")
    expected_modified_incident["assigned_user_mail"] = ""
    expected_modified_incident["assigned_user_pretty_name"] = ""
    expected_modified_incident["in_mirror_error"] = ""
    del expected_modified_incident["creation_time"]
    expected_modified_incident.get("alerts")[0]["host_ip_list"] = (
        expected_modified_incident.get("alerts")[0].get("host_ip").split(",")
    )

    expected_closing_entry = {}
    if close_xsoar_incident:
        expected_modified_incident["closeReason"] = XDR_RESOLVED_STATUS_TO_XSOAR[incident_status]
        expected_modified_incident["closeNotes"] = close_notes

        expected_closing_entry = {
            "Type": 1,
            "Contents": {
                "dbotIncidentClose": True,
                "closeReason": XDR_RESOLVED_STATUS_TO_XSOAR[incident_status],
                "closeNotes": close_notes,
            },
            "ContentsFormat": "json",
        }

    mocker.patch("CortexXDRIR.get_last_mirrored_in_time", return_value=0)
    mocker.patch("CortexXDRIR.check_if_incident_was_modified_in_xdr", return_value=True)
    mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=50)
    mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
    mocker.patch.object(Client, "get_multiple_incidents_extra_data", return_value=raw_incident["reply"])
    with capfd.disabled():
        response = get_remote_data_command(client, args)
    sort_all_list_incident_fields(expected_modified_incident)

    assert response.mirrored_object == expected_modified_incident

    if close_xsoar_incident:
        assert expected_closing_entry in response.entries
    else:
        assert expected_closing_entry not in response.entries


def test_get_remote_data_command_sync_owners(requests_mock, mocker):
    """
    Given:
        -  an XDR client
        - arguments (id and lastUpdate time set to a lower than incident modification time)
        - a raw incident (get-extra-data results) with assigned mail moo@demisto.com
    When
        - running get_remote_data_command
    Then
        - the mirrored_object in the GetRemoteDataResponse is the same as the modified raw incident with the equivalent
        owner of the assigned mail
        - the entries in the GetRemoteDataResponse in empty
    """
    import copy

    from CortexXDRIR import Client, get_remote_data_command, sort_all_list_incident_fields

    mocker.patch.object(demisto, "params", return_value={"sync_owners": True})
    mocker.patch.object(demisto, "findUser", return_value={"email": "moo@demisto.com", "username": "username"})
    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"id": 1, "lastUpdate": 0}
    raw_incident = load_test_data("./test_data/get_incident_extra_data.json")
    raw_incident["reply"]["incident"]["assigned_user_mail"] = "moo@demisto.com"

    expected_modified_incident = raw_incident["reply"]["incident"].copy()
    expected_modified_incident["alerts"] = copy.deepcopy(raw_incident["reply"].get("alerts").get("data"))
    expected_modified_incident["network_artifacts"] = raw_incident["reply"].get("network_artifacts").get("data")
    expected_modified_incident["file_artifacts"] = raw_incident["reply"].get("file_artifacts").get("data")
    expected_modified_incident["id"] = expected_modified_incident.get("incident_id")
    expected_modified_incident["assigned_user_mail"] = "moo@demisto.com"
    expected_modified_incident["assigned_user_pretty_name"] = None
    expected_modified_incident["owner"] = "username"
    expected_modified_incident["in_mirror_error"] = ""
    del expected_modified_incident["creation_time"]
    expected_modified_incident.get("alerts")[0]["host_ip_list"] = (
        expected_modified_incident.get("alerts")[0].get("host_ip").split(",")
    )

    # make sure get-extra-data is returning an incident
    mocker.patch("CortexXDRIR.get_last_mirrored_in_time", return_value=0)
    mocker.patch("CortexXDRIR.check_if_incident_was_modified_in_xdr", return_value=True)
    mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=50)
    mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
    mocker.patch.object(Client, "get_multiple_incidents_extra_data", return_value=raw_incident["reply"])
    response = get_remote_data_command(client, args)
    sort_all_list_incident_fields(expected_modified_incident)

    assert response.mirrored_object == expected_modified_incident
    assert response.entries[0].get("Contents") == {"dbotIncidentReopen": True}


@pytest.mark.parametrize("last_update", ["2020-11-18T13:16:52.005381+02:00", "2024-03-21T17:02:02.000000645Z"])
def test_get_modified_remote_data_command(requests_mock, last_update):
    """
    Given:
        - an XDR client
        - arguments - lastUpdate time
        - raw incidents (result of client.get_incidents)
    When
        - running get_modified_remote_data_command
    Then
        - the method is returning a list of incidents IDs that were modified
    """
    from CortexXDRIR import Client, get_modified_remote_data_command

    get_incidents_list_response = load_test_data("./test_data/get_incidents_list.json")
    requests_mock.post(f"{XDR_URL}/public_api/v1/incidents/get_incidents/", json=get_incidents_list_response)

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"lastUpdate": last_update}

    response, _ = get_modified_remote_data_command(client, args)

    assert response.modified_incident_ids == ["1", "2"]


def test_get_contributing_event_command(requests_mock):
    from CortexXDRIR import Client, get_contributing_event_command

    contributing_events = load_test_data("./test_data/contributing_events.json")
    requests_mock.post(f"{XDR_URL}/public_api/v1/alerts/get_correlation_alert_data/", json=contributing_events)

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {
        "alert_ids": "[1111]",
    }

    response = get_contributing_event_command(client, args)

    assert response.outputs[0].get("alertID") == args.get("alert_ids").strip("[]")
    assert len(response.outputs[0].get("events")) == 1


def test_replace_featured_field_command(requests_mock):
    from CortexXDRIR import Client, replace_featured_field_command

    replace_featured_field = load_test_data("./test_data/replace_featured_field.json")
    requests_mock.post(f"{XDR_URL}/public_api/v1/featured_fields/replace_ad_groups", json=replace_featured_field)
    expected_response = {
        "fieldType": "ad_groups",
        "fields": [
            {"value": "new value", "comment": "this is a comment", "type": "ou"},
            {"value": "one new value", "comment": "", "type": ""},
        ],
    }

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {
        "ad_type": '["ou"]',
        "comments": '["this is a comment"]',
        "field_type": "ad_groups",
        "values": '["new value", "one new value"]',
    }

    response = replace_featured_field_command(client, args)

    assert response.outputs == expected_response
    assert len(response.outputs.get("fields")) == 2


def test_failure_to_update_incident():
    from CortexXDRIR import Client, update_incident_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)

    with pytest.raises(ValueError, match="Can't provide both assignee_email/assignee_name and unassign_user"):
        update_incident_command(client=client, args={"unassign_user": "true", "assigned_user_mail": "user", "status": "new"})


def test_update_incident(requests_mock):
    from CortexXDRIR import Client, update_incident_command

    update_incident_response = load_test_data("./test_data/update_incident.json")
    requests_mock.post(f"{XDR_URL}/public_api/v1/incidents/update_incident/", json=update_incident_response)

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {
        "incident_id": "1",
        "status": "new",
        "add_comment": "new comment",
    }
    readable_output, outputs, _ = update_incident_command(client, args)

    assert outputs is None
    assert readable_output == "Incident 1 has been updated"


@pytest.mark.parametrize("incident_changed, delta", [(True, {"CortexXDRIRstatus": "investigating"}), (False, {})])
def test_update_remote_system_command(incident_changed, delta):
    from CortexXDRIR import Client, update_remote_system_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    data = {"CortexXDRIRstatus": "uninvestigated"}
    expected_remote_id = "remote_id"
    args = {
        "remoteId": expected_remote_id,
        "data": data,
        "entries": [],
        "incidentChanged": incident_changed,
        "delta": delta,
        "status": 2,
    }
    actual_remote_id = update_remote_system_command(client, args)
    assert actual_remote_id == expected_remote_id


@pytest.mark.parametrize(
    "data",
    [
        {"close_reason": "Resolved", "status": "Other"},
        {"CortexXDRIRstatus": "resolved", "close_reason": "Resolved", "status": "False Positive"},
        {"status": "under_investigation"},
        {"status": "Resolved", "resolve_comment": "comment"},
        {"status": "False Positive", "resolve_comment": "comment"},
    ],
)
def test_update_remote_system_command_should_not_close_xdr_incident(mocker, data):
    """
    Given:
        - an XDR client with 'close_xdr_incident' set to False.
        - arguments indicating the incident was closed in XSOAR.
    When:
        - running update_remote_system_command with 'close_xdr_incident' set to False.
    Then:
        - the incident in XDR should not be closed.
        - other updates to the incident should still be applied.
    """
    from CortexXDRIR import Client, update_remote_system_command

    client = Client(
        base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False, params={"close_xdr_incident": False}
    )

    delta = copy.deepcopy(data)
    expected_remote_id = "remote_id"

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

    mock_update_incident_command = mocker.patch("CortexXDRIR.update_incident_command")
    update_remote_system_command(client, args)
    update_args = mock_update_incident_command.call_args[0][1]
    if data.get("status") in XSOAR_RESOLVED_STATUS_TO_XDR:
        assert "status" not in update_args
        assert "resolve_comment" not in update_args
    else:
        assert "status" in update_args
        if data.get("resolve_comment"):
            assert "resolve_comment" in update_args

    # checks when close_all_alerts is true -> should update only the alerts status
    client._params["close_alerts_in_xdr"] = True
    mock_update_related_alerts = mocker.patch("CortexXDRIR.update_related_alerts")
    update_remote_system_command(client, args)

    if mock_update_related_alerts.called:
        update_args = mock_update_related_alerts.call_args[0][1]
        assert "status" in update_args


def test_update_remote_system_command_incident_changed_but_no_delta(mocker):
    """
    Given:
        - an XDR client
        - arguments indicating the incident was changed in XSOAR but no delta found
    When:
        - running update_remote_system_command
    Then:
        - no update will happen when incident was changed in XSOAR but no delta found
    """
    from CortexXDRIR import Client, update_remote_system_command

    client = Client(
        base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False, params={"close_xdr_incident": False}
    )

    expected_remote_id = "remote_id"
    args = {
        "remoteId": expected_remote_id,
        "data": {},
        "entries": [],
        "incidentChanged": True,
        "delta": {},
        "status": 2,
    }

    mock_get_update_args = mocker.patch("CoreIRApiModule.get_update_args")
    incident_id = update_remote_system_command(client, args)
    assert mock_get_update_args.call_count == 0
    assert incident_id == expected_remote_id


def test_update_remote_system_command_closing_alerts_and_including_resolve_comment(mocker):
    """
    Given:
        - An XDR client configured with parameters for closing alerts in XDR.
        - Expected remote incident ID and arguments representing a resolved XSOAR incident with a delta
         containing close reason and resolve comment.

    When:
        - Calling update_remote_system_command with the provided client and arguments.

    Then:
        - The get_update_args function should be called once to prepare the update arguments.
        - The update_related_alerts function should be called once to handle the closure of related alerts.
        - The returned incident ID should match the expected remote incident ID.
    """
    from CortexXDRIR import Client, update_remote_system_command

    client = Client(
        base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False, params={"close_alerts_in_xdr": True}
    )

    expected_remote_id = "remote_id"
    args = {
        "remoteId": expected_remote_id,
        "data": {"CortexXDRIRstatus": "resolved", "close_reason": "Resolved", "status": "test"},
        "entries": [],
        "incidentChanged": True,
        "delta": {"close_reason": "resolved", "resolve_comment": "", "status": "resolved_true_positive"},
        "status": 2,
    }

    mocker.patch("CortexXDRIR.update_incident_command")
    mock_get_update_args = mocker.patch("CortexXDRIR.get_update_args", return_value=args.get("delta"))
    mock_update_related_alerts = mocker.patch("CortexXDRIR.update_related_alerts")
    incident_id = update_remote_system_command(client, args)
    assert mock_get_update_args.call_count == 1
    assert mock_update_related_alerts.call_count == 1
    assert incident_id == expected_remote_id


def test_get_update_args_close_incident_without_status_handler(mocker):
    """
      Given:
          - Arguments indicating that the incident was changed in XSOAR but closed without providing a reason.
      When:
          - Running the 'get_update_args.handle_outgoing_issue_closure' function with the provided arguments.
      Then:
          - The 'get_update_args.handle_outgoing_issue_closure' function should append the 'status' field in the delta
    with the value 'resolved_other'.
    """
    from CommonServerPython import UpdateRemoteSystemArgs
    from CoreIRApiModule import get_update_args

    args = {
        "remoteId": "remote_id",
        "data": {"closeNotes": "ancd", "status": "test"},
        "entries": [],
        "incidentChanged": True,
        "delta": {"runStatus": "1", "incident_id": "remote_id"},
        "status": 2,
    }

    parsed_args = UpdateRemoteSystemArgs(args)
    mocker.patch("CoreIRApiModule.handle_outgoing_incident_owner_sync")
    mocker.patch("CoreIRApiModule.handle_user_unassignment")
    parsed_args_delta = get_update_args(parsed_args)
    assert parsed_args_delta.get("status") == "resolved_other"


@freeze_time("1997-10-05 15:00:00 GMT")
def test_fetch_incidents_extra_data(requests_mock, mocker):
    """
    Given:
        - List of fetched incidents.
        - List of multiple extra of incidents data response
    When
        - Running fetch_incident
    Then
        - Verify the returned result is as we expected
    """
    from CortexXDRIR import Client, fetch_incidents

    raw_multiple_extra_data = load_test_data("./test_data/get_multiple_incidents_extra_data.json")
    raw_all_alerts_incident_2 = load_test_data("./test_data/get_extra_data_all_alerts.json").get("reply", {})

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=10, proxy=False)
    mocker.patch.object(demisto, "params", return_value={"extra_data": True, "mirror_direction": "Incoming"})
    mocker.patch.object(Client, "get_incident_extra_data", return_value=raw_all_alerts_incident_2)
    mocker.patch.object(
        Client, "get_multiple_incidents_extra_data", return_value=raw_multiple_extra_data.get("reply", {}).get("incidents", [])
    )
    mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
    mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
    mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=2)
    next_run, incidents = fetch_incidents(client, "3 month", "MyInstance", exclude_artifacts=False, last_run={})
    assert len(incidents) == 2
    assert incidents[0]["name"] == "XDR Incident 1 - desc1"
    assert json.loads(incidents[0]["rawJSON"]).get("incident_id") == "1"
    assert json.loads(incidents[1]["rawJSON"]).get("incident_id") == "2"


def test_get_incident_extra_data(mocker):
    """
    Given:
        -  an XDR client
        - arguments (id)
    When
        - Running get_incident_extra_data_command
    Then
        - Verify the returned result is as we expected
    """
    from CortexXDRIR import Client, get_incident_extra_data_command

    get_incident_extra_data_response = (
        load_test_data("./test_data/get_incident_extra_data_host_id_array.json").get("reply", {}).get("incidents", [])
    )
    mocker.patch.object(Client, "get_multiple_incidents_extra_data", return_value=get_incident_extra_data_response)
    mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=2)

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"incident_id": "1"}
    _, outputs, raw_incident = get_incident_extra_data_command(client, args)

    expected_output = {
        "Process(val.Name && val.Name == obj.Name)": [
            {
                "Name": "wildfire-test-pe-file.exe",
                "CommandLine": '"C:\\Users\\Administrator\\Downloads\\wildfire-test-pe-file.exe"',
                "Hostname": "AAAAAA",
            }
        ],
        "Endpoint(val.Hostname==obj.Hostname)": [{"Hostname": "AAAAAA", "ID": "1234"}],
    }
    assert raw_incident == get_incident_extra_data_response[0]
    assert expected_output["Process(val.Name && val.Name == obj.Name)"] == outputs["Process(val.Name && val.Name == obj.Name)"]
    assert expected_output["Endpoint(val.Hostname==obj.Hostname)"] == outputs["Endpoint(val.Hostname==obj.Hostname)"]


@pytest.mark.parametrize(
    "custom_mapping, expected_resolved_status",
    [
        (
            "Known Issue=Other,Duplicate Incident=Duplicate,False Positive=False Positive,"
            "True Positive=Resolved,Security Testing=Other,Other=Other",
            ["Other", "Duplicate", "Duplicate", "False Positive", "Resolved", "Other", "Other", "Resolved", "Resolved"],
        ),
        (
            "Known Issue=Other,Duplicate Incident=Other,False Positive=False Positive,"
            "True Positive=Resolved,Security Testing=Other,Other=Other",
            ["Other", "Other", "Duplicate", "False Positive", "Resolved", "Other", "Other", "Resolved", "Resolved"],
        ),
        (
            "Duplicate Incident=Other,Security Testing=Other,Other=Other",
            ["Other", "Other", "Duplicate", "False Positive", "Resolved", "Other", "Other", "Resolved", "Resolved"],
        ),
        # Expecting default mapping to be used when no mapping provided.
        ("", list(XDR_RESOLVED_STATUS_TO_XSOAR.values())),
        # Expecting default mapping to be used when improper mapping is provided.
        ("Duplicate=RANDOM1, Other=Random2", list(XDR_RESOLVED_STATUS_TO_XSOAR.values())),
        ("Duplicate Incident=Random3", list(XDR_RESOLVED_STATUS_TO_XSOAR.values())),
        # Expecting default mapping to be used when improper mapping *format* is provided.
        ("Duplicate Incident=Other False Positive=Other", list(XDR_RESOLVED_STATUS_TO_XSOAR.values())),
        # Expecting default mapping to be used for when improper key-value pair *format* is provided.
        (
            "Duplicate Incident=Other, False Positive=Other True Positive=Other",
            ["Other", "Other", "Duplicate", "False Positive", "Resolved", "Security Testing", "Other", "Resolved", "Resolved"],
        ),
    ],
    ids=[
        "case-1",
        "case-2",
        "case-3",
        "empty-case",
        "improper-input-case-1",
        "improper-input-case-2",
        "improper-input-case-3",
        "improper-input-case-4",
    ],
)
def test_xdr_to_xsoar_flexible_close_reason_mapping(capfd, mocker, custom_mapping, expected_resolved_status):
    """
    Given:
        - A custom XDR->XSOAR close-reason mapping
        - Expected resolved XSOAR status according to the custom mapping.
    When
        - Handling incoming closing-incident (handle_incoming_closing_incident(...) executed).
    Then
        - The resolved XSOAR statuses match the expected statuses for all possible XDR close-reasons.
    """
    from CortexXDRIR import handle_incoming_incident

    mocker.patch.object(
        demisto, "params", return_value={"mirror_direction": "Both", "custom_xdr_to_xsoar_close_reason_mapping": custom_mapping}
    )

    all_xdr_close_reasons = XDR_RESOLVED_STATUS_TO_XSOAR.keys()

    for i, xdr_close_reason in enumerate(all_xdr_close_reasons):
        # Mock an xdr incident with "resolved" status.
        incident_data = load_test_data("./test_data/resolved_incident_data.json")
        # Set incident status to be tested close-reason.
        incident_data["status"] = xdr_close_reason

        # Overcoming expected non-empty stderr test failures (Errors are submitted to stderr when improper mapping is provided).
        with capfd.disabled():
            close_entry = handle_incoming_incident(incident_data)
        assert close_entry["Contents"]["closeReason"] == expected_resolved_status[i]


@pytest.mark.parametrize(
    "custom_mapping, direction, should_raise_error",
    [
        ("Other=Other,Duplicate=Other,False Positive=False Positive,Resolved=True Positive", XSOAR_TO_XDR, False),
        ("Known Issue=Other,Duplicate Incident=Duplicate,False Positive=False Positive", XDR_TO_XSOAR, False),
        ("Duplicate Incident=Random", XSOAR_TO_XDR, True),
        ("Duplicate=RANDOM1, Other=Random2", XDR_TO_XSOAR, True),
        # Inverted map provided
        ("Duplicate=Duplicate Incident", XDR_TO_XSOAR, True),
        ("Duplicate Incident=Duplicate", XSOAR_TO_XDR, True),
        # Improper mapping
        ("Random1, Random2", XDR_TO_XSOAR, True),
        ("Random1, Random2", XSOAR_TO_XDR, True),
    ],
    ids=["case-1", "case-2", "case-3", "case-4", "case-5", "case-6", "case-7", "case-8"],
)
def test_test_module(capfd, custom_mapping, direction, should_raise_error):
    """
    Given:
        - mock client with username and api_key (basic auth)
    When:
        - run `test_module` function
    Then:
        - Ensure no error is raised, and return `ok`
    """
    from CortexXDRIR import Client, validate_custom_close_reasons_mapping

    # using two different credentials object as they both fields need to be encrypted
    base_url = urljoin("dummy_url", "/public_api/v1")
    proxy = demisto.params().get("proxy")
    verify_cert = not demisto.params().get("insecure", False)

    Client(base_url=base_url, proxy=proxy, verify=verify_cert, timeout=120, params=demisto.params())
    # Overcoming expected non-empty stderr test failures (Errors are submitted to stderr when improper mapping is provided).
    with capfd.disabled():
        if should_raise_error:
            with pytest.raises(DemistoException):
                validate_custom_close_reasons_mapping(mapping=custom_mapping, direction=direction)
        else:
            try:
                validate_custom_close_reasons_mapping(mapping=custom_mapping, direction=direction)
            except DemistoException as e:
                pytest.fail(f"Unexpected exception raised for input {input}: {e}")


@freeze_time("1997-10-05 15:00:00 GMT")
def test_convert_datetime_to_epoch():
    """
    Given:
      - Datetime object

    When:
      - Calling convert_datetime_to_epoch()

    Then:
      - Returned epoch int matches expected
    """
    from datetime import datetime

    from CortexXDRIR import convert_datetime_to_epoch

    input_datetime = datetime.now()
    assert convert_datetime_to_epoch(input_datetime) == 876063600


@freeze_time("1997-10-05 15:00:00 GMT")
def test_convert_epoch_to_milli():
    """
    Given:
      - Epoch timestamp

    When:
      - Calling convert_epoch_to_milli()

    Then:
      - Returned timestamp matches expected milliseconds
    """
    from CortexXDRIR import convert_epoch_to_milli

    input_epoch = 1577836800
    assert convert_epoch_to_milli(input_epoch) == 1577836800000


@freeze_time("1997-10-05 15:00:00 GMT")
def test_convert_datetime_to_epoch_millis():
    """
    Given:
      - Datetime object

    When:
      - Calling convert_datetime_to_epoch_millis()

    Then:
      - Returned epoch timestamp matches expected milliseconds
    """
    from datetime import datetime

    from CortexXDRIR import convert_datetime_to_epoch_millis

    input_datetime = datetime.now()
    assert convert_datetime_to_epoch_millis(input_datetime) == 876063600


@freeze_time("1997-10-05 15:00:00 GMT")
def test_generate_current_epoch_utc():
    """
    Given: Nothing

    When:
      - Calling generate_current_epoch_utc()

    Then:
      - Returned value is integer epoch timestamp
    """
    from CortexXDRIR import generate_current_epoch_utc

    epoch = generate_current_epoch_utc()
    assert isinstance(epoch, int)
    assert epoch > 876000


def test_generate_key():
    """
    Given: None

    When: Calling generate_key()

    Then: Verify the returned result is as we expected
    """
    from CortexXDRIR import generate_key

    key = generate_key()
    assert len(key) == 128


def test_create_auth():
    """
    Given: Client ID and client secret

    When: Calling create_auth()

    Then: Verify the returned result is as we expected
    """
    from CortexXDRIR import create_auth

    auth = create_auth("client_id")
    assert len(auth) == 3


def test_clear_trailing_whitespace():
    """
    Given:
      - list of dictionary containing a value of String with trailing whitespace

    When:
      - Calling clear_trailing_whitespace()

    Then:
      - Trailing whitespace should be removed
    """
    from CortexXDRIR import clear_trailing_whitespace

    alerts = [{"example": "value  "}]
    actual = clear_trailing_whitespace(alerts)
    assert actual == [{"example": "value"}]


def test_filter_and_save_unseen_incident_limit_test():
    """
    Given:
      - List of incidents with creation times
      - Last fetch time

    When:
      - Calling filter_and_save_unseen_incident multiple times

    Then:
      - Returns maximum number of incidents per run
    """
    from CortexXDRIR import filter_and_save_unseen_incident

    incident = [{"id": "1", "creation_time": 1577836800000}, {"id": "2", "creation_time": 1577836800001}]
    assert filter_and_save_unseen_incident(incident, 1, 1) == [{"id": "1", "creation_time": 1577836800000}]


@freeze_time("1997-10-05 15:00:00 GMT")
def test_get_headers(mocker):
    """
    Given:
    - A dictionary of parameters including 'apikey', 'apikey_id', and 'prevent_only'

    When:
    - Calling get_headers with the given parameters

    Then:
    - Returns a dictionary containing the expected headers
    """
    from CortexXDRIR import get_headers

    mocker.patch("secrets.choice", return_value="test")
    # Define the parameters
    params = {"apikey": "test_api_key", "apikey_id": "test_api_key_id", "prevent_only": False}

    headers = get_headers(params)

    assert len(headers) == 4
    assert headers["x-xdr-nonce"] == "test" * 64


@freeze_time("1997-10-05 15:00:00 GMT")
def test_get_last_mirrored_in_time_old_incident(mocker):
    """
    Given:
        - An old incident with a lastmirroredintime set

    When:
        - Calling get_last_mirrored_in_time

    Then:
        - Return the timestamp converted from the lastmirroredintime
    """
    from CortexXDRIR import get_last_mirrored_in_time

    demisto_incidents = [{"CustomFields": {"lastmirroredintime": "2020-01-01"}}]
    mocker.patch.object(demisto, "get_incidents", return_value=demisto_incidents)

    args = {}

    assert get_last_mirrored_in_time(args) == 1577836800000


def test_get_last_mirrored_in_time_new_incident_6_0(mocker):
    """
    Given:
        - A new 6.0 incident with a last_update set

    When:
        - Calling get_last_mirrored_in_time

    Then:
        - Return the timestamp converted from the last_update minus 120 seconds
    """
    from CortexXDRIR import get_last_mirrored_in_time

    mocker.patch.object(demisto, "get_incidents", return_value=[])
    args = {"last_update": "2020-01-01T00:02:00Z"}

    assert get_last_mirrored_in_time(args) == 1577836800000


def test_incident_modified():
    """
    Given:
        - incident_id
        - last_mirrored_time
        - last_modified_incidents
    When:
        - Calling check_if_incident_was_modified_in_xdr

    Then:
        - Verify the returned result is as we expected
    """
    from CortexXDRIR import check_if_incident_was_modified_in_xdr

    incident_id = "2"
    last_mirrored_time = 1578901000
    last_modified_incidents = {"1": 1578900000, "2": 1578905000}
    assert check_if_incident_was_modified_in_xdr(incident_id, last_mirrored_time, last_modified_incidents)


def test_create_parsed_alert(mocker):
    """
    Given:
            - product
            - vendor
            - local_ip
            - local_port
            - remote_ip
            - remote_port
            - event_timestamp
            - severity
            - alert_name
            - alert_description
    When:
        - Calling create_parsed_alert().

    Then:
        - Verify the returned result is as we expected
    """
    from CortexXDRIR import create_parsed_alert

    parsed_alert = create_parsed_alert(
        product="product",
        vendor="vendor",
        local_ip="1.1.1.1",
        local_port="1",
        remote_ip="2.2.2.2",
        remote_port="2",
        event_timestamp="",
        severity="high",
        alert_name="example",
        alert_description="Malicious File",
    )

    assert parsed_alert.get("alert_description") == "Malicious File"
    assert parsed_alert.get("severity") == "high"


def test_sync_incoming_incident_owners(mocker):
    """
    Given:
        - incident_data dict with assigned_user_mail

    When:
        - Calling sync_incoming_incident_owners().

    Then:
        - Verify the incident_data result is as we expected.
    """
    from CortexXDRIR import sync_incoming_incident_owners

    incident_data = {"assigned_user_mail": "www.example@test.com"}
    mocker.patch.object(demisto, "params", return_value={"sync_owners": True})
    mocker.patch.object(demisto, "findUser", return_value={"username": "tester"})
    sync_incoming_incident_owners(incident_data)
    assert incident_data.get("owner") == "tester"


def test_handle_incoming_user_unassignment(mocker):
    """
    Given:
        - incident_data dict with incident_id, owner.

    When:
        - Calling handle_incoming_user_unassignment().

    Then:
        - Verify the returned result is as we expected.
    """
    from CortexXDRIR import handle_incoming_user_unassignment

    incident_data = {"incident_id": "1", "owner": "tester"}
    mocker.patch.object(demisto, "params", return_value={"sync_owners": True})
    handle_incoming_user_unassignment(incident_data)
    assert not incident_data.get("owner")


def test_get_endpoints_by_status_command(mocker):
    """
    Given:
        - incident_data dict with incident_id, owner.

    When:
        - Calling get_endpoints_by_status_command().

    Then:
        - Verify the returned result is as we expected.
    """
    from CortexXDRIR import Client, get_endpoints_by_status_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"status": ["new", "in_progress"], "last_seen_gte": "11", "last_seen_lte": "1"}
    mocker.patch.object(Client, "get_endpoints_by_status", return_value=["1", {"endpoint_count": 2}])
    res = get_endpoints_by_status_command(client, args)
    assert res.readable_output == "['new', 'in_progress'] endpoints count: 1"


class TestGetIncidents:
    def test_get_multiple_incidents_extra_data(self, requests_mock, mocker):
        """
        Given: Incidents returned from client.
        When: Running get_multiple_incidents_extra_data.
        Then: Ensure the outputs contain the incidents from the client.
        """
        from CortexXDRIR import Client

        multiple_extra_data = load_test_data("./test_data/get_multiple_incidents_extra_data.json")
        alert_limit = multiple_extra_data["reply"]["alerts_limit_per_incident"]
        requests_mock.post(f"{XDR_URL}/incidents/get_multiple_incidents_extra_data/", json=multiple_extra_data)
        mocker.patch.object(demisto, "command", return_value="xdr-get-incident-extra-data")
        mocker.patch.object(Client, "_http_request", return_value=multiple_extra_data)
        client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=10, proxy=False)
        len_incidents = len(multiple_extra_data["reply"]["incidents"])
        outputs = Client.get_multiple_incidents_extra_data(
            client,
            statuses=["new"],
            starred=True,
            starred_incidents_fetch_window=1575806909185,
            incident_id_list=["1", "2"],
            exclude_artifacts=True,
        )
        assert len(outputs) == len_incidents
        assert outputs[0]["alerts"]["total_count"] <= alert_limit
        assert outputs[1]["alerts"]["total_count"] <= alert_limit


def test_get_incident_extra_data_incident_not_exist(mocker):
    """
    Given:
        -  an XDR client
        - arguments (id)
    When
        - Running get_incident_extra_data_command
    Then
        - Verify that if the incident id is not found, it returns an error.
    """
    from CortexXDRIR import Client, get_incident_extra_data_command

    mocker.patch.object(Client, "get_multiple_incidents_extra_data", return_value=[])
    mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=2)

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"incident_id": "1"}
    with pytest.raises(DemistoException) as e:
        _, outputs, raw_incident = get_incident_extra_data_command(client, args)
    assert str(e.value) == "Incident 1 is not found"


def test_sort_all_incident_data_fields_fetch_case_get_multiple_incidents_extra_data_format(mocker):
    """
    Given:
        -  raw incident in get_incident_extra_data format- alerts and artifacts not in
        incident data information
    When
        - Running sort_all_list_incident_fields
    Then
        - Verify that alerts and artifacts are found.
    """
    from CortexXDRIR import sort_all_list_incident_fields, sort_incident_data

    incident_case_get_multiple_incidents_extra_data = (
        load_test_data("./test_data/get_multiple_incidents_extra_data.json").get("reply").get("incidents")[0]
    )
    incident_data = sort_incident_data(incident_case_get_multiple_incidents_extra_data)
    sort_all_list_incident_fields(incident_data)
    assert incident_data.get("alerts")
    assert incident_data.get("incident_sources") == ["XDR Agent"]
    assert incident_data.get("status") == "new"
    assert len(incident_data.get("file_artifacts")) == 1


def test_update_alerts_in_xdr_command_expected_result(mocker):
    """
    Given:
        -  an XDR client
        - arguments (incident_id)
    When
        - Running update_alerts_in_xdr_command
    Then
        - Verify update alerts
    """
    from CortexXDRIR import Client, update_alerts_in_xdr_command

    xdrIr_client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=10, proxy=False)
    http_request = mocker.patch.object(xdrIr_client, "_http_request")
    http_request.return_value = {"reply": {"alerts_ids": ["1", "2", "3"]}}
    args = {"alert_ids": "1,2,3", "severity": "high", "status": "resolved_threat_handled", "comment": "fixed from test"}
    res = update_alerts_in_xdr_command(xdrIr_client, args)
    assert res.readable_output == "Alerts with IDs 1,2,3 have been updated successfully."


def test_update_alerts_in_xdr_command_fail_to_update(mocker):
    """
    Given:
        -  an XDR client
        - arguments (incident_id)
    When
        - Running update_alerts_in_xdr_command
    Then
        - Did not find alerts to update - raise an error
    """
    from CortexXDRIR import Client, update_alerts_in_xdr_command

    xdrIr_client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=10, proxy=False)
    http_request = mocker.patch.object(xdrIr_client, "_http_request")
    http_request.return_value = {"reply": {"alerts_ids": []}}
    args = {"alert_ids": "1,2,3", "severity": "high", "status": "resolved_threat_handled", "comment": "fixed from test"}
    with pytest.raises(DemistoException) as e:
        update_alerts_in_xdr_command(xdrIr_client, args)
    assert e.value.message == "Could not find alerts to update, please make sure you used valid alert IDs."


def test_update_alerts_in_xdr_command_invalid_response_no_reply(mocker):
    """
    Given:
        -  an XDR client
        - arguments (incident_id)
    When
        - Running update_alerts_in_xdr_command
    Then
        - Verify that if the incident id is not found, it returns an error.
    """
    from CortexXDRIR import Client, update_alerts_in_xdr_command

    xdrIr_client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=10, proxy=False)
    http_request = mocker.patch.object(xdrIr_client, "_http_request")
    http_request.return_value = {"alerts_ids": ["1", "2", "3"]}
    args = {"alert_ids": "1,2,3", "severity": "high", "status": "resolved_threat_handled", "comment": "fixed from test"}
    with pytest.raises(DemistoException) as e:
        update_alerts_in_xdr_command(xdrIr_client, args)
    assert e.value.message == (
        "Parse Error. Response not in format, can't find reply key. The response {'alerts_ids': ['1', '2', '3']}."
    )


def test_update_alerts_in_xdr_command_invalid_response_no_alerts_ids(mocker):
    """
    Given:
        -  an XDR client
        - arguments (incident_id)
    When
        - Running update_alerts_in_xdr_command
    Then
        - Verify that if the incident id is not found, it returns an error.
    """
    from CortexXDRIR import Client, update_alerts_in_xdr_command

    xdrIr_client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=10, proxy=False)
    http_request = mocker.patch.object(xdrIr_client, "_http_request")
    http_request.return_value = {"reply": {"alerts_ids": []}}
    args = {"alert_ids": "1,2,3", "severity": "high", "status": "resolved_threat_handled", "comment": "fixed from test"}
    with pytest.raises(DemistoException) as e:
        update_alerts_in_xdr_command(xdrIr_client, args)
    assert e.value.message == "Could not find alerts to update, please make sure you used valid alert IDs."


@pytest.mark.parametrize(
    "incident_changed, delta", [(True, {"CortexXDRIRstatus": "resolved", "close_reason": "False Positive"}), (False, {})]
)
def test_update_remote_system_command_update_alerts(mocker, incident_changed, delta):
    """
    Given:
        - an XDR client
        - arguments (incident fields)
    When
        - update_remote_system_command which triggers Running update_alerts_in_xdr_command
    Then
        - Verify alerts related to incident have been changed when closing the incident
    """
    from CortexXDRIR import Client, update_remote_system_command

    client = Client(
        base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False, params={"close_alerts_in_xdr": True}
    )
    data = {"CortexXDRIRstatus": "resolved", "status": "test"}
    expected_remote_id = "remote_id"
    args = {
        "remoteId": expected_remote_id,
        "data": data,
        "entries": [],
        "incidentChanged": incident_changed,
        "delta": delta,
        "status": 2,
    }
    with patch("CortexXDRIR.update_incident_command") as mock_update_incident_command:
        get_incident_extra_data_mock = mocker.patch.object(client, "get_incident_extra_data")
        get_incident_extra_data_mock.return_value = {"alerts": {"data": [{"alert_id": "123"}]}}
        mock_update_incident_command.return_value = {}
        http_request_mock = mocker.patch.object(client, "update_alerts_in_xdr_request")
        http_request_mock.return_value = "1,2,3"
        update_remote_system_command(client, args)


def test_update_alerts_in_xdr_request_called_with():
    """
    Given:
        - an XDR client
        - arguments (incident fields)
    When
        - update_alerts_in_xdr_request is called
    Then
        - the http request is called with the right args
    """
    alerts_ids = "1,2,3"
    severity = "High"
    status = "resolved"
    comment = "i am a test"
    from CortexXDRIR import Client

    client = Client(
        base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False, params={"close_alerts_in_xdr": True}
    )
    with patch.object(client, "_http_request") as mock_http_request, patch("CortexXDRIR.get_headers") as get_headers_mock:
        mock_http_request.return_value = {"reply": {"alerts_ids": alerts_ids}}
        get_headers_mock.return_value = {
            "x-xdr-timestamp": 123,
            "x-xdr-nonce": 456,
            "x-xdr-auth-id": str(678),
            "Authorization": 123,
        }
        client.update_alerts_in_xdr_request(alerts_ids, severity, status, comment)
        mock_http_request.assert_called_once_with(
            method="POST",
            url_suffix="/alerts/update_alerts",
            json_data={
                "request_data": {
                    "alert_id_list": "1,2,3",
                    "update_data": {"severity": "High", "status": "resolved", "comment": "i am a test"},
                }
            },
            headers={"x-xdr-timestamp": 123, "x-xdr-nonce": 456, "x-xdr-auth-id": "678", "Authorization": 123},
            timeout=120,
        )


def test_update_alerts_in_xdr_request_invalid_response():
    """
    Given:
        - an XDR client
        - arguments (incident fields)
    When
        - update_alerts_in_xdr_request is called
    Then
        - response is not in format-  raise an error
    """
    alerts_ids = "1,2,3"
    severity = "High"
    status = "resolved"
    comment = "i am a test"
    from CortexXDRIR import Client

    client = Client(
        base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False, params={"close_alerts_in_xdr": True}
    )
    with (
        patch.object(client, "_http_request") as mock_http_request,
        patch("CortexXDRIR.get_headers") as get_headers_mock,
        pytest.raises(DemistoException) as e,
    ):
        mock_http_request.return_value = {"replys": {"alerts_ids": alerts_ids}}
        get_headers_mock.return_value = {
            "x-xdr-timestamp": 123,
            "x-xdr-nonce": 456,
            "x-xdr-auth-id": str(678),
            "Authorization": 123,
        }
        client.update_alerts_in_xdr_request(alerts_ids, severity, status, comment)
    assert e.value.message == (
        "Parse Error. Response not in format, can't find reply key. The response {'replys': {'alerts_ids': '1,2,3'}}."
    )


def test_update_alerts_in_xdr_command():
    """
    Given:
        - an XDR client
        - arguments (incident fields)
    When
        - test_update_alerts_in_xdr_command is called
    Then
        - raises an error since there is no field to update
    """
    from CommonServerPython import DemistoException
    from CortexXDRIR import Client, update_alerts_in_xdr_command

    args = {"alert_ids": "1"}
    client = Client(
        base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False, params={"close_alerts_in_xdr": True}
    )
    with pytest.raises(DemistoException) as e:
        update_alerts_in_xdr_command(client, args)
    assert e.value.message == "Can not find a field to update for alerts ['1'], please fill in severity/status/comment."


def test_main(mocker):
    """
    Given:
        - Only the required params in the configuration.
    When:
        - Running a command.
    Then:
        - Validate that the code executes gracefully.
    """
    from CortexXDRIR import main

    mocker.patch.object(demisto, "params", return_value={"url": "test_url"})
    mocker.patch.object(demisto, "command", return_value="test-module")
    mock_client = mocker.patch("CortexXDRIR.Client", autospec=True)
    mock_client.test_module.return_value = "ok"
    main()


@freeze_time("1993-06-17 11:00:00 GMT")
def test_core_http_request_xpanse_tenant(mocker):
    """
    Unit test to verify behavior in Xpanse tenants on the Xsiam platform with XSOAR Marketplace.

    This test ensures that when working with Xpanse tenants on the Xsiam platform integrated with the
    XSOAR Marketplace, the http_request function from CommonServerPython is used instead of _apiCall,
    as required in Xsiam tenants (CIAC-10878).

    Given:
        - Only the required params in the configuration.
    When:
        - Running a get_incidents to test the http_request function in CoreIRApiModule.
    Then:
        - Should fail since command '_apiCall' is not available via engine.
    """
    from CommonServerPython import BaseClient
    from CortexXDRIR import Client

    base_url = urljoin("dummy_url", "/public_api/v1")
    client = Client(base_url=base_url, proxy=False, verify=False, timeout=120, params=False)
    mocker.patch("CoreIRApiModule.FORWARD_USER_RUN_RBAC", new=False)
    mocker.patch.object(demisto, "_apiCall", return_value=Exception("command '_apiCall' is not available via engine (85)"))
    mocker.patch.object(BaseClient, "_http_request", return_value={"reply": {"incidents": [{"incident": {"incident_id": "1"}}]}})
    res = client.get_incidents(incident_id_list=["1"])
    assert res == [{"incident": {"incident_id": "1"}}]


def test_get_xsoar_close_reasons(mocker):
    mock_response = {
        "body": '{"sysConf":{"incident.closereasons":"CustomReason1, CustomReason 2, Foo","versn":40},"defaultMap":{}}\n',
        "headers": {
            "Content-Length": ["104"],
            "X-Xss-Protection": ["1; mode=block"],
            "X-Content-Type-Options": ["nosniff"],
            "Strict-Transport-Security": ["max-age=10886400000000000; includeSubDomains"],
            "Vary": ["Accept-Encoding"],
            "Server-Timing": ["7"],
            "Date": ["Wed, 03 Jul 2010 09:11:35 GMT"],
            "X-Frame-Options": ["DENY"],
            "Content-Type": ["application/json"],
        },
        "status": "200 OK",
        "statusCode": 200,
    }
    mocker.patch.object(demisto, "internalHttpRequest", return_value=mock_response)
    assert get_xsoar_close_reasons() == list(XSOAR_RESOLVED_STATUS_TO_XDR.keys()) + ["CustomReason1", "CustomReason 2", "Foo"]


@freeze_time("1970-01-01 00:00:00.100")
def test_fetch_incidents_dedup():
    """
    Unit test to verify that incidents that occur in the same instant are not not missed or duplicated.

    Given:
        - Two incidents occur in the same instant.
    When:
        - Fetching incidents.
    Then:
        - Assert no incidents are missed or duplicated.
    """
    from CortexXDRIR import fetch_incidents

    last_run = {"time": 0}

    class MockClient:
        _incidents = load_test_data("./test_data/get_incidents_list_dedup.json")

        def save_modified_incidents_to_integration_context(self): ...

        def get_multiple_incidents_extra_data(self, gte_creation_time_milliseconds=0, limit=100, **_):
            return [inc for inc in self._incidents if inc["creation_time"] >= gte_creation_time_milliseconds][:limit]

    mock_client = MockClient()

    last_run, result_1 = fetch_incidents(
        client=mock_client,
        first_fetch_time="3 days",
        integration_instance={},
        exclude_artifacts=True,
        last_run=last_run,
        max_fetch=2,
    )

    assert len(result_1) == 2
    assert "XDR Incident 1" in result_1[0]["name"]
    assert "XDR Incident 2" in result_1[1]["name"]
    assert last_run["time"] == 100000001
    assert last_run["dedup_incidents"] == ["2"]

    last_run, result_2 = fetch_incidents(
        client=mock_client,
        first_fetch_time="3 days",
        integration_instance={},
        exclude_artifacts=True,
        last_run=last_run,
        max_fetch=2,
    )

    assert len(result_2) == 2
    assert "XDR Incident 3" in result_2[0]["name"]
    assert "XDR Incident 4" in result_2[1]["name"]
    assert last_run["time"] == 100000001
    assert last_run["dedup_incidents"] == ["2", "3", "4"]

    last_run, result_3 = fetch_incidents(
        client=mock_client,
        first_fetch_time="3 days",
        integration_instance={},
        exclude_artifacts=True,
        last_run=last_run,
        max_fetch=2,
    )

    assert len(result_3) == 2
    assert "XDR Incident 5" in result_3[0]["name"]
    assert "XDR Incident 6" in result_3[1]["name"]
    assert last_run["time"] == 100000002
    assert last_run["dedup_incidents"] == ["6"]

    # run empty test and assert last_run stays the same
    old_last_run = last_run.copy()

    last_run, empty_result = fetch_incidents(
        client=mock_client,
        first_fetch_time="3 days",
        integration_instance={},
        exclude_artifacts=True,
        last_run=last_run,
        max_fetch=2,
    )

    assert empty_result == []
    assert last_run == old_last_run


@freeze_time("2020-11-18T13:20:00.00000", tz_offset=0)
def test_get_modified_remote_data_default_xdr_delay(mocker):
    """
    Given:
        - an XDR client
        - arguments - lastUpdate time
        - raw incidents (result of client.get_incidents)
        - xdr_delay = None
    When
        - running get_modified_remote_data_command
    Then
        - the method is returning a list of incidents IDs that were modified after adding xdr_delay
    """
    from CommonServerPython import BaseClient
    from CortexXDRIR import Client, get_modified_remote_data_command

    mocker.patch.object(demisto, "getIntegrationContext")
    mocker.patch.object(
        BaseClient,
        "_http_request",
        return_value={"reply": {"total_count": 0, "result_count": 0, "incidents": [], "restricted_incident_ids": []}},
    )
    previous_last_update_time = "2020-11-18T13:15:00.000"
    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)

    modified_incidents_empty, new_last_run_time_empty = get_modified_remote_data_command(
        client,
        {
            "lastUpdate": previous_last_update_time,
        },
        previous_last_update_time,
    )

    assert not modified_incidents_empty.modified_incident_ids
    assert new_last_run_time_empty == "2020-11-18 13:19:00.001"


@freeze_time("2020-11-18T13:20:00.00000", tz_offset=0)
def test_get_modified_remote_data_two_minutes_xdr_delay(mocker):
    """
    Given:
        - an XDR client
        - arguments - lastUpdate time
        - raw incidents (result of client.get_incidents)
        - xdr_delay = 2 minutes
    When
        - running get_modified_remote_data_command
    Then
        - the method is returning a list of incidents IDs that were modified after adding xdr_delay
    """
    from CommonServerPython import BaseClient
    from CortexXDRIR import Client, get_modified_remote_data_command

    mocker.patch.object(demisto, "getIntegrationContext")
    mocker.patch.object(BaseClient, "_http_request", return_value=load_test_data("./test_data/get_incidents_list.json"))
    previous_last_update_time = "2020-11-18T13:15:00.000"
    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)

    incidents_response, new_last_time_stamp = get_modified_remote_data_command(
        client, {"lastUpdate": previous_last_update_time}, previous_last_update_time, xdr_delay=2
    )

    assert new_last_time_stamp == "2020-11-18 13:18:00.001"
    assert incidents_response.modified_incident_ids == ["1", "2"]


@freeze_time("2020-11-18T13:20:00.00000", tz_offset=0)
def test_mirror_in_empty_last_update(mocker):
    """
    Given:
        - an XDR client
        - Empty mirror-in args - lastUpdate time = '' (e.g. {'lastUpdate': ''}) may happen the first mirror-in iteration
        - raw incidents (result of client.get_incidents)
    When
        - Running get_modified_remote_data_command function
    Then
        - Make sure we set a default last_update time.
    """
    from CommonServerPython import BaseClient
    from CortexXDRIR import Client, get_modified_remote_data_command

    mocker.patch.object(demisto, "getIntegrationContext")
    mocker.patch.object(BaseClient, "_http_request", return_value=load_test_data("./test_data/get_incidents_list.json"))
    mock_debug = mocker.patch.object(demisto, "debug")

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    _, _ = get_modified_remote_data_command(client, {"lastUpdate": ""})

    expected_log = "Mirror last update is: last_update='' will set it to default_last_update='2020-11-18 13:18:00'"
    assert mock_debug.call_args_list[1].args[0] == expected_log
    assert "last_update='2020-11-18 13:18:00'" in mock_debug.call_args_list[2].args[0]


def test_mirror_in_wrong_last_update(mocker):
    """
    Given:
        - an XDR client
        - Wrong mirror-in args - lastUpdate time = 'abcdefg' (e.g. {'lastUpdate': 'abcdefg'})
        - raw incidents (result of client.get_incidents)
    When
        - Running get_modified_remote_data_command function
    Then
        - Make sure we raise an exception with the expected message.
    """
    from CommonServerPython import BaseClient
    from CortexXDRIR import Client, get_modified_remote_data_command

    mocker.patch.object(demisto, "getIntegrationContext")
    mocker.patch.object(BaseClient, "_http_request", return_value=load_test_data("./test_data/get_incidents_list.json"))

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    with pytest.raises(DemistoException) as e:
        _, _ = get_modified_remote_data_command(client, {"lastUpdate": "abcdefg"})

    assert e.value.message == "Failed to parse last_update='abcdefg' got last_update_utc=None"


def test_get_distribution_url_command_without_download():
    """
    Given:
        - `download_package` argument set to False.
    When:
        - Calling `get_distribution_url_command` without downloading the package.
    Then:
        - Should return a CommandResults object with the distribution URL and no file download.
    """
    from CoreIRApiModule import get_distribution_url_command

    client = MagicMock()
    client.get_distribution_url = MagicMock(return_value="https://example.com/distribution")

    args = {
        "distribution_id": "12345",
        "package_type": "x64",
        "download_package": "false",
        "integration_context_brand": "PaloAltoNetworksXDR",
    }

    result = get_distribution_url_command(client, args)
    client.get_distribution_url.assert_called_once_with("12345", "x64")
    assert isinstance(result, CommandResults)
    assert result.outputs == {"id": "12345", "url": "https://example.com/distribution"}
    assert result.outputs_prefix == "PaloAltoNetworksXDR.Distribution"
    assert result.outputs_key_field == "id"
    assert "[Distribution URL](https://example.com/distribution)" in result.readable_output


def test_get_distribution_url_command_with_download(mocker):
    """
    Given:
        - `download_package` set to True.
    When:
        - Calling `get_distribution_url_command` with downloading the package.
    Then:
        - Should return a list with CommandResults for the distribution URL and the downloaded file information.
    """
    from CoreIRApiModule import get_distribution_url_command

    client = MagicMock()
    client.get_distribution_url = MagicMock(return_value="https://example.com/distribution")
    client._http_request = MagicMock(return_value=b"mock_binary_data")

    args = {
        "distribution_id": "12345",
        "package_type": "x64",
        "download_package": "true",
        "integration_context_brand": "PaloAltoNetworksXDR",
    }
    mocker.patch(
        "CortexXDRIR.fileResult",
        return_value={
            "Contents": "",
            "ContentsFormat": "text",
            "Type": 3,
            "File": "xdr-agent-install-package.msi",
            "FileID": "11111",
        },
    )
    result = get_distribution_url_command(client, args)
    client.get_distribution_url.assert_called_once_with("12345", "x64")
    client._http_request.assert_called_once_with(method="GET", full_url="https://example.com/distribution", resp_type="content")
    assert isinstance(result, list)
    assert len(result) == 2
    command_result = result[1]
    assert isinstance(command_result, CommandResults)
    assert command_result.outputs == {"id": "12345", "url": "https://example.com/distribution"}
    assert command_result.outputs_prefix == "PaloAltoNetworksXDR.Distribution"
    assert command_result.outputs_key_field == "id"
    assert "Installation package downloaded successfully." in command_result.readable_output


def test_get_distribution_url_command_without_download_not_supported_type():
    """
    Given:
        - `download_package` argument set to True but package_type is not x64 or x86.
    When:
        - Calling `get_distribution_url_command` without downloading the package.
    Then:
        - Should raise a demisto error.
    """
    from CoreIRApiModule import get_distribution_url_command

    client = MagicMock()
    client.get_distribution_url = MagicMock(return_value="https://example.com/distribution")

    args = {
        "distribution_id": "12345",
        "package_type": "sh",
        "download_package": "true",
        "integration_context_brand": "PaloAltoNetworksXDR",
    }
    with pytest.raises(DemistoException) as e:
        get_distribution_url_command(client, args)
    client.get_distribution_url.assert_called_once_with("12345", "sh")
    assert e.value.message == "`download_package` argument can be used only for package_type 'x64' or 'x86'."


def test_handle_incoming_incident(capfd, mocker):
    """
    Given:
        - incident data of resolved incident
    When
        - Handling incoming closing-incident (handle_incoming_closing_incident(...) executed).
    Then
        - a resolved entry is being added
    """
    from CommonServerPython import EntryFormat, EntryType
    from CortexXDRIR import handle_incoming_incident

    custom_mapping = (
        "Known Issue=Other,Duplicate Incident=Duplicate,False Positive=False Positive,"
        "True Positive=Resolved,Security Testing=Other,Other=Other"
    )
    mocker.patch.object(
        demisto, "params", return_value={"mirror_direction": "Both", "custom_xdr_to_xsoar_close_reason_mapping": custom_mapping}
    )

    for xdr_reopen_reason in XDR_OPEN_STATUS_TO_XSOAR:
        incident_data = load_test_data("./test_data/resolved_incident_data.json")
        # Set incident status to be tested reopen-reason.
        incident_data["status"] = xdr_reopen_reason

        # Overcoming expected non-empty stderr test failures (Errors are submitted to stderr when improper mapping is provided).
        with capfd.disabled():
            reopen_entry = handle_incoming_incident(incident_data)
        assert reopen_entry == {
            "Type": EntryType.NOTE,
            "Contents": {"dbotIncidentReopen": True},
            "ContentsFormat": EntryFormat.JSON,
        }


def test_get_remote_data_command_exclude_fields(mocker):
    """
    Given:
        - An XDR client with base URL, headers, and mock HTTP requests
        - Arguments (`id` set to 1 and `lastUpdate` set to 0, which is lower than incident modification time)
    When:
        - Running `get_remote_data_command` with different combinations of `exclude_artifacts`,
        `excluded_alert_fields`, and `remove_nulls_from_alerts` arguments
    Then:
        - The correct `POST` request is made to the `/incidents/get_multiple_incidents_extra_data/`
        endpoint with the appropriate parameters
        - The request data contains correct filters, exclusions, and sorting options based on the provided arguments
    """
    from CortexXDRIR import Client, get_remote_data_command

    client = Client(base_url=f"{XDR_URL}/public_api/v2", verify=False, timeout=120, proxy=False)
    args = {"id": 1, "lastUpdate": 0}
    mocker.patch.object(Client, "headers", return_value={"Authorization": "Bearer test_token"})
    # make sure get-extra-data is returning an incident
    mocker.patch("CortexXDRIR.get_last_mirrored_in_time", return_value=0)
    mocker.patch("CortexXDRIR.check_if_incident_was_modified_in_xdr", return_value=True)
    mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=50)
    mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
    client._http_request = MagicMock()

    # Test case 1: no excluded data
    get_remote_data_command(client, args)
    client._http_request.assert_called_with(
        method="POST",
        url_suffix="/incidents/get_multiple_incidents_extra_data/",
        json_data={
            "request_data": {
                "search_to": 100,
                "sort": {"field": "creation_time", "keyword": "asc"},
                "full_alert_fields": True,
                "search_from": 0,
                "filters": [{"field": "incident_id_list", "operator": "in", "value": ["1"]}],
            }
        },
        headers=client.headers,
        timeout=120,
    )

    # Test case 2: With excluded_alert_fields
    excluded_alert_fields = ["fieldA", "fieldB"]
    get_remote_data_command(client, args, excluded_alert_fields=excluded_alert_fields)
    client._http_request.assert_called_with(
        method="POST",
        url_suffix="/incidents/get_multiple_incidents_extra_data/",
        json_data={
            "request_data": {
                "search_to": 100,
                "sort": {"field": "creation_time", "keyword": "asc"},
                "full_alert_fields": True,
                "search_from": 0,
                "alert_fields_to_exclude": ["fieldA", "fieldB"],
                "filters": [{"field": "incident_id_list", "operator": "in", "value": ["1"]}],
            }
        },
        headers=client.headers,
        timeout=120,
    )

    # Test case 3: With remove_nulls_from_alerts
    get_remote_data_command(client, args, remove_nulls_from_alerts=True)
    client._http_request.assert_called_with(
        method="POST",
        url_suffix="/incidents/get_multiple_incidents_extra_data/",
        json_data={
            "request_data": {
                "search_to": 100,
                "sort": {"field": "creation_time", "keyword": "asc"},
                "full_alert_fields": True,
                "search_from": 0,
                "drop_nulls": True,
                "filters": [{"field": "incident_id_list", "operator": "in", "value": ["1"]}],
            }
        },
        headers=client.headers,
        timeout=120,
    )

    # Test case 5: With remove_nulls_from_alerts, excluded_alert_fields
    get_remote_data_command(client, args, remove_nulls_from_alerts=True, excluded_alert_fields=excluded_alert_fields)
    client._http_request.assert_called_with(
        method="POST",
        url_suffix="/incidents/get_multiple_incidents_extra_data/",
        json_data={
            "request_data": {
                "search_to": 100,
                "sort": {"field": "creation_time", "keyword": "asc"},
                "full_alert_fields": True,
                "search_from": 0,
                "alert_fields_to_exclude": ["fieldA", "fieldB"],
                "drop_nulls": True,
                "filters": [{"field": "incident_id_list", "operator": "in", "value": ["1"]}],
            }
        },
        headers=client.headers,
        timeout=120,
    )


@pytest.fixture
def mock_client():
    from CortexXDRIR import Client

    mock = MagicMock(Client)
    mock.get_multiple_incidents_extra_data = MagicMock()
    return mock


def test_fetch_incidents_multiple_incidents_extra_data_with_excluded_fields(mock_client):
    """
    Given:
        - An XDR client.
        - Parameters for fetching incidents including `exclude_artifacts`, `excluded_alert_fields`,
          and `remove_nulls_from_alerts`.
    When:
        - Running `fetch_incidents` to fetch multiple incidents with provided parameters.
    Then:
        - The correct call is made to `get_multiple_incidents_extra_data` with expected arguments.
        - The call parameters correctly reflect the input values, such as filters, exclusions,
          and sorting options.
    """
    from CortexXDRIR import fetch_incidents

    # Prepare test inputs
    first_fetch_time = "2023-01-01T00:00:00Z"
    integration_instance = "test_integration"
    last_run = {"time": 0, "incidents_from_previous_run": [], "dedup_incidents": []}
    statuses = ["open"]
    starred = False
    starred_incidents_fetch_window = None
    excluded_alert_fields = ["fieldA", "fieldB"]
    remove_nulls_from_alerts = True
    max_fetch = 10

    fetch_incidents(
        mock_client,
        first_fetch_time=first_fetch_time,
        integration_instance=integration_instance,
        last_run=last_run,
        exclude_artifacts=False,
        max_fetch=max_fetch,
        statuses=statuses,
        starred=starred,
        starred_incidents_fetch_window=starred_incidents_fetch_window,
        excluded_alert_fields=excluded_alert_fields,
        remove_nulls_from_alerts=remove_nulls_from_alerts,
    )
    mock_client.get_multiple_incidents_extra_data.assert_called_with(
        gte_creation_time_milliseconds=0,
        statuses=statuses,
        limit=max_fetch + len(last_run["dedup_incidents"]),
        starred=starred,
        starred_incidents_fetch_window=None,
        exclude_artifacts=False,
        excluded_alert_fields=excluded_alert_fields,
        remove_nulls_from_alerts=remove_nulls_from_alerts,
    )


def test_fetch_incidents_incidents_extra_datat_with_excluded_fields(mocker):
    """
    Given:
        - An XDR client.
        - Parameters for fetching incidents including `exclude_artifacts`, `excluded_alert_fields`,
          and `remove_nulls_from_alerts`.
    When:
        - Running `fetch_incidents` to fetch multiple incidents with provided parameters.
    Then:
        - The correct call is made to `get_multiple_incidents_extra_data` with expected arguments.
        - The call parameters correctly reflect the input values, such as filters, exclusions,
          and sorting options.
    """
    from CortexXDRIR import Client, fetch_incidents

    mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
    client = Client(base_url=f"{XDR_URL}/public_api/v2", verify=False, timeout=120, proxy=False)
    first_fetch_time = "2023-01-01T00:00:00Z"
    integration_instance = "test_integration"
    last_run = {"time": 0, "incidents_from_previous_run": [], "dedup_incidents": []}
    statuses = ["open"]
    starred = False
    starred_incidents_fetch_window = None
    excluded_alert_fields = ["fieldA", "fieldB"]
    remove_nulls_from_alerts = True
    max_fetch = 10

    raw_incident = load_test_data("./test_data/get_multiple_incidents_extra_data.json").get("reply", {}).get("incidents")[0]
    raw_incident["incident"]["alert_count"] = 10000
    raw_incident["incident"]["incident_id"] = 11

    mocker.patch.object(Client, "get_multiple_incidents_extra_data", return_value=[raw_incident])
    mock_get_incident_extra_data = mocker.patch.object(Client, "get_incident_extra_data", return_value=raw_incident)
    fetch_incidents(
        client,
        first_fetch_time=first_fetch_time,
        integration_instance=integration_instance,
        last_run=last_run,
        exclude_artifacts=False,
        max_fetch=max_fetch,
        statuses=statuses,
        starred=starred,
        starred_incidents_fetch_window=starred_incidents_fetch_window,
        excluded_alert_fields=excluded_alert_fields,
        remove_nulls_from_alerts=remove_nulls_from_alerts,
    )
    # Assume the alert count is above ALERTS_LIMIT_PER_INCIDENTS
    mock_get_incident_extra_data.assert_called_with(
        incident_id=11,
        exclude_artifacts=False,
        excluded_alert_fields=excluded_alert_fields,
        remove_nulls_from_alerts=remove_nulls_from_alerts,
    )


def test_handle_excluded_data_param_old_param():
    """
    Given:
        - The `excluded_data_from_alerts` parameter with various combinations of fields to exclude.
    When:
        - Calling the `handle_excluded_data_from_alerts_param` function.
    Then:
        - Ensure that the function correctly separates `null_values` from the rest of the exclusions.
        - Verify that the returned tuple matches the expected exclusions list and boolean value for `null_values`.
    """
    from CortexXDRIR import handle_excluded_data_from_alerts_param

    excluded_data_from_alerts = ["a", "b"]
    assert handle_excluded_data_from_alerts_param(excluded_data_from_alerts) == (["a", "b"], False)
    excluded_data_from_alerts = ["null_values", "b"]
    assert handle_excluded_data_from_alerts_param(excluded_data_from_alerts) == (["b"], True)


def test_extract_paths_and_names_valid_mapping():
    """
    Given:
        - Dictionary mapping file paths to file names
    When:
        - Calling extract_paths_and_names
    Then:
        - Returns tuple with separate lists of paths and names
    """
    from CortexXDRIR import extract_paths_and_names

    paths = ["C:\\folder\\file.exe", "/home/user/script.py", "document.txt"]

    file_paths, file_names = extract_paths_and_names(paths)

    assert set(file_paths) == {"C:\\folder\\file.exe", "/home/user/script.py", "document.txt"}
    assert set(file_names) == {"file.exe", "script.py", "document.txt"}
    assert len(file_paths) == len(file_names) == 3


def test_extract_paths_and_names_empty_mapping():
    """
    Given:
        - Empty dictionary
    When:
        - Calling extract_paths_and_names
    Then:
        - Returns tuple with empty lists
    """
    from CortexXDRIR import extract_paths_and_names

    paths = []
    file_paths, file_names = extract_paths_and_names(paths)

    assert file_paths == []
    assert file_names == []


def test_extract_paths_and_names_single_item():
    """
    Given:
        - Dictionary with single mapping
    When:
        - Calling extract_paths_and_names
    Then:
        - Returns tuple with single item lists
    """
    from CortexXDRIR import extract_paths_and_names

    paths = ["C:\\test\\file.exe"]
    file_paths, file_names = extract_paths_and_names(paths)

    assert file_paths == ["C:\\test\\file.exe"]
    assert file_names == ["file.exe"]


def test_api_key_list_command(requests_mock):
    """
    Given:
        - an XDR client
        - arguments (api_id, role, expires_before, expires_after)
    When
        - Running api_key_list_command
    Then
        - Verify the returned result is as we expected
    """
    from CortexXDRIR import Client, api_key_list_command

    api_keys_response = load_test_data("./test_data/get_api_keys.json")
    requests_mock.post(f"{XDR_URL}/public_api/v1/api_keys/get_api_keys/", json=api_keys_response)

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {
        "api_id": "1",
        "role": "Admin",
        "expires_before": "2024-01-01T00:00:00Z",
        "expires_after": "2020-01-01T00:00:00Z",
    }

    response = api_key_list_command(client, args)

    assert response.outputs == api_keys_response["reply"]["DATA"]
    assert response.outputs_prefix == "PaloAltoNetworksXDR.APIKeyData"
    assert "API Keys" in response.readable_output


def test_api_key_delete_command(requests_mock):
    """
    Given:
        - an XDR client
        - arguments (api_id)
    When
        - Running api_key_delete_command
    Then
        - Verify the returned result is as we expected
    """
    from CortexXDRIR import Client, api_key_delete_command

    requests_mock.post(f"{XDR_URL}/public_api/v1/api_keys/delete/", json={"reply": "ok"})

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"api_id": "1,2"}

    response = api_key_delete_command(client, args)

    assert response.readable_output == "API Keys deleted successfully."


@freeze_time("1993-06-17 11:00:00 GMT")
def test_fetch_incidents_name_generation(mocker):
    """
    Given:
        - Raw incidents from XDR (V4 with incident_name and Legacy without it)
    When:
        - Running fetch_incidents
    Then:
        - Verify the incident name is generated correctly for both cases
    """
    from CortexXDRIR import Client, fetch_incidents

    # 1. XDR V4 (has incident_name)
    # 2. XDR Legacy (no incident_name, has description)
    raw_incidents = [
        {
            "incident": {
                "incident_id": "100",
                "incident_name": "V4 Incident Name",
                "creation_time": 740314800000,
                "description": "V4 Description",
            }
        },
        {"incident": {"incident_id": "200", "creation_time": 740314800000, "description": "Legacy Description"}},
    ]

    mocker.patch.object(Client, "get_multiple_incidents_extra_data", return_value=raw_incidents)
    mocker.patch.object(Client, "save_modified_incidents_to_integration_context")
    mocker.patch.object(demisto, "params", return_value={"exclude_fields": False, "mirror_direction": "Incoming"})
    mocker.patch("CortexXDRIR.ALERTS_LIMIT_PER_INCIDENTS", new=50)

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)

    _, incidents = fetch_incidents(client, "3 month", "MyInstance", exclude_artifacts=False, last_run={})

    assert len(incidents) == 2
    assert incidents[0]["name"] == "XDR Incident 100 - V4 Incident Name"
    assert incidents[1]["name"] == "XDR Incident 200 - Legacy Description"


def test_get_asset_list_command(mocker):
    """
    Given:
        - asset_id_list in args
    When:
        - Running get_asset_list_command
    Then:
        - Verify the client.get_asset is called and results are correct with rich data
    """
    from CortexXDRIR import Client, get_asset_list_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"asset_id": "asset1,asset2"}
    mock_assets = [
        {
            "xdm.asset.id": "asset1",
            "xdm.asset.name": "name1",
            "xdm.asset.related_issues.critical_assets": 5,
            "xdm.asset.related_issues.critical_issues": 10,
            "xdm.asset.first_observed": 1609459200000,
            "xdm.asset.last_observed": 1609545600000,
        },
        {
            "xdm.asset.id": "asset2",
            "xdm.asset.name": "name2",
            "xdm.asset.related_issues.critical_assets": 0,
            "xdm.asset.related_issues.critical_issues": 1,
            "xdm.asset.first_observed": 1609459200000,
            "xdm.asset.last_observed": 1609545600000,
        },
    ]
    mocker.patch.object(Client, "get_asset", side_effect=[[mock_assets[0]], [mock_assets[1]]])

    res = get_asset_list_command(client, args)
    assert isinstance(res.outputs, list)
    assert len(res.outputs) == 2
    assert res.outputs[0]["xdm_asset_id"] == "asset1"
    assert res.outputs[1]["xdm_asset_id"] == "asset2"
    assert res.outputs[0]["xdm_asset_related_issues_critical_assets"] == 5
    assert "Cortex XDR Assets" in res.readable_output
    assert "asset1" in res.readable_output
    assert "name1" in res.readable_output
    assert "asset2" in res.readable_output
    assert "name2" in res.readable_output


def test_get_asset_list_command_list(mocker):
    """
    Given:
        - No asset_id in args, but filter and sort
    When:
        - Running get_asset_list_command
    Then:
        - Verify the client.list_assets is called and results are correct with rich data
    """
    from CortexXDRIR import Client, get_asset_list_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {
        "filter_json": '{"field": "xdm.asset.name", "operator": "contains", "value": "test"}',
        "sort_field": "xdm.asset.name",
        "sort_order": "DESC",
        "limit": "2",
    }
    mock_assets = [
        {
            "xdm.asset.id": "asset1",
            "xdm.asset.name": "test-asset-1",
            "xdm.asset.related_issues.critical_assets": 1,
            "xdm.asset.related_issues.critical_issues": 2,
            "xdm.asset.first_observed": 1609459200000,
            "xdm.asset.last_observed": 1609545600000,
        },
        {
            "xdm.asset.id": "asset2",
            "xdm.asset.name": "test-asset-2",
            "xdm.asset.related_issues.critical_assets": 0,
            "xdm.asset.related_issues.critical_issues": 0,
            "xdm.asset.first_observed": 1609459200000,
            "xdm.asset.last_observed": 1609545600000,
        },
    ]
    mocker.patch.object(Client, "list_assets", return_value=mock_assets)

    res = get_asset_list_command(client, args)
    assert isinstance(res.outputs, list)
    assert len(res.outputs) == 2
    assert res.outputs[0]["xdm_asset_id"] == "asset1"
    assert res.outputs[1]["xdm_asset_name"] == "test-asset-2"
    assert "Cortex XDR Assets" in res.readable_output
    assert "test-asset-1" in res.readable_output


def test_get_asset_schema_command(mocker):
    """
    Given:
        - No args
    When:
        - Running get_asset_schema_command
    Then:
        - Verify the client.get_asset_schema is called and results are correct
    """
    from CortexXDRIR import Client, get_asset_schema_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_schema = [
        {"name": "xdm.asset.id", "type": "string", "description": "Asset ID"},
        {"name": "xdm.asset.name", "type": "string", "description": "Asset Name"},
        {"name": "xdm.asset.status", "type": "enum", "description": "Asset Status"},
    ]
    mocker.patch.object(Client, "get_asset_schema", return_value=mock_schema)

    res = get_asset_schema_command(client, {})
    assert isinstance(res.outputs, list)
    assert res.outputs == mock_schema
    assert len(res.outputs) == 3
    assert res.outputs[0]["name"] == "xdm.asset.id"
    assert res.outputs[2]["type"] == "enum"
    assert "Cortex XDR Asset Schema" in res.readable_output


def test_get_asset_schema_field_options_command(mocker):
    """
    Given:
        - field_name in args
    When:
        - Running get_asset_schema_field_options_command
    Then:
        - Verify the client.get_asset_schema_field_options is called and results are correct
    """
    from CortexXDRIR import Client, get_asset_schema_field_options_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"field_name": "xdm.asset.status"}
    mock_options = [
        {"value": "active", "description": "Active Asset"},
        {"value": "inactive", "description": "Inactive Asset"},
        {"value": "pending", "description": "Pending Asset"},
    ]
    mocker.patch.object(Client, "get_asset_schema_field_options", return_value=mock_options)

    res = get_asset_schema_field_options_command(client, args)
    assert isinstance(res.outputs, dict)
    assert res.outputs["field_name"] == "xdm.asset.status"
    assert res.outputs["options"] == mock_options
    assert len(res.outputs["options"]) == 3
    assert res.outputs["options"][0]["value"] == "active"
    assert "Cortex XDR Asset Schema Options for xdm.asset.status" in res.readable_output


def test_create_asset_group_command(mocker):
    """
    Given:
        - group_name, group_type, group_description, membership_predicate_json in args
    When:
        - Running create_asset_group_command
    Then:
        - Verify the client.create_asset_group is called and results are correct
    """
    from CortexXDRIR import Client, create_asset_group_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {
        "group_name": "group1",
        "group_type": "static",
        "group_description": "description1",
        "membership_predicate_json": '{"field": "xdm.asset.name", "operator": "eq", "value": "test"}',
    }
    mock_res = {"group_id": "g1", "group_name": "group1", "status": "created"}
    mocker.patch.object(Client, "create_asset_group", return_value=mock_res)

    res = create_asset_group_command(client, args)
    assert res.readable_output == "Asset group created successfully"
    assert res.raw_response == mock_res
    assert res.raw_response["status"] == "created"


def test_delete_asset_group_command(mocker):
    """
    Given:
        - group_id in args
    When:
        - Running delete_asset_group_command
    Then:
        - Verify the client.delete_asset_group is called
    """
    from CortexXDRIR import Client, delete_asset_group_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"group_id": "g1"}
    mocker.patch.object(Client, "delete_asset_group")

    res = delete_asset_group_command(client, args)
    assert res.readable_output == "Asset group deleted successfully."


def test_list_asset_groups_command(mocker):
    """
    Given:
        - No args
    When:
        - Running list_asset_groups_command
    Then:
        - Verify the client.list_asset_groups is called and results are correct
    """
    from CortexXDRIR import Client, list_asset_groups_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_groups = [
        {
            "XDM.ASSET_GROUP.ID": "g1",
            "XDM.ASSET_GROUP.NAME": "group1",
            "XDM.ASSET_GROUP.TYPE": "static",
            "XDM.ASSET_GROUP.DESCRIPTION": "desc1",
        },
        {
            "XDM.ASSET_GROUP.ID": "g2",
            "XDM.ASSET_GROUP.NAME": "group2",
            "XDM.ASSET_GROUP.TYPE": "dynamic",
            "XDM.ASSET_GROUP.DESCRIPTION": "desc2",
        },
    ]
    mocker.patch.object(Client, "list_asset_groups", return_value=mock_groups)

    res = list_asset_groups_command(client, {})
    assert isinstance(res.outputs, list)
    assert len(res.outputs) == 2
    assert res.outputs[0]["XDM_ASSET_GROUP_ID"] == "g1"
    assert res.outputs[1]["XDM_ASSET_GROUP_NAME"] == "group2"
    assert "Cortex XDR Asset Groups" in res.readable_output
    assert "group1" in res.readable_output
    assert "group2" in res.readable_output


def test_update_asset_group_command(mocker):
    """
    Given:
        - group_id, group_name, group_type, group_description, membership_predicate_json in args
    When:
        - Running update_asset_group_command
    Then:
        - Verify the client.update_asset_group is called
    """
    from CortexXDRIR import Client, update_asset_group_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {
        "group_id": "g1",
        "group_name": "new_name",
        "group_type": "dynamic",
        "group_description": "new_desc",
        "membership_predicate_json": '{"field": "xdm.asset.name", "operator": "contains", "value": "test"}',
    }
    mocker.patch.object(Client, "update_asset_group")

    res = update_asset_group_command(client, args)
    assert res.readable_output == "Asset group updated successfully"


def test_replace_dots_in_keys():
    """
    Given:
        - A data structure (dict, list, or primitive) containing keys with dots.
    When:
        - Calling replace_dots_in_keys.
    Then:
        - All dots in dictionary keys should be replaced with underscores.
        - Nested structures (dicts within lists, etc.) should be processed correctly.
    """
    from CortexXDRIR import replace_dots_in_keys

    # Test with a simple dictionary
    data = {"a.b": 1, "c.d.e": "value"}
    expected = {"a_b": 1, "c_d_e": "value"}
    assert replace_dots_in_keys(data) == expected

    # Test with nested dictionary
    data = {"a.b": {"c.d": 1}}
    expected = {"a_b": {"c_d": 1}}
    assert replace_dots_in_keys(data) == expected

    # Test with list of dictionaries
    data = [{"a.b": 1}, {"c.d": 2}]
    expected = [{"a_b": 1}, {"c_d": 2}]
    assert replace_dots_in_keys(data) == expected

    # Test with complex nested structure
    data = {"outer.key": [{"inner.key": 1, "another.key": {"deep.key": "deep.value"}}, "simple string"], "primitive": 123}
    expected = {"outer_key": [{"inner_key": 1, "another_key": {"deep_key": "deep.value"}}, "simple string"], "primitive": 123}
    assert replace_dots_in_keys(data) == expected

    # Test with no dots
    data = {"key": "value", "list": [1, 2, 3]}
    assert replace_dots_in_keys(data) == data

    # Test with non-dict/list input
    assert replace_dots_in_keys("string.with.dots") == "string.with.dots"
    assert replace_dots_in_keys(123) == 123


@pytest.mark.parametrize(
    "file_name, file_content",
    [
        ("test_script.py", b"print('hello world')"),
        ("script_with_spaces.py", b"import os\nos.listdir('.')"),
        ("empty_script.py", b""),
    ],
    ids=["simple-script", "multi-line-script", "empty-script"],
)
def test_automation_script_create_command(mocker, tmp_path, file_name, file_content):
    """
    Given:
        - An XDR client
        - An entry_id pointing to a file to upload as an automation script
    When:
        - Running automation_script_create_command
    Then:
        - Verify the command returns a success message
        - Verify the client.create_automation_script is called with the correct file data
    """
    from CortexXDRIR import Client, automation_script_create_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)

    # Create a temporary file to simulate the war room file
    test_file = tmp_path / file_name
    test_file.write_bytes(file_content)

    mocker.patch.object(demisto, "getFilePath", return_value={"path": str(test_file), "name": file_name})
    mock_create = mocker.patch.object(Client, "create_automation_script")

    args = {"entry_id": "test_entry_id"}
    result = automation_script_create_command(client, args)

    assert result.readable_output == "Automation script created successfully."
    mock_create.assert_called_once()
    call_args = mock_create.call_args
    files_arg = call_args[0][0] if call_args[0] else call_args[1]["files"]
    assert files_arg["file"][0] == file_name
    assert files_arg["file"][1] == file_content


@pytest.mark.parametrize(
    "field, value, expected_filename",
    [
        ("script_uid", "abc123", "automation_script_abc123"),
        ("script_name", "my_script", "automation_script_my_script"),
        ("script_uid", "uid-with-dashes", "automation_script_uid-with-dashes"),
    ],
    ids=["by-uid", "by-name", "uid-with-special-chars"],
)
def test_automation_script_get_command(mocker, field, value, expected_filename):
    """
    Given:
        - An XDR client
        - Arguments specifying a field and value to identify the script
    When:
        - Running automation_script_get_command
    Then:
        - Verify the command returns a file result with the correct filename
        - Verify the client.get_automation_script is called with the correct request data
    """
    from CortexXDRIR import Client, automation_script_get_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)

    mock_file_content = b"script file content bytes"
    mocker.patch.object(Client, "get_automation_script", return_value=mock_file_content)
    mock_file_result = mocker.patch(
        "CortexXDRIR.fileResult",
        return_value={"Contents": "", "ContentsFormat": "text", "Type": 3, "File": expected_filename, "FileID": "123"},
    )

    args = {"field": field, "value": value}
    result = automation_script_get_command(client, args)

    expected_request_data = {"request_data": {"filter": {"field": field, "value": value}}}
    Client.get_automation_script.assert_called_once_with(expected_request_data)
    mock_file_result.assert_called_once_with(filename=expected_filename, data=mock_file_content)
    assert result["File"] == expected_filename


@pytest.mark.parametrize(
    "field, value",
    [
        ("script_uid", "abc123"),
        ("script_name", "my_script"),
        ("script_uid", "uid-456"),
    ],
    ids=["delete-by-uid", "delete-by-name", "delete-by-uid-2"],
)
def test_automation_script_delete_command(mocker, field, value):
    """
    Given:
        - An XDR client
        - Arguments specifying a field and value to identify the script to delete
    When:
        - Running automation_script_delete_command
    Then:
        - Verify the command returns a success message
        - Verify the client.delete_automation_script is called with the correct request data
    """
    from CortexXDRIR import Client, automation_script_delete_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_delete = mocker.patch.object(Client, "delete_automation_script")

    args = {"field": field, "value": value}
    result = automation_script_delete_command(client, args)

    expected_request_data = {"request_data": {"filter": {"field": field, "value": value}}}
    mock_delete.assert_called_once_with(expected_request_data)
    assert result.readable_output == "Automation script deleted successfully."


@pytest.mark.parametrize(
    "file_name, file_content",
    [
        ("test_playbook.json", b'{"name": "test playbook"}'),
        ("playbook.yml", b"name: test\nsteps:\n  - step1"),
        ("empty_playbook.json", b""),
    ],
    ids=["json-playbook", "yaml-playbook", "empty-playbook"],
)
def test_automation_playbook_create_command(mocker, tmp_path, file_name, file_content):
    """
    Given:
        - An XDR client
        - An entry_id pointing to a file to upload as an automation playbook
    When:
        - Running automation_playbook_create_command
    Then:
        - Verify the command returns a success message
        - Verify the client.create_automation_playbook is called with the correct file data
    """
    from CortexXDRIR import Client, automation_playbook_create_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)

    # Create a temporary file to simulate the war room file
    test_file = tmp_path / file_name
    test_file.write_bytes(file_content)

    mocker.patch.object(demisto, "getFilePath", return_value={"path": str(test_file), "name": file_name})
    mock_create = mocker.patch.object(Client, "create_automation_playbook")

    args = {"entry_id": "test_entry_id"}
    result = automation_playbook_create_command(client, args)

    assert result.readable_output == "Automation playbook created successfully."
    mock_create.assert_called_once()
    call_args = mock_create.call_args
    files_arg = call_args[0][0] if call_args[0] else call_args[1]["files"]
    assert files_arg["file"][0] == file_name
    assert files_arg["file"][1] == file_content


@pytest.mark.parametrize(
    "field, value, expected_filename",
    [
        ("playbook_uid", "pb-123", "automation_playbook_pb-123"),
        ("playbook_name", "my_playbook", "automation_playbook_my_playbook"),
        ("playbook_uid", "uid-with-dashes", "automation_playbook_uid-with-dashes"),
    ],
    ids=["by-uid", "by-name", "uid-with-special-chars"],
)
def test_automation_playbook_get_command(mocker, field, value, expected_filename):
    """
    Given:
        - An XDR client
        - Arguments specifying a field and value to identify the playbook
    When:
        - Running automation_playbook_get_command
    Then:
        - Verify the command returns a file result with the correct filename
        - Verify the client.get_automation_playbook is called with the correct request data
    """
    from CortexXDRIR import Client, automation_playbook_get_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)

    mock_file_content = b"playbook file content bytes"
    mocker.patch.object(Client, "get_automation_playbook", return_value=mock_file_content)
    mock_file_result = mocker.patch(
        "CortexXDRIR.fileResult",
        return_value={"Contents": "", "ContentsFormat": "text", "Type": 3, "File": expected_filename, "FileID": "456"},
    )

    args = {"field": field, "value": value}
    result = automation_playbook_get_command(client, args)

    expected_request_data = {"request_data": {"filter": {"field": field, "value": value}}}
    Client.get_automation_playbook.assert_called_once_with(expected_request_data)
    mock_file_result.assert_called_once_with(filename=expected_filename, data=mock_file_content)
    assert result["File"] == expected_filename


@pytest.mark.parametrize(
    "field, value",
    [
        ("playbook_uid", "pb-123"),
        ("playbook_name", "my_playbook"),
        ("playbook_uid", "uid-789"),
    ],
    ids=["delete-by-uid", "delete-by-name", "delete-by-uid-2"],
)
def test_automation_playbook_delete_command(mocker, field, value):
    """
    Given:
        - An XDR client
        - Arguments specifying a field and value to identify the playbook to delete
    When:
        - Running automation_playbook_delete_command
    Then:
        - Verify the command returns a success message
        - Verify the client.delete_automation_playbook is called with the correct request data
    """
    from CortexXDRIR import Client, automation_playbook_delete_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_delete = mocker.patch.object(Client, "delete_automation_playbook")

    args = {"field": field, "value": value}
    result = automation_playbook_delete_command(client, args)

    expected_request_data = {"request_data": {"filter": {"field": field, "value": value}}}
    mock_delete.assert_called_once_with(expected_request_data)
    assert result.readable_output == "Automation playbook deleted successfully."


@pytest.mark.parametrize(
    "args, expected_filters, expected_sort, expected_search_from, expected_search_to",
    [
        (
            {"case_id": "100", "status": "new"},
            [
                {"field": "case_id", "operator": "in", "value": [100]},
                {"field": "status_progress", "operator": "in", "value": ["new"]},
            ],
            {},
            # automatic pagination: no page/page_size -> window [0, default limit=50)
            0,
            50,
        ),
        (
            {"severity": "high", "sort_field": "creation_time", "sort_order": "asc"},
            [{"field": "severity", "operator": "in", "value": ["high"]}],
            {"field": "creation_time", "keyword": "asc"},
            0,
            50,
        ),
        (
            {"case_domain": "example.com", "limit": "10"},
            [{"field": "case_domain", "operator": "in", "value": ["example.com"]}],
            {},
            # automatic pagination driven by limit -> window [0, 10)
            0,
            10,
        ),
        (
            # manual pagination with page + page_size -> limit is ignored
            {"case_domain": "example.com", "limit": "10", "page": "2", "page_size": "5"},
            [{"field": "case_domain", "operator": "in", "value": ["example.com"]}],
            {},
            10,
            15,
        ),
        (
            # manual pagination with only page -> page_size falls back to limit (default 50)
            {"case_domain": "example.com", "page": "1"},
            [{"field": "case_domain", "operator": "in", "value": ["example.com"]}],
            {},
            50,
            100,
        ),
    ],
)
def test_case_list_command(args, expected_filters, expected_sort, expected_search_from, expected_search_to):
    """
    Given:
        - args for case list command
    When:
        - Running case_list_command
    Then:
        - Verify the client.search_cases is called with correct filters, sort and pagination window
    """
    from CortexXDRIR import Client, case_list_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    with patch.object(Client, "search_cases", return_value=[{"case_id": 1}]) as mock_search:
        case_list_command(client, args)

        call_args = mock_search.call_args[0][0]
        assert call_args["request_data"]["filters"] == expected_filters
        assert call_args["request_data"]["sort"] == expected_sort
        assert call_args["request_data"]["search_from"] == expected_search_from
        assert call_args["request_data"]["search_to"] == expected_search_to


@pytest.mark.parametrize(
    "args, expected_search_from, expected_limit",
    [
        # automatic pagination: default limit=50, no page/page_size -> window [0, 50)
        ({"extra_data": "true"}, 0, 50),
        # automatic pagination driven by limit -> window [0, 10)
        ({"extra_data": "true", "limit": "10"}, 0, 10),
        # manual pagination with page + page_size -> limit ignored, window [10, 15)
        ({"extra_data": "true", "limit": "10", "page": "2", "page_size": "5"}, 10, 15),
    ],
    ids=["auto-default", "auto-limit", "manual-paged"],
)
def test_case_list_command_extra_data_pagination(mocker, args, expected_search_from, expected_limit):
    """
    Given:
        - args for case list command with extra_data=true
    When:
        - Running case_list_command (extra data path)
    Then:
        - Verify client.get_multiple_incidents_extra_data is called with a paging window
          that respects manual ('page'/'page_size') vs automatic ('limit') pagination
    """
    from CortexXDRIR import Client, case_list_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_extra_data = mocker.patch.object(Client, "get_multiple_incidents_extra_data", return_value=[])

    case_list_command(client, args)

    call_kwargs = mock_extra_data.call_args.kwargs
    assert call_kwargs["search_from"] == expected_search_from
    assert call_kwargs["limit"] == expected_limit


@pytest.mark.parametrize(
    "args, expected_update_data",
    [
        # status mapping to the exact API strings
        ({"case_id": "100", "status": "new"}, {"status_progress": "New"}),
        ({"case_id": "100", "status": "under_investigation"}, {"status_progress": "In Progress"}),
        # notes-only update (operational comment, e.g. a ServiceNow ticket id)
        ({"case_id": "100", "notes": "SNOW-12345"}, {"notes": "SNOW-12345"}),
        # assigned_user update
        ({"case_id": "100", "assigned_user": "analyst@example.com"}, {"assigned_user": "analyst@example.com"}),
        # user_severity update
        ({"case_id": "100", "user_severity": "high"}, {"user_severity": "high"}),
        # clearing user_severity with an explicit empty string
        ({"case_id": "100", "user_severity": ""}, {"user_severity": ""}),
        # each resolve_reason mapping (requires status Resolved)
        (
            {"case_id": "100", "status": "resolved", "resolve_reason": "resolved_known_issue"},
            {"status_progress": "Resolved", "resolve_reason": "Resolved - Known Issue"},
        ),
        (
            {"case_id": "100", "status": "resolved", "resolve_reason": "resolved_duplicate"},
            {"status_progress": "Resolved", "resolve_reason": "Resolved - Duplicate Case"},
        ),
        (
            {"case_id": "100", "status": "resolved", "resolve_reason": "resolved_false_positive"},
            {"status_progress": "Resolved", "resolve_reason": "Resolved - False Positive"},
        ),
        (
            {"case_id": "100", "status": "resolved", "resolve_reason": "resolved_true_positive"},
            {"status_progress": "Resolved", "resolve_reason": "Resolved - True Positive"},
        ),
        (
            {"case_id": "100", "status": "resolved", "resolve_reason": "resolved_security_testing"},
            {"status_progress": "Resolved", "resolve_reason": "Resolved - Security Testing"},
        ),
        (
            {"case_id": "100", "status": "resolved", "resolve_reason": "resolved_other"},
            {"status_progress": "Resolved", "resolve_reason": "Resolved - Other"},
        ),
        # resolving with a resolve_comment as well
        (
            {
                "case_id": "100",
                "status": "resolved",
                "resolve_reason": "resolved_known_issue",
                "resolve_comment": "done",
            },
            {
                "status_progress": "Resolved",
                "resolve_reason": "Resolved - Known Issue",
                "resolve_comment": "done",
            },
        ),
    ],
)
def test_case_update_command(args, expected_update_data):
    """
    Given:
        - args for case update command
    When:
        - Running case_update_command
    Then:
        - Verify the client.update_case is called with the correct update_data payload
    """
    from CortexXDRIR import Client, case_update_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    with patch.object(Client, "update_case") as mock_update:
        res = case_update_command(client, args)

        assert res.readable_output == f"Case {args['case_id']} updated successfully"
        mock_update.assert_called_with(args["case_id"], request_data={"request_data": {"update_data": expected_update_data}})


@pytest.mark.parametrize(
    "args, expected_error",
    [
        # resolving without a resolve_reason
        (
            {"case_id": "100", "status": "resolved"},
            "The 'resolve_reason' argument is required when resolving a case",
        ),
        # resolve_reason provided without status Resolved
        (
            {"case_id": "100", "resolve_reason": "resolved_other"},
            "can only be provided when 'status' is set to 'Resolved'",
        ),
        # resolve_comment provided without status Resolved
        (
            {"case_id": "100", "resolve_comment": "some comment"},
            "can only be provided when 'status' is set to 'Resolved'",
        ),
        # empty update (no updatable fields)
        ({"case_id": "100"}, "No fields to update were provided"),
    ],
)
def test_case_update_command_errors(args, expected_error):
    """
    Given:
        - args that violate the case update conditional rules
    When:
        - Running case_update_command
    Then:
        - A DemistoException is raised with a clear message and no API call is made
    """
    from CortexXDRIR import Client, case_update_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    with patch.object(Client, "update_case") as mock_update:
        with pytest.raises(DemistoException, match=expected_error):
            case_update_command(client, args)
        mock_update.assert_not_called()


@pytest.mark.parametrize(
    "args, expected_update_data",
    [
        # custom_fields valid JSON object only -> merged keys appear in update_data
        (
            {"case_id": "100", "custom_fields": '{"my_custom_field": "value", "another_field": 42}'},
            {"my_custom_field": "value", "another_field": 42},
        ),
        # custom_fields combined with a standard field (notes) -> both present
        (
            {"case_id": "100", "notes": "see ticket", "custom_fields": '{"my_custom_field": "value"}'},
            {"notes": "see ticket", "my_custom_field": "value"},
        ),
        # custom_fields key colliding with a standard field name must NOT override the standard value
        (
            {"case_id": "100", "notes": "authoritative", "custom_fields": '{"notes": "override-attempt", "extra": 1}'},
            {"notes": "authoritative", "extra": 1},
        ),
    ],
)
def test_case_update_command_custom_fields(args, expected_update_data):
    """
    Given:
        - args for case update command including a custom_fields JSON object
    When:
        - Running case_update_command
    Then:
        - Verify the parsed custom_fields are merged into update_data and standard fields win on collision
    """
    from CortexXDRIR import Client, case_update_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    with patch.object(Client, "update_case") as mock_update:
        res = case_update_command(client, args)

        assert res.readable_output == f"Case {args['case_id']} updated successfully"
        mock_update.assert_called_with(args["case_id"], request_data={"request_data": {"update_data": expected_update_data}})


@pytest.mark.parametrize(
    "custom_fields_value",
    [
        # invalid JSON string
        "{not valid json}",
        # valid JSON but not an object (a list)
        "[1, 2, 3]",
        # valid JSON but not an object (a bare string)
        '"a string"',
    ],
)
def test_case_update_command_custom_fields_errors(custom_fields_value):
    """
    Given:
        - args with a custom_fields value that is not a valid JSON object
    When:
        - Running case_update_command
    Then:
        - A DemistoException is raised with a clear message and no API call is made
    """
    from CortexXDRIR import Client, case_update_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    args = {"case_id": "100", "custom_fields": custom_fields_value}
    with patch.object(Client, "update_case") as mock_update:
        with pytest.raises(DemistoException, match="The 'custom_fields' argument must be a valid JSON object."):
            case_update_command(client, args)
        mock_update.assert_not_called()


@pytest.mark.parametrize(
    "mock_response, expected_count_network, expected_count_file",
    [
        (
            [
                {
                    "network_artifacts": {"DATA": [{"id": 1}], "TOTAL_COUNT": 1},
                    "file_artifacts": {"DATA": [{"id": 2}], "TOTAL_COUNT": 1},
                }
            ],
            1,
            1,
        ),
        (
            [
                {
                    "network_artifacts": {"DATA": [], "TOTAL_COUNT": 0},
                    "file_artifacts": {"DATA": [{"id": 2}], "TOTAL_COUNT": 1},
                }
            ],
            0,
            1,
        ),
        (
            [
                {
                    "network_artifacts": {"DATA": [], "TOTAL_COUNT": 0},
                    "file_artifacts": {"DATA": [], "TOTAL_COUNT": 0},
                }
            ],
            0,
            0,
        ),
    ],
)
def test_case_artifact_list_command(mock_response, expected_count_network, expected_count_file):
    """
    Given:
        - mock response for get_case_artifacts
    When:
        - Running case_artifact_list_command
    Then:
        - Verify the number of returned results matches expected artifacts
    """
    from CortexXDRIR import Client, case_artifact_list_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    with patch.object(Client, "get_case_artifacts", return_value=mock_response):
        res = case_artifact_list_command(client, {"case_id": "100"})

        if expected_count_network == 0 and expected_count_file == 0:
            assert len(res) == 1
            assert "No artifacts found" in res[0].readable_output
        else:
            count = 0
            if expected_count_network > 0:
                count += 1
            if expected_count_file > 0:
                count += 1
            assert len(res) == count


@pytest.mark.parametrize(
    "args, expected_request_data",
    [
        (
            {"name": "test_bioc", "severity": "high", "page": "1", "limit": "10", "extra_data": "true"},
            {
                "request_data": {
                    "filters": [
                        {"field": "name", "operator": "EQ", "value": "test_bioc"},
                        {"field": "severity", "operator": "EQ", "value": "SEV_040_HIGH"},
                    ],
                    "extended_view": True,
                    "search_from": 10,
                    "search_to": 20,
                }
            },
        ),
        (
            {},
            {
                "request_data": {
                    "extended_view": False,
                    "search_from": 0,
                    "search_to": 50,
                    "filters": [],
                }
            },
        ),
    ],
)
def test_bioc_list_command(mocker, args, expected_request_data):
    """
    Given:
        - Arguments for filtering BIOCs.
    When:
        - Running bioc_list_command.
    Then:
        - Verify the client.get_biocs is called with correct parameters.
    """
    from CortexXDRIR import Client, bioc_list_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_reply = {"objects": [{"rule_id": "1", "name": "test_bioc", "type": "host", "severity": "high", "status": "enabled"}]}
    mocker.patch.object(Client, "get_biocs", return_value=mock_reply)

    res = bioc_list_command(client, args)

    assert res.outputs == mock_reply["objects"]
    Client.get_biocs.assert_called_with(expected_request_data)


@pytest.mark.parametrize(
    "args, expected_request_data, expected_output, expected_error",
    [
        (
            {
                "name": "test_bioc",
                "severity": "high",
                "type": "bioc_type",
                "is_xql": "true",
                "comment": "test_comment",
                "status": "enabled",
                "mitre_technique_id_and_name": "T1000",
                "mitre_tactic_id_and_name": "TA0001",
                "indicator": '{"field": "value"}',
            },
            {
                "request_data": [
                    {
                        "rule_id": None,
                        "name": "test_bioc",
                        "severity": "SEV_040_HIGH",
                        "type": "bioc_type",
                        "is_xql": True,
                        "comment": "test_comment",
                        "status": "enabled",
                        "mitre_technique_id_and_name": "T1000",
                        "mitre_tactic_id_and_name": "TA0001",
                        "indicator": {"field": "value"},
                    }
                ]
            },
            {"rule_id": "1", "readable_output": "BIOC created successfully"},
            None,
        ),
        (
            {"name": "test_bioc", "severity": "high", "indicator": "invalid_json"},
            None,
            None,
            "Unable to parse 'indicator'. Please use the JSON format.",
        ),
    ],
)
def test_bioc_create_command(mocker, args, expected_request_data, expected_output, expected_error):
    """
    Given:
        - Arguments for creating a BIOC.
    When:
        - Running bioc_create_command.
    Then:
        - Verify the client.insert_or_update_biocs is called and results are correct.
    """
    from CortexXDRIR import Client, bioc_create_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_reply = {"added_objects": [{"id": "1", "status": "BIOC created successfully"}]}
    mocker.patch.object(Client, "insert_or_update_biocs", return_value=mock_reply)

    if expected_error:
        with pytest.raises(DemistoException) as e:
            bioc_create_command(client, args)
        assert expected_error in str(e.value)
    else:
        res = bioc_create_command(client, args)
        assert res.outputs == {"rule_id": expected_output["rule_id"]}
        assert res.readable_output == expected_output["readable_output"]
        Client.insert_or_update_biocs.assert_called_with(expected_request_data)


@pytest.mark.parametrize(
    "mock_reply, expected_output, expected_readable_output",
    [
        (
            {"updated_objects": [{"id": "1", "status": "BIOC updated successfully"}]},
            {"rule_id": "1"},
            "BIOC updated successfully",
        ),
        (
            {"updated_objects": []},
            {},
            "No BIOCs updated.",
        ),
    ],
)
def test_bioc_update_command(mocker, mock_reply, expected_output, expected_readable_output):
    """
    Given:
        - Arguments for updating a BIOC.
    When:
        - Running bioc_update_command.
    Then:
        - Verify the client.insert_or_update_biocs is called and results are correct.
    """
    from CortexXDRIR import Client, bioc_update_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mocker.patch.object(Client, "insert_or_update_biocs", return_value=mock_reply)

    args = {"rule_id": "1", "name": "test_bioc", "severity": "high"}
    res = bioc_update_command(client, args)

    assert res.outputs == expected_output
    assert res.readable_output == expected_readable_output


@pytest.mark.parametrize(
    "mock_reply, expected_output",
    [
        ({"objects": ["1"]}, "BIOC with id 1 deleted successfully."),
        ({"objects": ["1", "2"]}, "BIOCs with ids 1, 2 deleted successfully."),
        ({"objects": []}, "No BIOCs were found to delete."),
    ],
)
def test_bioc_delete_command(mocker, mock_reply, expected_output):
    """
    Given:
        - Arguments for deleting a BIOC.
    When:
        - Running bioc_delete_command.
    Then:
        - Verify the client.delete_biocs is called and results are correct.
    """
    from CortexXDRIR import Client, bioc_delete_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mocker.patch.object(Client, "delete_biocs", return_value=mock_reply)

    args = {"name": "test_bioc"}
    res = bioc_delete_command(client, args)

    assert res.readable_output == expected_output


@pytest.mark.parametrize(
    "args, expected_request_data",
    [
        (
            {"name": "test_rule", "page": "1", "limit": "10", "extra_data": "true"},
            {
                "request_data": {
                    "filters": [{"field": "name", "operator": "EQ", "value": "test_rule"}],
                    "extended_view": True,
                    "search_from": 10,
                    "search_to": 20,
                }
            },
        ),
        (
            {"filter_json": '[{"field": "name", "operator": "EQ", "value": "test_rule"}]'},
            {
                "request_data": {
                    "filters": [{"field": "name", "operator": "EQ", "value": "test_rule"}],
                    "extended_view": False,
                    "search_from": 0,
                    "search_to": 50,
                }
            },
        ),
    ],
)
def test_correlation_rule_list_command(mocker, args, expected_request_data):
    """
    Given:
        - Arguments for filtering correlation rules.
    When:
        - Running correlation_rule_list_command.
    Then:
        - Verify the client.get_correlation_rules is called and results are correct.
    """
    from CortexXDRIR import Client, correlation_rule_list_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_reply = {"objects": [{"rule_id": "1", "name": "test_rule", "description": "desc", "is_enabled": True}]}
    mocker.patch.object(Client, "get_correlation_rules", return_value=mock_reply)

    res = correlation_rule_list_command(client, args)

    assert res.outputs == mock_reply["objects"]
    Client.get_correlation_rules.assert_called_with(expected_request_data)


@pytest.mark.parametrize(
    "args, expected_request_data, expected_output",
    [
        (
            {
                "name": "test_rule",
                "severity": "high",
                "xql_query": "dataset = xdr_data",
                "is_enabled": "true",
                "timezone": "UTC",
                "dataset": "xdr_data",
                "alert_category": "category",
                "execution_mode": "real_time",
                "mapping_strategy": "strategy",
                "description": "desc",
                "alert_name": "alert",
                "alert_description": "alert_desc",
                "search_window": "1h",
                "schedule_linux": "* * * * *",
                "schedule": "daily",
                "drilldown_query_timeframe": "1h",
                "investigation_query_link": "link",
                "suppression_enabled": "true",
                "suppression_duration": "1h",
                "suppression_fields": "field1",
                "user_defined_severity": "medium",
                "user_defined_category": "cat",
                "mitre_defs_json": '{"tactic": "test"}',
                "alert_fields": '{"field": "value"}',
            },
            {
                "request_data": [
                    {
                        "rule_id": None,
                        "name": "test_rule",
                        "severity": "SEV_040_HIGH",
                        "xql_query": "dataset = xdr_data",
                        "is_enabled": True,
                        "action": "ALERTS",
                        "timezone": "UTC",
                        "dataset": "xdr_data",
                        "alert_category": "CATEGORY",
                        "execution_mode": "REAL_TIME",
                        "mapping_strategy": "STRATEGY",
                        "description": "desc",
                        "alert_name": "alert",
                        "alert_description": "alert_desc",
                        "search_window": "1h",
                        "crontab": "* * * * *",
                        "simple_schedule": "daily",
                        "drilldown_query_timeframe": "1h",
                        "investigation_query_link": "link",
                        "suppression_enabled": True,
                        "suppression_duration": "1h",
                        "suppression_fields": "field1",
                        "user_defined_severity": "medium",
                        "user_defined_category": "cat",
                        "mitre_defs": {"tactic": "test"},
                        "alert_fields": {"field": "value"},
                    }
                ]
            },
            {"rule_id": "1", "readable_output": "Rule created successfully"},
        )
    ],
)
def test_correlation_rule_create_command(mocker, args, expected_request_data, expected_output):
    """
    Given:
        - Arguments for creating a correlation rule.
    When:
        - Running correlation_rule_create_command.
    Then:
        - Verify the client.create_or_update_correlation_rules is called and results are correct.
    """
    from CortexXDRIR import Client, correlation_rule_create_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_reply = {"added_objects": [{"id": "1", "status": "Rule created successfully"}]}
    mocker.patch.object(Client, "create_or_update_correlation_rules", return_value=mock_reply)

    res = correlation_rule_create_command(client, args)

    assert res.outputs == {"rule_id": expected_output["rule_id"]}
    assert res.readable_output == expected_output["readable_output"]
    Client.create_or_update_correlation_rules.assert_called_with(expected_request_data)


@pytest.mark.parametrize(
    "args, expected_request_data, expected_output",
    [
        (
            {"rule_id": "1", "name": "test_rule", "severity": "high", "is_enabled": "true"},
            {
                "request_data": [
                    {
                        "rule_id": "1",
                        "name": "test_rule",
                        "severity": "SEV_040_HIGH",
                        "xql_query": None,
                        "is_enabled": True,
                        "action": "ALERTS",
                        "timezone": None,
                        "dataset": None,
                        "alert_category": None,
                        "execution_mode": None,
                        "mapping_strategy": None,
                        "description": None,
                        "alert_name": None,
                        "alert_description": None,
                        "search_window": None,
                        "crontab": None,
                        "simple_schedule": None,
                        "drilldown_query_timeframe": None,
                        "investigation_query_link": None,
                        "suppression_enabled": None,
                        "suppression_duration": None,
                        "suppression_fields": None,
                        "user_defined_severity": None,
                        "user_defined_category": None,
                        "mitre_defs": {},
                        "alert_fields": {},
                    }
                ]
            },
            {"rule_id": "1", "readable_output": "Rule updated successfully"},
        )
    ],
)
def test_correlation_rule_update_command(mocker, args, expected_request_data, expected_output):
    """
    Given:
        - Arguments for updating a correlation rule.
    When:
        - Running correlation_rule_update_command.
    Then:
        - Verify the client.create_or_update_correlation_rules is called and results are correct.
    """
    from CortexXDRIR import Client, correlation_rule_update_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_reply = {"updated_objects": [{"id": "1", "status": "Rule updated successfully"}]}
    mocker.patch.object(Client, "create_or_update_correlation_rules", return_value=mock_reply)

    res = correlation_rule_update_command(client, args)

    assert res.outputs == {"rule_id": expected_output["rule_id"]}
    assert res.readable_output == expected_output["readable_output"]
    Client.create_or_update_correlation_rules.assert_called_with(expected_request_data)


@pytest.mark.parametrize(
    "mock_reply, args, expected_output, expected_filters",
    [
        (
            {"objects": ["1"], "objects_count": 1},
            {"rule_id": "1"},
            "Correlation Rule 1 was deleted.",
            {"request_data": {"filters": [{"field": "rule_id", "operator": "EQ", "value": 1}]}},
        ),
        (
            {"objects": ["1", "2"], "objects_count": 2},
            {"rule_id": "1,2"},
            "Correlation Rules 1, 2 were deleted.",
            {
                "request_data": {
                    "filters": [
                        {"field": "rule_id", "operator": "EQ", "value": 1},
                        {"field": "rule_id", "operator": "EQ", "value": 2},
                    ]
                }
            },
        ),
        (
            {"objects": [], "objects_count": 0},
            {"rule_id": "1"},
            "Could not find any correlation rules to delete.",
            {"request_data": {"filters": [{"field": "rule_id", "operator": "EQ", "value": 1}]}},
        ),
        (
            {"objects": ["1"], "objects_count": 1},
            {"rule_id": "1,invalid"},
            "Correlation Rule 1 was deleted.",
            {"request_data": {"filters": [{"field": "rule_id", "operator": "EQ", "value": 1}]}},
        ),
    ],
)
def test_correlation_rule_delete_command(mocker, mock_reply, args, expected_output, expected_filters):
    """
    Given:
        - Arguments for deleting correlation rules.
    When:
        - Running correlation_rule_delete_command.
    Then:
        - Verify the client.delete_correlation_rules is called and results are correct.
    """
    from CortexXDRIR import Client, correlation_rule_delete_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mocker.patch.object(Client, "delete_correlation_rules", return_value=mock_reply)

    res = correlation_rule_delete_command(client, args)

    assert res.readable_output == expected_output
    Client.delete_correlation_rules.assert_called_with(expected_filters)


def test_create_filters_for_bioc_and_correlation_rules_all_fields():
    """
    Given:
        - All possible arguments for creating filters.
    When:
        - Running create_filters_for_bioc_and_correlation_rules.
    Then:
        - Verify the returned filters list contains all expected filters.
    """
    from CortexXDRIR import create_filters_for_bioc_and_correlation_rules

    args = {
        "name": "test_name",
        "severity": "high",
        "type": "bioc_type",
        "is_xql": "true",
        "comment": "test_comment",
        "status": "enabled",
        "indicator": "test_indicator",
        "mitre_technique_id_and_name": "T1000,T1001",
        "mitre_tactic_id_and_name": "TA0001,TA0002",
        "xql_query": "test_query",
        "is_enabled": "true",
        "description": "test_description",
        "alert_name": "test_alert_name",
        "alert_category": "test_alert_category",
        "alert_description": "test_alert_description",
        "alert_fields": "field1,field2",
        "execution_mode": "real_time",
        "search_window": "1h",
        "schedule": "daily",
        "timezone": "UTC",
        "schedule_linux": "* * * * *",
        "suppression_enabled": "true",
        "suppression_duration": "1h",
        "suppression_fields": "field1",
        "dataset": "xdr_data",
        "user_defined_severity": "medium",
        "user_defined_category": "category",
        "mitre_defs_json": '{"tactic": "test"}',
        "investigation_query_link": "link",
        "drilldown_query_timeframe": "1h",
        "mapping_strategy": "strategy",
        "alert_domain": "domain",
    }

    filters = create_filters_for_bioc_and_correlation_rules(args)

    assert {"field": "name", "operator": "EQ", "value": "test_name"} in filters
    assert {"field": "severity", "operator": "EQ", "value": "SEV_040_HIGH"} in filters
    assert {"field": "type", "operator": "EQ", "value": "bioc_type"} in filters
    assert {"field": "is_xql", "operator": "EQ", "value": True} in filters
    assert {"field": "comment", "operator": "EQ", "value": "test_comment"} in filters
    assert {"field": "status", "operator": "EQ", "value": "enabled"} in filters
    assert {"field": "indicator", "operator": "EQ", "value": "test_indicator"} in filters
    assert {"field": "mitre_technique_id_and_name", "operator": "EQ", "value": ["T1000", "T1001"]} in filters
    assert {"field": "mitre_tactic_id_and_name", "operator": "EQ", "value": ["TA0001", "TA0002"]} in filters
    assert {"field": "xql_query", "operator": "EQ", "value": "test_query"} in filters
    assert {"field": "is_enabled", "operator": "EQ", "value": True} in filters
    assert {"field": "description", "operator": "EQ", "value": "test_description"} in filters
    assert {"field": "alert_name", "operator": "EQ", "value": "test_alert_name"} in filters
    assert {"field": "alert_category", "operator": "EQ", "value": "test_alert_category"} in filters
    assert {"field": "alert_description", "operator": "EQ", "value": "test_alert_description"} in filters
    assert {"field": "alert_fields", "operator": "EQ", "value": ["field1", "field2"]} in filters
    assert {"field": "execution_mode", "operator": "EQ", "value": "real_time"} in filters
    assert {"field": "search_window", "operator": "EQ", "value": "1h"} in filters
    assert {"field": "schedule", "operator": "EQ", "value": "daily"} in filters
    assert {"field": "timezone", "operator": "EQ", "value": "UTC"} in filters
    assert {"field": "schedule_linux", "operator": "EQ", "value": "* * * * *"} in filters
    assert {"field": "suppression_enabled", "operator": "EQ", "value": True} in filters
    assert {"field": "suppression_duration", "operator": "EQ", "value": "1h"} in filters
    assert {"field": "suppression_fields", "operator": "EQ", "value": "field1"} in filters
    assert {"field": "dataset", "operator": "EQ", "value": "xdr_data"} in filters
    assert {"field": "user_defined_severity", "operator": "EQ", "value": "medium"} in filters
    assert {"field": "user_defined_category", "operator": "EQ", "value": "category"} in filters
    assert {"field": "mitre_defs", "operator": "EQ", "value": {"tactic": "test"}} in filters
    assert {"field": "investigation_query_link", "operator": "EQ", "value": "link"} in filters
    assert {"field": "drilldown_query_timeframe", "operator": "EQ", "value": "1h"} in filters
    assert {"field": "mapping_strategy", "operator": "EQ", "value": "strategy"} in filters
    assert {"field": "alert_domain", "operator": "EQ", "value": "domain"} in filters


@pytest.mark.parametrize(
    "mock_response, args, expected_outputs_prefix, expected_hr_contains",
    [
        (
            {
                "vulnerabilityID": "CVE-2021-44228",
                "description": "Apache Log4j2 RCE",
                "cvss": {"score": 10.0},
                "publishedDate": "2021-12-10",
            },
            {"vulnerability_id": "CVE-2021-44228"},
            "PaloAltoNetworksXDR.Vulnerability",
            "CVE-2021-44228",
        ),
        (
            {
                "vulnerabilityID": "CVE-2023-0001",
                "description": "Test vulnerability",
                "cvss": {},
                "publishedDate": None,
            },
            {"vulnerability_id": "CVE-2023-0001"},
            "PaloAltoNetworksXDR.Vulnerability",
            "CVE-2023-0001",
        ),
    ],
)
def test_get_vulnerability_details_command(mocker, mock_response, args, expected_outputs_prefix, expected_hr_contains):
    """
    Given:
        - A vulnerability ID to look up
    When:
        - Running get_vulnerability_details_command
    Then:
        - Verify the returned CommandResults contains the expected vulnerability data
    """
    from CortexXDRIR import Client, get_vulnerability_details_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mocker.patch.object(Client, "get_vulnerability_details", return_value=mock_response)

    result = get_vulnerability_details_command(client, args)

    assert result.outputs_prefix == expected_outputs_prefix
    assert result.outputs_key_field == "vulnerabilityID"
    assert result.outputs == mock_response
    assert result.raw_response == mock_response
    assert expected_hr_contains in result.readable_output
    assert "Vulnerability Details" in result.readable_output


def test_endpoint_triage_preset_list_command(mocker):
    """
    Given:
        - An XDR client
    When:
        - Running endpoint_triage_preset_list_command
    Then:
        - Verify the returned CommandResults contains the expected presets
    """
    from CortexXDRIR import Client, endpoint_triage_preset_list_command

    mock_presets = [
        {
            "name": "Full Triage",
            "uuid": "uuid-1234",
            "os": "windows",
            "type": "full",
            "created_by": "admin",
            "description": "Full triage preset",
        },
        {
            "name": "Quick Triage",
            "uuid": "uuid-5678",
            "os": "linux",
            "type": "quick",
            "created_by": "admin",
            "description": "Quick triage preset",
        },
    ]

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mocker.patch.object(Client, "get_triage_presets", return_value=mock_presets)

    result = endpoint_triage_preset_list_command(client)

    assert result.outputs_prefix == "PaloAltoNetworksXDR.EndpointTriagePreset"
    assert result.outputs_key_field == "uuid"
    assert result.outputs == mock_presets
    assert result.raw_response == mock_presets
    assert len(result.outputs) == 2
    assert "Endpoint Triage Presets" in result.readable_output
    assert "Full Triage" in result.readable_output
    assert "Quick Triage" in result.readable_output


def test_endpoint_triage_preset_list_command_empty(mocker):
    """
    Given:
        - An XDR client with no triage presets
    When:
        - Running endpoint_triage_preset_list_command
    Then:
        - Verify the returned CommandResults contains an empty list
    """
    from CortexXDRIR import Client, endpoint_triage_preset_list_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mocker.patch.object(Client, "get_triage_presets", return_value=[])

    result = endpoint_triage_preset_list_command(client)

    assert result.outputs == []
    assert result.outputs_prefix == "PaloAltoNetworksXDR.EndpointTriagePreset"


@pytest.mark.parametrize(
    "mock_response, expected_status_text",
    [
        ({"status": "OK"}, "Cortex XDR health status: OK"),
        ({"status": "ERROR"}, "Cortex XDR health status: ERROR"),
        ({}, "Cortex XDR health status: unknown"),
    ],
)
def test_healthcheck_run_command(mocker, mock_response, expected_status_text):
    """
    Given:
        - An XDR client
    When:
        - Running healthcheck_run_command
    Then:
        - Verify the returned CommandResults contains the expected health status
    """
    from CortexXDRIR import Client, healthcheck_run_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mocker.patch.object(Client, "run_healthcheck", return_value=mock_response)

    result = healthcheck_run_command(client)

    assert result.outputs_prefix == "PaloAltoNetworksXDR.HealthStatus"
    assert result.outputs == mock_response
    assert result.raw_response == mock_response
    assert expected_status_text in result.readable_output


@pytest.mark.parametrize(
    "args, expected_request_data",
    [
        (
            {"endpoint_id": "agent1,agent2"},
            {"request_data": {"agent_ids": ["agent1", "agent2"]}},
        ),
        (
            {"endpoint_id": "agent1", "collector_uuid": "preset-uuid-123"},
            {"request_data": {"agent_ids": ["agent1"], "collector_uuid": "preset-uuid-123"}},
        ),
        (
            {"endpoint_id": "agent1"},
            {"request_data": {"agent_ids": ["agent1"]}},
        ),
    ],
)
def test_endpoint_triage_command(mocker, args, expected_request_data):
    """
    Given:
        - Endpoint IDs and optional collector_uuid
    When:
        - Running endpoint_triage_command
    Then:
        - Verify the client.triage_endpoint is called with correct request data
        - Verify the returned CommandResults contains the expected outputs
    """
    from CortexXDRIR import Client, endpoint_triage_command

    mock_reply = {
        "TRIAGE_ID": "triage-123",
        "SUCCESSFUL_AGENT_IDS": ["agent1"],
        "UNSUCCESSFUL_AGENT_IDS": [],
    }

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_triage = mocker.patch.object(Client, "triage_endpoint", return_value=mock_reply)

    result = endpoint_triage_command(client, args)

    mock_triage.assert_called_once_with(expected_request_data)
    assert result.outputs_prefix == "PaloAltoNetworksXDR.EndpointTriage"
    assert result.outputs == mock_reply
    assert result.raw_response == mock_reply
    assert "Triage Endpoint Results" in result.readable_output


@pytest.mark.parametrize(
    "args, expected_filters, expected_sort",
    [
        (
            {"issue_id": "100", "status": "new"},
            [
                {"field": "id", "operator": "in", "value": [100]},
                {"field": "status.progress", "operator": "in", "value": ["New"]},
            ],
            {},
        ),
        (
            {"severity": "high", "sort_field": "severity", "sort_order": "desc"},
            [{"field": "severity", "operator": "in", "value": ["high"]}],
            {"field": "severity", "keyword": "desc"},
        ),
        (
            {"domain": "network", "limit": "10"},
            [{"field": "issue_domain", "operator": "in", "value": ["network"]}],
            {},
        ),
        (
            {"external_id": "ext-1,ext-2"},
            [{"field": "external_id", "operator": "in", "value": ["ext-1", "ext-2"]}],
            {},
        ),
        (
            {"detection_method": "xdr_agent"},
            [{"field": "detection.method", "operator": "in", "value": ["xdr_agent"]}],
            {},
        ),
        (
            {"sort_field": "issue_id", "sort_order": "asc"},
            [],
            {"field": "id", "keyword": "asc"},
        ),
    ],
)
def test_list_issues_command(args, expected_filters, expected_sort):
    """
    Given:
        - args for issue list command
    When:
        - Running list_issues_command
    Then:
        - Verify the client.list_issues is called with correct filters and sort arguments
    """
    from CortexXDRIR import Client, list_issues_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    with patch.object(Client, "list_issues", return_value=[{"id": 1, "name": "test issue"}]) as mock_list:
        result = list_issues_command(client, args)

        call_args = mock_list.call_args[0][0]
        if expected_filters:
            assert call_args["request_data"]["filters"] == expected_filters
        else:
            assert "filters" not in call_args["request_data"]
        if expected_sort:
            assert call_args["request_data"]["sort"] == expected_sort
        else:
            assert "sort" not in call_args["request_data"]
        assert result.outputs_prefix == "PaloAltoNetworksXDR.Issue"
        assert result.outputs_key_field == "id"
        assert "Issues" in result.readable_output


@pytest.mark.parametrize(
    "args, expected_issue_data",
    [
        (
            {
                "name": "Test Issue",
                "description": "A test issue",
                "observation_time": "2024-01-01T00:00:00Z",
                "domain": "network",
                "category": "security",
                "severity": "high",
            },
            {
                "name": "Test Issue",
                "description": "A test issue",
                "issue_domain": "network",
                "category": "security",
                "severity": "HIGH",
            },
        ),
        (
            {
                "name": "Issue with extras",
                "description": "Extended issue",
                "observation_time": "2024-01-01T00:00:00Z",
                "domain": "identity",
                "category": "compliance",
                "severity": "medium",
                "type": "misconfiguration",
                "is_excluded": "true",
                "is_starred": "false",
                "assigned_to": "admin@example.com",
                "asset_id": "asset-1,asset-2",
            },
            {
                "name": "Issue with extras",
                "description": "Extended issue",
                "issue_domain": "identity",
                "category": "compliance",
                "severity": "MEDIUM",
                "type": "misconfiguration",
                "is_excluded": True,
                "is_starred": False,
                "assigned_to": "admin@example.com",
                "asset_ids": ["asset-1", "asset-2"],
            },
        ),
    ],
)
def test_create_issue_command(args, expected_issue_data):
    """
    Given:
        - args for issue create command
    When:
        - Running create_issue_command
    Then:
        - Verify the client.create_issue is called with correct issue data
        - Verify the returned CommandResults contains the expected outputs
    """
    from CortexXDRIR import Client, create_issue_command

    mock_reply = {"external_id": "ext-123", "id": 1}
    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    with patch.object(Client, "create_issue", return_value=mock_reply) as mock_create:
        result = create_issue_command(client, args)

        call_args = mock_create.call_args[0][0]
        issue_data = call_args["request_data"]["issue"]
        for key, value in expected_issue_data.items():
            assert issue_data[key] == value
        assert result.outputs_prefix == "PaloAltoNetworksXDR.Issue"
        assert result.outputs_key_field == "external_id"
        assert result.outputs == mock_reply
        assert "Created Issue" in result.readable_output


def test_create_issue_command_invalid_json():
    """
    Given:
        - args with invalid JSON in normalized_fields_json
    When:
        - Running create_issue_command
    Then:
        - Verify a DemistoException is raised for invalid JSON
    """
    from CortexXDRIR import Client, create_issue_command

    args = {
        "name": "Test",
        "description": "desc",
        "observation_time": "2024-01-01T00:00:00Z",
        "domain": "network",
        "category": "security",
        "severity": "high",
        "normalized_fields_json": "invalid-json{",
    }

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    with pytest.raises(DemistoException, match="Invalid JSON format in field"):
        create_issue_command(client, args)


@pytest.mark.parametrize(
    "args, expected_update_data",
    [
        (
            {"issue_id": "100", "status": "new"},
            {"status": "New"},
        ),
        (
            {"issue_id": "200", "severity": "high"},
            {"severity": "HIGH"},
        ),
        (
            {
                "issue_id": "300",
                "status": "resolved",
                "resolve_reason": "resolved_false_positive",
                "resolve_comment": "Not a real issue",
            },
            {
                "status": "Resolved",
                "status_resolution_reason": "resolved - false positive",
                "status_resolution_comment": "Not a real issue",
            },
        ),
        (
            {"issue_id": "400", "status": "in_progress", "severity": "low"},
            {"severity": "LOW", "status": "In Progress"},
        ),
        (
            {
                "issue_id": "500",
                "resolve_reason": "resolved_known_issue",
            },
            {
                "status_resolution_reason": "resolved - known issue",
            },
        ),
    ],
)
def test_update_issue_command(args, expected_update_data):
    """
    Given:
        - args for issue update command
    When:
        - Running update_issue_command
    Then:
        - Verify the client.update_issue is called with correct arguments
        - Verify the readable output confirms the update
    """
    from CortexXDRIR import Client, update_issue_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    with patch.object(Client, "update_issue") as mock_update:
        result = update_issue_command(client, args)

        assert result.readable_output == f"Issue with ID {args['issue_id']} updated successfully"
        mock_update.assert_called_with(args["issue_id"], {"request_data": {"update_data": expected_update_data}})


def test_normalize_case_data_record_maps_fields_and_nests_data():
    """
    Given:
        - A raw record from the get_multiple_incidents_extra_data endpoint, wrapped in an
          "incident" key and containing nested alerts, file_artifacts and network_artifacts.
    When:
        - Running normalize_case_data_record (used by the xdr-case-list extra_data path).
    Then:
        - Incident fields are renamed to their case-shaped equivalents (e.g. incident_id -> case_id).
        - Nested alerts/artifacts are surfaced under Issues/FileArtifacts/NetworkArtifacts.
        - Each nested record inherits the parent case_id when one is not already set.
    """
    from CortexXDRIR import normalize_case_data_record

    incident_record = {
        "incident": {
            "incident_id": "100",
            "incident_name": "My Case",
            "status": "new",
            "incident_domain": "example.com",
            "alert_count": 3,
            "description": "some description",
        },
        "alerts": {"data": [{"alert_id": "a1"}, {"alert_id": "a2", "case_id": "999"}]},
        "file_artifacts": {"data": [{"name": "file.exe"}]},
        "network_artifacts": {"data": [{"ip": "1.2.3.4"}]},
    }

    case = normalize_case_data_record(incident_record)

    # Renamed incident fields.
    assert case["case_id"] == "100"
    assert case["case_name"] == "My Case"
    assert case["status_progress"] == "new"
    assert case["case_domain"] == "example.com"
    assert case["issue_count"] == 3
    # Fields without a mapping are kept as-is.
    assert case["description"] == "some description"
    assert "incident_id" not in case

    # Nested data surfaced under the case-shaped keys.
    assert case["Issues"] == [
        {"alert_id": "a1", "case_id": "100"},
        {"alert_id": "a2", "case_id": "999"},
    ]
    assert case["FileArtifacts"] == [{"name": "file.exe", "case_id": "100"}]
    assert case["NetworkArtifacts"] == [{"ip": "1.2.3.4", "case_id": "100"}]


def test_normalize_case_data_record_flat_record_without_nested_data():
    """
    Given:
        - A flat record (no "incident" wrapper) and without any nested alerts/artifacts.
    When:
        - Running normalize_case_data_record.
    Then:
        - The flat fields are mapped and no Issues/FileArtifacts/NetworkArtifacts keys are added.
    """
    from CortexXDRIR import normalize_case_data_record

    case = normalize_case_data_record({"incident_id": "200", "incident_name": "Flat Case"})

    assert case["case_id"] == "200"
    assert case["case_name"] == "Flat Case"
    assert "Issues" not in case
    assert "FileArtifacts" not in case
    assert "NetworkArtifacts" not in case


def test_case_list_command_extra_data_returns_normalized_cases(mocker):
    """
    Given:
        - args for the case list command with extra_data=true.
        - The client returns raw extra-data records with nested alerts/artifacts.
    When:
        - Running case_list_command.
    Then:
        - The extra-data client method is used (search_cases is NOT called).
        - The outputs are normalized into case-shaped records (mapped fields + nested data).
        - The CommandResults metadata (outputs_prefix, key field, raw_response) is correct.
    """
    from CortexXDRIR import Client, case_list_command

    raw_records = [
        {
            "incident": {
                "incident_id": "100",
                "incident_name": "My Case",
                "status": "new",
                "incident_domain": "example.com",
            },
            "alerts": {"data": [{"alert_id": "a1"}]},
            "file_artifacts": {"data": [{"name": "file.exe"}]},
            "network_artifacts": {"data": [{"ip": "1.2.3.4"}]},
        }
    ]

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_extra_data = mocker.patch.object(Client, "get_multiple_incidents_extra_data", return_value=raw_records)
    mock_search = mocker.patch.object(Client, "search_cases")

    result = case_list_command(client, {"extra_data": "true", "case_id": "100"})

    # The extra-data path was taken, not the regular search path.
    mock_extra_data.assert_called_once()
    mock_search.assert_not_called()

    assert result.outputs_prefix == "PaloAltoNetworksXDR.Case"
    assert result.outputs_key_field == "case_id"
    assert result.raw_response == raw_records

    assert isinstance(result.outputs, list)
    assert len(result.outputs) == 1
    case = result.outputs[0]
    assert case["case_id"] == "100"
    assert case["case_name"] == "My Case"
    assert case["case_domain"] == "example.com"
    assert case["Issues"] == [{"alert_id": "a1", "case_id": "100"}]
    assert case["FileArtifacts"] == [{"name": "file.exe", "case_id": "100"}]
    assert case["NetworkArtifacts"] == [{"ip": "1.2.3.4", "case_id": "100"}]


def test_case_list_command_extra_data_maps_sort_field(mocker):
    """
    Given:
        - args for the case list command with extra_data=true and sort_field=case_id.
    When:
        - Running case_list_command.
    Then:
        - The case-shaped sort field is translated to the incident-shaped sort field
          (case_id -> incident_id) before calling the extra-data client method.
    """
    from CortexXDRIR import Client, case_list_command

    client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False)
    mock_extra_data = mocker.patch.object(Client, "get_multiple_incidents_extra_data", return_value=[])

    case_list_command(client, {"extra_data": "true", "sort_field": "case_id", "sort_order": "asc"})

    call_kwargs = mock_extra_data.call_args.kwargs
    assert call_kwargs["sort_field"] == "incident_id"
    assert call_kwargs["sort_order"] == "asc"