SentinelOne V2
Use the SentinelOne integration to send requests to your management server and get responses with data pulled from agents or from the management database.
Endpoint · SentinelOne
Details
| ID | SentinelOne V2 |
|---|---|
| Provider | SentinelOne |
| Category | Endpoint |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Use the SentinelOne integration to send requests to your management server and get responses with data pulled from agents or from the management database.
This integration was integrated and tested with versions 2.0 and 2.1 of SentinelOne V2
Some changes have been made that might affect your existing content.
If you are upgrading from a previous version of this integration, see Breaking Changes.
Configure SentinelOne v2 in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL (e.g., https://usea1.sentinelone.net) | True | |
| API Token | False | |
| API Version | True | |
| Fetch incidents | False | |
| Incident type | False | |
| Fetch incidents from type | False | |
| Fetch incidents from UAM Alert type | False | |
| First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year) | False | |
| Minimum risk score for importing incidents (0-10), where 0 is low risk and 10 is high risk. Relevant for API version 2.0. | False | |
| Defines Alert severity to fetch. Supported values: Low, Medium, High, Critical. | False | |
| Define which Alerts should be fetched. | False | |
| Define which Threats should be fetched. | False | |
| Fetch limit: The maximum number of threats or alerts to fetch | False | |
| Site IDs | Comma-separated list of site IDs to fetch incidents for. Leave blank to fetch all sites. | False |
| Block Site IDs | Comma-separated list of site IDs for where hashes should be blocked. If left blank all hashes will be blocked globally. If filled out with site ids all hashes will be no longer be blocked globally, they will now be blocked in the scope of those sites. | False |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Incidents Fetch Interval | False | |
| Incident Mirroring Direction | Choose the direction to mirror the incident: Incoming (from SentinelOne to Cortex XSOAR), Outgoing (from Cortex XSOAR to SentinelOne), or Incoming and Outgoing (from/to Cortex XSOAR and SentinelOne). Cortex XSOAR only parameter. | False |
| Close Mirrored XSOAR Incident | When selected, closing the SentinelOne ticket is mirrored in Cortex XSOAR. | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
sentinelone-list-agents
Returns all agents that match the specified criteria.
Base Command
sentinelone-list-agents
Input
| Argument Name | Description | Required |
|---|---|---|
| computer_name | The computer name by which to filter the results. It can match a partial computer name value (substring). | Optional |
| scan_status | A comma-separated list of scan statuses by which to filter the results, for example: “started,aborted”. Possible values are: started, none, finished, aborted. | Optional |
| os_type | Included operating system types, for example: “windows”. Possible values are: windows, windows_legacy, macos, linux. | Optional |
| created_at | Endpoint creation timestamp, for example: “2018-02-27T04:49:26.257525Z”. | Optional |
| min_active_threats | Minimum number of threats per agent. | Optional |
| limit | The maximum number of agents to return. Default is 10. | Optional |
| params | Query params field=value pairs delimited by comma (e.g., activeThreats=3,gatewayIp=1.2.3.4). Query params are OR’d. | Optional |
| columns | A comma-separated list of additionals fields to display. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Agents.NetworkStatus | string | The agent network status. |
| SentinelOne.Agents.ID | string | The agent ID. |
| SentinelOne.Agents.AgentVersion | string | The agent software version. |
| SentinelOne.Agents.IsDecommissioned | boolean | Whether the agent is decommissioned. |
| SentinelOne.Agents.IsActive | boolean | Whether the agent is active. |
| SentinelOne.Agents.LastActiveDate | date | When was the agent last active. |
| SentinelOne.Agents.RegisteredAt | date | The registration date of the agent. |
| SentinelOne.Agents.ExternalIP | string | The agent IP address. |
| SentinelOne.Agents.ThreatCount | number | Number of active threats. |
| SentinelOne.Agents.EncryptedApplications | boolean | Whether disk encryption is enabled. |
| SentinelOne.Agents.OSName | string | Name of operating system. |
| SentinelOne.Agents.ComputerName | string | Name of agent computer. |
| SentinelOne.Agents.MachineType | string | Machine type. |
| SentinelOne.Agents.Domain | string | Domain name of the agent. |
| SentinelOne.Agents.CreatedAt | date | Creation time of the agent. |
| SentinelOne.Agents.SiteName | string | Site name associated with the agent. |
| SentinelOne.Agents.Tags | unknown | Tags associated with the agent. |
sentinelone-create-white-list-item
Creates an exclusion item that matches the specified input filter.
Base Command
sentinelone-create-white-list-item
Input
| Argument Name | Description | Required |
|---|---|---|
| exclusion_type | Exclusion item type. Possible values are: file_type, path, white_hash, certificate, browser. | Required |
| exclusion_value | Value of the exclusion item for the exclusion list. | Required |
| os_type | Operating system type. Required for hash exclusions. Possible values are: windows, windows_legacy, macos, linux. | Required |
| description | Description for adding the exclusion item. | Optional |
| exclusion_mode | Exclusion mode (path exclusion only). Possible values are: suppress, disable_in_process_monitor_deep, disable_in_process_monitor, disable_all_monitors, disable_all_monitors_deep. | Optional |
| path_exclusion_type | Excluded path for a path exclusion list. | Optional |
| group_ids | A comma-separated list of group IDs by which to filter. | Optional |
| site_ids | A comma-separated list of site IDs by which to filter. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Exclusions.ID | string | The entity ID on the allow list. |
| SentinelOne.Exclusions.Type | string | The item type on the allow list. |
| SentinelOne.Exclusions.CreatedAt | date | Time when the allow list item was created. |
sentinelone-get-white-list
Lists all exclusion items that match the specified input filter.
Base Command
sentinelone-get-white-list
Input
| Argument Name | Description | Required |
|---|---|---|
| item_ids | List of IDs by which to filter, for example: “225494730938493804,225494730938493915”. | Optional |
| os_types | A comma-separated list of operating system types by which to filter, for example: “windows, linux”. Possible values are: windows, windows_legacy, macos, linux. | Optional |
| exclusion_type | Exclusion type. Possible values are: file_type, path, white_hash, certificate, browser. | Optional |
| limit | The maximum number of items to return. Default is 10. | Optional |
| include_parent | Whether to include parent information of each item. Default value is false. Default is false. | Optional |
| include_children | Whether to include children information of each item. Default value is false. Default is false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Exclusions.ID | string | The exclusion item ID. |
| SentinelOne.Exclusions.Type | string | The exclusion item type. |
| SentinelOne.Exclusions.CreatedAt | date | Timestamp when the exclusion item was added. |
| SentinelOne.Exclusions.Value | string | Value of the exclusion item. |
| SentinelOne.Exclusions.Source | string | Source of the exclusion item. |
| SentinelOne.Exclusions.UserID | string | User ID of the user qho added the exclusion item. |
| SentinelOne.Exclusions.UpdatedAt | date | Timestamp when the exclusion item was updated. |
| SentinelOne.Exclusions.OsType | string | Operating system type of the exclusion item. |
| SentinelOne.Exclusions.UserName | string | User name of the user who added the exclusion item. |
| SentinelOne.Exclusions.Mode | string | A comma-separated list of modes by which to filter (path exclusions only), for example: “suppress”. |
sentinelone-get-hash
Gets the file reputation verdict by a SHA1 hash.
Base Command
sentinelone-get-hash
Input
| Argument Name | Description | Required |
|---|---|---|
| hash | The content hash. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Hash.Rank | Number | The hash reputation (1-10). |
| SentinelOne.Hash.Verdict | String | The hash reputation verdict. |
| SentinelOne.Hash.Hash | String | The content hash. |
sentinelone-get-threats
Returns threats according to the specified filters.
Base Command
sentinelone-get-threats
Input
| Argument Name | Description | Required |
|---|---|---|
| content_hash | A comma-separated list of content hashes of the threat. | Optional |
| mitigation_status | A comma-separated list of mitigation statuses. Possible values are: mitigated, active, blocked, suspicious, pending, suspicious_resolved. | Optional |
| created_before | Searches for threats created before this timestamp, for example: “2018-02-27T04:49:26.257525Z”, “10 days”, “5 months”, “2 hours”. | Optional |
| created_after | Searches for threats created after this timestamp, for example: “2018-02-27T04:49:26.257525Z”, “10 days”, “5 months”, “2 hours”. | Optional |
| created_until | Searches for threats created on or before this timestamp, for example: “2018-02-27T04:49:26.257525Z”, “10 days”, “5 months”, “2 hours”. | Optional |
| created_from | Search for threats created on or after this timestamp, for example: “2018-02-27T04:49:26.257525Z”, “10 days”, “5 months”, “2 hours”. | Optional |
| resolved | Whether to only return resolved threats. Possible values are: false, true. Default is false. | Optional |
| display_name | Threat display name. For API version 2.0 it can be a partial display name, doesn’t have to be an exact match. | Optional |
| limit | The maximum number of threats to return. Default is 20. | Optional |
| query | Full free-text search for fields. Can be “content_hash”, “file_display_name”, “file_path”, “computer_name”, or “uuid”. | Optional |
| threat_ids | A comma-separated list of threat IDs, for example: “225494730938493804,225494730938493915”. | Optional |
| classifications | A comma-separated list of threat classifications to search, for example: “Malware”, “Network”, “Benign”. Possible values are: Engine, Static, Cloud, Behavioral. | Optional |
| rank | Risk level threshold to retrieve (1-10). Relevant for API version 2.0 only. | Optional |
| site_ids | A comma-separated list of site IDs to search for threats, for example: “225494730938493804,225494730938493915”. | Optional |
| incident_statuses | Incident status. Example: “IN_PROGRESS, UNRESOLVED”. | Optional |
| include_resolved_param | Whether to include the resolved parameter in the query. Possible values are: false, true. Default is false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Threat.ID | String | The threat ID. |
| SentinelOne.Threat.AgentComputerName | String | The agent computer name. |
| SentinelOne.Threat.CreatedDate | Date | The threat creation date. |
| SentinelOne.Threat.SiteID | String | The site ID. |
| SentinelOne.Threat.Classification | string | The threat classification. |
| SentinelOne.Threat.ClassificationSource | string | Source of the threat classification. |
| SentinelOne.Threat.ConfidenceLevel | string | SentinelOne threat confidence level. |
| SentinelOne.Threat.FileSha256 | string | SHA256 hash of the file content. |
| SentinelOne.Threat.MitigationStatus | String | The agent mitigation status. |
| SentinelOne.Threat.AgentID | String | The threat agent ID. |
| SentinelOne.Threat.Rank | Number | The number representing the cloud reputation (1-10). |
| SentinelOne.Threat.MarkedAsBenign | Boolean | Whether the threat is marked as benign. Relevant for version 2.0 only. |
sentinelone-threat-summary
Returns a dashboard threat summary. Can only be used with API V2.1.
Base Command
sentinelone-threat-summary
Input
| Argument Name | Description | Required |
|---|---|---|
| group_ids | A comma-separated list of group IDs by which to filter, for example: “225494730938493804,225494730938493915”. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Threat.NotResolved | Number | Number of unresolved threats in the system. |
| SentinelOne.Threat.SuspiciousNotMitigatedNotResolved | Number | Number of unmitigated suspicious threats in the system. |
| SentinelOne.Threat.SuspiciousNotResolved | Number | Number of unresolved suspicious threats in the system. |
| SentinelOne.Threat.Resolved | Number | Number of resolved threats in the system. |
| SentinelOne.Threat.InProgress | Number | Number of active threats in the system. |
| SentinelOne.Threat.Total | Number | Total number of threats in the system. |
| SentinelOne.Threat.NotMitigated | Number | Number of unmitigated threats in the system. |
| SentinelOne.Threat.MaliciousNotResolved | Number | Number of unresolved malicious threats in the system. |
| SentinelOne.Threat.NotMitigatedNotResolved | Number | Number of unmitigated and unresolved threats in the system. |
sentinelone-mark-as-threat
Marks suspicious threats as threats. Can only be used with API V2.0.
Base Command
sentinelone-mark-as-threat
Input
| Argument Name | Description | Required |
|---|---|---|
| threat_ids | A comma-separated list of threat IDs. | Optional |
| target_scope | Scope to use for exclusions. Possible values are: site, tenant. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Threat.ID | String | The threat ID. |
| SentinelOne.Threat.MarkedAsThreat | Boolean | Whether the suspicious threat was successfully marked as a threat. |
sentinelone-mitigate-threat
Applies a mitigation action to a group of threats that match the specified input filter.
Base Command
sentinelone-mitigate-threat
Input
| Argument Name | Description | Required |
|---|---|---|
| action | Mitigation action. Possible values are: kill, quarantine, un-quarantine, remediate, rollback-remediation. | Required |
| threat_ids | A comma-separated list of threat IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Threat.ID | String | The threat ID. |
| SentinelOne.Threat.Mitigated | Boolean | Whether the threat was successfully mitigated. |
| SentinelOne.Threat.Mitigation.Action | String | The mitigation action performed. |
sentinelone-resolve-threat
Resolves threats using the threat ID. Can only be used with API V2.0.
Base Command
sentinelone-resolve-threat
Input
| Argument Name | Description | Required |
|---|---|---|
| threat_ids | A comma-separated list of threat IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Threat.ID | String | The threat ID. |
| SentinelOne.Threat.Resolved | Boolean | Whether the threat was successfully resolved. |
sentinelone-get-agent
Returns the details of an agent according to the agent ID.
Base Command
sentinelone-get-agent
Input
| Argument Name | Description | Required |
|---|---|---|
| agent_id | A comma-separated string of agent IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Agent.NetworkStatus | string | The agent network status. |
| SentinelOne.Agent.ID | string | The agent ID. |
| SentinelOne.Agent.AgentVersion | string | The agent software version. |
| SentinelOne.Agent.IsDecommissioned | boolean | Whether the agent is decommissioned. |
| SentinelOne.Agent.IsActive | boolean | Whether the agent is active. |
| SentinelOne.Agent.LastActiveDate | date | When was the agent last active. |
| SentinelOne.Agent.RegisteredAt | date | The registration date of the agent. |
| SentinelOne.Agent.ExternalIP | string | The agent IP address. |
| SentinelOne.Agent.ThreatCount | number | Number of active threats. |
| SentinelOne.Agent.EncryptedApplications | boolean | Whether disk encryption is enabled. |
| SentinelOne.Agent.OSName | string | Name of the operating system. |
| SentinelOne.Agent.ComputerName | string | Name of the agent computer. |
| SentinelOne.Agent.MachineType | string | Machine type. |
| SentinelOne.Agent.Domain | string | Domain name of the agent. |
| SentinelOne.Agent.CreatedAt | date | Agent creation time. |
| SentinelOne.Agent.SiteName | string | Site name associated with the agent. |
sentinelone-get-sites
Returns all sites that match the specified criteria.
Base Command
sentinelone-get-sites
Input
| Argument Name | Description | Required |
|---|---|---|
| updated_at | Timestamp of the last update, for example: “2018-02-27T04:49:26.257525Z”. | Optional |
| query | Full-text search for fields: name, account_name. | Optional |
| site_type | Site type. Possible values are: Trial, Paid, POC, DEV, NFR. | Optional |
| features | Returns sites that support the specified features. Possible values are: firewall-control, device-control, ioc. | Optional |
| state | Site state. Possible values are: active, deleted, expired. | Optional |
| suite | The suite of product features active for this site. Possible values are: Core, Complete. | Optional |
| admin_only | Sites for which the user has admin privileges. Possible values are: true, false. | Optional |
| account_id | Account ID, for example: “225494730938493804”. | Optional |
| site_name | Site name, for example: “My Site”. | Optional |
| created_at | Timestamp of the site creation, for example: “2018-02-27T04:49:26.257525Z”. | Optional |
| limit | Maximum number of results to return. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Site.Creator | string | The site creator name. |
| SentinelOne.Site.Name | string | The site name. |
| SentinelOne.Site.Type | string | The site type. |
| SentinelOne.Site.AccountName | string | The site account name. |
| SentinelOne.Site.State | string | The site state. |
| SentinelOne.Site.HealthStatus | boolean | The health status of the site. |
| SentinelOne.Site.Suite | string | The suite to which the site belongs. |
| SentinelOne.Site.ActiveLicenses | number | Number of active licenses for the site. |
| SentinelOne.Site.ID | string | ID of the site. |
| SentinelOne.Site.TotalLicenses | number | Number of total licenses for the site. |
| SentinelOne.Site.CreatedAt | date | Timestamp when the site was created. |
| SentinelOne.Site.Expiration | string | Timestamp when the site will expire. |
| SentinelOne.Site.UnlimitedLicenses | boolean | Whether the site has unlimited licenses. |
sentinelone-get-site
Returns information about the site, according to the site ID.
Base Command
sentinelone-get-site
Input
| Argument Name | Description | Required |
|---|---|---|
| site_id | ID of the site. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Site.Creator | string | The site creator name. |
| SentinelOne.Site.Name | string | The site name. |
| SentinelOne.Site.Type | string | The site type. |
| SentinelOne.Site.AccountName | string | The site account name. |
| SentinelOne.Site.State | string | The site state. |
| SentinelOne.Site.HealthStatus | boolean | The health status of the site. |
| SentinelOne.Site.Suite | string | The suite to which the site belongs. |
| SentinelOne.Site.ActiveLicenses | number | Number of active licenses for the site. |
| SentinelOne.Site.ID | string | ID of the site. |
| SentinelOne.Site.TotalLicenses | number | Number of total licenses for the site. |
| SentinelOne.Site.CreatedAt | date | Timestamp when the site was created. |
| SentinelOne.Site.Expiration | string | Timestamp when the site will expire. |
| SentinelOne.Site.UnlimitedLicenses | boolean | Whether the site has unlimited licenses. |
| SentinelOne.Site.AccountID | string | Site account ID. |
| SentinelOne.Site.IsDefault | boolean | Whether the site is the default site. |
sentinelone-reactivate-site
Reactivates an expired site.
Base Command
sentinelone-reactivate-site
Input
| Argument Name | Description | Required |
|---|---|---|
| site_id | Site ID. For example: “225494730938493804”. | Required |
| unlimited | If false, an expiration should be supplied. | Optional |
| expiration | Expiration date in case unlimited is false, for example, “2019-08-03T04:49:26.257525Z”. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Site.ID | string | Site ID. |
| SentinelOne.Site.Reactivated | boolean | Whether the site was reactivated. |
sentinelone-get-activities
Returns a list of activities.
Base Command
sentinelone-get-activities
Input
| Argument Name | Description | Required |
|---|---|---|
| created_after | Return activities created after this timestamp, for example: “2018-02-27T04:49:26.257525Z”. | Optional |
| user_emails | Email address of the user who invoked the activity (if applicable). | Optional |
| group_ids | List of group IDs by which to filter, for example: “225494730938493804,225494730938493915”. | Optional |
| created_until | Return activities created on or before this timestamp, for example: “2018-02-27T04:49:26.257525Z”. | Optional |
| include_hidden | Include internal activities hidden from display. Possible values are: true, false. | Optional |
| activities_ids | A comma-separated list of activity IDs by which to filter, for example: “225494730938493804,225494730938493915”. | Optional |
| created_before | Return activities created before this timestamp, for example: “2018-02-27T04:49:26.257525Z”. | Optional |
| threats_ids | A comma-separated list of threat IDs for which to return activities, for example: “225494730938493804,225494730938493915”. | Optional |
| activity_types | A comma-separated list of activity codes to return, for example: “52,53,71,72”. | Optional |
| user_ids | A comma-separated list of user IDs for users that invoked the activity (if applicable), for example: “225494730938493804,225494730938493915”. | Optional |
| created_from | Return activities created on or after this timestamp, for example: “2018-02-27T04:49:26.257525Z”. | Optional |
| created_between | Return activities created within this range (inclusive), for example: “1514978764288-1514978999999”. | Optional |
| agent_ids | Return activities related to specified agents. For example: “225494730938493804,225494730938493915”. | Optional |
| limit | Maximum number of items to return (1-100). | Optional |
| sort_by | Field to sort results by. Possible values are: activityType, createdAt, id. | Optional |
| sort_order | Order to sort by. Possible values are: asc, desc. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Activity.AgentID | String | Related agent (if applicable). |
| SentinelOne.Activity.AgentUpdatedVersion | String | Agent’s new version (if applicable). |
| SentinelOne.Activity.SiteID | String | Related site (if applicable). |
| SentinelOne.Activity.UserID | String | The user who invoked the activity (if applicable). |
| SentinelOne.Activity.SecondaryDescription | String | Secondary description. |
| SentinelOne.Activity.OsFamily | String | Agent’s operating system type (if applicable). Can be “linux”, “macos”, “windows”, or “windows_legacy”. |
| SentinelOne.Activity.ActivityType | Number | Activity type. |
| SentinelOne.Activity.data.SiteID | String | The site ID. |
| SentinelOne.Activity.data.SiteName | String | The site name. |
| SentinelOne.Activity.data.username | String | The name of the site creator. |
| SentinelOne.Activity.Hash | String | Threat file hash (if applicable). |
| SentinelOne.Activity.UpdatedAt | Date | Activity last updated time (UTC). |
| SentinelOne.Activity.Comments | String | Comments for the activity. |
| SentinelOne.Activity.ThreatID | String | Related threat (if applicable). |
| SentinelOne.Activity.PrimaryDescription | String | Primary description for the activity. |
| SentinelOne.Activity.GroupID | String | Related group (if applicable). |
| SentinelOne.Activity.ID | String | Activity ID. |
| SentinelOne.Activity.CreatedAt | Date | Activity creation time (UTC). |
| SentinelOne.Activity.Description | String | Extra activity information. |
sentinelone-get-groups
Returns data for the specified group.
Base Command
sentinelone-get-groups
Input
| Argument Name | Description | Required |
|---|---|---|
| group_type | Group type, for example: “static”. | Optional |
| group_ids | A comma-separated list of group IDs by which to filter, for example: “225494730938493804,225494730938493915”. | Optional |
| group_id | Group ID by which to filter, for example: “225494730938493804”. | Optional |
| is_default | Whether this is the default group. Possible values are: true, false. | Optional |
| name | The name of the group. | Optional |
| query | Free-text search. | Optional |
| rank | The priority of a dynamic group over others, for example, “1”, which is the highest priority. | Optional |
| limit | Maximum number of items to return (1-200). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Group.siteId | String | The ID of the site of which this group is a member. |
| SentinelOne.Group.filterName | String | If the group is dynamic, the name of the filter which is used to associate agents. |
| SentinelOne.Group.creatorId | String | The ID of the user who created the group. |
| SentinelOne.Group.name | String | The name of the group. |
| SentinelOne.Group.creator | String | The user who created the group. |
| SentinelOne.Group.rank | Number | The rank, which sets the priority of a dynamic group over others. |
| SentinelOne.Group.updatedAt | Date | Timestamp of the last update. |
| SentinelOne.Group.totalAgents | Number | Number of agents in the group. |
| SentinelOne.Group.filterId | String | If the group is dynamic, the group ID of the filter that is used to associate agents. |
| SentinelOne.Group.isDefault | Boolean | Whether the groups is the default group of the site. |
| SentinelOne.Group.inherits | Boolean | Whether the policy is inherited from a site. “False” if the group has its own edited policy. |
| SentinelOne.Group.type | String | Group type. Can be static or dynamic |
| SentinelOne.Group.id | String | The ID of the group. |
| SentinelOne.Group.createdAt | Date | Timestamp of group creation. |
sentinelone-move-agent
Moves agents to a new group.
Base Command
sentinelone-move-agent
Input
| Argument Name | Description | Required |
|---|---|---|
| group_id | The ID of the group to move the agent to. | Required |
| agents_ids | Agents IDs. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Agent.AgentsMoved | Number | The number of agents that were moved to another group. |
sentinelone-delete-group
Deletes a group, by the group ID.
Base Command
sentinelone-delete-group
Input
| Argument Name | Description | Required |
|---|---|---|
| group_id | The ID of the group to delete. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.DeleteGroup.Success | String | The status of the command. |
sentinelone-connect-agent
Connects agents to the network.
Base Command
sentinelone-connect-agent
Input
| Argument Name | Description | Required |
|---|---|---|
| agent_id | A comma-separated list of agent IDs to connect to the network. Run the list-agents command to get a list of agent IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Agent.AgentsAffected | Number | The number of affected agents. |
| SentinelOne.Agent.NetworkStatus | String | Agent network status. |
| SentinelOne.Agent.ID | String | Input agents’ IDs. |
sentinelone-disconnect-agent
Disconnects agents from the network.
Base Command
sentinelone-disconnect-agent
Input
| Argument Name | Description | Required |
|---|---|---|
| agent_id | A comma-separated list of agent IDs to disconnect from the network. Run the list-agents command to get a list of agent IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Agent.NetworkStatus | String | Agent network status. |
| SentinelOne.Agent.ID | String | Input agents’ IDs. |
sentinelone-broadcast-message
Broadcasts a message to all agents that match the input filters.
Base Command
sentinelone-broadcast-message
Input
| Argument Name | Description | Required |
|---|---|---|
| message | The message to broadcast to agents. | Required |
| active_agent | Whether to only include active agents. Default is “false”. Possible values are: true, false. | Optional |
| group_id | A comma-separated list of group IDs by which to filter the results. | Optional |
| agent_id | A comma-separated list of agent IDs by which to filter the results. | Optional |
| domain | A comma-separated of included network domains. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.BroadcastMessage.Affected | String | Number of affected endpoints. |
sentinelone-get-events
Returns all Deep Visibility events that match the query.
Base Command
sentinelone-get-events
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | Maximum number of items to return (1-100). Default is 50. | Optional |
| query_id | QueryId obtained when creating a query in the sentinelone-create-query command. Example: “q1xx2xx3”. | Required |
| cursor | Cursor pointer to get next page of results from query. | Optional |
| columns | A comma-separated list of additionals fields to display. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Event.ProcessUID | String | Process unique identifier. |
| SentinelOne.Event.SHA256 | String | SHA256 hash of the file. |
| SentinelOne.Event.AgentOS | String | Operating system type. Can be “windows”, “linux”, “macos”, or “windows_legac”. |
| SentinelOne.Event.ProcessID | Number | The process ID. |
| SentinelOne.Event.User | String | User assigned to the event. |
| SentinelOne.Event.Time | Date | Process start time. |
| SentinelOne.Event.Endpoint | String | The agent name. |
| SentinelOne.Event.SiteName | String | Site name. |
| SentinelOne.Event.EventType | String | Event type. Can be “events”, “file”, “ip”, “url”, “dns”, “process”, “registry”, “scheduled_task”, or “logins”. |
| SentinelOne.Event.ProcessName | String | The name of the process. |
| SentinelOne.Event.MD5 | String | MD5 hash of the file. |
| SentinelOne.Event.SourceIP | String | The source ip. |
| SentinelOne.Event.SourcePort | String | The source port. |
| SentinelOne.Event.DestinationIP | String | The destination IP. |
| SentinelOne.Event.DestinationPort | String | The destination port. |
| SentinelOne.Event.SourceProcessUser | String | The source process user. |
| SentinelOne.Event.SourceProcessCommandLine | String | The source process command line. |
| SentinelOne.Event.DNSRequest | String | The DNS Request. |
| SentinelOne.Event.FileFullName | String | The file full name. |
| SentinelOne.Event.EventTime | String | The event time. |
| Event.ID | String | Event process ID. |
| Event.Name | String | Event name. |
| Event.Type | String | Event type. |
| SentinelOne.Cursor.Event | String | cursor to recieve next page |
sentinelone-create-query
Runs a Deep Visibility query and returns the queryId. You can use the queryId for all other commands, such as the sentinelone-get-events command.
Base Command
sentinelone-create-query
Input
| Argument Name | Description | Required |
|---|---|---|
| query | The query string for which to return events. | Required |
| from_date | Query start date, for example, “2019-08-03T04:49:26.257525Z”. Limited to 93 days ago. | Required |
| to_date | Query end date, for example, “2019-08-03T04:49:26.257525Z”. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Query.FromDate | Date | Query start date. |
| SentinelOne.Query.Query | String | The search query string. |
| SentinelOne.Query.QueryID | String | The query ID. |
| SentinelOne.Query.ToDate | Date | Query end date. |
sentinelone-get-processes
Returns a list of Deep Visibility events from query by event type - process.
Base Command
sentinelone-get-processes
Input
| Argument Name | Description | Required |
|---|---|---|
| query_id | The queryId that is returned when creating a query under Create Query. Example: “q1xx2xx3”. Get the query_id from the “get-query-id” command. | Required |
| limit | Maximum number of items to return (1-100). Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Event.ParentProcessID | Number | Parent process ID. |
| SentinelOne.Event.ProcessUID | String | The process unique identifier. |
| SentinelOne.Event.SHA1 | String | SHA1 hash of the process image. |
| SentinelOne.Event.SubsystemType | String | Process sub-system. |
| SentinelOne.Event.ParentProcessStartTime | Date | The parent process start time. |
| SentinelOne.Event.ProcessID | Number | The process ID. |
| SentinelOne.Event.ParentProcessUID | String | Parent process unique identifier. |
| SentinelOne.Event.User | String | User assigned to the event. |
| SentinelOne.Event.Time | Date | Start time of the process. |
| SentinelOne.Event.ParentProcessName | String | Parent process name. |
| SentinelOne.Event.SiteName | String | Site name. |
| SentinelOne.Event.EventType | String | The event type. |
| SentinelOne.Event.Endpoint | String | The agent name (endpoint). |
| SentinelOne.Event.IntegrityLevel | String | Process integrity level. |
| SentinelOne.Event.CMD | String | Process CMD. |
| SentinelOne.Event.ProcessName | String | Process name. |
| SentinelOne.Event.ProcessDisplayName | String | Process display name. |
sentinelone-shutdown-agent
Sends a shutdown command to all agents that match the input filter.
Base Command
sentinelone-shutdown-agent
Input
| Argument Name | Description | Required |
|---|---|---|
| query | A free-text search term that will match applicable attributes (sub-string match). Note: A device’s physical addresses will only be matched if they start with the search term (not if they contain the search term). | Optional |
| agent_id | A comma-separated list of agents IDs to shutdown. | Optional |
| group_id | The ID of the network group. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Agent.ID | String | The ID of the agent that was shutdown. |
sentinelone-uninstall-agent
Sends an uninstall command to all agents that match the input filter.
Base Command
sentinelone-uninstall-agent
Input
| Argument Name | Description | Required |
|---|---|---|
| query | A free-text search term that will match applicable attributes (sub-string match). Note: A device’s physical addresses will only be matched if they start with the search term (not if they contain the search term). | Optional |
| agent_id | A comma-separated list of agents IDs to shutdown. | Optional |
| group_id | The ID of the network group. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.uninstall.Affected | String | Number of affected agents. |
sentinelone-update-threats-verdict
Updates the analyst verdict to a group of threats that match the specified input filter. Relevant for API version 2.1.
Base Command
sentinelone-update-threats-verdict
Input
| Argument Name | Description | Required |
|---|---|---|
| verdict | Analyst verdict action. Possible values are: undefined, true_positive, false_positive, suspicious. | Required |
| threat_ids | A comma-separated list of threat IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Threat.ID | String | The threat ID. |
| SentinelOne.Threat.Updated | Boolean | Whether the threat was successfully updated in the analyst verdict. |
| SentinelOne.Threat.Update.Action | String | Name of the analyst verdict action performed on the threats. |
sentinelone-update-alerts-verdict
Updates the analyst verdict to a group of alerts that match the specified input filter. Relevant for API version 2.1.
Base Command
sentinelone-update-alerts-verdict
Input
| Argument Name | Description | Required |
|---|---|---|
| verdict | Analyst verdict action. Possible values are: undefined, true_positive, false_positive, suspicious. | Required |
| alert_ids | A comma-separated list of alert IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Alert.ID | String | The alert ID. |
| SentinelOne.Alert.Updated | Boolean | Whether the alert was successfully updated in the analyst verdict. |
| SentinelOne.Alert.Update.Action | String | Name of the analyst verdict action performed on the alerts. |
sentinelone-create-star-rule
Creates a custom STAR rule. Relevant for API version 2.1.
Base Command
sentinelone-create-star-rule
Input
| Argument Name | Description | Required |
|---|---|---|
| name | The name of the STAR rule. | Required |
| rule_severity | The rule severity. Possible values are: Low, Medium, High, Critical. | Required |
| expiration_mode | Type of expiration mode. Possible values are: Permanent, Temporary. | Required |
| query_type | Type of the query. For now it’s “events”. Possible values are: events, processes. | Required |
| query | The query string for which to return events. | Required |
| description | The description of the STAR rule. | Optional |
| expiration_date | If expiration mode is “Temporary” then it should be supplied, for example, “2019-08-03T04:49:26.257525Z” . | Optional |
| site_ids | A comma-separated list of site IDs. | Optional |
| group_ids | A comma-separated list of Group IDs. | Optional |
| account_ids | A comma-separated list of Account IDs. | Optional |
| network_quarantine | Whether to enable the network quarantine of the STAR rule. Possible values are: true, false. | Required |
| treatAsThreat | The treatAsThreat type. Possible values are: Malicious, Suspicious, UNDEFINED. | Required |
| query_lang | The query language version. Supported values are “1.0” and “2.0”. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.StarRule.ID | String | The STAR rule ID. |
| SentinelOne.StarRule.Name | String | The STAR rule name. |
| SentinelOne.StarRule.Status | String | The status of the STAR rule. |
| SentinelOne.StarRule.Severity | String | The severity of the STAR rule. |
| SentinelOne.StarRule.Description | String | The description of the STAR rule. |
| SentinelOne.StarRule.NetworkQuarantine | Boolean | The network quarantine of the STAR rule. |
| SentinelOne.StarRule.TreatAsThreat | String | The Treat As Threat of the STAR rule. |
| SentinelOne.StarRule.ExpirationMode | String | The expiration mode of the STAR rule. |
| SentinelOne.StarRule.ExpirationDate | String | The expiration date of the STAR rule. |
| SentinelOne.StarRule.ScopeHierarchy | String | The scope hierarchy of the STAR rule. |
| SentinelOne.StarRule.CreatedAt | String | The created time for the STAR rule. |
| SentinelOne.StarRule.UpdatedAt | String | The updated time for the STAR rule. |
| SentinelOne.StarRule.QueryLanguage | String | The Query language for the STAR rule. |
sentinelone-get-star-rules
Get a list of custom detection rules for a given scope. Relevant for API version 2.1.
Base Command
sentinelone-get-star-rules
Input
| Argument Name | Description | Required |
|---|---|---|
| status | A comma-separated list of the status of the STAR rule. Available options are: “Activating, Active, Deleted, Deleting, Disabled, Disabling and Draft”.Example: “Draft,Active”. | Optional |
| creator_contains | Free-text filter by rule creator (supports multiple values). Example: “Service Pack 1”. | Optional |
| queryType | Return rules with the filtered type. Example: “events”. Possible values are: events, processes. | Optional |
| query | Free-text filter by S1 query (supports multiple values). Example: “Service Pack 1”. | Optional |
| description_contains | Free-text filter by rule description (supports multiple values). Example: “Service Pack 1”. | Optional |
| ruleIds | A comma-separated list of Rules IDs. Example: “225494730938493804,225494730938493915”. | Optional |
| name_contains | Free-text filter by rule name (supports multiple values). Example: “Service Pack 1”. | Optional |
| accountIds | A comma-separated list of Account IDs to filter by. Example: “225494730938493804,225494730938493915”. | Optional |
| expirationMode | Return rules with the filtered expiration mode. Example: “Permanent”. Possible values are: Temporary, Permanent. | Optional |
| limit | Limit number of returned items (1-1000). Example: “10”. | Optional |
| siteIds | A comma-separated list of site IDs to filter by. Example: “225494730938493804,225494730938493915”. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.StarRule.ID | Number | The STAR rule ID. |
| SentinelOne.StarRule.Creator | string | The STAR rule creator. |
| SentinelOne.StarRule.Name | string | The STAR rule name. |
| SentinelOne.StarRule.Status | string | The STAR rule status. |
| SentinelOne.StarRule.Severity | string | The STAR rule severity. |
| SentinelOne.StarRule.GeneratedAlerts | Number | The number of STAR rule generated alerts. |
| SentinelOne.StarRule.Description | string | The STAR rule description. |
| SentinelOne.StarRule.StatusReason | string | The STAR rule status reason. |
| SentinelOne.StarRule.ExpirationMode | string | The STAR rule expiration mode. |
| SentinelOne.StarRule.ExpirationDate | Date | The STAR rule expiration date. |
| SentinelOne.StarRule.Expired | Boolean | Whether the STAR rule expired. |
sentinelone-update-star-rule
Updates a custom STAR rule. Relevant for API version 2.1.
Base Command
sentinelone-update-star-rule
Input
| Argument Name | Description | Required |
|---|---|---|
| rule_id | Rule ID Example: “225494730938493804”. | Required |
| name | The name of the STAR rule. | Required |
| rule_severity | The rule severity. Possible values are: Low, Medium, High, Critical. | Required |
| expiration_mode | Type of expiration mode. Possible values are: Permanent, Temporary. | Required |
| query_type | Type of the query. For now it’s “events”. Possible values are: events, processes. | Required |
| query | The query string for which to return events. | Required |
| description | The description of the STAR rule. | Optional |
| expiration_date | If expiration mode is “Temporary” then it should be supplied, for example, “2019-08-03T04:49:26.257525Z”. | Optional |
| site_ids | A comma-separated list of site IDs. | Optional |
| group_ids | A comma-separated list of group IDs. | Optional |
| account_ids | A comma-separated list of account IDs. | Optional |
| network_quarantine | Whether to enable the network quarantine of the STAR rule. Possible values are: true, false. | Required |
| treatAsThreat | The treatAsThreat. Possible values are: Malicious, Suspicious, UNDEFINED. | Required |
| query_lang | The query language version. Supported values are “1.0” and “2.0”. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.StarRule.ID | String | The STAR rule ID. |
| SentinelOne.StarRule.Name | String | The STAR rule name. |
| SentinelOne.StarRule.Status | String | The status of the STAR rule. |
| SentinelOne.StarRule.Severity | String | The severity of the STAR rule. |
| SentinelOne.StarRule.Description | String | The description of the STAR rule. |
| SentinelOne.StarRule.NetworkQuarantine | Boolean | The network quarantine of the STAR rule. |
| SentinelOne.StarRule.TreatAsThreat | String | The Treat As Threat of the STAR rule. |
| SentinelOne.StarRule.ExpirationMode | String | The expiration mode of the STAR rule. |
| SentinelOne.StarRule.ExpirationDate | String | The expiration date of the STAR rule. |
| SentinelOne.StarRule.ScopeHierarchy | String | The scope hierarchy of the STAR rule. |
| SentinelOne.StarRule.CreatedAt | String | The created time for the STAR rule. |
| SentinelOne.StarRule.UpdatedAt | String | The updated time for the STAR rule. |
| SentinelOne.StarRule.QueryLanguage | String | The Query language for the STAR rule. |
sentinelone-enable-star-rules
Activate Custom Detection rules that match the specified input filter. Relevant for API version 2.1.
Base Command
sentinelone-enable-star-rules
Input
| Argument Name | Description | Required |
|---|---|---|
| rule_ids | A comma-separated list of STAR rule IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.StarRule.ID | String | The Rule ID. |
| SentinelOne.StarRule.Enabled | Boolean | Whether the STAR rule was successfully enabled. |
sentinelone-disable-star-rules
Disable Custom Detection rules that match the specified input filter. Relevant for API version 2.1.
Base Command
sentinelone-disable-star-rules
Input
| Argument Name | Description | Required |
|---|---|---|
| rule_ids | A comma-separated list of STAR rule IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.StarRule.ID | String | The Rule ID. |
| SentinelOne.StarRule.Disabled | Boolean | Whether the STAR rule was successfully disabled. |
sentinelone-delete-star-rule
Deletes Custom Detection Rules that match the specified input filter. Relevant for API version 2.1.
Base Command
sentinelone-delete-star-rule
Input
| Argument Name | Description | Required |
|---|---|---|
| rule_ids | A comma-separated list of STAR rule IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.StarRule.ID | String | The Rule ID. |
| SentinelOne.StarRule.Deleted | Boolean | Whether the STAR rule was successfully deleted. |
sentinelone-get-blocklist
Retrieve the blocklist (“blacklist” in SentinelOne documentation). You can filter by SHA1 or SHA256 hash. If the global flag is true, then group_ids, site_ids, and account_ids are ignored.
Base Command
sentinelone-get-blocklist
Input
| Argument Name | Description | Required |
|---|---|---|
| global | Whether the global list is accessible. (Same as tenant flag in API docs.). Possible values are: true, false. Default is true. |
Optional |
| group_ids | Comma-separated list of group IDs to filter by. | Optional |
| site_ids | Comma-separated list of site IDs to filter by. | Optional |
| account_ids | Comma-separated list of account IDs to filter by. | Optional |
| offset | The number of records to skip (for paging). Default is 0. | Optional |
| limit | The maximum number of records to return. Default is 1000. | Optional |
| hash | Hash to search for in the blocklist. | Optional |
| sha1 | SHA1 hash to search for in the blocklist. | Optional |
| sha256Value | SHA256 hash to search for in the blocklist. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Blocklist.UserId | String | User ID. |
| SentinelOne.Blocklist.UpdatedAt | String | When entry was most recently updated. |
| SentinelOne.Blocklist.Value | String | File hash. |
| SentinelOne.Blocklist.ScopePath | String | SentinelOne list scope. |
| SentinelOne.Blocklist.Type | String | Block list type. |
| SentinelOne.Blocklist.Source | String | Source of entry. |
| SentinelOne.Blocklist.ID | String | Entry ID. |
| SentinelOne.Blocklist.CreatedAt | String | Date entry was created. |
| SentinelOne.Blocklist.Description | String | Description of the blocklist. |
| SentinelOne.Blocklist.OSType | String | Operating system type block is enforced on. |
| SentinelOne.Blocklist.ScopeName | String | Name of the blocklist scope. |
sentinelone-add-hash-to-blocklist
Add a hash to the blocklist in SentinelOne.
If a scope is provided (site, account, or group), the hash will be added to that specific scope.
If no scope is provided, the hash will be added to the global blocklist.
Base Command
sentinelone-add-hash-to-blocklist
Input
| Argument Name | Description | Required |
|---|---|---|
| sha1 | SHA1 hash to add to the blocklist. | Optional |
| sha256Value | SHA256 hash to add to the blocklist. | Optional |
| source | String describing the source of the block. Default is XSOAR. | Optional |
| os_type | Type of operating system. Possible values are: windows, linux, macos. | Required |
| description | Note stored in SentinelOne about the block. Default is Blocked from XSOAR. | Optional |
| site_ids | Comma-separated string of site IDs to add the hash to. | Optional |
| account_ids | Comma-separated string of account IDs to add the hash to. | Optional |
| group_ids | Comma-separated string of group IDs to add the hash to. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.AddHashToBlocklist.hash | unknown | Hash of the file. |
| SentinelOne.AddHashToBlocklist.status | unknown | Status of the action to add a hash to the blocklist. |
sentinelone-remove-hash-from-blocklist
Remove a hash from the blocklist in SentinelOne.
If a scope is provided (site, account, or group), the hash will be removed from that specific scope.
If no scope is provided, the hash will be removed from the global blocklist.
Base Command
sentinelone-remove-hash-from-blocklist
Input
| Argument Name | Description | Required |
|---|---|---|
| sha1 | SHA1 hash to remove from the blocklist. | Optional |
| sha256Value | SHA256 hash to remove from the blocklist. | Optional |
| os_type | Optional operating system type. If not supplied, will remove the SHA1 hash across all platforms. Possible values are: windows, macos, linux. | Optional |
| site_ids | Comma-separated string of site IDs to remove the hash from. | Optional |
| account_ids | Comma-separated string of account IDs to remove the hash from. | Optional |
| group_ids | Comma-separated string of group IDs to remove the hash from. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.RemoveHashFromBlocklist.hash | unknown | Hash of the file. |
| SentinelOne.RemoveHashFromBlocklist.status | unknown | Status of the action to remove a hash from the blocklist. |
sentinelone-fetch-file
Invokes a fetch files command against an agent endpoint.
Base Command
sentinelone-fetch-file
Input
| Argument Name | Description | Required |
|---|---|---|
| agent_id | Agent ID to retrieve the file from. | Required |
| file_path | File path to download the file from. | Required |
| password | Password to protect the zip file with. | Required |
Context Output
There is no context output for this command.
sentinelone-download-fetched-file
Download a file fetched using th sentinelone-fetch-file command to submit the request and the sentinelone-get-activities command to get the download path.
Base Command
sentinelone-download-fetched-file
Input
| Argument Name | Description | Required |
|---|---|---|
| agent_id | SentinelOne agent ID. Default is Agent ID. | Required |
| activity_id | Activity ID in the get-activities command. | Required |
| password | Password used in the sentinelone-fetch-file command. | Required |
Context Output
There is no context output for this command.
sentinelone-write-threat-note
Add a threat note to one or more threats. Relevant for API version 2.1.
Base Command
sentinelone-write-threat-note
Input
| Argument Name | Description | Required |
|---|---|---|
| threat_ids | A comma-separated list of threat IDs. | Required |
| note | Threat Note Text. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Threat.ID | String | The threat ID. |
| SentinelOne.Threat.Note | String | The threat note. |
| SentinelOne.Threat.Status | String | Whether the note was added successfully. |
sentinelone-create-ioc
Add an IoC to the Threat Intelligence database. Relevant for API version 2.1.
Base Command
sentinelone-create-ioc
Input
| Argument Name | Description | Required |
|---|---|---|
| name | Threat Intelligence indicator name. | Required |
| source | The source of the identified Threat Intelligence indicator. | Required |
| type | The type of the Threat Intelligence indicator. Possible values are: DNS, IPV4, IPV6, MD5, SHA1, SHA256, URL. | Required |
| method | The comparison method used by SentinelOne to trigger the event. Possible values are: EQUALS. | Required |
| validUntil | Expiration date for the Threat Intelligence indicator. | Required |
| value | The value of the Threat Intelligence indicator. | Required |
| account_ids | List of account IDs to filter by. | Required |
| externalId | The unique identifier of the indicator as provided by the Threat Intelligence source. | Optional |
| description | Description of the Threat Intelligence indicator. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.IOC.UUID | String | The IOC UUID. |
| SentinelOne.IOC.Name | String | Threat Intelligence indicator name. |
| SentinelOne.IOC.Source | String | The source of the identified Threat Intelligence indicator. |
| SentinelOne.IOC.Type | String | The type of the Threat Intelligence indicator. |
| SentinelOne.IOC.BatchId | String | The IOC batch ID. |
| SentinelOne.IOC.Creator | String | The IOC creator. |
| SentinelOne.IOC.Scope | String | The IOC scope. |
| SentinelOne.IOC.ScopeId | String | The IOC scope ID. |
| SentinelOne.IOC.ValidUntil | String | Expiration date for the Threat Intelligence indicator. |
| SentinelOne.IOC.Description | String | Description of the Threat Intelligence indicator. |
| SentinelOne.IOC.ExternalId | String | The unique identifier of the indicator as provided by the Threat Intelligence source. |
sentinelone-delete-ioc
Delete an IOC from the Threat Intelligence database that matches a filter. Relevant for API version 2.1.
Base Command
sentinelone-delete-ioc
Input
| Argument Name | Description | Required |
|---|---|---|
| account_ids | List of account IDs to filter by. | Required |
| uuids | UUID of Threat Intelligence indicator. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.IOC.UUID | String | The IOC UUID. |
| SentinelOne.IOC.Deleted | Boolean | Whether the Threat Intelligence indicator was deleted. |
sentinelone-get-iocs
Get the IOCs of a specified account that match the filter. Relevant for API version 2.1.
Base Command
sentinelone-get-iocs
Input
| Argument Name | Description | Required |
|---|---|---|
| account_ids | List of account IDs to filter by. | Required |
| limit | Limit number of returned items (1-1000). Default is 1000. | Optional |
| upload_time_gte | The time (greater than or equal to) at which the Threat Intelligence indicator was uploaded to the SentinelOne database. Example: “2022-07-13T20:33:29.007906Z”. | Optional |
| upload_time_lte | The time (less than or equal to) at which the Threat Intelligence indicator was uploaded to the SentinelOne database. Example: “2022-07-13T20:33:29.007906Z”. | Optional |
| cursor | Cursor position returned by the last request. Should be used for iterating over more than 1000 items. Example: “YWdlbnRfaWQ6NTgwMjkzODE=”. | Optional |
| uuids | A list of unique IDs of the parent process of the indicator of compromise. Example: “2cffae871197f20d864fe8363eee6651”. | Optional |
| type | The type of the Threat Intelligence indicator. Possible values are: DNS, IPV4, IPV6, MD5, SHA1, SHA256, URL. | Optional |
| batch_id | Unique ID of the uploaded indicators batch. Example: “atmtn000000028a881bcf939dc6d92ab55443”. | Optional |
| source | List of the sources of the identified Threat Intelligence indicator. Example: “AlienVault”. | Optional |
| value | The value of the Threat Intelligence indicator. Example: “175.0.x.x”. | Optional |
| external_id | The unique identifier of the indicator as provided by the Threat Intelligence source. Example: “e277603e-1060-5ad4-9937-c26c97f1ca68”. | Optional |
| name_contains | A comma-separated list of free-text filtered by the indicator name. Example: “foo.dll”. | Optional |
| creator_contains | A comma-separated list of free-text filtered by the user who uploaded the Threat Intelligence indicator. Example: “admin@sentinelone.com”. | Optional |
| description_contains | A comma-separated list of free-text filtered by the description of the indicator. Example: “Malicious-activity”. | Optional |
| category_in | The categories of the Threat Intelligence indicator. Example: The malware type associated with the IOC. | Optional |
| updated_at_gte | The time (greater or equal to) at which the indicator was last updated in the SentinelOne database. Example: “2021-07-13T20:33:29.007906Z”. | Optional |
| updated_at_lte | The time (less than or equal to) at which the indicator was last updated in the SentinelOne database. Example: “2021-07-13T20:33:29.007906Z”. | Optional |
| creation_time_gte | Creation time (greater than or equal to) as set by the user. Example: “2021-07-13T20:33:29.007906Z”. | Optional |
| creation_time_lte | Creation time (less than or equal to) as set by the user. Example: “2021-07-13T20:33:29.007906Z”. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.IOC.UUID | String | The IOC UUID. |
| SentinelOne.IOC.Creator | String | Threat Intelligence indicator creator. |
| SentinelOne.IOC.Name | String | Threat Intelligence indicator name. |
| SentinelOne.IOC.Value | String | Threat Intelligence indicator value. |
| SentinelOne.IOC.Description | String | Threat Intelligence indicator description. |
| SentinelOne.IOC.Type | String | Threat Intelligence indicator type. |
| SentinelOne.IOC.ExternalId | String | Threat Intelligence indicator external ID. |
| SentinelOne.IOC.Source | String | Threat Intelligence indicator source. |
| SentinelOne.IOC.UploadTime | String | Threat Intelligence indicator upload time. |
| SentinelOne.IOC.ValidUntil | String | Threat Intelligence indicator expiration time. |
sentinelone-create-power-query
Deprecated. Start a Deep Visibility Power query to get back status and potential results (ping afterwards using the queryId if query has not finished). Relevant for API version 2.1
Base Command
sentinelone-create-power-query
Input
| Argument Name | Description | Required |
|---|---|---|
| query | Events matching the query search term will be returned. | Required |
| from_date | Events created after this timestamp. | Required |
| to_date | Events created before or at this timestamp. | Required |
| limit | Limit number of returned items (1-100000). | Optional |
Context Output
There is no context output for this command.
sentinelone-ping-power-query
Deprecated. Ping a Deep Visibility Power query using the queryId argument if results have not returned from an initial Power query or a previous ping. Relevant for API version 2.1.
Base Command
sentinelone-ping-power-query
Input
| Argument Name | Description | Required |
|---|---|---|
| queryId | QueryId. | Required |
Context Output
There is no context output for this command.
sentinelone-update-threats-status
Updates the incident status to a group of threats that match the specified input filter. Relevant for API version 2.1.
Base Command
sentinelone-update-threats-status
Input
| Argument Name | Description | Required |
|---|---|---|
| status | Incident status. Possible values are: in_progress, resolved, unresolved. | Required |
| threat_ids | A comma-separated list of threat IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Threat.ID | String | The threat ID. |
| SentinelOne.Threat.Updated | Boolean | Whether the threat was successfully updated. |
| SentinelOne.Threat.Status | String | Name of the status performed on the threats. |
sentinelone-update-alerts-status
Updates the incident status to a group of alerts that match the specified input filter. Relevant for API version 2.1.
Base Command
sentinelone-update-alerts-status
Input
| Argument Name | Description | Required |
|---|---|---|
| status | Incident status. Possible values are: in_progress, resolved, unresolved. | Required |
| alert_ids | A comma-separated list of alert IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Alert.ID | String | The alert ID. |
| SentinelOne.Alert.Updated | Boolean | Whether the alert was successfully updated. |
| SentinelOne.Alert.Status | String | The status performed on the alerts. |
sentinelone-expire-site
Expire the site of the given ID
Base Command
sentinelone-expire-site
Input
| Argument Name | Description | Required |
|---|---|---|
| site_id | A valid site ID. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Site.ID | String | The site ID. |
| SentinelOne.Site.Name | String | The site name. |
| SentinelOne.Site.State | String | The site state. |
| SentinelOne.Site.SKU | String | The SKU of product features active for this site. |
| SentinelOne.Site.SiteType | String | The site type. |
| SentinelOne.Site.Suite | String | The site suite. |
| SentinelOne.Site.TotalLicenses | String | The total licenses. |
| SentinelOne.Site.AccountID | String | The account ID. |
| SentinelOne.Site.Creator | String | Full name of the creating user. |
| SentinelOne.Site.CreatorID | String | ID of the creating user. |
| SentinelOne.Site.Description | String | Description of the site. |
| SentinelOne.Site.Expiration | String | Expiration date of the site. |
sentinelone-fetch-threat-file
Fetch a file associated with the threat that matches the filter.
Base Command
sentinelone-fetch-threat-file
Input
| Argument Name | Description | Required |
|---|---|---|
| threat_id | Please provide the Valid Threat ID. Example: 14629133470822878. | Required |
| password | File encryption password. (At least 10 characters, three out of this list “uppercase”, “lowercase”, “digits” and “symbols” are mandatory. Maximum length is 256 characters.). | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Threat.ID | String | The threat ID. |
| SentinelOne.Threat.Downloadable | Boolean | Whether the file is downloadable. |
| SentinelOne.Threat.ZippedFile | String | Details of the zipped folder. |
sentinelone-get-alerts
Get the list of alerts that matches the filter provided. Relevant for API version 2.1.
Base Command
sentinelone-get-alerts
Input
| Argument Name | Description | Required |
|---|---|---|
| created_from | Greater than or equal to the time created. Example: “2018-02-27T04:49:26.257525Z”, “10 days”, “2 hours”,”5 months”. | Required |
| created_until | Less than or equal to the time created. Example: “2018-02-27T04:49:26.257525Z”, “10 days”, “2 hours”,”5 months”. | Optional |
| ruleName | Free-text filter by rule name. Example: “rule1”. | Optional |
| incidentStatus | Incident status. Example: “IN_PROGRESS”. | Optional |
| analystVerdict | Analyst verdict. Example: “TRUE_POSITIVE”. | Optional |
| alert_ids | A comma-separated list of alert IDs. | Optional |
| limit | Limit number of returned items (1-1000). Default is 1000. | Optional |
| site_ids | A comma-separated list of site IDs to filter by. Example: “225494730938493804,225494730938493915”. | Optional |
| cursor | Cursor position returned by the last request. Should be used for iterating over more than 1000 items. Example: “YWdlbnRfaWQ6NTgwMjkzODE=”. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Alert.EventType | String | Event type. |
| SentinelOne.Alert.RuleName | String | The rule name. |
| SentinelOne.Alert.SrcProcUser | String | Source process user. |
| SentinelOne.Alert.SrcProcName | String | Source process name. |
| SentinelOne.Alert.SrcProcPath | String | Source process file path. |
| SentinelOne.Alert.SrcProcCommandline | String | The command line |
| SentinelOne.Alert.SrcProcSHA1 | String | Source process SHA1 file hash. |
| SentinelOne.Alert.SrcProcStartTime | String | PID start time. |
| SentinelOne.Alert.SrcProcStorylineId | String | Source process story line ID. |
| SentinelOne.Alert.SrcParentProcName | String | Source parent process name. |
| SentinelOne.Alert.SrcParentProcPath | String | Source parent process file path. |
| SentinelOne.Alert.SrcParentProcCommandline | String | Source parent process command line. |
| SentinelOne.Alert.SrcParentProcStartTime | String | PID start time. |
| SentinelOne.Alert.SrcParentProcUser | String | Source parent process user. |
| SentinelOne.Alert.SrcParentProcSHA1 | String | Source parent process SHA1 file hash. |
| SentinelOne.Alert.SrcProcSignerIdentity | String | Source process file signer identity. |
| SentinelOne.Alert.SrcParentProcSignerIdentity | String | Source parent process file signer identity. |
| SentinelOne.Alert.AlertCreatedAt | String | The the alert was created. |
| SentinelOne.Alert.AlertId | String | Alert ID. |
| SentinelOne.Alert.AnalystVerdict | String | Analyst verdict. |
| SentinelOne.Alert.IncidentStatus | String | Incident status |
| SentinelOne.Alert.EndpointName | String | Endpoint name |
| SentinelOne.Alert.AgentId | String | Agent ID. |
| SentinelOne.Alert.AgentUUID | String | Agent UUID. |
| SentinelOne.Alert.dvEventId | String | Deep Visibility event ID. |
| SentinelOne.Alert.AgentOS | String | Agent operating system. |
| SentinelOne.Alert.AgentVersion | String | Agent version. |
| SentinelOne.Alert.SiteId | String | Site ID. |
| SentinelOne.Alert.RuleId | String | Rule ID. |
sentinelone-get-installed-applications
Get the installed applications for a specific agent.
Base Command
sentinelone-get-installed-applications
Input
| Argument Name | Description | Required |
|---|---|---|
| agent_ids | A comma-separated list of agent IDs. Example: 14629133470822878,14627455454652878. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Application.Name | String | The application name. |
| SentinelOne.Application.Publisher | String | The publisher. |
| SentinelOne.Application.Size | String | The size of the application in bytes. |
| SentinelOne.Application.Version | String | The version of the application. |
| SentinelOne.Application.InstalledOn | String | The date the application was installed. |
sentinelone-initiate-endpoint-scan
Initiate the endpoint virus scan on provided agent IDs.
Base Command
sentinelone-initiate-endpoint-scan
Input
| Argument Name | Description | Required |
|---|---|---|
| agent_ids | A comma-separated list of Agent IDs. Example: 14629133470822878,14627455454652878. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Agent.AgentID | String | The Agent ID. |
| SentinelOne.Agent.Initiated | Boolean | Whether the scan was initiated. |
sentinelone-remove-item-from-whitelist
Remove an item from the SentinelOne exclusion list
Base Command
sentinelone-remove-item-from-whitelist
Input
| Argument Name | Description | Required |
|---|---|---|
| item | Value of the item to be removed from the exclusion list. | Required |
| os_type | OS type. Can be “windows”, “windows_legacy”, “macos”, or “linux”. Possible values are: windows, windows_legacy, macos, linux. | Optional |
| exclusion_type | Exclusion item type. The options are: file_type, path, white_hash, certificate, or browser. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.RemoveItemFromWhitelist.status | String | Status on if items were removed from whitelist or not found on whitelist. |
| SentinelOne.RemoveItemFromWhitelist.item | String | Item removed fom whitelist. |
sentinelone-run-remote-script
Run a remote script that was uploaded to the SentinelOne Script Library.
Base Command
sentinelone-run-remote-script
Input
| Argument Name | Description | Required |
|---|---|---|
| account_ids | A comma-separated list of account IDs. | Required |
| output_destination | Output destination. Possible values: DataSetCloud/Local/None/SentinelCloud. Possible values are: DataSetCloud, Local, None, SentinelCloud. | Required |
| task_description | Task description. | Required |
| script_id | Script ID. | Required |
| output_directory | Output directory. | Optional |
| agent_ids | A comma-separated list of agent IDs on which the script should run. | Required |
| singularity_xdr_Keyword | Singularityxdr keyword. | Optional |
| singularity_xdr_Url | Singularityxdr keyword. | Optional |
| api_key | Api key. | Optional |
| input_params | Input params. | Optional |
| password | Password. | Optional |
| script_runtime_timeout_seconds | Script runtime timout in seconds for current execution. | Optional |
| requires_approval | If set to true, execution will require approval. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.RunRemoteScript.pendingExecutionId | string | ID of the created pending execution. Present only if pending flag is true. |
| SentinelOne.RunRemoteScript.pending | boolean | Flag indicating if the requested script execution requires approval and is created as a pending execution. |
| SentinelOne.RunRemoteScript.affected | number | Number of entities affected by the requested operation. |
| SentinelOne.RunRemoteScript.parentTaskId | string | The parent task ID of the script execution task. Null in case of pending execution. |
sentinelone-get-remote-script-task-status
Get remote script tasks using a variety of filters.
Base Command
sentinelone-get-remote-script-task-status
Input
| Argument Name | Description | Required |
|---|---|---|
| account_ids | A comma-separated list of account IDs. Example: ‘225494730938493804,225494730938493915’. | Optional |
| computer_name_contains | Free-text filter by agent computer name (supports multiple values). | Optional |
| count_only | If true, only total number of items will be returned, without any of the actual objects. | Optional |
| created_at_gt | Created at greater than datetime. Example: ‘2018-02-27T04:49:26.257525Z’. | Optional |
| created_at_gte | Created at greater or equal than datetime. Example: ‘2018-02-27T04:49:26.257525Z’. | Optional |
| created_at_lt | Created at lesser than datetime. Example: ‘2018-02-27T04:49:26.257525Z’. | Optional |
| created_at_lte | Created at lesser or equal than datetime. Example: ‘2018-02-27T04:49:26.257525Z’. | Optional |
| cursor | Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: ‘YWdlbnRfaWQ6NTgwMjkzODE=’. | Optional |
| description_contains | Only include tasks with specific description. | Optional |
| detailed_status_contains | Only include tasks with specific detailed status. | Optional |
| group_ids | Comma-separated list of Group IDs to filter by. Example: ‘225494730938493804,225494730938493915’. | Optional |
| ids | Comma-separated list of IDs to filter by. Example: ‘225494730938493804,225494730938493915’. | Optional |
| initiated_by_contains | Only include tasks from specific initiating user. | Optional |
| limit | Limit number of returned items (1-1000). Example: ‘10’. | Optional |
| parent_task_id | Parent task ID to fetch the status by. Example: ‘225494730938493804’. | Required |
| parent_task_id_in | Comma-separated list of IDs to filter by. | Optional |
| query | A free-text search term that will match applicable attributes (sub-string match). | Optional |
| site_ids | Comma-separated list of Site IDs to filter by. Example: ‘225494730938493804,225494730938493915’. | Optional |
| status | Status of the script task. Example: ‘created’. | Optional |
| tenant | A tenant scope request. | Optional |
| updated_at_gt | Updated at greater than datetime. Example: ‘2018-02-27T04:49:26.257525Z’. | Optional |
| updated_at_gte | Updated at greater or equal than datetime. Example: ‘2018-02-27T04:49:26.257525Z’. | Optional |
| updated_at_lt | Updated at lesser than datetime. Example: ‘2018-02-27T04:49:26.257525Z’. | Optional |
| updated_at_lte | Updated at lesser or equal than datetime. Example: ‘2018-02-27T04:49:26.257525Z’. | Optional |
| uuid_contains | Free-text filter by agent UUID (supports multiple values). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.GetRemoteScript.id | string | ID of the task. |
| SentinelOne.GetRemoteScript.accountId | string | Account ID where this script is executed. |
| SentinelOne.GetRemoteScript.accountName | string | Account name where this script is executed. |
| SentinelOne.GetRemoteScript.agentId | string | Agent ID where this script is executed. |
| SentinelOne.GetRemoteScript.agentIsActive | boolean | The status of the agent. |
| SentinelOne.GetRemoteScript.agentMachineType | string | Agent machine type. |
| SentinelOne.GetRemoteScript.agentOsType | string | Agent operating system type. |
| SentinelOne.GetRemoteScript.agentUuid | string | Agent UUID. |
| SentinelOne.GetRemoteScript.createdAt | string | The script created at datetime. |
| SentinelOne.GetRemoteScript.description | string | The description of the remote script. |
| SentinelOne.GetRemoteScript.detailedStatus | string | The detailed status of the remote script. |
| SentinelOne.GetRemoteScript.groupId | string | Group ID where this script is executed. |
| SentinelOne.GetRemoteScript.groupName | string | Group name where this script is executed. |
| SentinelOne.GetRemoteScript.initiatedBy | string | Remote script initiate by. |
| SentinelOne.GetRemoteScript.initiatedById | string | ID of the remote script initiator. |
| SentinelOne.GetRemoteScript.parentTaskId | string | Parent task ID of the remote script. |
| SentinelOne.GetRemoteScript.siteId | string | Site ID where this script is executed. |
| SentinelOne.GetRemoteScript.siteName | string | Site name where this script is executed. |
| SentinelOne.GetRemoteScript.status | string | Status of the remote script. |
| SentinelOne.GetRemoteScript.statusCode | string | Status code of the remote script. |
| SentinelOne.GetRemoteScript.statusDescription | string | Status description of the remote script. |
| SentinelOne.GetRemoteScript.type | string | Type of remote script. |
| SentinelOne.GetRemoteScript.updateAt | string | Remote script upated at. |
sentinelone-get-remote-script-task-results
Get a script’s result download URL.
Base Command
sentinelone-get-remote-script-task-results
Input
| Argument Name | Description | Required |
|---|---|---|
| computer_names | A comma-separated list of partial or whole computer names, which ran scripts. | Optional |
| task_ids | A comma-separated list of task IDs to get a download link for. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.RemoteScriptResults.taskId | string | ID of the task. |
| SentinelOne.RemoteScriptResults.fileName | string | File name. |
| SentinelOne.RemoteScriptResults.downloadUrl | string | Download URL. |
sentinelone-remote-script-automate-results
Automate a remote script’s execution cycle and return the script’s results.
Base Command
sentinelone-remote-script-automate-results
Input
| Argument Name | Description | Required |
|---|---|---|
| account_ids | A comma-separated list of account IDs. | Required |
| output_destination | Output destination. Possible values are: DataSetCloud, Local, None, SentinelCloud. | Required |
| task_description | Task description. | Required |
| script_id | Script ID. | Required |
| output_directory | Output directory. | Optional |
| agent_ids | A comma-separated list of agent IDs on which the script should run. | Required |
| singularity_xdr_Keyword | Singularity XDR keyword. | Optional |
| singularity_xdr_Url | Singularity XDR URL. | Optional |
| api_key | API key. | Optional |
| input_params | Input parameters. | Optional |
| password | Password. | Optional |
| script_runtime_timeout_seconds | Script runtime timeout in seconds for current execution. | Optional |
| requires_approval | If set to true, execution will require approval. | Optional |
| interval | Indicates how long to wait between command execution (in seconds) when ‘polling’ argument is true. Minimum value is 10 seconds. Default is 60. | Optional |
| timeout | Indicates the time in seconds until the polling sequence timeouts. Default is 600. | Optional |
| parent_task_id | Parent task ID to fetch the status by. Example: ‘225494730938493804’. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.RemoteScriptResults.taskId | string | ID of the task. |
| SentinelOne.RemoteScriptResults.fileName | string | File name. |
| SentinelOne.RemoteScriptResults.downloadUrl | string | Download URL. |
sentinelone-get-power-query-results
Automate a power query and return the query results. (The maximum timeout of 300 seconds is allowed.)
Base Command
sentinelone-get-power-query-results
Input
| Argument Name | Description | Required |
|---|---|---|
| account_ids | A comma-separated list of account IDs. | Optional |
| site_ids | A comma-separated list of site IDs on which the query should run. | Optional |
| query | Events matching the query search term will be returned. | Required |
| from_date | Events created after this date. Example: ‘2018-02-27T04:49:26.257525Z’. | Required |
| to_date | Events created before or at this date. Example: ‘2018-02-27T04:49:26.257525Z’. | Required |
| limit | Limit number of returned items (1-100000). | Optional |
| interval | Indicates how long to wait between command execution (in seconds) when ‘polling’ argument is true. Minimum value is 10 seconds. | Optional |
| timeout | Indicates the time in seconds until the polling sequence timeouts. | Optional |
| query_id | QueryId. Example: pq3be5e2747f716cxxxxxxxxxxxxx20a0. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.PowerQuery.ResultIndex | List | Result from the power query in list of objects format |
get-mapping-fields
Returns the list of fields for an incident type.
Base Command
get-mapping-fields
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
There is no context output for this command.
update-remote-system
Pushes local changes to the remote system.
Base Command
update-remote-system
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
There is no context output for this command.
get-remote-data
Get remote data from a remote incident. This method does not update the current incident, and should be used for debugging purposes.
Base Command
get-remote-data
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The ticket ID. | Required |
| lastUpdate | Retrieve entries that were created after lastUpdate. | Required |
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 time. Note that this method is here for debugging purposes. The get-modified-remote-data command is used as part of a Mirroring feature, which is available from version 6.1.
Base Command
get-modified-remote-data
Input
| Argument Name | Description | Required |
|---|---|---|
| last_update | Retrieve entries that were created after lastUpdate. | Optional |
Context Output
There is no context output for this command.
sentinelone-get-dv-query-status
Returns status of a Deep Visibility Query
Base Command
sentinelone-get-dv-query-status
Input
| Argument Name | Description | Required |
|---|---|---|
| query_id | The queryId that is returned when creating a query under Create Query. Example: “q1xx2xx3”. Get the query_id from the “get-query-id” command. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Query.Status.progressStatus | string | Progress Query Status |
| SentinelOne.Query.Status.queryModeInfo.lastActivatedAt | string | Last Activated At |
| SentinelOne.Query.Status.queryModeInfo.mode | string | Query Mode |
| SentinelOne.Query.Status.responseState | string | State of the Query |
| SentinelOne.Query.Status.warnings | string | Warnings during Query |
| SentinelOne.Query.Status.QueryId | string | QueryID From Request |
sentinelone-get-agent-mac
Returns network interface details for a given Agent ID. This includes MAC address details and interface description.
Base Command
sentinelone-get-agent-mac
Input
| Argument Name | Description | Required |
|---|---|---|
| agent_id | AgentId of the System. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.MAC | string | Agent network interface details. |
| SentinelOne.MAC.agent_id | string | AgentID |
| SentinelOne.MAC.hostname | string | Hostname |
| SentinelOne.MAC.int_name | string | Interface Name |
| SentinelOne.MAC.ip | string | IP Address |
| SentinelOne.MAC.mac | string | MAC Address |
sentinelone-get-accounts
Returns details of accounts.
Base Command
sentinelone-get-accounts
Input
| Argument Name | Description | Required |
|---|---|---|
| account_id | Can filter on one account ID. Otherwise, it returns information from all accounts. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Accounts.AccountType | string | The account type. |
| SentinelOne.Accounts.ActiveAgents | number | The account number of active agents. |
| SentinelOne.Accounts.NumberOfSites | number | The account number of sites. |
| SentinelOne.Accounts.State | string | The account state. |
| SentinelOne.Accounts.CreatedAt | string | The account creation date. |
| SentinelOne.Accounts.Expiration | string | The account expiration date. |
| SentinelOne.Accounts.ID | string | The account ID. |
| SentinelOne.Accounts.Name | string | The account name. |
sentinelone-get-threat-notes
Returns threat notes.
Base Command
sentinelone-get-threat-notes
Input
| Argument Name | Description | Required |
|---|---|---|
| threat_id | The ID of the threat. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Notes.CreatedAt | string | The note creation date. |
| SentinelOne.Notes.Creator | string | The note creator. |
| SentinelOne.Notes.CreatorID | string | The note creator ID. |
| SentinelOne.Notes.Edited | boolean | Whether the note was edited or not.. |
| SentinelOne.Notes.ID | string | The note ID. |
| SentinelOne.Notes.Text | string | The note text. |
| SentinelOne.Notes.UpdatedAt | string | The note updated time. |
sentinelone-list-installed-singularity-marketplace-applications
Returns all installed singularity marketplace applications that match the specified filter values.
Base Command
sentinelone-list-installed-singularity-marketplace-applications
Input
| Argument Name | Description | Required |
|---|---|---|
| account_ids | A comma-separated list of account IDs. | Optional |
| application_catalog_id | Filter results by application catalog id. | Optional |
| creator_contains | Free-text filter by application creator. | Optional |
| ids | A comma-separated list of applications IDs. | Optional |
| name_contains | Free-text filter by application name | Optional |
| site_ids | A comma-separated list of site IDs. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.InstalledApps.ID | string | The application ID. |
| SentinelOne.InstalledApps.Account | string | The account name. |
| SentinelOne.InstalledApps.AccountId | string | The account ID. |
| SentinelOne.InstalledApps.ApplicationCatalogId | string | The application Catalog ID. |
| SentinelOne.InstalledApps.ApplicationCatalogName | string | The application Catalog name. |
| SentinelOne.InstalledApps.AlertMessage | string | The alert message. |
| SentinelOne.InstalledApps.CreatedAt | date | Application created at. |
| SentinelOne.InstalledApps.Creator | string | Application creator. |
| SentinelOne.InstalledApps.CreatorId | string | Application creator ID. |
| SentinelOne.InstalledApps.DesiredStatus | string | Application desired status. |
| SentinelOne.InstalledApps.HasAlert | boolean | Application has alert. |
| SentinelOne.InstalledApps.LastEntityCreatedAt | date | Application last entity created at. |
| SentinelOne.InstalledApps.Modifier | string | Modifier. |
| SentinelOne.InstalledApps.ModifierId | string | Modifier ID. |
| SentinelOne.InstalledApps.ScopeId | string | The scope ID. |
| SentinelOne.InstalledApps.ScopeLevel | string | The scope level. |
| SentinelOne.InstalledApps.Status | string | Status of application. |
| SentinelOne.InstalledApps.UpdatedAt | string | Application updated at. |
| SentinelOne.InstalledApps.ApplicationInstanceName | string | Application instance name. |
sentinelone-get-service-users
Returns all service users that match the specified filter values.
Base Command
sentinelone-get-service-users
Input
| Argument Name | Description | Required |
|---|---|---|
| account_ids | A comma-separated list of account IDs. | Optional |
| role_ids | A comma-separated list of rbac roles to filter by. | Optional |
| ids | A comma-separated list of service user IDs to filter by. | Optional |
| site_ids | A comma-separated list of site IDs. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.ServiceUsers.ID | string | The service user ID. |
| SentinelOne.ServiceUsers.ApiTokenCreatedAt | date | Api token created at. |
| SentinelOne.ServiceUsers.ApiTokenExpiresAt | date | Api token expires at. |
| SentinelOne.ServiceUsers.CreatedAt | date | Service user created at. |
| SentinelOne.ServiceUsers.CreatedById | string | The service user created by Id. |
| SentinelOne.ServiceUsers.CreatedByName | string | The service user created by name. |
| SentinelOne.ServiceUsers.Description | string | Service user description. |
| SentinelOne.ServiceUsers.LastActivation | date | Last activation date. |
| SentinelOne.ServiceUsers.Name | string | Service user name. |
| SentinelOne.ServiceUsers.Scope | string | Service user scope. |
| SentinelOne.ServiceUsers.UpdatedAt | date | Service user updated at. |
| SentinelOne.ServiceUsers.UpdatedById | string | Service user updated by Id. |
| SentinelOne.ServiceUsers.UpdatedByName | string | Service user updated by name. |
| SentinelOne.ServiceUsers.ScopeRolesRoleId | string | Scope roles role Id. |
| SentinelOne.ServiceUsers.ScopeRolesRoleName | string | Scope roles role name. |
| SentinelOne.ServiceUsers.ScopeRolesAccountName | string | Scope roles account name. |
| SentinelOne.ServiceUsers.ScopeRolesId | string | Scope roles Id. |
Incident Mirroring
You can enable incident mirroring between Cortex XSOAR incidents and SentinelOne v2 corresponding events (available from Cortex XSOAR version 6.0.0).
To set up the mirroring:
- Enable Fetching incidents in your instance configuration.
-
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 SentinelOne v2 events (mirroring incoming fields) will be reflected in Cortex XSOAR incidents. Outgoing Any changes in Cortex XSOAR incidents will be reflected in SentinelOne v2 events (outgoing mirrored fields). Incoming And Outgoing Changes in Cortex XSOAR incidents and SentinelOne v2 events will be reflected in both directions.
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 SentinelOne v2.
sentinelone-threat-download-from-cloud
Download a file associated with the threat from the Cloud (BinaryVault).
Base Command
sentinelone-threat-download-from-cloud
Input
| Argument Name | Description | Required |
|---|---|---|
| threat_id | Please provide the Valid Threat ID. Example: 14629133470822878. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Threat.ID | String | The threat ID. |
| SentinelOne.Threat.Downloadable | Boolean | Whether the file is downloadable. |
| SentinelOne.Threat.ZippedFile | String | Details of the zipped folder. |
sentinelone-create-bulk-ioc
Adds a bulk list of IoCs to the Threat Intelligence database. To use this command, the user must upload a JSON file containing a list of IoC objects(each object represents a single IoC to be created) with the required attributes specified in the sentinelone-create-ioc command. Relevant for API version 2.1.
Base Command
sentinelone-create-bulk-ioc
Input
| Argument Name | Description | Required |
|---|---|---|
| entry_id | Entry ID of uploaded IOCs JSON file. | Required |
| account_ids | List of account IDs to filter by. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.IOC.UUID | String | The IOC UUID. |
| SentinelOne.IOC.Name | String | Threat Intelligence indicator name. |
| SentinelOne.IOC.Source | String | The source of the identified Threat Intelligence indicator. |
| SentinelOne.IOC.Type | String | The type of the Threat Intelligence indicator. |
| SentinelOne.IOC.BatchId | String | The IOC batch ID. |
| SentinelOne.IOC.Creator | String | The IOC creator. |
| SentinelOne.IOC.Scope | String | The IOC scope. |
| SentinelOne.IOC.ScopeId | String | The IOC scope ID. |
| SentinelOne.IOC.ValidUntil | String | Expiration date for the Threat Intelligence indicator. |
| SentinelOne.IOC.Description | String | Description of the Threat Intelligence indicator. |
| SentinelOne.IOC.ExternalId | String | The unique identifier of the indicator as provided by the Threat Intelligence source. |
sentinelone-run-powerquery
Run a PowerQuery, where you can pipe one or many search expressions into a set of commands to transform, manipulate, group, and summarize your data.
Base Command
sentinelone-run-powerquery
Input
| Argument Name | Description | Required |
|---|---|---|
| singularity_xdr_url | Singularity Data Lake XDR URL. | Required |
| singularity_xdr_api_key | A Log Read Access API key. | Required |
| query | The query, in PowerQuery syntax. | Required |
| start_time | Start time for your query. | Optional |
| end_time | End time for your query. | Optional |
| priority | Query execution priority (defaults to “low”). Default is low. | Optional |
| recurring | Optional (defaults to false). When set to true, a materialized view of your query is created. Possible values are: true, false. Default is false. | Optional |
| team_emails | Comma-separated list of account emails to query, enabling Cross Team Search. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.PowerQuery.Results.status | String | The status of the PowerQuery execution. |
| SentinelOne.PowerQuery.Results.matchingEvents | Number | Number of events that match the query’s initial filter. |
| SentinelOne.PowerQuery.Results.omittedEvents | Number | Number of events omitted from the final result due to memory limits. |
| SentinelOne.PowerQuery.Results.results | List | A list of result rows returned by the PowerQuery, where each object represents one row (column:value pairs). |
sentinelone-abort-endpoint-scan
Abort the endpoint virus scan on provided agent IDs.
Base Command
sentinelone-abort-endpoint-scan
Input
| Argument Name | Description | Required |
|---|---|---|
| agent_ids | A comma-separated list of Agent IDs. Example: 14629133470822878,14627455454652878. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Agent.AgentID | String | The Agent ID. |
| SentinelOne.Agent.Aborted | Boolean | Whether the scan was aborted. |
sentinelone-threat-analysis
Returns threat analysis. Can only be used with API V2.1.
Base Command
sentinelone-threat-analysis
Input
| Argument Name | Description | Required |
|---|---|---|
| threat_id | Threat ID to get the analysis, for example: “2341398296147451190”. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Threat.AgentDetectionInfo | 18 | Agent detection time information. |
| SentinelOne.Threat.AgentRealtimeInfo | 18 | Agent realtime information. |
| SentinelOne.Threat.ThreatInfo | 18 | Threat information. |
sentinelone-endpoint-fetch-logs
Get the Agent and Endpoint logs from Agents for provided agent IDs
Base Command
sentinelone-endpoint-fetch-logs
Input
| Argument Name | Description | Required |
|---|---|---|
| agent_ids | A comma-separated list of Agent IDs. Example: 14629133470822878,14627455454652878. | Required |
| agents_logs | Fetch Agent logs. Possible values are: true, false. Default is true. | Required |
| customer_facing_logs | Fetch customer-facing logs. Possible values are: true, false. Default is false. | Required |
| platform_logs | Actively fetch logs from the relevant platform (Windows, macOS, or Linux). Possible values are: true, false. Default is false. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Agent.Affected | String | Number of affected endpoints. |
sentinelone-update-uam-alert-verdict
Updates the analyst verdict for a group of UAM alerts. Relevant for API version 2.1.
Base Command
sentinelone-update-uam-alert-verdict
Input
| Argument Name | Description | Required |
|---|---|---|
| analyst_verdict | UAM alert analyst verdict. Possible values are: False positive - Benign, False positive - Benign but suspicious, False positive - System error, False positive - Undefined, False positive - User error, True positive - Advanced persistent threat, True positive - Benign, True positive - Benign but suspicious, True positive - Data exfiltration, True positive - Denial of service, True positive - Exploitation tools, True positive - Insider threat, True positive - Malware, True positive - Phishing attack, True positive - Policy violation, True positive - PUA/Adware, True positive - Ransomware, True positive - Unauthorized access, True positive - Undefined, Undefined. | Required |
| alert_ids | A comma-separated list of UAM alert IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.UAMAlert.ID | String | The UAM alert ID. |
| SentinelOne.UAMAlert.Updated | Boolean | Whether the analyst verdict was successfully updated. |
| SentinelOne.UAMAlert.AnalystVerdict | String | Name of the analyst verdict performed on the alerts. |
sentinelone-update-uam-alert-status
Updates the status for a group of UAM alerts. Relevant for API version 2.1.
Base Command
sentinelone-update-uam-alert-status
Input
| Argument Name | Description | Required |
|---|---|---|
| status | UAM alert status. Possible values are: New, In progress, Resolved. | Required |
| alert_ids | A comma-separated list of UAM alert IDs. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.UAMAlert.ID | String | The UAM alert ID. |
| SentinelOne.UAMAlert.Updated | Boolean | Whether the status was successfully updated. |
| SentinelOne.UAMAlert.Status | String | Name of the status performed on the alerts. |
sentinelone-export-threat-events
Exports the threat’s events as a JSON File. Relevant for API version 2.1.
Base Command
sentinelone-export-threat-events
Input
| Argument Name | Description | Required |
|---|---|---|
| threat_id | Please provide the Valid Threat ID. Example: 14629133470822878. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Export.Events.ThreatId | String | The threat ID. |
| SentinelOne.Export.Events.Filename | String | The name of the file containing the Events. |
sentinelone-export-full-threat-timeline
Exports the threat’s full timeline as a JSON File. Relevant for API version 2.1.
Base Command
sentinelone-export-full-threat-timeline
Input
| Argument Name | Description | Required |
|---|---|---|
| threat_id | Please provide the Valid Threat ID. Example: 14629133470822878. | Required |
| limit | The max number of timeline entries to return, default is 5000. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SentinelOne.Export.Timeline.ThreatId | String | The threat ID. |
| SentinelOne.Export.Timeline.Filename | String | The name of the file containing the Timeline. |
Configuration parameters
url— Server URL (e.g., https://usea1.sentinelone.net) (required)credentials—api_version— API Version (required)isFetch— Fetch incidentsincidentType— Incident typefetch_type— Fetch incidents from typefetch_uam_alert_type— Fetch incidents from UAM Alert typefetch_time— First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year)fetch_threat_rank— Minimum risk score for importing incidents (0-10), where 0 is low risk and 10 is high risk. Relevant for API version 2.0.fetch_severity— Defines Alert severity to fetch.fetch_incidentStatus— Define which Alerts should be fetched.fetch_threat_incident_statuses— Define which Threats should be fetched.fetch_limit— Fetch limit: The maximum number of threats or alerts to fetchfetch_site_ids— Site IDsblock_site_ids— Block Site IDsinsecure— Trust any certificate (not secure)proxy— Use system proxy settingstoken— API Token (Deprecated)incidentFetchInterval— Incidents Fetch Intervalmirror_direction— Incident Mirroring Directionclose_xsoar_incident— Close Mirrored XSOAR Incident
Commands (78)
-
get-mapping-fieldsReturns the list of fields for an incident type.
-
get-modified-remote-dataGets the list of incidents that were modified since the last update time. Note that this method is here for debugging purposes. The get-modified-remote-data command is used as part of a Mirroring feature, which is available from version 6.1.
-
get-remote-dataGet remote data from a remote incident. This method does not update the current incident, and should be used for debugging purposes.
-
sentinelone-abort-endpoint-scanAbort the endpoint virus scan on provided agent IDs.
-
sentinelone-add-hash-to-blocklistAdd a hash to the blocklist in SentinelOne. Supports scoping by site, group, or account. If Scope not provided, the block will be global.
-
sentinelone-agent-processesDeprecatedDeprecated. Retrieves running processes for a specific agent.
-
sentinelone-broadcast-messageBroadcasts a message to all agents that match the input filters.
-
sentinelone-connect-agentConnects agents to the network.
-
sentinelone-create-bulk-iocAdd bulk list of IoCs to the Threat Intelligence database. Relevant for API version 2.1.
-
sentinelone-create-iocAdd an IoC to the Threat Intelligence database. Relevant for API version 2.1.
-
sentinelone-create-power-queryDeprecatedDeprecated. Use ***sentinelone-get-power-query-results*** instead. Start a Deep Visibility Power query to get back status and potential results (ping afterwards using the queryId if query has not finished). Relevant for API version 2.1.
-
sentinelone-create-queryRuns a Deep Visibility query and returns the queryId. You can use the queryId for all other commands, such as the sentinelone-get-events command.
-
sentinelone-create-star-ruleCreates a custom STAR rule. Relevant for API version 2.1.
-
sentinelone-create-white-list-itemCreates an exclusion item that matches the specified input filter.
-
sentinelone-delete-groupDeletes a group, by the group ID.
-
sentinelone-delete-iocDelete an IOC from the Threat Intelligence database that matches a filter. Relevant for API version 2.1.
-
sentinelone-delete-star-ruleDeletes Custom Detection Rules that match the specified input filter. Relevant for API version 2.1.
-
sentinelone-disable-star-rulesDisable Custom Detection rules that match the specified input filter. Relevant for API version 2.1.
-
sentinelone-disconnect-agentDisconnects agents from the network.
-
sentinelone-download-fetched-fileDownload a file fetched using th sentinelone-fetch-file command to submit the request and the sentinelone-get-activities command to get the download path.
-
sentinelone-enable-star-rulesActivate Custom Detection rules that match the specified input filter. Relevant for API version 2.1.
-
sentinelone-endpoint-fetch-logsGet the Agent and Endpoint logs from Agents for provided agent IDs.
-
sentinelone-expire-siteExpire the site of the given ID.
-
sentinelone-export-full-threat-timelineExports the threat's full timeline as a JSON File. Relevant for API version 2.1.
-
sentinelone-export-threat-eventsExports the threat's events as a JSON File. Relevant for API version 2.1.
-
sentinelone-fetch-fileInvokes a fetch files command against an agent endpoint.
-
sentinelone-fetch-threat-fileFetch a file associated with the threat that matches the filter.
-
sentinelone-get-accountsReturns details of accounts.
-
sentinelone-get-activitiesReturns a list of activities.
-
sentinelone-get-agentReturns the details of an agent according to the agent ID.
-
sentinelone-get-agent-macReturns network interface details for a given Agent ID. This includes MAC address details and interface description.
-
sentinelone-get-alertsGet the list of alerts that matches the filter provided. Relevant for API version 2.1.
-
sentinelone-get-blocklistRetrieve the blocklist ("blacklist" in SentinelOne). You can filter by SHA1 or SHA256 hash. If the `global` flag is `true`, then group_ids, site_ids, and account_ids are ignored.
-
sentinelone-get-dv-query-statusReturns status of a Deep Visibility Query.
-
sentinelone-get-eventsReturns all Deep Visibility events that match the query.
-
sentinelone-get-groupsReturns data for the specified group.
-
sentinelone-get-hashGets the file reputation by a SHA1 hash.
-
sentinelone-get-installed-applicationsGet the installed applications for a specific agent.
-
sentinelone-get-iocsGet the IOCs of a specified account that match the filter. Relevant for API version 2.1.
-
sentinelone-get-power-query-resultsAutomate a power query and return the query results. (The maximum timeout of 300 seconds is allowed.)
-
sentinelone-get-processesReturns a list of Deep Visibility events from query by event type - process.
-
sentinelone-get-remote-script-task-resultsGet a script's result download URL.
-
sentinelone-get-remote-script-task-statusGet remote scripts tasks using a variety of filters.
-
sentinelone-get-service-usersReturns all service users that match the specified filter values.
-
sentinelone-get-siteReturns information about the site, according to the site ID.
-
sentinelone-get-sitesReturns all sites that match the specified criteria.
-
sentinelone-get-star-rulesGet a list of custom detection rules for a given scope. Relevant for API version 2.1.
-
sentinelone-get-threat-notesReturns threat notes.
-
sentinelone-get-threatsReturns threats according to the specified filters.
-
sentinelone-get-white-listLists all exclusion items that match the specified input filter.
-
sentinelone-initiate-endpoint-scanInitiate the endpoint virus scan on provided agent IDs.
-
sentinelone-list-agentsReturns all agents that match the specified criteria.
-
sentinelone-list-installed-singularity-marketplace-applicationsReturns all installed singularity marketplace applications that match the specified filter values.
-
sentinelone-mark-as-threatMarks suspicious threats as threats. Can only be used with API V2.0.
-
sentinelone-mitigate-threatApplies a mitigation action to a group of threats that match the specified input filter.
-
sentinelone-move-agentMoves agents to a new group.
-
sentinelone-ping-power-queryDeprecatedDeprecated. Use ***sentinelone-get-power-query-results*** instead. Ping a Deep Visibility Power query using the queryId argument if results have not returned from an initial Power query or a previous ping. Relevant for API version 2.1.
-
sentinelone-reactivate-siteReactivates an expired site.
-
sentinelone-remote-script-automate-resultsAutomate a remote script's execution cycle and return the script's results.
-
sentinelone-remove-hash-from-blocklistRemove a hash from the global blocklist in SentinelOne.
-
sentinelone-remove-item-from-whitelistRemove an item from the SentinelOne exclusion list.
-
sentinelone-resolve-threatResolves threats using the threat ID. Can only be used with API V2.0.
-
sentinelone-run-powerqueryRun a PowerQuery, where you can pipe one or many search expressions into a set of commands to transform, manipulate, group, and summarize your data.
-
sentinelone-run-remote-scriptRun a remote script that was uploaded to the SentinelOne Script Library.
-
sentinelone-shutdown-agentSends a shutdown command to all agents that match the input filter.
-
sentinelone-threat-analysisReturns threat analysis. Can only be used with API V2.1.
-
sentinelone-threat-download-from-cloudDownload a file associated with the threat from the Cloud (BinaryVault).
-
sentinelone-threat-summaryReturns a dashboard threat summary. Can only be used with API V2.1.
-
sentinelone-uninstall-agentSends an uninstall command to all agents that match the input filter.
-
sentinelone-update-alerts-statusUpdates the incident status to a group of alerts that match the specified input filter. Relevant for API version 2.1.
-
sentinelone-update-alerts-verdictUpdates the analyst verdict to a group of alerts that match the specified input filter. Relevant for API version 2.1.
-
sentinelone-update-star-ruleUpdates a custom STAR rule. Relevant for API version 2.1.
-
sentinelone-update-threats-statusUpdates the incident status to a group of threats that match the specified input filter. Relevant for API version 2.1.
-
sentinelone-update-threats-verdictUpdates the analyst verdict to a group of threats that match the specified input filter. Relevant for API version 2.1.
-
sentinelone-update-uam-alert-statusUpdates the status for a group of UAM alerts. Relevant for API version 2.1.
-
sentinelone-update-uam-alert-verdictUpdates the analyst verdict for a group of UAM alerts. Relevant for API version 2.1.
-
sentinelone-write-threat-noteAdd a threat note to one or more threats. Relevant for API version 2.1.
-
update-remote-systemPushes local changes to the remote system.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 import io import json import requests import traceback from datetime import datetime import zipfile from collections.abc import Callable import urllib3 from dateutil.parser import parse """ IMPORTS """ # Disable insecure warnings urllib3.disable_warnings() """ GLOBALS """ IS_VERSION_2_1: bool OS_COUNT = 4 MIRROR_DIRECTION = { "None": None, "Incoming": "In", "Outgoing": "Out", "Incoming And Outgoing": "Both", } INCIDENT_STATUS = {"in_progress", "resolved", "unresolved"} SENTINELONE_INCIDENT_OUTGOING_ARGS = { "analystVerdict": "Analyst verdict of the incident", "incidentStatus": "Incident status", "uamStatus": "UAM alert status", "uamAnalystVerdict": "UAM alert analyst verdict", } ANALYST_VERDICT = { "True positive": "true_positive", "Suspicious": "suspicious", "False positive": "false_positive", "Undefined": "undefined", } THREAT_STATUS = { "Unresolved": "unresolved", "Resolved": "resolved", "In progress": "in_progress", } UAM_ALERT_STATUS = { "New": "NEW", "In progress": "IN_PROGRESS", "Resolved": "RESOLVED", } # Reverse: API enum value → XSOAR display label (matches singleSelect field values) UAM_ALERT_STATUS_INCOMING = {v: k for k, v in UAM_ALERT_STATUS.items()} UAM_ANALYST_VERDICT = { "False positive - Benign": "FALSE_POSITIVE_BENIGN", "False positive - Benign but suspicious": "FALSE_POSITIVE_BENIGN_BUT_SUSPICIOUS", "False positive - System error": "FALSE_POSITIVE_SYSTEM_ERROR", "False positive - Undefined": "FALSE_POSITIVE_UNDEFINED", "False positive - User error": "FALSE_POSITIVE_USER_ERROR", "True positive - Advanced persistent threat": "TRUE_POSITIVE_ADVANCED_PERSISTENT_THREAT", "True positive - Benign": "TRUE_POSITIVE_BENIGN", "True positive - Benign but suspicious": "TRUE_POSITIVE_BENIGN_BUT_SUSPICIOUS", "True positive - Data exfiltration": "TRUE_POSITIVE_DATA_EXFILTRATION", "True positive - Denial of service": "TRUE_POSITIVE_DENIAL_OF_SERVICE", "True positive - Exploitation tools": "TRUE_POSITIVE_EXPLOITATION_TOOLS", "True positive - Insider threat": "TRUE_POSITIVE_INSIDER_THREAT", "True positive - Malware": "TRUE_POSITIVE_MALWARE", "True positive - Phishing attack": "TRUE_POSITIVE_PHISHING_ATTACK", "True positive - Policy violation": "TRUE_POSITIVE_POLICY_VIOLATION", "True positive - PUA/Adware": "TRUE_POSITIVE_PUA_ADWARE", "True positive - Ransomware": "TRUE_POSITIVE_RANSOMWARE", "True positive - Unauthorized access": "TRUE_POSITIVE_UNAUTHORIZED_ACCESS", "True positive - Undefined": "TRUE_POSITIVE_UNDEFINED", "Undefined": "UNDEFINED", } # Reverse: API enum value → XSOAR display label (matches singleSelect field values) UAM_ANALYST_VERDICT_INCOMING = {v: k for k, v in UAM_ANALYST_VERDICT.items()} UAM_SEVERITY_MAPPING = {"CRITICAL": 4, "HIGH": 3, "MEDIUM": 2, "LOW": 1, "INFO": 0.5} """ HELPER FUNCTIONS """ def get_threats_outputs(threats, rank: int = 0): for threat in threats: threat_rank = int(threat.get("rank") or 0) if IS_VERSION_2_1 or threat_rank >= rank: threat_info = threat.get("threatInfo", {}) if IS_VERSION_2_1 else threat agent_realtime_info = threat.get("agentRealtimeInfo", {}) if IS_VERSION_2_1 else threat entry = { "ID": threat.get("id"), "AgentComputerName": agent_realtime_info.get("agentComputerName"), "CreatedDate": threat_info.get("createdAt"), "SiteID": agent_realtime_info.get("siteId"), "SiteName": agent_realtime_info.get("siteName"), "Classification": threat_info.get("classification"), "ClassificationSource": threat_info.get("classificationSource"), "MitigationStatus": threat_info.get("mitigationStatus"), "AgentID": agent_realtime_info.get("agentId"), "ConfidenceLevel": threat_info.get("confidenceLevel"), "FileContentHash": threat_info.get("sha1") if IS_VERSION_2_1 else threat_info.get("fileContentHash"), "ThreatName": threat_info.get("threatName"), "FileSha256": threat_info.get("fileSha256"), "AgentOsType": agent_realtime_info.get("agentOsType"), "FilePath": threat_info.get("filePath"), "Username": threat_info.get("processUser") if IS_VERSION_2_1 else threat_info.get("username"), "Description": threat_info.get("description"), # Only available in 2.0 "FileDisplayName": threat.get("fileDisplayName"), # Only available in 2.0 "Rank": threat_info.get("rank"), # Only available in 2.0 "MarkedAsBenign": threat_info.get("markedAsBenign"), # Only available in 2.0 "InQuarantine": threat_info.get("inQuarantine"), # Only available in 2.0 "FileMaliciousContent": threat_info.get("fileMaliciousContent"), # Only available in 2.0 } remove_nulls_from_dictionary(entry) yield entry def get_agents_outputs(agents, column_to_display: list | None = None): for agent in agents: entry = { "ID": agent.get("id"), "NetworkStatus": agent.get("networkStatus"), "AgentVersion": agent.get("agentVersion"), "IsDecommissioned": agent.get("isDecommissioned"), "IsActive": agent.get("isActive"), "LastActiveDate": agent.get("lastActiveDate"), "RegisteredAt": agent.get("registeredAt"), "ExternalIP": agent.get("externalIp"), "ThreatCount": agent.get("activeThreats"), "EncryptedApplications": agent.get("encryptedApplications"), "OSName": agent.get("osName"), "ComputerName": agent.get("computerName"), "MachineType": agent.get("machineType"), "Domain": agent.get("domain"), "CreatedAt": agent.get("createdAt"), "SiteName": agent.get("siteName"), "Tags": agent.get("tags"), } for c in set(column_to_display or []).intersection(agent.keys()): entry[c] = agent[c] remove_nulls_from_dictionary(entry) yield entry class Client(BaseClient): def __init__(self, base_url, verify=True, proxy=False, headers=None, block_site_ids=None): super().__init__(base_url, verify, proxy, headers=headers) self.block_site_ids = block_site_ids def remove_hash_from_blocklist_request(self, hash_id) -> dict: body = {"data": {"ids": [hash_id]}} response = self._http_request(method="DELETE", url_suffix="restrictions", json_data=body) return response.get("data") or {} def add_hash_to_blocklist_request(self, value, sha256Value, os_type, description="", source="") -> dict: """ Only supports adding to the Global block list """ # We do not use the assign_params function, because if these values are empty or None, we still want them # sent to the server value = value or "" sha256Value = sha256Value or "" data = { "value": value, "sha256Value": sha256Value, "source": source, "osType": os_type, "type": "black_hash", "description": description, } filt = {"tenant": True} body = {"data": data, "filter": filt} response = self._http_request(method="POST", url_suffix="restrictions", json_data=body) return response.get("data") or {} def add_hash_to_blocklists_request( self, value, sha256Value, os_type, site_ids="", description="", source="", group_ids="", account_ids="", ) -> dict: """ Supports adding hashes to multiple scoped site blocklists """ filt = {} if site_ids: filt["siteIds"] = site_ids if group_ids: filt["groupIds"] = group_ids if account_ids: filt["accountIds"] = account_ids # If no scoping fields, set tenant True for global if not filt: filt["tenant"] = True value = value or "" sha256Value = sha256Value or "" data = { "value": value, "sha256Value": sha256Value, "source": source, "osType": os_type, "type": "black_hash", "description": description, } body = {"data": data, "filter": filt} demisto.debug(f"Adding hash to blocklist with filter: {filt}") response = self._http_request(method="POST", url_suffix="restrictions", json_data=body, ok_codes=[200]) return response.get("data") or {} def get_blocklist_request( self, tenant: bool, group_ids: str = None, site_ids: str = None, account_ids: str = None, skip: int = None, limit: int = None, os_type: str = None, sort_by: str = None, sort_order: str = None, value_contains: str = None, ) -> list[dict]: """ We use the `value_contains` instead of `value` parameter because in our testing (API 2.1) the `value` parameter is case sensitive. So if an analyst put in the hash with uppercase entries and it's searched using lowercase, this search will not find it """ params = assign_params( tenant=tenant, groupIds=group_ids, siteIds=site_ids, accountIds=account_ids, skip=skip, limit=limit, osTypes=os_type, sortBy=sort_by, sortOrder=sort_order, value__contains=value_contains, ) response = self._http_request(method="GET", url_suffix="restrictions", params=params) return response.get("data", []) def fetch_file_request(self, agent_id, file_path, password) -> dict: body = {"data": {"password": password, "files": [file_path]}} response = self._http_request( method="POST", url_suffix=f"agents/{agent_id}/actions/fetch-files", json_data=body, ) return response.get("data", {}) def download_fetched_file_request(self, agent_id, activity_id) -> bytes: return self._http_request( method="GET", url_suffix=f"agents/{agent_id}/uploads/{activity_id}", resp_type="content", ) def get_activities_request( self, created_after: str = None, user_emails: str = None, group_ids=None, created_until: str = None, activities_ids=None, include_hidden: str = None, created_before: str = None, threats_ids=None, activity_types=None, user_ids=None, created_from: str = None, created_between: str = None, agent_ids: str = None, sort_by: str = None, sort_order: str = None, limit: str = "50", ): params = assign_params( created_at__gt=created_after, userEmails=user_emails, groupIds=argToList(group_ids), created_at__lte=created_until, ids=argToList(activities_ids), includeHidden=include_hidden, created_at__lt=created_before, threatIds=argToList(threats_ids), activityTypes=argToList(activity_types), userIds=argToList(user_ids), created_at__gte=created_from, createdAt_between=created_between, agentIds=argToList(agent_ids), sortBy=sort_by, sortOrder=sort_order, limit=int(limit), ) response = self._http_request(method="GET", url_suffix="activities", params=params) return response.get("data", {}) def get_threats_request( self, content_hash=None, mitigation_status=None, created_before=None, created_after=None, created_until=None, created_from=None, updated_from=None, resolved="false", display_name=None, query=None, threat_ids=None, limit=20, classifications=None, site_ids=None, rank=None, include_resolved_param=True, incident_statuses=None, ): keys_to_ignore = ["displayName__like" if IS_VERSION_2_1 else "displayName"] created_before_parsed = None created_after_parsed = None created_until_parsed = None created_from_parsed = None updated_from_parsed = None if created_before: created_before_parsed = dateparser.parse(created_before, settings={"TIMEZONE": "UTC"}) if created_after: created_after_parsed = dateparser.parse(created_after, settings={"TIMEZONE": "UTC"}) if created_until: created_until_parsed = dateparser.parse(created_until, settings={"TIMEZONE": "UTC"}) if created_from: created_from_parsed = dateparser.parse(created_from, settings={"TIMEZONE": "UTC"}) if updated_from: updated_from_parsed = dateparser.parse(updated_from, settings={"TIMEZONE": "UTC"}) params = assign_params( contentHashes=argToList(content_hash), mitigationStatuses=argToList(mitigation_status), createdAt__lt=created_before_parsed, createdAt__gt=created_after_parsed, createdAt__lte=created_until_parsed, createdAt__gte=created_from_parsed, updatedAt__gte=updated_from_parsed, resolved=argToBoolean(resolved) if argToBoolean(include_resolved_param) else None, displayName__like=display_name, displayName=display_name, query=query, ids=threat_ids, limit=int(limit), classifications=argToList(classifications), siteIds=site_ids, rank=int(rank) if rank else None, keys_to_ignore=keys_to_ignore, incidentStatuses=incident_statuses.lower() if incident_statuses else None, ) response = self._http_request(method="GET", url_suffix="threats", params=params, ok_codes=[200]) return response.get("data", {}) def mark_as_threat_request(self, threat_ids, target_scope): endpoint_url = "threats/mark-as-threat" payload = {"filter": {"ids": threat_ids}, "data": {"targetScope": target_scope}} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def mitigate_threat_request(self, threat_ids, action): endpoint_url = f"threats/mitigate/{action}" payload = {"filter": {"ids": threat_ids}} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def resolve_threat_request(self, threat_ids): endpoint_url = "threats/mark-as-resolved" payload = {"filter": {"ids": threat_ids}} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def get_groups_request(self, params: dict): response = self._http_request(method="GET", url_suffix="groups", params=params) return response.get("data", {}) def delete_group_request(self, group_id=None): endpoint_url = f"groups/{group_id}" response = self._http_request(method="DELETE", url_suffix=endpoint_url) return response.get("data", {}) def get_sites_request(self, params): response = self._http_request(method="GET", url_suffix="sites", params=params) return response.get("data", {}) def move_agent_request(self, group_id, agents_id): endpoint_url = f"groups/{group_id}/move-agents" payload = {"filter": {"ids": agents_id}} response = self._http_request(method="PUT", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def get_agent_processes_request(self, agents_ids=None): """ [DEPRECATED BY SentinelOne] Returns empty array. To get processes of an Agent, see Applications. """ endpoint_url = "agents/processes" params = {"ids": agents_ids} response = self._http_request(method="GET", url_suffix=endpoint_url, params=params) return response.get("data", {}) def get_site_request(self, site_id): endpoint_url = f"sites/{site_id}" response = self._http_request(method="GET", url_suffix=endpoint_url) return response.get("data", {}) def reactivate_site_request(self, site_id, expiration, unlimited): endpoint_url = f"sites/{site_id}/reactivate" payload = {"data": {"expiration": expiration, "unlimited": unlimited}} response = self._http_request(method="PUT", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def get_threat_summary_request(self, site_ids=None, group_ids=None): endpoint_url = "private/threats/summary" params = {"siteIds": site_ids, "groupIds": group_ids} response = self._http_request(method="GET", url_suffix=endpoint_url, params=params) return response.get("data", {}) def get_threat_analysis_request(self, threat_id): endpoint_url = f"private/threats/{threat_id}/analysis" response = self._http_request(method="GET", url_suffix=endpoint_url) return response.get("data", {}) def list_agents_request(self, params: dict): response = self._http_request(method="GET", url_suffix="agents", params=params) return response.get("data", {}) def get_agent_request(self, agent_ids): # Accepts a comma-separated string if isinstance(agent_ids, list): agent_ids = ",".join(agent_ids) params = {"ids": agent_ids} response = self._http_request(method="GET", url_suffix="agents", params=params) return response.get("data", {}) def connect_to_network_request(self, agent_ids): endpoint_url = "agents/actions/connect" payload = {"filter": {"ids": agent_ids}} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def disconnect_from_network_request(self, agents_id): endpoint_url = "agents/actions/disconnect" payload = {"filter": {"ids": agents_id}} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def broadcast_message_request(self, message, filters): endpoint_url = "agents/actions/broadcast" payload = {"data": {"message": message}, "filter": filters} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def uninstall_agent_request(self, query, agent_id=None, group_id=None): endpoint_url = "agents/actions/uninstall" payload = { "filter": assign_params( query=query, ids=agent_id, groupIds=group_id, ) } response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def shutdown_agents_request(self, query, agent_id=None, group_id=None): endpoint_url = "agents/actions/shutdown" payload = {"filter": assign_params(query=query, ids=agent_id, groupIds=group_id)} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def create_query_request(self, query, from_date, to_date): endpoint_url = "dv/init-query" payload = {"query": query, "fromDate": from_date, "toDate": to_date} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}).get("queryId") def create_status_request(self, query_id=None): endpoint_url = "dv/query-status" params = {"query_id": query_id} response = self._http_request(method="GET", url_suffix=endpoint_url, params=params) return response.get("data", {}) def get_events_request(self, query_id=None, limit=None, cursor=None): endpoint_url = "dv/events" params = {"query_id": query_id, "cursor": cursor, "limit": limit} response = self._http_request(method="GET", url_suffix=endpoint_url, params=params) events = response.get("data", {}) pagination = response.get("pagination") return events, pagination def get_processes_request(self, query_id=None, limit=None): endpoint_url = "dv/events/process" params = {"query_id": query_id, "limit": limit} response = self._http_request(method="GET", url_suffix=endpoint_url, params=params) return response.get("data", {}) def get_hash_reputation_request(self, hash_): """ [DEPRECATED by S1] IN 2.1 """ endpoint_url = f"hashes/{hash_}/reputation" response = self._http_request(method="GET", url_suffix=endpoint_url) return response def get_hash_verdict_request(self, hash_): endpoint_url = f"hashes/{hash_}/verdict" response = self._http_request(method="GET", url_suffix=endpoint_url) return response def get_hash_classification_request(self, hash_): """ [DEPRECATED by S1] IN BOTH 2.0 and 2.1 """ endpoint_url = f"hashes/{hash_}/classification" response = self._http_request(method="GET", url_suffix=endpoint_url) return response def get_exclusions_request( self, item_ids=None, os_types=None, exclusion_type: str = None, limit: int = 10, value_contains: str | None = None, ok_codes: list = [200], include_children: bool | None = None, include_parents: bool | None = None, ): """ When includeChildren and includeParents are set to True in API request- it will return all items in the exclusion list. If left blank they default to false and the API call will return a subset of the exclusion list. """ endpoint_url = "exclusions" params = assign_params( ids=item_ids, osTypes=os_types, type=exclusion_type, value__contains=value_contains, includeChildren=include_children, includeParents=include_parents, limit=limit, ) response = self._http_request(method="GET", url_suffix=endpoint_url, params=params, ok_codes=ok_codes) return response.get("data", {}) def create_exclusion_item_request( self, exclusion_type, exclusion_value, os_type, description=None, exclusion_mode=None, path_exclusion_type=None, group_ids=None, site_ids=None, ): if group_ids != []: demisto.debug(f"Group IDs: {group_ids}") payload = { "filter": {"groupIds": group_ids, "siteIds": site_ids}, "data": assign_params( type=exclusion_type, value=exclusion_value, osType=os_type, description=description, mode=exclusion_mode, pathExclusionType=path_exclusion_type, ), } else: payload = { "filter": {"siteIds": site_ids}, "data": assign_params( type=exclusion_type, value=exclusion_value, osType=os_type, description=description, mode=exclusion_mode, pathExclusionType=path_exclusion_type, ), } response = self._http_request(method="POST", url_suffix="exclusions", json_data=payload) if "data" in response: return response.get("data")[0] return {} def remove_exclusion_item_request(self, item_id) -> dict: body = {"data": {"ids": [item_id]}} response = self._http_request(method="DELETE", url_suffix="exclusions", json_data=body, ok_codes=[200]) return response.get("data") or {} def update_threat_analyst_verdict_request(self, threat_ids, action): endpoint_url = "threats/analyst-verdict" payload = { "data": {"analystVerdict": action}, "filter": {"ids": threat_ids, "tenant": "true"}, } response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def update_alert_analyst_verdict_request(self, alert_ids, action): endpoint_url = "cloud-detection/alerts/analyst-verdict" payload = { "data": {"analystVerdict": action}, "filter": {"ids": alert_ids, "tenant": "true"}, } response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def _create_filter_dict(self, filter_dict): return {filter_key: filter_value for filter_key, filter_value in filter_dict.items() if filter_value} def create_star_rule_request( self, name, description, query, query_type, rule_severity, account_ids, group_ids, site_ids, expiration_mode, expiration_date, network_quarantine, treatAsThreat, query_lang, ): endpoint_url = "cloud-detection/rules" filter_dict = { "siteIds": site_ids, "groupIds": group_ids, "accountIds": account_ids, } filter_dict = self._create_filter_dict(filter_dict) payload = { "data": { "expiration": expiration_date, "networkQuarantine": network_quarantine, "status": "Draft", "queryType": query_type, "expirationMode": expiration_mode, "severity": rule_severity, "treatAsThreat": treatAsThreat, "s1ql": query, "name": name, "description": description, }, "filter": {"tenant": "true", **filter_dict}, } if query_lang: payload["data"]["queryLang"] = query_lang response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def get_star_rule_request(self, params): endpoint_url = "cloud-detection/rules" response = self._http_request(method="GET", url_suffix=endpoint_url, params=params) return response.get("data", []) def update_star_rule_request( self, rule_id, name, description, query, query_type, rule_severity, account_ids, group_ids, site_ids, expiration_mode, expiration_date, network_quarantine, treatAsThreat, query_lang, ): endpoint_url = f"cloud-detection/rules/{rule_id}" filter_dict = { "siteIds": site_ids, "groupIds": group_ids, "accountIds": account_ids, } filter_dict = self._create_filter_dict(filter_dict) payload = { "data": { "expiration": expiration_date, "networkQuarantine": network_quarantine, "status": "Draft", "queryType": query_type, "expirationMode": expiration_mode, "severity": rule_severity, "treatAsThreat": treatAsThreat, "s1ql": query, "name": name, "description": description, }, "filter": {"tenant": "true", **filter_dict}, } if query_lang: payload["data"]["queryLang"] = query_lang response = self._http_request(method="PUT", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def enable_star_rule_request(self, rule_ids): endpoint_url = "cloud-detection/rules/enable" payload = {"filter": {"ids": rule_ids}} response = self._http_request(method="PUT", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def disable_star_rule_request(self, rule_ids): endpoint_url = "cloud-detection/rules/disable" payload = {"filter": {"ids": rule_ids}} response = self._http_request(method="PUT", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def delete_star_rule_request(self, rule_ids): endpoint_url = "cloud-detection/rules" payload = {"filter": {"ids": rule_ids}} response = self._http_request(method="DELETE", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def write_threat_note_request(self, threat_ids, note): endpoint_url = "threats/notes" payload = {"data": {"text": note}, "filter": {"ids": threat_ids}} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def get_threat_notes_request(self, threatid): endpoint_url = f"threats/{threatid}/notes" response = self._http_request(method="GET", url_suffix=endpoint_url) return response.get("data", {}) def create_ioc_request( self, name, source, ioc_type, method, validUntil, value, account_ids, externalId, description, ): endpoint_url = "threat-intelligence/iocs" payload = { "filter": {"accountIds": account_ids}, "data": [ { "source": source, "type": ioc_type, "method": method, "validUntil": validUntil, "name": name, "value": value, "externalId": externalId, "description": description, } ], } response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def create_bulk_ioc_request(self, iocs_from_file, account_ids): endpoint_url = "threat-intelligence/iocs" required_fields = {"source", "type", "value", "method", "validUntil", "name"} normalized_iocs: list[dict] = [] for idx, ioc in enumerate(iocs_from_file): if not isinstance(ioc, dict): raise DemistoException(f"IOC at index {idx} is not an object.") missing = [field for field in required_fields if field not in ioc or ioc.get(field) in (None, "")] if missing: raise DemistoException(f"IOC at index {idx} missing required field(s): {', '.join(missing)}") # Normalize to API expectations (upper-case TYPE and METHOD) ioc_type = ioc.get("type", "").strip().upper() method = ioc.get("method", "").strip().upper() # Build the outbound object preserving optional fields if present outbound = { "source": ioc.get("source", ""), "type": ioc_type, "method": method, "value": ioc.get("value", ""), "validUntil": ioc.get("validUntil", ""), "name": ioc.get("name", ""), } # Optional fields as per current single-IOC code for opt in ("externalId", "description"): if opt in ioc and ioc.get(opt) not in (None, ""): outbound[opt] = ioc.get(opt) normalized_iocs.append(outbound) payload = { "filter": {"accountIds": account_ids}, "data": normalized_iocs, } response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def run_powerquery_request( self, sdl_url, sdl_api_key, query, start_time, end_time, priority, recurring, team_emails, ): if not sdl_url.startswith("https://"): raise DemistoException("Invalid URL: must start with https://") headers = { "Authorization": f"Bearer {sdl_api_key}", "Content-Type": "application/json", } payload = {"query": query} optional_params = { "startTime": start_time, "endTime": end_time, "priority": priority, "recurring": recurring, "teamEmails": team_emails, } payload.update( {param_name: param_value for param_name, param_value in optional_params.items() if param_value not in [None, ""]} ) return self._http_request( method="POST", full_url=f"{sdl_url.rstrip('/')}/api/powerQuery", headers=headers, json_data=payload, ) def delete_ioc_request(self, account_ids, uuids): endpoint_url = "threat-intelligence/iocs" payload = {"filter": {"accountIds": account_ids, "uuids": uuids}} response = self._http_request(method="DELETE", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def get_iocs_request(self, params): endpoint_url = "threat-intelligence/iocs" response = self._http_request(method="GET", url_suffix=endpoint_url, params=params) data = response.get("data") pagination = response.get("pagination") return data, pagination def get_accounts_request(self, account_id: str = None): response = self._http_request( method="GET", url_suffix=f"accounts/{account_id}" if account_id else "accounts", ) return response.get("data", {}) def create_power_query_request(self, limit, query, from_date, to_date): endpoint_url = "dv/events/pq" payload = { "limit": limit, "query": query, "toDate": to_date, "fromDate": from_date, } response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def ping_power_query_request(self, params): endpoint_url = "dv/events/pq-ping" response = self._http_request(method="GET", url_suffix=endpoint_url, params=params) return response.get("data", []) def update_threat_status_request(self, threat_ids, status): endpoint_url = "threats/incident" payload = {"data": {"incidentStatus": status}, "filter": {"ids": threat_ids}} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def update_alert_status_request(self, alert_ids, status): endpoint_url = "cloud-detection/alerts/incident" payload = {"data": {"incidentStatus": status}, "filter": {"ids": alert_ids}} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def expire_site_request(self, site_id): endpoint_url = f"sites/{site_id}/expire-now" response = self._http_request(method="POST", url_suffix=endpoint_url) return response.get("data", {}) def fetch_threat_file_request(self, password, threat_ids): endpoint_url = "threats/fetch-file" payload = {"data": {"password": password}, "filter": {"ids": threat_ids}} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def download_url_request(self, threat_id): endpoint_url = f"threats/{threat_id}/timeline" query_params = assign_params( skip=0, limit=30, sortOrder="desc", ) response = self._http_request( method="GET", url_suffix=endpoint_url, params=query_params, retries=3, backoff_factor=5, status_list_to_retry=[200, 202], ) urls_found = [] data = [] if response["data"] is not None: data = response["data"] for i in data: if i["data"].get("downloadUrl") is not None: urls_found.append(i["data"].get("downloadUrl")) for item in urls_found: if item[:8] == "/agents/": return item return "-1" def get_alerts_request(self, query_params): endpoint_url = "cloud-detection/alerts" response = self._http_request(method="GET", url_suffix=endpoint_url, params=query_params) alerts = response.get("data", {}) pagination = response.get("pagination") return alerts, pagination def get_uam_alerts_graphql_req(self, timestamp, view_type, limit, cursor=None, filter_by_updated_at=False): graphql_endpoint = "unifiedalerts/graphql" after_clause = f'after: "{cursor}"' if cursor else "after: null" demisto.debug(after_clause) # Select filter field based on use case filter_field = "updatedAt" if filter_by_updated_at else "createdAt" sort_field = "updatedAt" if filter_by_updated_at else "createdAt" use_case = "mirroring (updatedAt)" if filter_by_updated_at else "polling (createdAt)" demisto.debug(f"Fetching UAM alerts for {use_case}, timestamp: {timestamp}") query = f""" query Alerts {{ alerts( first: {limit} viewType: {view_type} {after_clause} sort: {{ by: "{sort_field}", order: ASC }} filters: [ {{ fieldId: "{filter_field}" dateTimeRange: {{ start: {timestamp} }} }} ] ) {{ totalCount edges {{ node {{ id name description severity status result analystVerdict attackSurfaces classification confidenceLevel externalId createdAt updatedAt firstSeenAt lastSeenAt detectedAt noteExists dataSources storylineId ticketId fileName fileHash analytics {{ uid name type typeValue category }} assignee {{ userId fullName email }} detectionSource {{ product vendor engine }} asset {{ id name agentUuid agentVersion assetTypeClassifier category subcategory type connectivityToConsole osType osVersion pendingReboot lastLoggedInUser policy }} detectionTime {{ asset {{ agentVersion consoleIpAddress domain ipV4 ipV6 lastLoggedInUser osName osRevision osType policy subscriptionTime }} attacker {{ host ip }} cloud {{ accountId cloudProvider image instanceId instanceSize location network providerDetails {{ ... on DetectionAws {{ accountId imageId instanceId instanceType region role securityGroups subnetIds tags vpcId }} ... on DetectionAzure {{ imageId instanceId instanceType region resourceGroup subscriptionId tags }} ... on DetectionGcp {{ imageId instanceId instanceType projectId serviceAccount tags vpcId zone }} }} tags }} kubernetes {{ clusterName namespaceName nodeName podName containerId containerImageName containerLabels containerName containerNetworkStatus controllerName controllerType controllerLabels namespaceLabels nodeLabels podLabels }} scope {{ accountId accountName groupName siteName }} targetUser {{ name emailAddress domain }} }} process {{ cmdLine parentName file {{ certSubject md5 name path sha1 sha256 }} }} realTime {{ scope {{ account {{ id name }} group {{ id name }} site {{ id name }} }} }} }} }} pageInfo {{ hasNextPage hasPreviousPage startCursor endCursor }} }} }} """ # demisto.debug(f"S1 UAM GraphQL Request Query: {query}") response = self._http_request(method="POST", url_suffix=graphql_endpoint, json_data={"query": query}) alerts_data = response.get("data", {}).get("alerts", {}) uam_alerts = alerts_data.get("edges", []) page_info = alerts_data.get("pageInfo", {}) return uam_alerts, page_info def update_uam_alert_status_request(self, alert_id: str, status: str) -> dict: """ Update UAM alert status via GraphQL mutation Args: alert_id: The UAM alert ID status: The status value (NEW, IN_PROGRESS, RESOLVED) Returns: Dictionary containing the mutation response """ graphql_endpoint = "unifiedalerts/graphql" demisto.debug(f"UAM GraphQL Mutation - Update status for alert ID: {alert_id}, status: {status}") query = """ mutation AlertTriggerActions($id: String!, $status: Status!) { alertTriggerActions( filter: { or: [ { and: [ { fieldId: "id", stringEqual: { value: $id } } ] } ] }, actions: [ { id: "S1/alert/statusUpdate", payload: { status: { value: $status } } } ] ) { __typename ... on ActionsTriggered { actions { actionId alertCount success { id } failure { id errorType errorMessage } skip { id } } } } } """ variables = {"id": alert_id, "status": status} response = self._http_request( method="POST", url_suffix=graphql_endpoint, json_data={"query": query, "variables": variables}, ) demisto.debug(f"UAM GraphQL Mutation - Update status response: {response}") return (response or {}).get("data", {}) def update_uam_alert_analyst_verdict_request(self, alert_id: str, analyst_verdict: str) -> dict: """ Update UAM alert analyst verdict via GraphQL mutation Args: alert_id: The UAM alert ID analyst_verdict: The analyst verdict value (e.g., TRUE_POSITIVE_MALWARE) Returns: Dictionary containing the mutation response """ graphql_endpoint = "unifiedalerts/graphql" query = """ mutation AlertTriggerActions($id: String!, $verdict: AnalystVerdict!) { alertTriggerActions( filter: { or: [ { and: [ { fieldId: "id", stringEqual: { value: $id } } ] } ] }, actions: [ { id: "S1/alert/analystVerdictUpdate", payload: { analystVerdict: { value: $verdict } } } ] ) { __typename ... on ActionsTriggered { actions { actionId alertCount success { id } failure { id errorType errorMessage } skip { id } } } } } """ # 2. Map values into the variables dictionary variables = {"id": alert_id, "verdict": analyst_verdict} # 3. Send the request with query and variables separated demisto.debug(f"S1 UAM GraphQL Mutation - Update Analyst Verdict for ID: {alert_id}") response = self._http_request( method="POST", url_suffix=graphql_endpoint, json_data={"query": query, "variables": variables}, ) return (response or {}).get("data", {}) def get_uam_alert_by_id(self, alert_id: str) -> dict: """ Fetch a single UAM alert by ID using GraphQL query. Args: alert_id: The UAM alert UUID Returns: Dictionary containing the alert data """ graphql_endpoint = "unifiedalerts/graphql" query = """ query Alert($id: ID!) { alert(id: $id) { id name description severity status result analystVerdict attackSurfaces classification confidenceLevel externalId createdAt updatedAt firstSeenAt lastSeenAt detectedAt noteExists dataSources storylineId ticketId fileName fileHash analytics { uid name type typeValue category } assignee { userId fullName email } detectionSource { product vendor engine } asset { id name agentUuid agentVersion assetTypeClassifier category subcategory type connectivityToConsole osType osVersion pendingReboot lastLoggedInUser policy } detectionTime { asset { agentVersion consoleIpAddress domain ipV4 ipV6 lastLoggedInUser osName osRevision osType policy subscriptionTime } scope { accountId accountName groupName siteName } } realTime { scope { account { id name } group { id name } site { id name } } } } } """ demisto.debug(f"S1 UAM GraphQL Query - Fetch Alert by ID {alert_id}") response = self._http_request( method="POST", url_suffix=graphql_endpoint, json_data={"query": query, "variables": {"id": alert_id}}, ) alert_data = response.get("data", {}).get("alert") if alert_data: # Wrap in node structure for consistency with _is_uam_alert and set_xsoar_incident_entries return {"node": alert_data} return {} def download_threat_file_request(self, endpoint_url): return self._http_request(method="GET", url_suffix=endpoint_url, resp_type="content") def download_threat_cloud_file(self, url): return self._http_request( method="GET", full_url=url, resp_type="content", headers={"Accept": "application/json"}, ) def get_installed_applications_request(self, query_params): endpoint_url = "agents/applications" response = self._http_request(method="GET", url_suffix=endpoint_url, params=query_params) return response.get("data", []) def initiate_endpoint_scan_request(self, agent_ids): endpoint_url = "agents/actions/initiate-scan" payload = {"filter": {"ids": agent_ids}, "data": {}} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def abort_endpoint_scan_request(self, agent_ids): endpoint_url = "agents/actions/abort-scan" payload = {"filter": {"ids": agent_ids}, "data": {}} response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def endpoint_fetch_logs_request(self, agent_ids, agent_logs, customer_facing_logs, platform_logs): endpoint_url = "agents/actions/fetch-logs" payload = { "filter": {"ids": agent_ids}, "data": { "agentLogs": agent_logs, "customerFacingLogs": customer_facing_logs, "platformLogs": platform_logs, }, } response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def get_s1_threats_information(self, threat_ids: str) -> dict: response = self._http_request(method="GET", url_suffix=f"threats?ids={threat_ids}") return response.get("data", []) def get_power_query_request( self, account_ids: list, site_ids: list, query: str, from_date: str, to_date: str, limit: Any, ): endpoint_url = "dv/events/pq" payload = assign_params( accountIds=account_ids, siteIds=site_ids, limit=limit, query=query, toDate=to_date, fromDate=from_date, ) response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def get_ping_power_query_request(self, query_id: str): endpoint_url = "dv/events/pq-ping" params = assign_params(queryId=query_id) response = self._http_request(method="GET", url_suffix=endpoint_url, params=params) return response.get("data", {}) def run_remote_script_request( self, account_ids: list, script_id: str, output_destination: str, task_description: str, output_directory: str, agent_ids: list, singularity_xdr_keyword: str, singularity_xdr_url: str, api_key: str, input_params: str, password: str, script_runtime_timeout_seconds: int, requires_approval: bool, ) -> dict: endpoint_url = "remote-scripts/execute" payload = { "filter": {"accountIds": account_ids, "ids": agent_ids}, "data": { "taskDescription": task_description, "outputDestination": output_destination, "scriptId": script_id, "outputDirectory": output_directory, "singularityxdrKeyword": singularity_xdr_keyword, "singularityxdrUrl": singularity_xdr_url, "apiKey": api_key, "inputParams": input_params, "password": password, "scriptRuntimeTimeoutSeconds": script_runtime_timeout_seconds, "requiresApproval": requires_approval, }, } payload["data"] = self.remove_empty_fields(payload.get("data", {})) response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}) def get_remote_script_status_request( self, account_ids: str = None, computer_name_contains: str = None, count_only: str = None, created_at_gt: str = None, created_at_gte: str = None, created_at_lt: str = None, created_at_lte: str = None, cursor: str = None, description_contains: str = None, detailed_status_contains: str = None, group_ids: str = None, ids: str = None, initiated_by_contains: str = None, limit: str = "50", parent_task_id: str = None, parent_task_id_in: str = None, query: str = None, site_ids: str = None, status: str = None, tenant: str = None, updated_at_gt: str = None, updated_at_gte: str = None, updated_at_lt: str = None, updated_at_lte: str = None, uuid_contains: str = None, ): params = assign_params( accountIds=argToList(account_ids), computerName__contains=computer_name_contains, countOnly=count_only, createdAt__gt=created_at_gt, createdAt__gte=created_at_gte, createdAt__lt=created_at_lt, createdAt__lte=created_at_lte, cursor=cursor, description__contains=description_contains, detailedStatus__contains=argToList(detailed_status_contains), groupIds=argToList(group_ids), ids=argToList(ids), initiatedBy__contains=argToList(initiated_by_contains), limit=int(limit), parentTaskId=parent_task_id, parentTaskId__in=argToList(parent_task_id_in), query=query, siteIds=argToList(site_ids), status=status, tenant=tenant, updatedAt__gt=updated_at_gt, updatedAt__gte=updated_at_gte, updatedAt__lt=updated_at_lt, updatedAt__lte=updated_at_lte, uuid__contains=uuid_contains, ) response = self._http_request(method="GET", url_suffix="remote-scripts/status", params=params) return response.get("data", {}) def get_remote_script_results_request(self, computer_names: list, task_ids: list): endpoint_url = "remote-scripts/fetch-files" payload = { "data": { "taskIds": task_ids, "computerNames": computer_names, } } payload["data"] = self.remove_empty_fields(payload.get("data", {})) response = self._http_request(method="POST", url_suffix=endpoint_url, json_data=payload) return response.get("data", {}).get("download_links", []) def list_installed_applications_request(self, params: dict): response = self._http_request( method="GET", url_suffix="singularity-marketplace/applications", params=params, ) return response.get("data", []), response.get("pagination", {}) def get_service_users_request(self, params: dict): response = self._http_request(method="GET", url_suffix="service-users", params=params) return response.get("data", []), response.get("pagination", {}) def remove_empty_fields(self, json_payload): """ Removes empty fields from a JSON payload and returns a new JSON object with non-empty fields. Parameters: - json_payload (dict): The input JSON payload. Returns: - dict: A new JSON object containing only non-empty fields. """ # Returning updated dictionary with non-empty fields return {key: value for key, value in json_payload.items() if str(value)} def threat_download_from_cloud_request(self, threat_id: str) -> dict[str, str]: """ Returns Information to download the file for the given threat_id from the cloud Parameters: - threat_id (str): The threat_id to download the file for. Returns: - dict: A new JSON object containing information to download the file or a message, if it isn't available """ endpoint_url = f"threats/{threat_id}/download-from-cloud" response = self._http_request(method="GET", url_suffix=endpoint_url) data: dict = response.get("data", {}) if "errors" in response: data["message"] = response.get("errors", [{}])[0].get("detail", "An Unknown Error occurred") return data def threat_export_raw_threat_timeline(self, threat_id: str, max_entries: int = 5000) -> list[dict[str, str]]: endpoint_url = f"threats/{threat_id}/timeline" query_params = assign_params( skip=0, limit=1000, sortOrder="desc", ) response = self._http_request( method="GET", url_suffix=endpoint_url, params=query_params, retries=3, backoff_factor=5, ) timeline: list = response.get("data", []) cursor = dict_safe_get(response, ["pagination", "nextCursor"]) while cursor and len(timeline) < max_entries: query_params["cursor"] = cursor response = self._http_request( method="GET", url_suffix=endpoint_url, params=query_params, retries=3, backoff_factor=5, ) timeline.extend(response.get("data", [])) cursor = dict_safe_get(response, ["pagination", "nextCursor"]) return timeline[:max_entries] def threat_export_events(self, threat_id: str) -> dict[str, Any]: endpoint_url = f"export/threats/{threat_id}/explore/events" # The format parameter is mandatory. Options are CSV and JSON. query_params = assign_params(format="json", eventTypes="events") return self._http_request( method="GET", url_suffix=endpoint_url, params=query_params, ) """ COMMANDS + REQUESTS FUNCTIONS """ def test_module(client: Client, is_fetch: bool, first_fetch: str = None): """ Performs basic get request to verify connection and creds. """ if is_fetch: first_fetch_date = dateparser.parse(first_fetch, settings={"TIMEZONE": "UTC"}) # type: ignore assert first_fetch_date is not None, f"could not parse {first_fetch}" last_fetch = date_to_timestamp(first_fetch_date) last_fetch_date_string = timestamp_to_datestring(last_fetch, "%Y-%m-%dT%H:%M:%S.%fZ") client.get_threats_request(limit=1, created_after=last_fetch_date_string) else: client._http_request(method="GET", url_suffix="activities/types") return "ok" def get_activities_command(client: Client, args: dict) -> CommandResults: """ Get a list of activities. """ context_entries = [] headers = [ "ID", "PrimaryDescription", "Data", "UserID", "CreatedAt", "ThreatID", "UpdatedAt", ] activities = client.get_activities_request(**args) for activity in activities: context_entries.append( { "Hash": activity.get("hash"), "ActivityType": activity.get("activityType"), "OsFamily": activity.get("osFamily"), "PrimaryDescription": activity.get("primaryDescription"), "Comments": activity.get("comments"), "AgentUpdatedVersion": activity.get("agentUpdatedVersion"), "UserID": activity.get("userId"), "ID": activity.get("id"), "Data": activity.get("data"), "CreatedAt": activity.get("createdAt"), "SecondaryDescription": activity.get("secondaryDescription"), "ThreatID": activity.get("threatId"), "GroupID": activity.get("groupId"), "UpdatedAt": activity.get("updatedAt"), "Description": activity.get("description"), "AgentID": activity.get("agentId"), "SiteID": activity.get("siteId"), } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One Activities", context_entries, headers=headers, removeNull=True, headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.Activity", outputs_key_field="ID", outputs=context_entries, raw_response=activities, ) def get_groups_command(client: Client, args: dict) -> CommandResults: """ Gets the group data. """ headers = ["id", "name", "type", "creator", "creatorId", "createdAt", "rank"] query_params = assign_params( type=args.get("group_type"), id=args.get("id"), groupIds=argToList(args.get("group_ids")), isDefault=args.get("is_default"), name=args.get("name"), query=args.get("query"), rank=args.get("rank"), limit=int(args.get("limit", 50)), ) groups = client.get_groups_request(query_params) return CommandResults( readable_output=tableToMarkdown( "Sentinel One Groups", groups, headers, headerTransform=pascalToSpace, removeNull=True, ), outputs_prefix="SentinelOne.Group", outputs_key_field="ID", outputs=groups, raw_response=groups, ) def delete_group(client: Client, args: dict) -> CommandResults: """ Deletes a group by ID. """ group_id = args.get("group_id") response = client.delete_group_request(group_id) if response.get("success"): success = f"Group: {group_id} was deleted successfully" success = f"The deletion of group: {group_id} has failed" context = {"Success": success} return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Delete Group", context, removeNull=True, headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.DeleteGroup", outputs_key_field="Success", outputs=context, raw_response=response, ) def move_agent_to_group_command(client: Client, args: dict) -> CommandResults: """ Move agents to a new group. """ group_id = args.get("group_id") agents_id = argToList(args.get("agents_ids", [])) agents_groups = client.move_agent_request(group_id, agents_id) # Parse response into context & content entries agents_moved = bool(agents_groups.get("agentsMoved") and int(agents_groups.get("agentsMoved")) > 0) date_time_utc = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") context_entries = { "Date": date_time_utc, "AgentsMoved": agents_groups.get("agentsMoved"), "AffectedAgents": agents_moved, } return CommandResults( readable_output=tableToMarkdown( f"Sentinel One - Moved Agents\nTotal of: {agents_groups.get('AgentsMoved', 0)}agents were Moved successfully", context_entries, removeNull=True, ), outputs_prefix="SentinelOne.Agent", outputs_key_field="Date", outputs=context_entries, raw_response=agents_groups, ) def get_agent_processes(client: Client, args: dict): """ Retrieve running processes for a specific agent. Note: This feature is obsolete and an empty array will always be returned """ headers = [ "ProcessName", "StartTime", "Pid", "MemoryUsage", "CpuUsage", "ExecutablePath", ] contents = [] context = {} agents_ids = args.get("agents_ids") processes = client.get_agent_processes_request(agents_ids) if processes: for process in processes: contents.append( { "ProcessName": process.get("processName"), "CpuUsage": process.get("cpuUsage"), "MemoryUsage": process.get("memoryUsage"), "StartTime": process.get("startTime"), "ExecutablePath": process.get("executablePath"), "Pid": process.get("pid"), } ) context["SentinelOne.Agent(val.Pid && val.Pid === obj.Pid)"] = processes demisto.results( { "Type": entryTypes["note"], "ContentsFormat": formats["json"], "Contents": contents, "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown("Sentinel One Agent Processes", contents, headers, removeNull=True), "EntryContext": context, } ) def get_threats_command(client: Client, args: dict) -> CommandResults: """ Gets a list of threats. Rank only relevant for API version 2.0 """ headers = [ "ID", "AgentComputerName", "CreatedDate", "SiteID", "SiteName", "Classification", "MitigationStatus", "ConfidenceLevel" if IS_VERSION_2_1 else "Rank", "AgentID", "FileContentHash", "MarkedAsBenign", ] threats = client.get_threats_request(**args) outputs = list(get_threats_outputs(threats, int(args.get("rank", 0)))) if threats else None return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Getting Threat List", outputs, metadata="Provides summary information and details for all the threats that matched your search criteria.", headers=headers, headerTransform=pascalToSpace, removeNull=True, ), outputs_prefix="SentinelOne.Threat", outputs_key_field="ID", outputs=outputs, raw_response=threats, ) def get_hash_command(client: Client, args: dict) -> CommandResults: """ Get hash verdict. Removed hash reputation since SentinelOne has deprecated it - Breaking BC. Removed hash classification since SentinelOne has deprecated it - Breaking BC. """ hash_ = args.get("hash") type_ = get_hash_type(hash_) if type_ == "Unknown": raise DemistoException("Enter a valid hash format.") hash_verdict = client.get_hash_verdict_request(hash_) reputation = hash_verdict.get("data", {}) contents = { "Verdict": reputation.get("verdict"), "Hash": hash_, } return CommandResults( readable_output=tableToMarkdown( "SentinelOne - Hash Reputation Verdict\nProvides hash reputation verdict:", contents, removeNull=True, ), outputs_prefix="SentinelOne.Hash", outputs_key_field="Hash", outputs=contents, raw_response=hash_verdict, ) def mark_as_threat_command(client: Client, args: dict) -> CommandResults: """ Mark suspicious threats as threats. Relevant for API version 2.0 """ context_entries = [] threat_ids = argToList(args.get("threat_ids")) target_scope = args.get("target_scope") # Make request and get raw response affected_threats = client.mark_as_threat_request(threat_ids, target_scope) # Parse response into context & content entries if affected_threats.get("affected") and int(affected_threats.get("affected")) > 0: title = f"Total of {affected_threats.get('affected')} provided threats were marked successfully" affected = True else: affected = False title = "No threats were marked" for threat_id in threat_ids: context_entries.append( { "MarkedAsThreat": affected, "ID": threat_id, } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Marking suspicious threats as threats \n" + title, context_entries, headerTransform=pascalToSpace, removeNull=True, ), outputs_prefix="SentinelOne.Threat", outputs_key_field="ID", outputs=context_entries, raw_response=affected_threats, ) def mitigate_threat_command(client: Client, args: dict) -> CommandResults: """ Apply a mitigation action to a group of threats. Relevant for API version 2.0 """ contents = [] context_entries = [] # Get arguments threat_ids = argToList(args.get("threat_ids")) action = args.get("action") # Make request and get raw response mitigated_threats = client.mitigate_threat_request(threat_ids, action) # Parse response into context & content entries if mitigated_threats.get("affected") and int(mitigated_threats.get("affected")) > 0: mitigated = True meta = f"Total of {mitigated_threats.get('affected')} provided threats were mitigated successfully" else: mitigated = False meta = "No threats were mitigated" for threat_id in threat_ids: contents.append( { "Mitigated": mitigated, "ID": threat_id, "Mitigation Action": action, } ) context_entries.append( { "Mitigated": mitigated, "ID": threat_id, "Mitigation": {"Action": action}, } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Mitigating threats", contents, metadata=meta, removeNull=True, ), outputs_prefix="SentinelOne.Threat", outputs_key_field="ID", outputs=context_entries, raw_response=mitigated_threats, ) def update_threat_analyst_verdict(client: Client, args: dict) -> CommandResults: """ Apply a update analyst verdict action to a group of threats. Relevant for API version 2.1 """ contents = [] context_entries = [] # Get arguments threat_ids = argToList(args.get("threat_ids")) action = args.get("verdict") # Make request and get raw response updated_threats = client.update_threat_analyst_verdict_request(threat_ids, action) # Parse response into context & content entries if updated_threats.get("affected") and int(updated_threats.get("affected")) > 0: updated = True meta = f"Total of {updated_threats.get('affected')} provided threats analyst verdict were updated successfully" else: updated = False meta = "No threats were updated" for threat_id in threat_ids: contents.append( { "Updated": updated, "ID": threat_id, "Analyst Verdict Action": action, } ) context_entries.append( { "Updated": updated, "ID": threat_id, "Update": {"Action": action}, } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Update threats analyst verdict", contents, metadata=meta, removeNull=True, ), outputs_prefix="SentinelOne.Threat", outputs_key_field="ID", outputs=context_entries, raw_response=updated_threats, ) def update_alert_analyst_verdict(client: Client, args: dict) -> CommandResults: """ Apply a update analyst verdict action to a group of alerts. Relevant for API version 2.1 """ contents = [] context_entries = [] # Get arguments alert_ids = argToList(args.get("alert_ids")) action = args.get("verdict") # Make request and get raw response updated_alerts = client.update_alert_analyst_verdict_request(alert_ids, action) # Parse response into context & content entries if updated_alerts.get("affected") and int(updated_alerts.get("affected")) > 0: updated = True meta = f"Total of {updated_alerts.get('affected')} provided alerts analyst verdict were updated successfully" else: updated = False meta = "No alerts were updated" for alert_id in alert_ids: contents.append( { "Updated": updated, "ID": alert_id, "Analyst Verdict Action": action, } ) context_entries.append( { "Updated": updated, "ID": alert_id, "Update": {"Action": action}, } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Update alerts analyst verdict", contents, metadata=meta, removeNull=True, ), outputs_prefix="SentinelOne.Alert", outputs_key_field="ID", outputs=context_entries, raw_response=updated_alerts, ) def update_uam_alert_status(client: Client, args: dict) -> CommandResults: """ Updates the status for a group of UAM Alerts. """ alert_ids = argToList(args.get("alert_ids")) status = args.get("status") if status not in UAM_ALERT_STATUS: raise DemistoException(f"Invalid status. Choose from: {', '.join(UAM_ALERT_STATUS.keys())}") status_value = UAM_ALERT_STATUS[status] context_entries = [] affected_count = 0 for alert_id in alert_ids: response = client.update_uam_alert_status_request(alert_id, status_value) demisto.debug(f"UAM alert status mutation response for [{alert_id}]: {response}") # Check if mutation was successful for this specific ID actions = response.get("alertTriggerActions", {}).get("actions", []) demisto.debug(f"UAM alert status mutation actions for [{alert_id}]: {actions}") success = any(action.get("success") for action in actions) if success: affected_count += 1 context_entries.append({"ID": alert_id, "Status": status, "Updated": success}) meta = ( f"Total of {affected_count} UAM alerts status were updated successfully." if affected_count > 0 else "No alerts were updated." ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Update UAM Alerts Status", context_entries, metadata=meta, removeNull=True, ), outputs_prefix="SentinelOne.UAMAlert", outputs_key_field="ID", outputs=context_entries, raw_response=context_entries, ) def update_uam_alert_analyst_verdict(client: Client, args: dict) -> CommandResults: """ Updates the analyst verdict for a group of UAM Alerts. """ alert_ids = argToList(args.get("alert_ids")) verdict = args.get("analyst_verdict") if verdict not in UAM_ANALYST_VERDICT: raise DemistoException(f"Invalid verdict. Choose from: {', '.join(UAM_ANALYST_VERDICT.keys())}") verdict_value = UAM_ANALYST_VERDICT[verdict] context_entries = [] affected_count = 0 for alert_id in alert_ids: response = client.update_uam_alert_analyst_verdict_request(alert_id, verdict_value) actions = response.get("alertTriggerActions", {}).get("actions", []) success = any(action.get("success") for action in actions) if success: affected_count += 1 context_entries.append({"ID": alert_id, "AnalystVerdict": verdict, "Updated": success}) meta = ( f"Total of {affected_count} UAM alerts analyst verdicts were updated successfully." if affected_count > 0 else "No alerts were updated." ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Update UAM Alerts Analyst Verdict", context_entries, metadata=meta, removeNull=True, ), outputs_prefix="SentinelOne.UAMAlert", outputs_key_field="ID", outputs=context_entries, raw_response=context_entries, ) def create_star_rule(client: Client, args: dict) -> CommandResults: """ Creates the custom STAR rule (cloud detection rule). Relavent for API version 2.1 """ context = {} # Get arguments name = args.get("name") description = args.get("description") query = args.get("query") query_type = args.get("query_type") rule_severity = args.get("rule_severity") account_ids = argToList(args.get("account_ids")) group_ids = argToList(args.get("group_ids")) site_ids = argToList(args.get("site_ids")) expiration_mode = args.get("expiration_mode") expiration_date = args.get("expiration_date") network_quarantine = argToBoolean(args.get("network_quarantine")) treatAsThreat = args.get("treatAsThreat") query_lang = args.get("query_lang") # if the expiration_mode is Temporary then expiration_date is required if expiration_mode == "Temporary" and expiration_date is None: raise DemistoException("You must provide expiration_date argument when you selected the Temporary as expiration_mode") # Make request and get raw response rule = client.create_star_rule_request( name, description, query, query_type, rule_severity, account_ids, group_ids, site_ids, expiration_mode, expiration_date, network_quarantine, treatAsThreat, query_lang, ) if rule: context = { "ID": rule.get("id"), "Name": rule.get("name"), "Status": rule.get("status"), "Severity": rule.get("severity"), "Description": rule.get("description"), "Network Quarantine": rule.get("networkQuarantine"), "Treat As Threat": rule.get("treatAsThreat"), "Expiration Mode": rule.get("expirationMode"), "Expiration Date": rule.get("expiration"), "Scope Hierarchy": rule.get("scope"), "Created At": rule.get("createdAt"), "Updated At": rule.get("updatedAt"), "Query Language": rule.get("queryLang"), } return CommandResults( readable_output=tableToMarkdown("Sentinel One - Create star rule", context, removeNull=True), outputs_prefix="SentinelOne.StarRule", outputs_key_field="ID", outputs=context, raw_response=rule, ) def get_star_rule(client: Client, args: dict) -> CommandResults: """ Get the custom STAR rule(s) (cloud detection rule). Relavent for API version 2.1 """ context_entries = [] query_params = assign_params( status=args.get("status"), creator__contains=args.get("creator_contains"), queryType=args.get("queryType"), query=args.get("query"), description__contains=args.get("description_contains"), ids=args.get("ruleIds"), name__contains=args.get("name_contains"), accountIds=args.get("accountIds"), expirationMode=args.get("expirationMode"), siteIds=args.get("siteIds"), limit=int(args.get("limit", 1000)), ) # Make request and get raw response rules = client.get_star_rule_request(query_params) if rules: # Parse response into context & content entries for rule in rules: context_entries.append( { "ID": rule.get("id"), "Creator": rule.get("creator"), "Name": rule.get("name"), "Status": rule.get("status"), "Severity": rule.get("severity"), "Generated Alerts": rule.get("generatedAlerts"), "Description": rule.get("description"), "Status Reason": rule.get("statusReason"), "Expiration Mode": rule.get("expirationMode"), "Expiration Date": rule.get("expiration"), "Expired": rule.get("expired"), } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Getting List of Star Rules", context_entries, removeNull=True, metadata="Provides summary information and details for all star rules that matched your search criteria.", headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.StarRule", outputs_key_field="ID", outputs=context_entries, raw_response=rules, ) def update_star_rule(client: Client, args: dict) -> CommandResults: """ Get the custom STAR rule(s) (cloud detection rule). Relavent for API version 2.1 """ context = {} # Get arguments rule_id = args.get("rule_id") name = args.get("name") description = args.get("description") query = args.get("query") query_type = args.get("query_type") rule_severity = args.get("rule_severity") account_ids = argToList(args.get("account_ids")) group_ids = argToList(args.get("group_ids")) site_ids = argToList(args.get("site_ids")) expiration_mode = args.get("expiration_mode") expiration_date = args.get("expiration_date") network_quarantine = argToBoolean(args.get("network_quarantine")) treatAsThreat = args.get("treatAsThreat") query_lang = args.get("query_lang") # if the expiration_mode is Temporary then expiration_date is required if expiration_mode == "Temporary" and expiration_date is None: raise DemistoException("You must provide expiration_date argument when you selected the Temporary as expiration_mode") # Make request and get raw response rule = client.update_star_rule_request( rule_id, name, description, query, query_type, rule_severity, account_ids, group_ids, site_ids, expiration_mode, expiration_date, network_quarantine, treatAsThreat, query_lang, ) if rule: context = { "ID": rule.get("id"), "Name": rule.get("name"), "Status": rule.get("status"), "Severity": rule.get("severity"), "Description": rule.get("description"), "Network Quarantine": rule.get("networkQuarantine"), "Treat As Threat": rule.get("treatAsThreat"), "Expiration Mode": rule.get("expirationMode"), "Expiration Date": rule.get("expiration"), "Scope Hierarchy": rule.get("scope"), "Created At": rule.get("createdAt"), "Updated At": rule.get("updatedAt"), "Query Language": rule.get("queryLang"), } return CommandResults( readable_output=tableToMarkdown("Sentinel One - Updated star rule", context, removeNull=True), outputs_prefix="SentinelOne.StarRule", outputs_key_field="ID", outputs=context, raw_response=rule, ) def enable_star_rules(client: Client, args: dict) -> CommandResults: """ Enables the custom STAR rule (cloud detection rule). Relavent for API version 2.1 """ context_entries = [] # Get arguments rule_ids = argToList(args.get("rule_ids")) # Make request and get raw response enabled_rules = client.enable_star_rule_request(rule_ids) # Parse response into context & content entries if enabled_rules.get("affected") and int(enabled_rules.get("affected")) > 0: enabled = True meta = f"Total of {enabled_rules.get('affected')} provided star rules were enabled successfully" else: enabled = False meta = "No star rules were enabled" for rule_id in rule_ids: context_entries.append({"ID": rule_id, "Enabled": enabled}) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Enable List of Star Rules", context_entries, removeNull=True, metadata=meta, headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.StarRule", outputs_key_field="ID", outputs=context_entries, raw_response=enabled_rules, ) def disable_star_rules(client: Client, args: dict) -> CommandResults: """ Disables the custom STAR rule (cloud detection rule). Relavent for API version 2.1 """ context_entries = [] # Get arguments rule_ids = argToList(args.get("rule_ids")) # Make request and get raw response disabled_rules = client.disable_star_rule_request(rule_ids) # Parse response into context & content entries if disabled_rules.get("affected") and int(disabled_rules.get("affected")) > 0: disabled = True meta = f"Total of {disabled_rules.get('affected')} provided star rules were disabled successfully" else: disabled = False meta = "No star rules were disabled" for rule_id in rule_ids: context_entries.append({"ID": rule_id, "Disabled": disabled}) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Disable List of Star Rules", context_entries, removeNull=True, metadata=meta, headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.StarRule", outputs_key_field="ID", outputs=context_entries, raw_response=disabled_rules, ) def delete_star_rule(client: Client, args: dict) -> CommandResults: """ Deletes the custom STAR rule (cloud detection rule). Relavent for API version 2.1 """ context_entries = [] # Get arguments rule_ids = argToList(args.get("rule_ids")) # Make request and get raw response deleted_rules = client.delete_star_rule_request(rule_ids) # Parse response into context & content entries if deleted_rules.get("affected") and int(deleted_rules.get("affected")) > 0: deleted = True meta = f"Total of {deleted_rules.get('affected')} provided star rules were deleted successfully" else: deleted = False meta = "No star rules were deleted" for rule_id in rule_ids: context_entries.append({"ID": rule_id, "Deleted": deleted}) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Deleted List of Star Rules", context_entries, removeNull=True, metadata=meta, headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.StarRule", outputs_key_field="ID", outputs=context_entries, raw_response=deleted_rules, ) def write_threat_note(client: Client, args: dict) -> CommandResults: """ Write the notes for particular threat(s). Relavent for API version 2.1 """ context_entries = [] # Get arguments note = args.get("note") threat_ids = argToList(args.get("threat_ids")) # Make request and get raw response threat_notes = client.write_threat_note_request(threat_ids, note) # Parse response into context & content entries if threat_notes.get("affected") and int(threat_notes.get("affected")) > 0: status = "Success" meta = f"Total of {threat_notes.get('affected')} provided threats. THreat notes were successfully Added for them" else: status = "Failed" meta = "No threat notes were Added" for threat_id in threat_ids: context_entries.append({"ID": threat_id, "Note": note, "Status": status}) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Write threat note", context_entries, removeNull=True, metadata=meta, headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.Threat", outputs_key_field="ID", outputs=context_entries, raw_response=threat_notes, ) def get_threat_notes(client: Client, args: dict) -> CommandResults: """ Get the note of a particular threat. """ threat_id = args.get("threat_id") context_entries = [] notes = client.get_threat_notes_request(threat_id) if notes: for note in notes: context_entries.append( { "CreatedAt": note.get("createdAt"), "Creator": note.get("creator"), "CreatorID": note.get("creatorId"), "Edited": note.get("edited"), "ID": note.get("id"), "Text": note.get("text"), "UpdatedAt": note.get("updatedAt"), } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Get Threat Notes", context_entries, headerTransform=pascalToSpace, removeNull=True, ), outputs_prefix="SentinelOne.Notes", outputs_key_field="ID", outputs=context_entries, raw_response=notes, ) def create_ioc(client: Client, args: dict) -> CommandResults: """ Add an IoC to the Threat Intelligence database. . Relavent for API version 2.1 """ context = {} # Get arguments name = args.get("name") source = args.get("source") ioc_type = args.get("type") method = args.get("method") validUntil = args.get("validUntil") value = args.get("value") account_ids = argToList(args.get("account_ids")) # not-requied arguments externalId = args.get("externalId") description = args.get("description") # Make request and get raw response ioc = client.create_ioc_request( name, source, ioc_type, method, validUntil, value, account_ids, externalId, description, )[0] if ioc: context = { "UUID": ioc.get("uuid"), "Name": ioc.get("name"), "Source": ioc.get("source"), "Type": ioc.get("type"), "Batch Id": ioc.get("batchId"), "Creator": ioc.get("creator"), "Scope": ioc.get("scope"), "Scope Id": ioc.get("scopeId")[0], "Valid Until": ioc.get("validUntil"), "Description": ioc.get("description"), "External Id": ioc.get("externalId"), } return CommandResults( readable_output=tableToMarkdown("Sentinel One - Create IOC", context, removeNull=True), outputs_prefix="SentinelOne.IOC", outputs_key_field="UUID", outputs=context, raw_response=ioc, ) def create_bulk_ioc(client: Client, args: dict) -> CommandResults: """ Add bulk IoC's to the Threat Intelligence database. . Relavent for API version 2.1 """ context_list = [] # Get arguments entry_id = args.get("entry_id") account_ids = argToList(args.get("account_ids")) # Resolve file path from entry_id try: file_info = demisto.getFilePath(entry_id) except Exception as e: raise DemistoException(f"Failed to retrieve file info for entry_id={entry_id}. Error: {str(e)}") if not file_info or not file_info.get("path"): raise DemistoException(f"Could not resolve file path for entry_id={entry_id}") file_path = file_info["path"] # Load JSON array of IOC objects try: with open(file_path, encoding="utf-8") as json_ioc_list: iocs_data = json.load(json_ioc_list) except json.JSONDecodeError as e: raise DemistoException(f"Invalid JSON in uploaded file: {str(e)}") except Exception as e: raise DemistoException(f"Failed reading uploaded file {file_path}: {str(e)}") if not isinstance(iocs_data, list): raise DemistoException("Uploaded JSON must be an array of IOC objects.") iocs = client.create_bulk_ioc_request(iocs_data, account_ids) for ioc in iocs: context_list.append( { "UUID": ioc.get("uuid"), "Name": ioc.get("name"), "Source": ioc.get("source"), "Type": ioc.get("type"), "Batch Id": ioc.get("batchId"), "Creator": ioc.get("creator"), "Scope": ioc.get("scope"), "Scope Id": ioc.get("scopeId")[0], "Valid Until": ioc.get("validUntil"), "Description": ioc.get("description"), "External Id": ioc.get("externalId"), } ) # Create readable output (markdown table) readable_output = tableToMarkdown("SentinelOne - Create IOCs", context_list, removeNull=True) return CommandResults( readable_output=readable_output, outputs_prefix="SentinelOne.IOCs", outputs_key_field="UUID", outputs=context_list, raw_response=iocs, ) def run_powerquery(client: Client, args: dict) -> CommandResults: outputs = {} # Get arguments sdl_url = args.get("singularity_xdr_url") sdl_api_key = args.get("singularity_xdr_api_key") query = args.get("query") start_time = args.get("start_time") end_time = args.get("end_time") priority = args.get("priority") recurring = argToBoolean(args.get("recurring")) if args.get("recurring") else None team_emails = argToList(args.get("team_emails")) or None pq_response = client.run_powerquery_request( sdl_url, sdl_api_key, query, start_time, end_time, priority, recurring, team_emails, ) # Extract columns and rows columns = [col.get("name") for col in pq_response.get("columns", [])] rows = pq_response.get("values", []) table_data = [dict(zip(columns, row)) for row in rows] summary = f"### SentinelOne PowerQuery Results\n**Query:** `{args.get('query')}` \n\n" md = summary + tableToMarkdown("Query Output", table_data) outputs = { "status": pq_response.get("status"), "matchingEvents": pq_response.get("matchingEvents"), "omittedEvents": pq_response.get("omittedEvents"), "results": table_data, } return CommandResults( readable_output=md, outputs_prefix="SentinelOne.PowerQuery.Results", outputs=outputs, raw_response=pq_response, ) def delete_ioc(client: Client, args: dict) -> CommandResults: """ Deletes an IoC from the Threat Intelligence database. Relavent for API version 2.1 """ context_entries = [] # Get arguments account_ids = argToList(args.get("account_ids")) uuids = argToList(args.get("uuids")) # Make request and get raw response deleted_iocs = client.delete_ioc_request(account_ids, uuids) # Parse response into context & content entries if deleted_iocs.get("affected") and int(deleted_iocs.get("affected")) > 0: deleted = True meta = f"Total of {deleted_iocs.get('affected')} provided IOCs were deleted successfully" else: deleted = False meta = "No IOC were deleted" for uuid in uuids: context_entries.append({"UUID": uuid, "Deleted": deleted}) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Delete List of IOCs", context_entries, removeNull=True, metadata=meta, headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.IOC", outputs_key_field="UUID", outputs=context_entries, raw_response=deleted_iocs, ) def get_iocs(client: Client, args: dict) -> CommandResults: """ Get the IOCs of a specified Account that match the filter. Relavent for API version 2.1 """ context_entries = [] query_params = assign_params( accountIds=args.get("account_ids"), uploadTime__gte=args.get("upload_time_gte"), uploadTime__lte=args.get("upload_time_lte"), limit=int(args.get("limit", 1000)), cursor=args.get("cursor"), uuids=args.get("uuids"), type=args.get("type"), batchId=args.get("batch_id"), source=args.get("source"), value=args.get("value"), externalId=args.get("external_id"), name__contains=args.get("name_contains"), creator__contains=args.get("creator_contains"), description__contains=args.get("description_contains"), category__in=args.get("category_in"), updatedAt__gte=args.get("updated_at_gte"), updatedAt__lte=args.get("updated_at_lte"), creationTime__gte=args.get("creation_time_gte"), creationTime__lte=args.get("creation_time_lte"), ) # Make request and get raw response iocs, pagination = client.get_iocs_request(query_params) if pagination["nextCursor"] is not None: demisto.results("Use the below cursor value to get the next page iocs \n {}".format(pagination["nextCursor"])) if iocs: # Parse response into context & content entries for ioc in iocs: context_entries.append( { "UUID": ioc.get("uuid"), "Creator": ioc.get("creator"), "Name": ioc.get("name"), "Value": ioc.get("value"), "Description": ioc.get("description"), "Type": ioc.get("type"), "External Id": ioc.get("externalId"), "Source": ioc.get("source"), "Upload Time": ioc.get("uploadTime"), "Valid Until": ioc.get("validUntil"), } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Getting List of IOCs", context_entries, removeNull=True, metadata="Provides summary information and details for all iocs that matched your search criteria.", headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.IOC", outputs_key_field="UUID", outputs=context_entries, raw_response=iocs, ) def create_power_query(client: Client, args: dict) -> CommandResults: """ Create the power query and get the events or get the query ID. Relavent for API version 2.1 """ context_entries = [] # Get arguments limit = int(args.get("limit", 1000)) query = args.get("query") from_date = args.get("from_date") to_date = args.get("to_date") # Make request and get raw response response = client.create_power_query_request(limit, query, from_date, to_date) if response["status"] == "RUNNING": context_entries.append({"queryId": response["queryId"]}) meta = "Ping a Deep Visibility Power Query using the queryId" elif response["status"] == "FINISHED": for row in response["data"]: temp = {} for i in range(len(row)): temp.update({response["columns"][i]["name"]: row[i]}) context_entries.append(temp) meta = "Provides summary information and details aboput the power query and its id \n your search criteria." else: meta = "" demisto.debug(f"{response['status']=} -> {meta=}") return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Create a Power Query and Get QueryId", context_entries, removeNull=True, metadata=meta, headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.PowerQuery", outputs=context_entries, raw_response=response, ) def ping_power_query(client: Client, args: dict) -> CommandResults: """ Create the power query and get the events or get the query ID. Relavent for API version 2.1 """ context_entries = [] query_params = assign_params(queryId=args.get("queryId")) response = client.ping_power_query_request(query_params) if response.get("data"): for row in response["data"]: temp = {} for i in range(len(row)): temp.update({response["columns"][i]["name"]: row[i]}) context_entries.append(temp) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Ping the Power Query", context_entries, removeNull=True, metadata="Provides summary information and details aboput the power query and its id your search criteria.", headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.PowerQuery", outputs=context_entries, raw_response=response, ) else: return CommandResults( readable_output="There is no data returned by the id that you provided, please re-check the id to ping" ) def update_threat_status(client: Client, args: dict) -> CommandResults: """ Apply a update status action to a group of threats. Relevant for API version 2.1 """ context_entries = [] # Get arguments threat_ids = argToList(args.get("threat_ids")) status = args.get("status") affected = 0 meta = "No threats were updated" for threat_id in threat_ids: # Make request and get raw response updated_threats = client.update_threat_status_request(threat_id, status) # Parse response into context & content entries if updated_threats.get("affected") and int(updated_threats.get("affected")) > 0: updated = True affected += 1 else: updated = False context_entries.append( { "Updated": updated, "ID": threat_id, "Status": status, } ) if affected > 0: meta = f"Total of {affected} provided threats status were updated successfully" return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Update threats status", context_entries, metadata=meta, removeNull=True, ), outputs_prefix="SentinelOne.Threat", outputs_key_field="ID", outputs=context_entries, raw_response=updated_threats, ) def update_alert_status(client: Client, args: dict) -> CommandResults: """ Updates the status for group of Alerts. Relevant for API version 2.1 """ context_entries = [] # Get arguments alert_ids = argToList(args.get("alert_ids")) status = args.get("status") affected = 0 meta = "No alerts were updated" for alert_id in alert_ids: # Make request and get raw response updated_alerts = client.update_alert_status_request(alert_id, status) # Parse response into content entries if updated_alerts.get("affected") and int(updated_alerts.get("affected")) > 0: updated = True affected += 1 else: updated = False context_entries.append( { "Updated": updated, "ID": alert_id, "Status": status, } ) if affected > 0: meta = f"Total of {affected} provided alerts status were updated successfully" return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Update alerts status", context_entries, metadata=meta, removeNull=True, ), outputs_prefix="SentinelOne.Alert", outputs_key_field="ID", outputs=context_entries, raw_response=updated_alerts, ) def expire_site(client: Client, args: dict) -> CommandResults: """ Expires the site from the server. Relavent to both API Versions """ context_entries = {} # Get arguments site_id = args.get("site_id") # Make request and get raw response Expired_site = client.expire_site_request(site_id) if Expired_site: context_entries = { "ID": Expired_site.get("id"), "Name": Expired_site.get("name"), "State": Expired_site.get("state"), "SKU": Expired_site.get("sku"), "Site Type": Expired_site.get("siteType"), "Suite": Expired_site.get("suite"), "Total Licences": Expired_site.get("totalLicenses"), "Account ID": Expired_site.get("accountId"), "Creator": Expired_site.get("creator"), "Creator ID": Expired_site.get("creatorId"), "Description": Expired_site.get("description"), "Expiration": Expired_site.get("expiration"), } return CommandResults( readable_output=tableToMarkdown("Sentinel One - Expire Site", context_entries, removeNull=True), outputs_prefix="SentinelOne.Site", outputs_key_field="ID", outputs=context_entries, raw_response=Expired_site, ) def fetch_threat_file(client: Client, args: dict) -> list[CommandResults]: """ Fetches the threat file. Relevent to both API Versions """ context_entries = [] # Get Arguments threat_ids = argToList(args.get("threat_id")) password = args.get("password") downloaded_files = client.fetch_threat_file_request(password, threat_ids) if downloaded_files.get("affected") and int(downloaded_files.get("affected")) > 0: downloadable = True meta = f"Total of {downloaded_files.get('affected')} provided threats were downloaded successfully" else: downloadable = False meta = "No threats were downloaded" files = [] for threat_id in threat_ids: zipped_file = "Session timeout, unable to download the Zip file." threat_file_download_endpoint = client.download_url_request(threat_id) if threat_file_download_endpoint != "-1": zip_file_data = client.download_threat_file_request(threat_file_download_endpoint) files.append( fileResult( filename=f"{threat_id}.zip", data=zip_file_data, file_type=EntryType.ENTRY_INFO_FILE, ) ) zipped_file = fileResult( filename=f"{threat_id}.zip", data=zip_file_data, file_type=EntryType.ENTRY_INFO_FILE, ) context_entries.append({"Downloadable": downloadable, "ID": threat_id, "ZippedFile": zipped_file}) return [ CommandResults( readable_output=tableToMarkdown( "Sentinel One - Fetch threat file", context_entries, metadata=meta, removeNull=False, ), outputs_prefix="SentinelOne.Threat", outputs_key_field="ID", outputs=context_entries, raw_response=downloaded_files, ), *files, ] def threat_download_from_cloud(client: Client, args: dict) -> list[CommandResults | list]: """ Downloads the threat file uploaded to the cloud (Binary """ threat_id: str = str(args.get("threat_id")) response = client.threat_download_from_cloud_request(threat_id) downloadable = False files = [] if "downloadUrl" in response: file_download_url = response["downloadUrl"] try: zip_file_data = client.download_threat_cloud_file(file_download_url) zipped_file = fileResult( filename=response["fileName"], data=zip_file_data, file_type=EntryType.ENTRY_INFO_FILE, ) files.append(zipped_file) downloadable = True except Exception: zipped_file = "File not available for Download from BinaryVault" context_entry = { "Downloadable": downloadable, "ID": threat_id, "ZippedFile": zipped_file, } return [ CommandResults( readable_output=tableToMarkdown("Sentinel One - Download From Cloud", context_entry, removeNull=False), outputs_prefix="SentinelOne.Threat", outputs_key_field="ID", outputs=context_entry, raw_response=zipped_file, ), *files, ] def get_alerts(client: Client, args: dict) -> CommandResults: """ Get the Alerts from server. Relevant to API Version 2.1 """ created_until = None created_from = None context_entries = [] headers = [ "AlertId", "EventType", "RuleName", "EndpointName", "SrcProcName", "SrcProcPath", "SrcProcCommandline", "SrcProcSHA1", "SrcProcStartTime", "SrcProcStorylineId", "SrcParentProcName", "AlertCreatedAt", "AgentId", "AgentUUID", "RuleName", ] if args.get("created_until"): created_until = dateparser.parse(str(args.get("created_until")), settings={"TIMEZONE": "UTC"}) if args.get("created_from"): created_from = dateparser.parse(str(args.get("created_from")), settings={"TIMEZONE": "UTC"}) query_params = assign_params( ruleName__contains=args.get("ruleName"), incidentStatus=args.get("incidentStatus"), analystVerdict=args.get("analystVerdict"), createdAt__lte=created_until, createdAt__gte=created_from, ids=argToList(args.get("alert_ids")), limit=int(args.get("limit", 1000)), siteIds=args.get("site_ids"), cursor=args.get("cursor"), ) alerts, pagination = client.get_alerts_request(query_params) if pagination["nextCursor"] is not None: demisto.results("Use the below cursor value to get the next page alerts \n {}".format(pagination["nextCursor"])) if alerts: for alert in alerts: alert_info = alert.get("alertInfo") rule_info = alert.get("ruleInfo") source_process_info = alert.get("sourceProcessInfo") source_parent_process_info = alert.get("sourceParentProcessInfo") agent_realtime_info = alert.get("agentRealtimeInfo") agent_detection_info = alert.get("agentDetectionInfo") context_entries.append( { "EventType": alert_info.get("eventType"), "RuleName": rule_info.get("name"), "SrcProcUser": source_process_info.get("user"), "SrcProcName": source_process_info.get("name"), "SrcProcPath": source_process_info.get("filePath"), "SrcProcCommandline": source_process_info.get("commandline"), "SrcProcSHA1": source_process_info.get("fileHashSha1"), "SrcProcStartTime": source_process_info.get("pidStarttime"), "SrcProcStorylineId": source_process_info.get("storyline"), "SrcParentProcName": source_parent_process_info.get("name"), "SrcParentProcPath": source_parent_process_info.get("filePath"), "SrcParentProcCommandline": source_parent_process_info.get("commandline"), "SrcParentProcStartTime": source_parent_process_info.get("pidStarttime"), "SrcParentProcUser": source_parent_process_info.get("user"), "SrcParentProcSHA1": source_parent_process_info.get("fileHashSha1"), "SrcProcSignerIdentity": source_process_info.get("fileSignerIdentity"), "SrcParentProcSignerIdentity": source_parent_process_info.get("fileSignerIdentity"), "AlertCreatedAt": alert_info.get("createdAt"), "AlertId": alert_info.get("alertId"), "AnalystVerdict": alert_info.get("analystVerdict"), "IncidentStatus": alert_info.get("incidentStatus"), "EndpointName": agent_realtime_info.get("name"), "AgentId": agent_realtime_info.get("id"), "AgentUUID": agent_detection_info.get("uuid"), "dvEventId": alert_info.get("dvEventId"), "AgentOS": agent_realtime_info.get("os"), "AgentVersion": agent_detection_info.get("version"), "SiteId": agent_detection_info.get("siteId"), "RuleId": rule_info.get("id"), } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Getting Alert List", context_entries, removeNull=True, metadata="Provides summary information and details for all the alerts that matched your search criteria.", headers=headers, headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.Alert", outputs_key_field="AlertId", outputs=context_entries, raw_response=alerts, ) def resolve_threat_command(client: Client, args: dict) -> CommandResults: """ Mark threats as resolved """ context_entries = [] threat_ids = argToList(args.get("threat_ids")) # Make request and get raw response resolved_threats = client.resolve_threat_request(threat_ids) # Parse response into context & content entries if resolved_threats.get("affected") and int(resolved_threats.get("affected")) > 0: resolved = True title = f"Total of {resolved_threats.get('affected')} provided threats were resolved successfully" else: resolved = False title = "No threats were resolved" for threat_id in threat_ids: context_entries.append( { "Resolved": resolved, "ID": threat_id, } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Resolving threats\n" + title, context_entries, removeNull=True, ), outputs_prefix="SentinelOne.Threat", outputs_key_field="ID", outputs=context_entries, raw_response=resolved_threats, ) def get_installed_applications(client: Client, args: dict) -> CommandResults: """ Get installed applications from agent. """ context_entries = [] headers = ["Name", "Publisher", "Size", "Version", "InstalledOn"] query_params = assign_params(ids=argToList(args.get("agent_ids"))) applications = client.get_installed_applications_request(query_params) if applications: for app in applications: context_entries.append( { "Name": app.get("name"), "Publisher": app.get("publisher"), "Size": app.get("size"), "Version": app.get("version"), "InstalledOn": app.get("installedDate"), } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Getting Installed Applications", context_entries, removeNull=True, metadata="Provides summary information and details for all installed applications" " that matched your search criteria.", headers=headers, headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.Application", outputs_key_field="Name", outputs=context_entries, raw_response=applications, ) def initiate_endpoint_scan(client: Client, args: dict) -> CommandResults: """ Initiate the endpoint virus scan on provided agent IDs """ context_entries = [] agent_ids = argToList(args.get("agent_ids")) initiated = client.initiate_endpoint_scan_request(agent_ids) if initiated.get("affected") and int(initiated.get("affected")) > 0: updated = True meta = f"Total of {initiated.get('affected')} provided agents were successfully initiated the scan" else: updated = False meta = "No agents scan was initiated" for agent_id in agent_ids: context_entries.append({"Agent ID": agent_id, "Initiated": updated}) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Initiate endpoint scan on provided Agent ID", context_entries, metadata=meta, removeNull=True, ), outputs_prefix="SentinelOne.Agent", outputs_key_field="Agent ID", outputs=context_entries, raw_response=initiated, ) def abort_endpoint_scan(client: Client, args: dict) -> CommandResults: """ Abort the endpoint virus scan on provided agent IDs """ context_entries = [] agent_ids = argToList(args.get("agent_ids")) aborted = client.abort_endpoint_scan_request(agent_ids) if aborted.get("affected") and int(aborted.get("affected")) > 0: updated = True meta = f"Total of {aborted.get('affected')} provided agents were successfully aborted the scan" else: updated = False meta = "No agents scan was aborted" for agent_id in agent_ids: context_entries.append({"Agent ID": agent_id, "Aborted": updated}) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Abort endpoint scan on provided Agent ID", context_entries, metadata=meta, removeNull=True, ), outputs_prefix="SentinelOne.Agent", outputs_key_field="Agent ID", outputs=context_entries, raw_response=aborted, ) def endpoint_fetch_logs(client: Client, args: dict) -> CommandResults: """ Get the Agent and Endpoint logs from Agents for provided agent IDs """ context = {} agent_ids = argToList(args.get("agent_ids")) agent_logs = argToBoolean(args.get("agents_logs")) customer_facing_logs = argToBoolean(args.get("customer_facing_logs")) platform_logs = argToBoolean(args.get("platform_logs")) response = client.endpoint_fetch_logs_request(agent_ids, agent_logs, customer_facing_logs, platform_logs) agents_affected = response.get("affected", 0) context = {"Affected": agents_affected} if agents_affected > 0: meta = "Fetch logs operation was successfully executed for the provided agent(s)." else: meta = "No entity was affected." return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Get the Agent and Endpoint logs", context, metadata=meta, removeNull=True, ), outputs_prefix="SentinelOne.Agent", outputs_key_field="Affected", outputs=context, raw_response=response, ) def get_white_list_command(client: Client, args: dict) -> CommandResults: """ List all white items matching the input filter """ context_entries = [] # Get arguments item_ids = argToList(args.get("item_ids", [])) os_types = argToList(args.get("os_types", [])) exclusion_type = args.get("exclusion_type") limit = int(args.get("limit", 10)) should_include_parent = argToBoolean(args.get("include_parent", False)) should_include_children = argToBoolean(args.get("include_children", False)) # Make request and get raw response exclusion_items = client.get_exclusions_request( item_ids, os_types, exclusion_type, limit, include_parents=should_include_parent, include_children=should_include_children, ) # Parse response into context & content entries for exclusion_item in exclusion_items: context_entries.append( { "ID": exclusion_item.get("id"), "Type": exclusion_item.get("type"), "CreatedAt": exclusion_item.get("createdAt"), "Value": exclusion_item.get("value"), "Source": exclusion_item.get("source"), "UserID": exclusion_item.get("userId"), "UpdatedAt": exclusion_item.get("updatedAt"), "OsType": exclusion_item.get("osType"), "UserName": exclusion_item.get("userName"), "Mode": exclusion_item.get("mode"), } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Listing exclusion items", context_entries, removeNull=True, metadata="Provides summary information and details for all the exclusion items that matched your search criteria.", ), outputs_prefix="SentinelOne.Exclusions", outputs_key_field="ID", outputs=context_entries, raw_response=exclusion_items, ) def get_item_ids_from_whitelist(client: Client, item: str, exclusion_type: str, os_type: str = None) -> list[str | None]: """ Return the IDs of the hash from the white. Helper function for remove_item_from_whitelist Limit is set to OS_COUNT here where is OS_COUNT is set to the number of Operating Systems a hash can be blocked. Currently there are only three platforms it is acceptable for a hash to be blocked 3 times. If more results are returned, an error will be thrown. A hash can occur more than once if it is blocked on more than one platform (Windwos, MacOS, Linux) """ item_ids: list = [] limit = OS_COUNT + 1 white_list = client.get_exclusions_request( item_ids, os_type, exclusion_type, limit, item, include_children=True, include_parents=True, ) demisto.debug(f"white_list: {white_list}") ret = [] # Validation check first if len(white_list) > limit: raise DemistoException("Received more than 3 results when querying by hash. This condition should not occur") for entry in white_list: # Second validation. E.g. if user passed in a hash value shorter than SHA1 length if (value := entry.get("value")) and value.lower() == item.lower(): ret.append(entry.get("id")) return ret def remove_item_from_whitelist(client: Client, args: dict) -> CommandResults: """ Remove a hash from the blocklist (SentinelOne Term: Blacklist) """ item = args.get("item") if not item: raise DemistoException("You must specify a valid item to be removed") os_type = args.get("os_type", None) exclusion_type = args.get("exclusion_type", None) item_ids = get_item_ids_from_whitelist(client, item, exclusion_type, os_type) if not item_ids: status = {"item": item, "status": "Not on whitelist"} result = None else: result = [] numRemoved = 0 for item_id in item_ids: numRemoved += 1 result.append(client.remove_exclusion_item_request(item_id=item_id)) status = { "item": item, "status": f"Removed {numRemoved} entries from whitelist", } return CommandResults( readable_output=f"{item}: {status['status']}.", outputs_prefix="SentinelOne.RemoveItemFromWhitelist", outputs_key_field="Value", outputs=status, raw_response=result, ) def create_white_item_command(client: Client, args: dict): """ Create white item. """ context_entries = [] title = "" group_ids = argToList(args.get("group_ids", [])) site_ids = argToList(args.get("site_ids", [])) exclusion_type = args.get("exclusion_type") exclusion_value = args.get("exclusion_value") os_type = args.get("os_type") description = args.get("description") exclusion_mode = args.get("exclusion_mode") path_exclusion_type = args.get("path_exclusion_type") if not site_ids: raise DemistoException("You must provide site_ids.") # Make request and get raw response new_item = client.create_exclusion_item_request( exclusion_type, exclusion_value, os_type, description, exclusion_mode, path_exclusion_type, group_ids, site_ids, ) # Parse response into context & content entries if new_item: title = "Sentinel One - Adding an exclusion item \n" + "The provided item was successfully added to the exclusion list" context_entries.append( { "ID": new_item.get("id"), "Type": new_item.get("type"), "CreatedAt": new_item.get("createdAt"), } ) return CommandResults( readable_output=tableToMarkdown(title, context_entries, removeNull=True, headerTransform=pascalToSpace), outputs_prefix="SentinelOne.Exclusion", outputs_key_field="ID", outputs=context_entries, raw_response=new_item, ) def get_sites_command(client: Client, args: dict) -> CommandResults: """ List all sites with filtering options """ context_entries = [] query_params = assign_params( updatedAt=args.get("updated_at"), query=args.get("query"), siteType=args.get("site_type"), features=args.get("features"), state=args.get("state"), suite=args.get("suite"), # HTTP 500 - server internal error when passing admin_only. adminOnly=argToBoolean(args.get("admin_only")) if args.get("admin_only") else None, accountId=args.get("account_id"), name=args.get("site_name"), createdAt=args.get("created_at"), limit=int(args.get("limit", 50)), siteIds=argToList(args.get("site_ids")), ) # Make request and get raw response raw_response = client.get_sites_request(query_params) sites, all_sites = raw_response.get("sites"), raw_response.get("allSites") # Parse response into context & content entries for site in sites: context_entries.append( { "ID": site.get("id"), "Creator": site.get("creator"), "Name": site.get("name"), "Type": site.get("siteType"), "AccountName": site.get("accountName"), "State": site.get("state"), "HealthStatus": site.get("healthStatus"), "Suite": site.get("suite"), "CreatedAt": site.get("createdAt"), "Expiration": site.get("expiration"), "UnlimitedLicenses": site.get("unlimitedLicenses"), "TotalLicenses": all_sites.get("totalLicenses"), "ActiveLicenses": all_sites.get("activeLicenses"), } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Getting List of Sites", context_entries, removeNull=True, metadata="Provides summary information and details for all sites that matched your search criteria.", headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.Site", outputs_key_field="ID", outputs=context_entries, raw_response=raw_response, ) def get_site_command(client: Client, args: dict) -> CommandResults: """ Get a specific site by ID """ # Init main vars context_entries = [] # Get arguments site_id = args.get("site_id") # Make request and get raw response site = client.get_site_request(site_id) # Parse response into context & content entries if site: context_entries.append( { "ID": site.get("id"), "Creator": site.get("creator"), "Name": site.get("name"), "Type": site.get("siteType"), "AccountName": site.get("accountName"), "State": site.get("state"), "HealthStatus": site.get("healthStatus"), "Suite": site.get("suite"), "CreatedAt": site.get("createdAt"), "Expiration": site.get("expiration"), "UnlimitedLicenses": site.get("unlimitedLicenses"), "TotalLicenses": site.get("totalLicenses"), "ActiveLicenses": site.get("activeLicenses"), "AccountID": site.get("accountId"), "IsDefault": site.get("isDefault"), } ) return CommandResults( readable_output=tableToMarkdown( f"Sentinel One - Summary About Site: {site_id}", context_entries, removeNull=True, metadata="Provides summary information and details for specific site ID", headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.Site", outputs_key_field="ID", outputs=context_entries, raw_response=site, ) def reactivate_site_command(client: Client, args: dict) -> CommandResults: """ Reactivate specific site by ID """ # Init main vars context = {} # Get arguments site_id = args.get("site_id") unlimited = args.get("unlimited") expiration = args.get("expiration") if unlimited is not None: unlimited = argToBoolean(unlimited) # if unlimited and expiration are not passed then error out if unlimited is None and expiration is None: raise DemistoException("You must provide unlimited argument or expiration argument as required.") # if unlimited is not passed but expiration is then set unlimited to False if unlimited is None: unlimited = False # if unlimited is False and no expiration then error out. if unlimited is False and expiration is None: raise DemistoException("You must provide expiration when unlimited is false") # Make request and get raw response site = client.reactivate_site_request(site_id, expiration, unlimited) # Parse response into context & content entries if site: context = { "ID": site.get("id"), "Reactivated": site.get("success"), } return CommandResults( readable_output=tableToMarkdown(f"Sentinel One - Reactivated Site: {site_id}", context, removeNull=True), outputs_prefix="SentinelOne.Site", outputs_key_field="ID", outputs=context, raw_response=site, ) def get_threat_summary_command(client: Client, args: dict) -> CommandResults: """ Get dashboard threat summary """ # Init main vars context_entries = {} site_ids = argToList(args.get("site_ids")) group_ids = argToList(args.get("group_ids")) # Make request and get raw response threat_summary = client.get_threat_summary_request(site_ids, group_ids) # Parse response into context & content entries if threat_summary: context_entries = { "InProgress": threat_summary.get("inProgress"), "MaliciousNotResolved": threat_summary.get("maliciousNotResolved"), "NotMitigated": threat_summary.get("notMitigated"), "NotMitigatedNotResolved": threat_summary.get("notMitigatedNotResolved"), "NotResolved": threat_summary.get("notResolved"), "Resolved": threat_summary.get("resolved"), "SuspiciousNotMitigatedNotResolved": threat_summary.get("suspiciousNotMitigatedNotResolved"), "SuspiciousNotResolved": threat_summary.get("suspiciousNotResolved"), "Total": threat_summary.get("total"), } return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Dashboard Threat Summary", context_entries, removeNull=True, headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.Threat", outputs_key_field="ID", outputs=context_entries, raw_response=threat_summary, ) def get_threat_analysis_command(client: Client, args: dict) -> CommandResults: """ Get threat analysis """ context_entries = {} threat_id = args.get("threat_id") # Make request and get raw response threat_analysis_response = client.get_threat_analysis_request(threat_id) # Parse response into context & content entries if threat_analysis_response: agent_detection_info = threat_analysis_response.get("agentDetectionInfo", {}) agent_realtime_info = threat_analysis_response.get("agentRealtimeInfo", {}) threat_info = threat_analysis_response.get("threatInfo", {}) # Build multi-section markdown (each dict → single row table) readable_output = "" if agent_detection_info: readable_output += tableToMarkdown( "SentinelOne - Agent Detection Info", [agent_detection_info], removeNull=True, ) if agent_realtime_info: readable_output += tableToMarkdown( "SentinelOne - Agent Realtime Info", [agent_realtime_info], removeNull=True, ) if threat_info: readable_output += tableToMarkdown("SentinelOne - Threat Info", [threat_info], removeNull=True) context_entries = { "AgentDetectionInfo": agent_detection_info, "AgentRealtimeInfo": agent_realtime_info, "ThreatInfo": threat_info, } return CommandResults( readable_output=readable_output, outputs_prefix="SentinelOne.Threat", outputs_key_field="ThreatInfo.threatId", outputs=context_entries, raw_response=threat_analysis_response, ) def export_full_threat_timeline(client: Client, args: dict) -> list[CommandResults | dict]: """Retrieve the full timeline information for the threat Args: client (Client): SentinelOne API client. args (Dict[str, Any]): Command arguments from XSOAR. Returns: list[CommandResults | list]: The Command Results including the file with the retrieved timeline entries """ threat_id: str = args.get("threat_id", "") max_entries: int = arg_to_number(args.get("limit")) or 5000 timeline = client.threat_export_raw_threat_timeline(threat_id, max_entries) file_result = fileResult( filename=f"{threat_id}_timeline.json", data=json.dumps(timeline), file_type=EntryType.ENTRY_INFO_FILE, ) context_entry = {"ThreatId": threat_id, "Filename": f"{threat_id}_timeline.json"} return [ CommandResults( readable_output=tableToMarkdown("Sentinel One - Threat Timeline", context_entry, removeNull=False), outputs_prefix="SentinelOne.Export.Timeline", outputs_key_field="ThreatId", outputs=context_entry, raw_response=timeline, ), file_result, ] def export_threat_events(client: Client, args: dict[str, Any]) -> list[CommandResults | dict]: """Retrieves the events related to the Threat and returns them as a file Args: client (Client): SentinelOne API client. args (Dict[str, Any]): Command arguments from XSOAR. Returns: list[CommandResults | list]: The Command Results including the file with the retrieved events """ threat_id: str = args.get("threat_id", "") threat_events = client.threat_export_events(threat_id) file_result = fileResult( filename=f"threats_{threat_id}.json", data=json.dumps(threat_events), file_type=EntryType.ENTRY_INFO_FILE, ) context_entry = {"ThreatId": threat_id, "Filename": f"threats_{threat_id}.json"} return [ CommandResults( readable_output=tableToMarkdown("Sentinel One - Threat Events", context_entry, removeNull=False), outputs_prefix="SentinelOne.Export.Events", outputs_key_field="ThreatId", outputs=context_entry, raw_response=threat_events, ), file_result, ] # Agents Commands def list_agents_command(client: Client, args: dict) -> CommandResults: """ List all agents matching the input filter """ # Get arguments query_params = {} if args.get("params"): param_list = argToList(args.get("params", "")) for field_value in param_list: f = field_value.split("=")[0] v = field_value.split("=")[1] query_params.update({f: v}) query_params.update( assign_params( active_threats=args.get("min_active_threats"), computerName__like=args.get("computer_name"), scan_status=args.get("scan_status"), osTypes=args.get("os_type"), created_at=args.get("created_at"), limit=int(args.get("limit", 10)), ) ) # Make request and get raw response agents = client.list_agents_request(query_params) column_to_display = argToList(args.get("columns")) # Parse response into context & content entries context_entries = list(get_agents_outputs(agents, column_to_display)) if agents else None return CommandResults( readable_output=tableToMarkdown( "Sentinel One - List of Agents", context_entries, headerTransform=pascalToSpace, removeNull=True, metadata="Provides summary information and details for all the agents that matched your search criteria", ), outputs_prefix="SentinelOne.Agents", outputs_key_field="ID", outputs=context_entries, raw_response=agents, ) def get_agent_command(client: Client, args: dict) -> CommandResults: """ Get single agent via ID """ # Get arguments agent_ids = argToList(args.get("agent_id")) # Make request and get raw response agents = client.get_agent_request(agent_ids) # Parse response into context & content entries context_entries = list(get_agents_outputs(agents)) if agents else None return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Get Agent Details", context_entries, headerTransform=pascalToSpace, removeNull=True, ), outputs_prefix="SentinelOne.Agent", outputs_key_field="ID", outputs=context_entries, raw_response=agents, ) def get_agent_mac_command(client: Client, args: dict) -> CommandResults: """ Get single agent mac details via ID """ # Set req list mac_list = [] # Get arguments agent_ids = argToList(args.get("agent_id")) # Make request and get raw response agents = client.get_agent_request(agent_ids) if agents: for agent in agents: hostname = agent.get("computerName") for interface in agent.get("networkInterfaces"): int_dict = {} int_dict["hostname"] = hostname int_dict["int_name"] = interface.get("name") int_dict["agent_id"] = agent.get("id") int_dict["ip"] = interface.get("inet") int_dict["mac"] = interface.get("physical") mac_list.append(int_dict) return CommandResults( outputs_prefix="SentinelOne.MAC", outputs=mac_list, readable_output=tableToMarkdown("SentinelOne MAC Address Results", mac_list), raw_response=agents, ) def connect_agent_to_network(client: Client, args: dict) -> Union[CommandResults, str]: """ Sends a "connect to network" command to all agents matching the input filter. """ agent_ids = argToList(args.get("agent_id")) # Make request and get raw response raw_response = client.connect_to_network_request(agent_ids) agents_affected = raw_response.get("affected", 0) # Parse response into context & content entries agents = client.list_agents_request({"ids": ",".join(agent_ids)}) contents = [{"NetworkStatus": agent.get("networkStatus"), "ID": agent.get("id")} for agent in agents] contents.append({"AgentsAffected": agents_affected}) return CommandResults( readable_output=f"{agents_affected} agent(s) successfully connected to the network.", outputs_prefix="SentinelOne.Agent", outputs_key_field="ID", outputs=contents, raw_response=raw_response, ) def disconnect_agent_from_network(client: Client, args: dict) -> Union[CommandResults, str]: """ Sends a "disconnect from network" command to all agents matching the input filter. """ agent_ids = argToList(args.get("agent_id")) # Make request and get raw response raw_response = client.disconnect_from_network_request(agent_ids) agents_affected = raw_response.get("affected", 0) agents = client.list_agents_request({"ids": ",".join(agent_ids)}) contents = [{"NetworkStatus": agent.get("networkStatus"), "ID": agent.get("id")} for agent in agents] return CommandResults( readable_output=f"{agents_affected} agent(s) successfully disconnected from the network.", outputs_prefix="SentinelOne.Agent", outputs_key_field="ID", outputs=contents, raw_response=raw_response, ) def broadcast_message(client: Client, args: dict) -> CommandResults: """ Broadcasts a message to all agents matching the input filter. """ context = {} message = args.get("message") filters = assign_params( isActive=argToBoolean(args.get("active_agent", "false")), groupIds=argToList(args.get("group_id")), ids=argToList(args.get("agent_id")), domains=argToList(args.get("domain")), ) response = client.broadcast_message_request(message, filters) agents_affected = response.get("affected", 0) context = {"Affected": agents_affected} if agents_affected > 0: meta = "The message was successfully delivered to the agent(s)" else: meta = "No messages were sent. Verify that the inputs are correct." return CommandResults( readable_output=tableToMarkdown("Sentinel One - Broadcast Message", context, metadata=meta, removeNull=True), outputs_prefix="SentinelOne.BroadcastMessage", outputs_key_field="Affected", outputs=context, raw_response=response, ) def shutdown_agents(client: Client, args: dict) -> str: """ Sends a shutdown command to all agents matching the input filter """ query = args.get("query", "") agent_id = argToList(args.get("agent_id")) group_id = argToList(args.get("group_id")) if not (agent_id or group_id): raise DemistoException("Expecting at least one of the following arguments to filter by: agent_id, group_id.") response = client.shutdown_agents_request(query, agent_id, group_id) affected_agents = response.get("affected", 0) if affected_agents > 0: return f"Shutting down {affected_agents} agent(s)." return "No agents were shutdown." def uninstall_agent(client: Client, args: dict) -> CommandResults: """ Sends an uninstall command to all agents matching the input filter. """ context = {} query = args.get("query", "") agent_id = argToList(args.get("agent_id")) group_id = argToList(args.get("group_id")) if not (agent_id or group_id): raise DemistoException("Expecting at least one of the following arguments to filter by: agent_id, group_id.") response = client.uninstall_agent_request(query, agent_id, group_id) affected_agents = response.get("affected", 0) context = {"Affected": affected_agents} meta = f"Uninstall was sent to {affected_agents} agent(s)." if affected_agents > 0 else "No agents were affected." return CommandResults( readable_output=tableToMarkdown("Sentinel One - Uninstall Agent", context, metadata=meta, removeNull=True), outputs_prefix="SentinelOne.uninstall", outputs_key_field="Affected", outputs=context, raw_response=response, ) # Event Commands def create_query(client: Client, args: dict) -> CommandResults: query = args.get("query") from_date = args.get("from_date") to_date = args.get("to_date") query_id = client.create_query_request(query, from_date, to_date) context_entries = { "Query": query, "FromDate": from_date, "ToDate": to_date, "QueryID": query_id, } return CommandResults( readable_output=f"The query ID is {query_id}", outputs_prefix="SentinelOne.Query", outputs_key_field="QueryID", outputs=context_entries, raw_response=query_id, ) def get_dv_query_status(client: Client, args: dict) -> CommandResults: query_id = args.get("query_id") status = client.create_status_request(query_id) status["QueryID"] = query_id return CommandResults( readable_output=tableToMarkdown("SentinelOne Query Status", [status]), outputs_prefix="SentinelOne.Query.Status", outputs_key_field="QueryID", outputs=status, raw_response=status, ) def get_events(client: Client, args: dict) -> Union[CommandResults, str]: """ Get all Deep Visibility events from query """ contents = [] event_standards = [] query_id = args.get("query_id") limit = int(args.get("limit", 50)) cursor = args.get("cursor", None) column_to_display = argToList(args.get("columns")) events, pagination = client.get_events_request(query_id, limit, cursor) context = {} if pagination and pagination.get("nextCursor") is not None: demisto.results("Use the below cursor value to get the next page events \n {}".format(pagination["nextCursor"])) context.update({"SentinelOne.Cursor.Event": pagination["nextCursor"]}) for event in events: contents.append( { "EventType": event.get("eventType"), "Endpoint": event.get("agentName"), "SiteName": event.get("siteName"), "User": event.get("user"), "Time": event.get("processStartTime"), "AgentOS": event.get("agentOs"), "ProcessID": event.get("pid"), "ProcessUID": event.get("srcProcUid") if IS_VERSION_2_1 else event.get("processUniqueKey"), "ProcessName": event.get("processName"), "FilePath": event.get("fileFullName"), "IPAddress": event.get("agentIp"), "MD5": event.get("md5"), "SHA256": event.get("sha256"), "SourceIP": event.get("srcIp"), "SourcePort": event.get("srcPort"), "DestinationIP": event.get("dstIp"), "DestinationPort": event.get("dstPort"), "SourceProcessUser": event.get("srcProcUser"), "SourceProcessCommandLine": event.get("srcProcCmdLine"), "DNSRequest": event.get("dnsRequest"), "FileFullName": event.get("fileFullName"), "EventTime": event.get("eventTime"), "EventID": event.get("id"), } ) for c in set(column_to_display).intersection(event.keys()): contents[-1][c] = event[c] event_standards.append( { "Type": event.get("eventType"), "Name": event.get("processName"), "ID": event.get("pid"), } ) # using the CommandResults.to_context in order to get the correct outputs key context.update( CommandResults( outputs_prefix="SentinelOne.Event", outputs_key_field=["ProcessID", "EventID"], outputs=contents, ) .to_context() .get("EntryContext", {}) ) context.update({"Event(val.ID && val.ID === obj.ID)": event_standards}) return CommandResults( readable_output=tableToMarkdown("SentinelOne Events", contents, removeNull=True), outputs=context, raw_response=events, ) def get_processes(client: Client, args: dict) -> CommandResults: """ Get Deep Visibility events from query by event type - process """ contents = [] query_id = args.get("query_id") limit = int(args.get("limit", 50)) processes = client.get_processes_request(query_id, limit) for process in processes: contents.append( { "EventType": process.get("eventType"), "Endpoint": process.get("agentName"), "SiteName": process.get("siteName"), "User": process.get("user"), "Time": process.get("processStartTime"), "ParentProcessID": process.get("parentPid"), "ParentProcessUID": process.get("parentProcessUniqueKey"), "ParentProcessName": process.get("parentProcessName"), "ProcessID": process.get("pid"), "ProcessUID": process.get("srcProcUid") if IS_VERSION_2_1 else process.get("processUniqueKey"), "ProcessName": process.get("processName"), "ProcessDisplayName": process.get("processDisplayName"), "SHA1": process.get("processImageSha1Hash"), "CMD": process.get('"processCmd'), "SubsystemType": process.get("processSubSystem"), "IntegrityLevel": process.get("processIntegrityLevel"), "ParentProcessStartTime": process.get("parentProcessStartTime"), } ) return CommandResults( readable_output=tableToMarkdown("SentinelOne Processes", contents, removeNull=True), outputs_prefix="SentinelOne.Event", outputs_key_field="ProcessID", outputs=contents, raw_response=processes, ) # Blocklist commands def add_hash_to_blocklist(client: Client, args: dict) -> CommandResults: """ Add a hash (SHA1 and/or SHA256) to the blocklist (SentinelOne Term: Blacklist) """ sha1 = args.get("sha1") sha256 = args.get("sha256Value") if not sha1 and not sha256: raise DemistoException("You must specify at least one valid SHA1 or SHA256 hash") # Build hash string for readable_output if sha1 and sha256: hash_str = f"sha1={sha1}, sha256={sha256}" elif sha1: hash_str = f"sha1={sha1}" elif sha256: hash_str = f"sha256={sha256}" else: hash_str = "no hash provided" # Combine block_site_ids from integration params with site_ids from command args block_site_ids = client.block_site_ids or [] site_ids_arg = argToList(args.get("site_ids")) if args.get("site_ids") else [] combined_site_ids = list(set(block_site_ids + site_ids_arg)) site_ids_str = ",".join(combined_site_ids) if combined_site_ids else None group_ids = args.get("group_ids") account_ids = args.get("account_ids") try: # Scoped request if any scope provided if site_ids_str or group_ids or account_ids: scope_map = { "site_ids": ("site", site_ids_str), "group_ids": ("group", group_ids), "account_ids": ("account", account_ids), } scope_parts = [f"{label}: {value}" for key, (label, value) in scope_map.items() if value] scope_str = ", ".join(scope_parts) if scope_parts else "unknown" demisto.debug(f"Adding {hash_str} to blocklist with scopes: {scope_str}") result = client.add_hash_to_blocklists_request( value=sha1, sha256Value=sha256, description=args.get("description"), os_type=args.get("os_type"), site_ids=site_ids_str, group_ids=group_ids, account_ids=account_ids, source=args.get("source"), ) status = { "hash": sha1 or sha256, "status": f"Added to {scope_str} blocklist", } if sha1: status["sha1"] = sha1 if sha256: status["sha256"] = sha256 # Add scope info dynamically if site_ids_str: status["site_ids"] = site_ids_str if group_ids: status["group_ids"] = group_ids if account_ids: status["account_ids"] = account_ids else: # Global blocklist demisto.debug(f"Adding {hash_str} to global blocklist") result = client.add_hash_to_blocklist_request( value=sha1, sha256Value=sha256, description=args.get("description"), os_type=args.get("os_type"), source=args.get("source"), ) status = {"hash": sha1 or sha256, "status": "Added to global blocklist"} if sha1: status["sha1"] = sha1 if sha256: status["sha256"] = sha256 except DemistoException as e: # When adding a hash to the blocklist that is already on the blocklist, # SentinelOne returns an error code, resuliting in the request raising an exception # # This section examines the error code returned. If the error is due to the hash # already being on the list, it is ignored and the returned status is updated js = e.res.json() errors = js.get("errors") if errors and len(errors) == 1: error = errors[0] code = error.get("code") title = error.get("title") detail = error.get("detail", "") if code == 4000030 and title == "Already Exists Error": status = {"hash": sha1 or sha256, "status": "Already on blocklist"} if sha1: status["sha1"] = sha1 if sha256: status["sha256"] = sha256 result = js elif code == 4000010 and title == "Validation Error": status = { "hash": sha1 or sha256, "status": f"Error: Invalid siteId - {detail}", } if sha1: status["sha1"] = sha1 if sha256: status["sha256"] = sha256 result = js else: raise e else: raise e readable_output = f"{hash_str}: {status['status']}." return CommandResults( readable_output=readable_output, outputs_prefix="SentinelOne.AddHashToBlocklist", outputs_key_field="Value", # `status` instead of `result` because we modify status based on the error/exception comments above outputs=status, raw_response=result, ) def get_hash_ids_from_blocklist( client: Client, hash_value: str, os_type: str = None, site_ids: str = None, group_ids: str = None, account_ids: str = None, ) -> list[str | None]: """ Return the IDs of the hash from the blocklist. Helper function for remove_hash_from_blocklist A hash (SHA1 or SHA256) can occur more than once if it is blocked on more than one platform (Windows, MacOS, Linux) """ ret: list = [] # Combine block_site_ids from integration params with site_ids from function argument block_site_ids = client.block_site_ids or [] site_ids_arg = argToList(site_ids) if site_ids else [] combined_site_ids = list(set(block_site_ids + site_ids_arg)) site_ids_str = ",".join(combined_site_ids) if combined_site_ids else None if site_ids_str or group_ids or account_ids: PAGE_SIZE = 20 site_ids = site_ids_str group_ids = group_ids account_ids = account_ids block_list = client.get_blocklist_request( tenant=False, skip=0, limit=PAGE_SIZE, os_type=os_type, site_ids=site_ids, group_ids=group_ids, account_ids=account_ids, # Sort by updatedAt to ensure the most recent entries are returned first # This is important because the blocklist can have multiple entries for the same hash sort_by="updatedAt", sort_order="asc", value_contains=hash_value, ) else: PAGE_SIZE = 4 block_list = client.get_blocklist_request( tenant=True, skip=0, limit=PAGE_SIZE, os_type=os_type, sort_by="updatedAt", sort_order="asc", value_contains=hash_value, ) # Validation check first if len(block_list) > 3: raise DemistoException("Received more than 3 results when querying by hash. This condition should not occur") for block_entry in block_list: # Second validation. E.g. if user passed in a hash value shorter than SHA1/SHA256 length candidates = [ block_entry.get("value"), block_entry.get("sha256Value"), ] for candidate in candidates: if candidate and candidate.lower() == hash_value.lower(): ret.append(block_entry.get("id")) return ret def remove_hash_from_blocklist(client: Client, args: dict) -> CommandResults: """ Remove a hash from the blocklist (SentinelOne Term: Blacklist) """ sha1 = args.get("sha1") sha256 = args.get("sha256Value") os_type = args.get("os_type", None) site_ids = args.get("site_ids") group_ids = args.get("group_ids") account_ids = args.get("account_ids") hash_ids = [] # Separate lists: one for context (raw hashes), one for War Room readability hash_values = [] readable_labels = [] try: if sha1: hash_values.append(sha1) ids = get_hash_ids_from_blocklist(client, sha1, os_type, site_ids, group_ids, account_ids) hash_ids.extend(ids) readable_labels.append(f"sha1={sha1}") if sha256: hash_values.append(sha256) ids = get_hash_ids_from_blocklist(client, sha256, os_type, site_ids, group_ids, account_ids) hash_ids.extend(ids) readable_labels.append(f"sha256={sha256}") if not hash_ids: status = {"hash": ", ".join(hash_values), "status": "Not on blocklist"} result = None else: result = [] numRemoved = 0 for hash_id in set(hash_ids): numRemoved += 1 result.append(client.remove_hash_from_blocklist_request(hash_id=hash_id)) status = { "hash": ", ".join(hash_values), "status": f"Removed {numRemoved} entries from blocklist", } except DemistoException as e: # Handle validation error for invalid siteId (4000010 error code) js = e.res.json() errors = js.get("errors") if ( errors and len(errors) == 1 and (error := errors[0]).get("code") == 4000010 and error.get("title") == "Validation Error" ): status = { "hash": ", ".join(hash_values), "status": f"Error: Invalid siteId - {error.get('detail')}", } result = js else: # Reraise the exception if it's not the expected validation error raise e return CommandResults( readable_output=f"{', '.join(readable_labels)}: {status['status']}.", outputs_prefix="SentinelOne.RemoveHashFromBlocklist", outputs_key_field="Value", outputs=status, raw_response=result, ) def get_blocklist(client: Client, args: dict) -> CommandResults: """ Retrieve the blocklist (SentinelOne Term: Blacklist) """ tenant_str = args.get("global", "false") tenant = tenant_str.lower() == "true" sort_by = "updatedAt" sort_order = "desc" offset = arg_to_number(int(args.get("offset", "0"))) limit = arg_to_number(int(args.get("limit", "100"))) group_ids = args.get("group_ids", None) site_ids = args.get("site_ids", None) account_ids = args.get("account_ids", None) # Accept legacy 'hash' argument, fallback to sha1 or sha256 legacy_hash = args.get("hash") sha1 = args.get("sha1") sha256 = args.get("sha256Value") value_contains = legacy_hash or sha1 or sha256 contents = [] block_list = client.get_blocklist_request( tenant=tenant, group_ids=group_ids, site_ids=site_ids, account_ids=account_ids, skip=offset, limit=limit, sort_by=sort_by, sort_order=sort_order, value_contains=value_contains, ) def get_hash_from_block_entry(block: dict) -> str | None: return block.get("value") or block.get("sha256Value") for block in block_list: contents.append( { "CreatedAt": block.get("createdAt"), "Description": block.get("description"), "ID": block.get("id"), "OSType": block.get("osType"), "ScopeName": block.get("scopeName"), "ScopePath": block.get("scopePath"), "Source": block.get("source"), "Type": block.get("type"), "UpdatedAt": block.get("updatedAt"), "UserId": block.get("userId"), "Value": get_hash_from_block_entry(block), } ) return CommandResults( readable_output=tableToMarkdown("SentinelOne Blocklist", contents, removeNull=True), outputs_prefix="SentinelOne.Blocklist", outputs_key_field="Value", outputs=contents, raw_response=block_list, ) # File Fetch Commands def fetch_file(client: Client, args: dict) -> str: """ Initiate a file fetch request on an agent """ agent_id = args.get("agent_id") file_path = args.get("file_path") password = args.get("password") client.fetch_file_request(agent_id, file_path, password) return f"Intiated fetch-file action for {file_path} on Agent {agent_id}" def extract_sentinelone_zip_file(zip_file_data: bytes, password: str) -> tuple[str, bytes]: """ Helper funciton for `download_fetched_file` """ file_archive = io.BytesIO(zip_file_data) zip_file = zipfile.ZipFile(file_archive) # Each .zip file returned by SentinelOne has a manifest.json file. Then it # Re-creates the folder paths inside the zip, and stores the collected file # (e.g. C/path/to/file.txt inside the zip) # # We assume only one file was collected, since that's how our integration commands are # implemented file_names = [name for name in zip_file.namelist() if name != "manifest.json"] if len(file_names) < 1: raise DemistoException("No file found in upload from agent. Perhaps the path submitted is wrong?") file_name = file_names[0] file_data = zip_file.read(file_name, password.encode("utf-8")) return file_name, file_data def download_fetched_file(client: Client, args: dict) -> list[CommandResults]: """ Download a file that has been requested by `fetch-file` """ agent_id = args.get("agent_id") activity_id = args.get("activity_id") password = args.get("password") assert isinstance(password, str) zip_file_data = client.download_fetched_file_request(agent_id, activity_id) path, file_data = extract_sentinelone_zip_file(zip_file_data, password) return [ CommandResults( readable_output=f"Successfully downloaded file `{path}`", outputs_prefix="SentinelOne.Download", outputs_key_field="Path", outputs={"Path": path}, ), fileResult(f"{path.replace('/', '_')}", file_data), ] def get_accounts(client: Client, args: dict) -> CommandResults: """ Get accounts info (ID, etc). """ account_id = args.get("account_id", None) context_entries = [] # Make request and get raw response accounts = client.get_accounts_request(account_id) if accounts: for account in accounts: context_entries.append( { "AccountType": account.get("accountType"), "ActiveAgents": account.get("activeAgents"), "NumberOfSites": account.get("numberOfSites"), "State": account.get("state"), "CreatedAt": account.get("createdAt"), "Expiration": account.get("expiration"), "ID": account.get("id"), "Name": account.get("name"), } ) return CommandResults( readable_output=tableToMarkdown( "Sentinel One - Get Accounts Details", context_entries, headerTransform=pascalToSpace, removeNull=True, ), outputs_prefix="SentinelOne.Accounts", outputs_key_field="ID", outputs=context_entries, raw_response=accounts, ) def run_remote_script_command(client: Client, args: dict) -> CommandResults: """ Run a remote script that was uploaded to the SentinelOne Script Library """ headers = ["pendingExecutionId", "pending", "affected", "parentTaskId"] # Get arguments account_ids = argToList(args.get("account_ids")) script_id = args.get("script_id", "") output_destination = args.get("output_destination", "") task_description = args.get("task_description", "") output_directory = args.get("output_directory", "") agent_ids = argToList(args.get("agent_ids")) singularity_xdr_keyword = args.get("singularity_xdr_Keyword", "") singularity_xdr_url = args.get("singularity_xdr_Url", "") api_key = args.get("api_key", "") input_params = args.get("input_params", "") password = args.get("password", "") script_runtime_timeout_seconds = int(args.get("script_runtime_timeout_seconds", 3600)) requires_approval = argToBoolean(args.get("requires_approval", False)) run_remote_script = client.run_remote_script_request( account_ids, script_id, output_destination, task_description, output_directory, agent_ids, singularity_xdr_keyword, singularity_xdr_url, api_key, input_params, password, script_runtime_timeout_seconds, requires_approval, ) return CommandResults( readable_output=tableToMarkdown( "SentinelOne - Run Remote Script", run_remote_script, headers=headers, removeNull=True, ), outputs_prefix="SentinelOne.RunRemoteScript", outputs=run_remote_script, raw_response=run_remote_script, ) def get_remote_script_status(client: Client, args: dict) -> CommandResults: """ Get the status of a remote script's tasks. """ headers = [ "id", "createdAt", "description", "statusDescription", "parentTaskId", "accountId", "accountName", "agentId", "agentIsActive", "agentOsType", "initiatedBy", "initiatedById", ] remote_script_statuses = client.get_remote_script_status_request(**args) return CommandResults( readable_output=tableToMarkdown( "SentinelOne - Get Remote Scripts Tasks Status", remote_script_statuses, headers=headers, removeNull=True, ), outputs_prefix="SentinelOne.GetRemoteScript", outputs=remote_script_statuses, raw_response=remote_script_statuses, ) def get_remote_script_results(client: Client, args: dict) -> list[CommandResults]: """ Get the remote script results """ context_entries = [] headers = ["taskId", "fileName"] # Get arguments computer_names = argToList(args.get("computer_names")) task_ids = argToList(args.get("task_ids")) results = client.get_remote_script_results_request(computer_names, task_ids) file_results = [] for result in results: if result.get("downloadUrl", ""): response = requests.get(url=result.get("downloadUrl")) zip_file_data = response.content file_results.append( fileResult( filename=result.get("fileName", ""), data=zip_file_data, file_type=EntryType.ENTRY_INFO_FILE, ) ) context_entries.append( { "taskId": result.get("taskId"), "fileName": result.get("fileName"), "downloadUrl": result.get("downloadUrl"), } ) return [ CommandResults( readable_output=tableToMarkdown( "SentinelOne - Get Remote Scripts Results", results, headers=headers, removeNull=True, ), outputs_prefix="SentinelOne.RemoteScriptResults", outputs_key_field="taskId", outputs=context_entries, raw_response=results, ), *file_results, ] def run_polling_command(client: Client, cmd: str, args: Dict[str, Any]): """ This command is combination of the **Run Remote Script**, **Remote Script Status Check**, and **Remote Script Results** commands. The polling command continuously polls the data until the status of the executed remote script is marked as complete, and then it returns the results of that remote script. Args: cmd (str): The command name. client (Client): SentinelOne API client. args (Dict[str, Any]): Command arguments from XSOAR. Returns: CommandResults: outputs, readable outputs and raw response for XSOAR. """ ScheduledCommand.raise_error_if_not_supported() interval = int(args.get("interval", 60)) timeout = int(args.get("timeout", 600)) if "parent_task_id" not in args: command_results = run_remote_script_command(client, args) output = command_results.raw_response if isinstance(output, dict): parent_task_id = output.get("parentTaskId") args["parent_task_id"] = parent_task_id scheduled_command = ScheduledCommand( command=cmd, next_run_in_seconds=interval, args=args, timeout_in_seconds=timeout, ) command_results.scheduled_command = scheduled_command return command_results parent_task_id = args.get("parent_task_id") status_args = {"parent_task_id": parent_task_id} status_check_command_results = get_remote_script_status(client, status_args) status_outputs = status_check_command_results.raw_response script_completed = False task_ids = [] if status_outputs and isinstance(status_outputs, list): for output in status_outputs: # Check if the script status is completed, and continue the loop if isinstance(output, dict) and output.get("status") == "completed": task_ids.append(output.get("id")) script_completed = True # Check if the script status is not completed, if not completed will break loop. # And mark the script_completed flag to False, so that the command rescheduled. if isinstance(output, dict) and output.get("status") != "completed": script_completed = False break if script_completed: results_args = {"task_ids": task_ids} final_command_results = get_remote_script_results(client, results_args) return final_command_results else: scheduled_command = ScheduledCommand( command=cmd, next_run_in_seconds=interval, args=args, timeout_in_seconds=timeout, ) return CommandResults(scheduled_command=scheduled_command) def remote_script_automate_results(client: Client, args: dict): return run_polling_command(client=client, cmd="sentinelone-remote-script-automate-results", args=args) def get_columns_from_result(columns: list): return [column["name"] for column in columns if column.get("name")] def get_power_query_output(cmd: str, interval: int, timeout: int, args: dict, query_response: dict): """ This method checks if the status of the Power Query results is finished. If it is finished, it will return the results; otherwise, it will call the schedule command. """ if query_response.get("status") == "FINISHED" and query_response.get("progress") == 100: headers = get_columns_from_result(query_response.get("columns", [])) context_entries = [dict(zip(headers, row)) for row in query_response.get("data", [])] readable_text = f"SentinelOne - Get Power Query Results for ID {query_response.get('queryId', '')}" recommendations = query_response.get("recommendations", []) if recommendations and len(recommendations) >= 1: recommendation = recommendations[0] readable_text += f"\nRecommendation: {str(recommendation)}" return CommandResults( readable_output=tableToMarkdown( readable_text, context_entries, removeNull=True, metadata="\nSummary information and details about the power query", headerTransform=pascalToSpace, ), outputs_prefix="SentinelOne.PowerQuery", outputs=context_entries, raw_response=query_response, ) else: scheduled_command = ScheduledCommand( command=cmd, next_run_in_seconds=interval, args=args, timeout_in_seconds=timeout, ) return CommandResults(scheduled_command=scheduled_command) def poll_power_query_results(client: Client, cmd: str, args: dict) -> CommandResults: """ This command polls the Power Query results when the status is 'finished'. If the status is not 'finished', it will continue to schedule the command next time it will ping Power Query and return the results once the status is 'finished'. Otherwise, it will schedule the command according to the specified interval. Args: cmd (str): The command name. client (Client): SentinelOne API client. args (Dict[str, Any]): Command arguments from XSOAR. Returns: CommandResults: outputs, readable outputs and raw response for XSOAR. """ ScheduledCommand.raise_error_if_not_supported() interval = int(args.get("interval", 10)) timeout = int(args.get("timeout", 300)) # Get arguments account_ids = argToList(args.get("account_ids")) site_ids = account_ids = argToList(args.get("site_ids")) limit = args.get("limit") query = args.get("query", "") from_date = args.get("from_date", "") to_date = args.get("to_date", "") if "query_id" not in args: power_query_response = client.get_power_query_request(account_ids, site_ids, query, from_date, to_date, limit) if isinstance(power_query_response, dict): args["query_id"] = power_query_response.get("queryId", "") return get_power_query_output(cmd, interval, timeout, args, power_query_response) query_id = args.get("query_id", "") ping_power_query_response = client.get_ping_power_query_request(query_id) return get_power_query_output(cmd, interval, timeout, args, ping_power_query_response) def get_power_query_results(client: Client, args: dict): return poll_power_query_results(client=client, cmd="sentinelone-get-power-query-results", args=args) def list_installed_singu_mark_apps_command(client: Client, args: dict) -> CommandResults: """ List all installed applications matching the input filter """ installed_applications = [] # Get arguments query_params = assign_params( accountIds=argToList(args.get("account_ids")), applicationCatalogId=args.get("application_catalog_id"), creator__contains=args.get("creator_contains"), id=argToList(args.get("ids")), limit=1000, name__contains=args.get("name_contains"), siteIds=argToList(args.get("site_ids")), ) # Make request and get raw response installed_applications_page, pagination = client.list_installed_applications_request(query_params) installed_applications.extend(installed_applications_page) while pagination and pagination.get("nextCursor"): demisto.debug("Got the next page for installed applications \n {}".format(pagination["nextCursor"])) query_params["cursor"] = pagination["nextCursor"] # The SentinelOne API does not accept other pagination parameters if the cursor is provided in the query. # Including additional pagination parameters alongside the cursor will result in a 400 error. # So removing the limit from the query params if query_params.get("limit"): del query_params["limit"] installed_applications_page, pagination = client.list_installed_applications_request(query_params) installed_applications.extend(installed_applications_page) all_scopes = [] if installed_applications: for each_app in installed_applications: for scope in each_app.get("scopes", []): scope["applicationCatalogId"] = each_app["applicationCatalogId"] scope["applicationCatalogName"] = each_app["name"] all_scopes.append(scope) meta = "Provides summary information and details for all the installed applications that matched specified filter values" else: meta = "The search filters provided are returning no results. Please review and adjust them accordingly." context_entries = [] for each_scope in all_scopes: entry = { "ID": each_scope.get("id"), "Account": each_scope.get("account"), "AccountId": each_scope.get("accountId"), "ApplicationCatalogId": each_scope.get("applicationCatalogId"), "ApplicationCatalogName": each_scope.get("applicationCatalogName"), "AlertMessage": each_scope.get("alertMessage"), "CreatedAt": each_scope.get("createdAt"), "Creator": each_scope.get("creator"), "CreatorId": each_scope.get("creatorId"), "DesiredStatus": each_scope.get("desiredStatus"), "HasAlert": each_scope.get("hasAlert"), "LastEntityCreatedAt": each_scope.get("lastEntityCreatedAt"), "Modifier": each_scope.get("modifier"), "ModifierId": each_scope.get("modifierId"), "ScopeId": each_scope.get("scopeId"), "ScopeLevel": each_scope.get("scopeLevel"), "Status": each_scope.get("status"), "UpdatedAt": each_scope.get("updatedAt"), "ApplicationInstanceName": each_scope.get("applicationInstanceName"), } context_entries.append(entry) return CommandResults( readable_output=tableToMarkdown( "SentinelOne - List of Installed Applications", context_entries, headerTransform=pascalToSpace, removeNull=True, metadata=meta, ), outputs_prefix="SentinelOne.InstalledApps", outputs_key_field="ID", outputs=context_entries, raw_response=installed_applications, ) def get_service_users_command(client: Client, args: dict) -> CommandResults: """ Get all service users matching the input filter """ service_users = [] # Get arguments query_params = assign_params( accountIds=argToList(args.get("account_ids")), roleIds=argToList(args.get("role_ids")), ids=args.get("ids"), limit=1000, siteIds=argToList(args.get("site_ids")), ) # Make request and get raw response service_users_page, pagination = client.get_service_users_request(query_params) service_users.extend(service_users_page) while pagination and pagination.get("nextCursor"): demisto.debug("Got the next page for service users \n {}".format(pagination["nextCursor"])) query_params["cursor"] = pagination["nextCursor"] # The SentinelOne API does not accept other pagination parameters if the cursor is provided in the query. # Including additional pagination parameters alongside the cursor will result in a 400 error. # So removing the limit from the query params if query_params.get("limit"): del query_params["limit"] service_users_page, pagination = client.get_service_users_request(query_params) service_users.extend(service_users_page) context_entries = [] if service_users: for each_service_user in service_users: entry = { "ID": each_service_user.get("id"), "ApiTokenCreatedAt": each_service_user.get("apiToken", {}).get("createdAt"), "ApiTokenExpiresAt": each_service_user.get("apiToken", {}).get("expiresAt"), "CreatedAt": each_service_user.get("createdAt"), "CreatedById": each_service_user.get("createdBy", {}).get("id"), "CreatedByName": each_service_user.get("createdBy", {}).get("name"), "Description": each_service_user.get("description"), "LastActivation": each_service_user.get("lastActivation"), "Name": each_service_user.get("name"), "Scope": each_service_user.get("scope"), "UpdatedAt": each_service_user.get("updatedAt"), "UpdatedById": each_service_user.get("updatedBy", {}).get("id"), "UpdatedByName": each_service_user.get("updatedBy", {}).get("name"), } if each_service_user.get("scopeRoles") and len(each_service_user.get("scopeRoles")) > 0: scope_role_items = each_service_user["scopeRoles"][0] if scope_role_items: entry["ScopeRolesRoleId"] = scope_role_items.get("roleId") entry["ScopeRolesRoleName"] = scope_role_items.get("roleName") entry["ScopeRolesAccountName"] = scope_role_items.get("accountName") entry["ScopeRolesId"] = scope_role_items.get("id") context_entries.append(entry) meta = "Provides summary information and details for all the service users that matched specified filter values" else: meta = "The search filters provided are returning no results. Please review and adjust them accordingly." return CommandResults( readable_output=tableToMarkdown( "SentinelOne - Get Service Users", context_entries, headerTransform=pascalToSpace, removeNull=True, metadata=meta, ), outputs_prefix="SentinelOne.ServiceUsers", outputs_key_field="ID", outputs=context_entries, raw_response=service_users, ) def get_mapping_fields_command(): """ Returns the list of fields to map in outgoing mirroring, for incidents. Supports both THREAT and UAM_ALERT incidents under SentinelOne Incident type. """ mapping_response = GetMappingFieldsResponse() incident_type_scheme = SchemeTypeMapping(type_name="SentinelOne Incident") for argument, description in SENTINELONE_INCIDENT_OUTGOING_ARGS.items(): incident_type_scheme.add_field(name=argument, description=description) mapping_response.add_scheme_type(incident_type_scheme) return mapping_response def _is_uam_alert(mirrored_object: dict) -> bool: """Returns True if the mirrored object represents a UAM_ALERT (has 'node' key), False for THREAT.""" return "node" in mirrored_object def set_xsoar_incident_entries( mirrored_object: dict, entries: list, remote_incident_id: str, close_xsoar_incident: bool, ): incident_type = "UAM_ALERT" if _is_uam_alert(mirrored_object) else "THREAT" demisto.debug( f"set_xsoar_incident_entries: id={remote_incident_id}, type={incident_type}, close_enabled={close_xsoar_incident}" ) if not _is_uam_alert(mirrored_object): # THREAT logic threat_status = mirrored_object.get("threatInfo", {}).get("incidentStatus") demisto.debug(f"THREAT incident status: {threat_status}") if threat_status == "resolved" and close_xsoar_incident: demisto.debug(f"THREAT incident is resolved, closing XSOAR incident: {remote_incident_id}") entries.append( { "Type": EntryType.NOTE, "Contents": { "dbotIncidentClose": True, "closeReason": "Incident was closed on SentinelOne", }, "ContentsFormat": EntryFormat.JSON, } ) return entries elif threat_status in (set(INCIDENT_STATUS) - {"resolved"}) and close_xsoar_incident: demisto.debug(f"THREAT incident is reopened (status={threat_status}), reopening XSOAR incident: {remote_incident_id}") entries.append( { "Type": EntryType.NOTE, "Contents": {"dbotIncidentReopen": True}, "ContentsFormat": EntryFormat.JSON, } ) return entries else: demisto.debug( f"THREAT incident no close/reopen action needed (status={threat_status}, close_enabled={close_xsoar_incident})" ) return [] else: # UAM_ALERT logic — status is already normalized to display labels by get_remote_incident_data node = mirrored_object.get("node", {}) status = node.get("status") demisto.debug(f"UAM_ALERT node status: {status}") if status == "Resolved" and close_xsoar_incident: demisto.debug(f"UAM_ALERT is resolved, closing XSOAR incident: {remote_incident_id}") entries.append( { "Type": EntryType.NOTE, "Contents": { "dbotIncidentClose": True, "closeReason": "UAM Alert was closed on SentinelOne", }, "ContentsFormat": EntryFormat.JSON, } ) return entries elif status in ("New", "In progress") and close_xsoar_incident: demisto.debug(f"UAM_ALERT is active (status={status}), reopening XSOAR incident: {remote_incident_id}") entries.append( { "Type": EntryType.NOTE, "Contents": {"dbotIncidentReopen": True}, "ContentsFormat": EntryFormat.JSON, } ) return entries else: demisto.debug(f"UAM_ALERT no close/reopen action needed (status={status}, close_enabled={close_xsoar_incident})") return [] def _process_uam_actions(actions_data: list, incident_id: str, operation: str) -> None: """Process UAM GraphQL mutation action results and log outcomes.""" for uam_action in actions_data: if not isinstance(uam_action, dict): continue success_items = uam_action.get("success", []) if success_items: demisto.debug(f"UAM_ALERT {operation} updated successfully for [{incident_id}], " f"affected IDs: {success_items}") else: failures = uam_action.get("failure", []) if failures: failure_msg = ( failures[0].get("errorMessage", "Unknown error") if isinstance(failures[0], dict) else str(failures[0]) ) demisto.debug(f"UAM_ALERT {operation} update failed for [{incident_id}]: {failure_msg}") else: demisto.debug(f"UAM_ALERT {operation} update for [{incident_id}]: no success or failure items returned") def update_remote_incident( client: Client, incident_id: str, incident_type: str, sentinelone_analyst_verdict: str = None, sentinelone_threat_status: str = None, sentinelone_uam_status: str = None, sentinelone_uam_analyst_verdict: str = None, closing_notes: str = "", ): """ Update remote incident in SentinelOne based on incident type Args: client: SentinelOne API client incident_id: The remote incident ID (threat ID or UAM alert ID) incident_type: Type of incident ("THREAT" or "UAM_ALERT") sentinelone_analyst_verdict: Analyst verdict for THREAT incidents sentinelone_threat_status: Threat status for THREAT incidents sentinelone_uam_status: UAM alert status for UAM_ALERT incidents sentinelone_uam_analyst_verdict: UAM analyst verdict for UAM_ALERT incidents closing_notes: Notes for closing the incident """ if incident_type == "THREAT": # Handle THREAT incident updates if sentinelone_analyst_verdict: action = ANALYST_VERDICT.get(sentinelone_analyst_verdict, None) if action: response = client.update_threat_analyst_verdict_request(threat_ids=argToList(incident_id), action=action) if response.get("affected") and int(response.get("affected")) > 0: demisto.debug( f"Successfully updated the threat analyst verdict of incident with remote ID [{incident_id}] to {action}" ) note = f"XSOAR - Updated the threat analyst verdict to {sentinelone_analyst_verdict}" client.write_threat_note_request(threat_ids=argToList(incident_id), note=note) else: demisto.debug(f"Unable to update the analyst verdict of incident with remote ID [{incident_id}]") if sentinelone_threat_status: action = THREAT_STATUS.get(sentinelone_threat_status, None) if action == "resolved": response = client.update_threat_status_request(threat_ids=argToList(incident_id), status=action) if response.get("affected") and int(response.get("affected")) > 0: demisto.debug( f"Successfully updated the threat status of incident with remote ID " f"[{incident_id}] and marked as resolved" ) note = "XSOAR - Marked as resolved \n" + closing_notes client.write_threat_note_request(threat_ids=argToList(incident_id), note=note) else: demisto.debug(f"Unable to Mark as resolved an incident with remote ID [{incident_id}]") if action != "resolved" and action is not None: response = client.update_threat_status_request(threat_ids=argToList(incident_id), status=action) if response.get("affected") and int(response.get("affected")) > 0: demisto.debug( f"Successfully updated the threat status of incident with remote ID [{incident_id}] to {action}" ) note = f"XSOAR - Updated the threat status to {sentinelone_threat_status}" client.write_threat_note_request(threat_ids=argToList(incident_id), note=note) else: demisto.debug(f"Unable to update the threat status of incident with remote ID [{incident_id}]") elif incident_type == "UAM_ALERT": demisto.debug( f"update_remote_incident: UAM_ALERT id={incident_id}, " f"uam_status={sentinelone_uam_status}, uam_verdict={sentinelone_uam_analyst_verdict}" ) if sentinelone_uam_status: status_action = UAM_ALERT_STATUS.get(sentinelone_uam_status, None) demisto.debug(f"UAM_ALERT status mapping: '{sentinelone_uam_status}' -> '{status_action}'") if status_action: try: response = client.update_uam_alert_status_request(incident_id, status_action) demisto.debug(f"UAM_ALERT status update raw response for [{incident_id}]: {response}") actions_triggered = (response or {}).get("alertTriggerActions") or {} raw_actions = actions_triggered.get("actions", []) if isinstance(actions_triggered, dict) else [] demisto.debug(f"UAM_ALERT status update actions for [{incident_id}]: {raw_actions}") if raw_actions: _process_uam_actions(raw_actions, incident_id, "status") else: demisto.debug(f"UAM_ALERT status update for [{incident_id}]: empty actions list in response") except Exception as e: demisto.debug(f"UAM_ALERT status update exception for [{incident_id}]: {str(e)}") else: demisto.debug(f"UAM_ALERT status '{sentinelone_uam_status}' not found in UAM_ALERT_STATUS mapping, skipping") if sentinelone_uam_analyst_verdict: verdict_action = UAM_ANALYST_VERDICT.get(sentinelone_uam_analyst_verdict, None) demisto.debug(f"UAM_ALERT verdict mapping: '{sentinelone_uam_analyst_verdict}' -> '{verdict_action}'") if verdict_action: try: response = client.update_uam_alert_analyst_verdict_request(incident_id, verdict_action) demisto.debug(f"UAM_ALERT verdict update raw response for [{incident_id}]: {response}") actions_triggered = (response or {}).get("alertTriggerActions") or {} raw_actions = actions_triggered.get("actions", []) if isinstance(actions_triggered, dict) else [] demisto.debug(f"UAM_ALERT verdict update actions for [{incident_id}]: {raw_actions}") if raw_actions: _process_uam_actions(raw_actions, incident_id, "verdict") else: demisto.debug(f"UAM_ALERT verdict update for [{incident_id}]: empty actions list in response") except Exception as e: demisto.debug(f"UAM_ALERT verdict update exception for [{incident_id}]: {str(e)}") else: demisto.debug( f"UAM_ALERT verdict '{sentinelone_uam_analyst_verdict}' not found in UAM_ANALYST_VERDICT mapping, skipping" ) if not sentinelone_uam_status and not sentinelone_uam_analyst_verdict: demisto.debug(f"UAM_ALERT no updates to apply for incident [{incident_id}]: both status and verdict are empty") def update_remote_system_command(client: Client, args: dict) -> str: """update-remote-system command: pushes local changes to the remote system :type client: ``Client`` :param client: XSOAR client to use :type args: ``Dict[str, Any]`` :param args: all command arguments, usually passed from ``demisto.args()``. ``args['data']`` the data to send to the remote system ``args['entries']`` the entries to send to the remote system ``args['incidentChanged']`` boolean telling us if the local incident indeed changed or not ``args['remoteId']`` the remote incident id args: A dictionary containing the data regarding a modified incident, including: data, entries, incident_changed, remote_incident_id, inc_status, delta :return: ``str`` containing the remote incident id - really important if the incident is newly created remotely :rtype: ``str`` """ parsed_args = UpdateRemoteSystemArgs(args) delta = parsed_args.delta remote_incident_id = parsed_args.remote_incident_id demisto.debug( f"update_remote_system_command: remote_id={remote_incident_id}, " f"incident_changed={parsed_args.incident_changed}, delta={delta}" ) try: if parsed_args.incident_changed: is_uam = bool( delta.get("sentineloneuamalertstatus") or delta.get("sentineloneuamalertanalystverdictmapped") or (parsed_args.data or {}).get("sentineloneuamalertid") ) demisto.debug(f"update_remote_system_command: is_uam={is_uam} for remote_id={remote_incident_id}") if is_uam: # For UAM alerts, use sentineloneuamalertid from incident data as the authoritative ID. # remote_incident_id (dbotMirrorId) can get corrupted; the custom field is reliable. uam_alert_id = (parsed_args.data or {}).get("sentineloneuamalertid") or remote_incident_id demisto.debug( f"update_remote_system_command: UAM alert ID resolved to [{uam_alert_id}] " f"(remote_id was [{remote_incident_id}])" ) sentinelone_uam_status = delta.get("sentineloneuamalertstatus", None) sentinelone_uam_analyst_verdict = delta.get("sentineloneuamalertanalystverdictmapped", None) closing_notes = delta.get("closeNotes", "") update_remote_incident( client, uam_alert_id, "UAM_ALERT", sentinelone_uam_status=sentinelone_uam_status, sentinelone_uam_analyst_verdict=sentinelone_uam_analyst_verdict, closing_notes=closing_notes, ) else: # Handle THREAT incident updates sentinelone_analyst_verdict = delta.get("sentinelonethreatanalystverdict", None) sentinelone_threat_status = delta.get("sentinelonethreatstatus", None) closing_notes = delta.get("closeNotes", "") update_remote_incident( client, remote_incident_id, "THREAT", sentinelone_analyst_verdict=sentinelone_analyst_verdict, sentinelone_threat_status=sentinelone_threat_status, closing_notes=closing_notes, ) except Exception as e: demisto.error(f"Error in SentinelOne outgoing mirror for incident {remote_incident_id}. Error message: {str(e)}") return remote_incident_id def get_remote_incident_data(client: Client, remote_incident_id: str): """ Called every time get-remote-data command runs. Gets the relevant incident entity from the remote system (SentinelOne). Supports both THREAT and UAM_ALERT incident types. SentinelOne uses distinct ID formats by design: - THREAT IDs: numeric strings (e.g. '2421825719122765698') - UAM Alert IDs: UUID strings (e.g. '019c8f6e-c443-7a6f-beeb-ed3bb438a986') We use str.isnumeric() to route directly to the correct API, avoiding a guaranteed 400 error from passing a UUID to the threats endpoint. """ demisto.debug(f"get_remote_incident_data: fetching id={remote_incident_id}") if remote_incident_id.replace("-", "").isalnum() and not remote_incident_id.isnumeric(): # UUID format — go directly to UAM_ALERT, skip threats API entirely demisto.debug(f"get_remote_incident_data: routing {remote_incident_id} as UAM_ALERT (UUID format)") uam_alert = client.get_uam_alert_by_id(remote_incident_id) if uam_alert and uam_alert.get("node"): node = uam_alert["node"] # Normalize API enum values to XSOAR display labels so the incoming mapper # writes values that match the singleSelect field's allowed options. raw_status = node.get("status", "") raw_verdict = node.get("analystVerdict", "") node["status"] = UAM_ALERT_STATUS_INCOMING.get(raw_status, raw_status) node["analystVerdict"] = UAM_ANALYST_VERDICT_INCOMING.get(raw_verdict, raw_verdict) demisto.debug( f"get_remote_incident_data: UAM alert {remote_incident_id} " f"status={raw_status!r}->{node['status']!r}, " f"analystVerdict={raw_verdict!r}->{node['analystVerdict']!r}" ) uam_alert["incident_type"] = "SentinelOne Incident" return uam_alert demisto.debug(f"UAM alert not found for {remote_incident_id}") return {} # Numeric format — fetch as THREAT demisto.debug(f"get_remote_incident_data: routing {remote_incident_id} as THREAT (numeric format)") try: mirrored_data_list = client.get_s1_threats_information(remote_incident_id) if mirrored_data_list and len(mirrored_data_list) > 0: mirrored_data = mirrored_data_list[0] mirrored_data["incident_type"] = "SentinelOne Incident" demisto.debug(f"Successfully fetched THREAT incident {remote_incident_id}") return mirrored_data demisto.debug(f"THREAT fetch returned empty for {remote_incident_id}") except Exception as e: demisto.debug(f"THREAT fetch failed for {remote_incident_id}: {str(e)}") demisto.debug(f"Could not find incident for {remote_incident_id}") return {} def get_remote_data_command(client: Client, args: dict, params: dict): """ get-remote-data command: Returns an updated remote incident. Args: args: id: incident id to retrieve. lastUpdate: when was the last time we retrieved data. Returns: GetRemoteDataResponse object, which contain the incident data to update. """ remote_args = GetRemoteDataArgs(args) remote_incident_id = remote_args.remote_incident_id mirrored_data = {} entries: list = [] try: demisto.debug( f"Performing get-remote-data command with incident id: {remote_incident_id} " f"and last_update: {remote_args.last_update}" ) # Fetch incident data (tries THREAT first, then UAM_ALERT) mirrored_data = get_remote_incident_data(client, remote_incident_id) if mirrored_data: incident_type = "UAM_ALERT" if _is_uam_alert(mirrored_data) else "THREAT" demisto.debug(f"Successfully fetched remote incident data: {incident_type} ({remote_incident_id})") # Process entries for incident close/reopen based on status changes close_xsoar_incident = demisto.params().get("close_xsoar_incident", False) entries = set_xsoar_incident_entries(mirrored_data, entries, remote_incident_id, close_xsoar_incident) else: demisto.debug(f"No incident found for incident {remote_incident_id}.") return GetRemoteDataResponse(mirrored_object=mirrored_data, entries=entries) except Exception as e: demisto.debug(f"Error fetching remote incident: {remote_incident_id}\nError message: {str(e)}") if not mirrored_data: mirrored_data = {"id": remote_incident_id} mirrored_data["in_mirror_error"] = str(e) return GetRemoteDataResponse(mirrored_object=mirrored_data, entries=entries) def get_modified_remote_data_command(client: Client, args: dict): """ Gets the modified remote incidents (both THREAT and UAM_ALERT). Args: args: last_update: the last time we retrieved modified incidents. Returns: GetModifiedRemoteDataResponse object, which contains a list of the retrieved incidents IDs. """ remote_args = GetModifiedRemoteDataArgs(args) last_update_utc = dateparser.parse(remote_args.last_update, settings={"TIMEZONE": "UTC"}) # convert to utc format assert last_update_utc is not None, f"could not parse{remote_args.last_update}" demisto.debug(f"Remote arguments last_update in UTC is {last_update_utc}") modified_ids_to_mirror = [] last_update_utc_str = last_update_utc.strftime("%Y-%m-%dT%H:%M:%S.%fZ") updated_at_timestamp = int(last_update_utc.timestamp() * 1000) # Fetch modified THREAT incidents using updatedAt raw_threats = client.get_threats_request(updated_from=last_update_utc_str, limit=1000, include_resolved_param=False) for threat in raw_threats: modified_ids_to_mirror.append(threat.get("id")) demisto.debug(f"THREAT modified: {threat.get('id')} (updatedAt: {threat.get('updatedAt', 'N/A')})") # Fetch modified UAM_ALERT incidents if configured fetch_uam_alert_type = demisto.params().get("fetch_uam_alert_type", "") if fetch_uam_alert_type: # Fetch UAM alerts for change detection using updatedAt timestamp uam_alerts, _ = client.get_uam_alerts_graphql_req( updated_at_timestamp, fetch_uam_alert_type, 1000, filter_by_updated_at=True ) for alert in uam_alerts: alert_id = alert.get("node", {}).get("id") alert_updated_at = alert.get("node", {}).get("updatedAt", "N/A") if alert_id: modified_ids_to_mirror.append(alert_id) demisto.debug(f"UAM_ALERT modified: {alert_id} (updatedAt: {alert_updated_at})") demisto.debug(f"All ids to mirror in are: {modified_ids_to_mirror}") return GetModifiedRemoteDataResponse(modified_ids_to_mirror) def get_mirroring_fields(params): """ Get tickets mirroring. """ return { "mirror_direction": MIRROR_DIRECTION.get(params.get("mirror_direction")), "mirror_instance": demisto.integrationInstance(), "incident_type": "SentinelOne Incident", } def fetch_threats(client: Client, args): incidents_threats = [] current_fetch = args.get("current_fetch") incident_statuses = args.get("fetch_threat_incident_statuses") threats = client.get_threats_request( limit=args.get("fetch_limit"), created_after=args.get("last_fetch_date_string"), site_ids=args.get("fetch_site_ids"), incident_statuses=",".join(incident_statuses).lower() if incident_statuses else None, include_resolved_param=False, ) for threat in threats: rank = threat.get("rank") threat.update(get_mirroring_fields(args)) try: rank = int(rank) except TypeError: rank = 0 # If no fetch threat rank is provided, bring everything, else only fetch above the threshold if IS_VERSION_2_1 or rank >= args.get("fetch_threat_rank"): incident = to_incident("Threat", threat) date_occurred_dt = parse(incident["occurred"]) incident_date = int(date_occurred_dt.timestamp() * 1000) if incident_date > int(args.get("last_fetch")): incidents_threats.append(incident) if incident_date > current_fetch: current_fetch = incident_date return incidents_threats, current_fetch def fetch_alerts(client: Client, args): incidents_alerts = [] current_fetch = args.get("current_fetch") query_params = assign_params( incidentStatus=",".join(args.get("fetch_incidentStatus")), createdAt__gte=args.get("last_fetch_date_string"), limit=args.get("fetch_limit"), siteIds=args.get("fetch_site_ids"), ) alerts, pagination = client.get_alerts_request(query_params) for alert in alerts: severity = alert.get("ruleInfo").get("severity") if str(severity) in args.get("fetch_severity"): incident = to_incident("Alert", alert) date_occurred_dt = parse(incident["occurred"]) incident_date = int(date_occurred_dt.timestamp() * 1000) if incident_date > args.get("last_fetch"): incidents_alerts.append(incident) if incident_date > current_fetch: current_fetch = incident_date return incidents_alerts, current_fetch def fetch_uam_alerts(client: Client, args): """ Fetch UAM alerts for polling/incident creation using createdAt filtering. This ensures new alerts are discovered during regular polling cycles. """ incidents = [] uam_current_fetch = args.get("uam_current_fetch") fetch_limit = args.get("fetch_limit") view_type = args.get("fetch_uam_alert_type") if not view_type: return [], uam_current_fetch # Use createdAt filtering for polling - catches new alerts only uam_alerts, page_info = client.get_uam_alerts_graphql_req( args.get("uam_last_fetch"), view_type, fetch_limit, filter_by_updated_at=False ) for alert in uam_alerts: alert.update(get_mirroring_fields(args)) incident = to_incident("UAM Alert", alert) date_occurred_dt = parse(incident["occurred"]) incident_date = int(date_occurred_dt.timestamp() * 1000) if incident_date > args.get("uam_last_fetch"): incidents.append(incident) if incident_date > uam_current_fetch: uam_current_fetch = incident_date return incidents, uam_current_fetch def fetch_handler(client: Client, args): last_run = demisto.getLastRun() last_fetch = last_run.get("time") uam_last_fetch = last_run.get("uam_time") fetch_type = args.get("fetch_type") fetch_uam = args.get("fetch_uam_alert_type") # Only initialize time-based timestamps for active fetch types. # This avoids freezing a stale timestamp for a fetch type that isn't enabled yet. # if the user enables it later, it will start fresh from first_fetch_time at that point. if fetch_type in ("Both", "Alerts", "Threats"): if not last_fetch: last_fetch = dateparser.parse(args.get("first_fetch_time"), settings={"TIMEZONE": "UTC"}) if not last_fetch: raise DemistoException("Please provide an initial First fetch timestamp") last_fetch = int(last_fetch.timestamp() * 1000) else: # fetch_type not active — preserve existing timestamp or default to 0 last_fetch = last_fetch or 0 if fetch_uam: if not uam_last_fetch: uam_last_fetch = dateparser.parse(args.get("first_fetch_time"), settings={"TIMEZONE": "UTC"}) if not uam_last_fetch: raise DemistoException("Please provide an initial First fetch timestamp") uam_last_fetch = int(uam_last_fetch.timestamp() * 1000) else: # UAM not active — preserve existing timestamp or default to 0 uam_last_fetch = uam_last_fetch or 0 current_fetch = last_fetch uam_current_fetch = uam_last_fetch last_fetch_date_string = timestamp_to_datestring(last_fetch, "%Y-%m-%dT%H:%M:%S.%fZ") args["last_fetch"] = last_fetch args["uam_last_fetch"] = uam_last_fetch args["last_fetch_date_string"] = last_fetch_date_string args["current_fetch"] = current_fetch args["uam_current_fetch"] = uam_current_fetch incidents = [] if fetch_type == "Both": alert_incidents, alert_current_fetch = fetch_alerts(client, args) threat_incidents, threat_current_fetch = fetch_threats(client, args) current_fetch = alert_current_fetch if alert_current_fetch > threat_current_fetch else threat_current_fetch incidents = alert_incidents + threat_incidents elif fetch_type == "Alerts": incidents, current_fetch = fetch_alerts(client, args) elif fetch_type == "Threats": incidents, current_fetch = fetch_threats(client, args) # Fetch UAM alerts independently if fetch_uam: uam_incidents, uam_current_fetch = fetch_uam_alerts(client, args) incidents += uam_incidents # Debug log if no incidents if not incidents: demisto.debug(f"[Fetch] {fetch_type=}, {fetch_uam=} -> " f"{incidents=} {current_fetch=} {uam_current_fetch=}") demisto.setLastRun({"time": current_fetch, "uam_time": uam_current_fetch}) demisto.incidents(incidents) def to_incident(type, data): if type == "Threat": # id is already at top level of threat data — XSOAR sets dbotMirrorId correctly incident_info = data.get("threatInfo", {}) if IS_VERSION_2_1 else data return { "rawJSON": json.dumps(data), "name": f"Sentinel One {type}: {incident_info.get('classification', 'Not classified')}", "occurred": incident_info.get("createdAt"), } elif type == "Alert": return { "rawJSON": json.dumps(data), "name": f"Sentinel One {type}: {data.get('ruleInfo').get('name')}", "occurred": data.get("alertInfo").get("createdAt"), } elif type == "UAM Alert": node = data.get("node", {}) raw_severity = node.get("severity", "") # Promote node.id to top level so XSOAR sets dbotMirrorId correctly. # The GraphQL edge dict has no top-level 'id'; without this dbotMirrorId gets corrupted. data["id"] = node.get("id", "") return { "rawJSON": json.dumps(data), "name": f"Sentinel One {type}: {node.get('name')}", "occurred": node.get("createdAt"), "severity": UAM_SEVERITY_MAPPING.get(raw_severity, 0), # type: ignore[assignment] "CustomFields": build_uam_custom_fields(node), # type: ignore[assignment] } return {"rawJSON": json.dumps(data)} def build_uam_custom_fields(node): account_info = node.get("realTime", {}).get("scope", {}).get("account", {}) group_info = node.get("realTime", {}).get("scope", {}).get("group", {}) site_info = node.get("realTime", {}).get("scope", {}).get("site", {}) cloud_info = node.get("detectionTime", {}).get("cloud") or {} kubernetes_info = node.get("detectionTime", {}).get("kubernetes") or {} asset_info = node.get("asset", {}) or {} return { "sentineloneaccountid": account_info.get("id", ""), "sentineloneaccountname": account_info.get("name", ""), "sentinelonegroupid": group_info.get("id", ""), "sentinelonegroupname": group_info.get("name", ""), "sentinelonesiteid": site_info.get("id", ""), "sentinelonesitename": site_info.get("name", ""), "sentineloneclassificationsource": node.get("classification", ""), "sentinelonecloudprovider": cloud_info.get("cloudProvider", ""), "sentinelonecloudprovideraccount": cloud_info.get("accountId", ""), "sentinelonecloudproviderimage": cloud_info.get("image", ""), "sentinelonecloudproviderinstanceid": cloud_info.get("instanceId", ""), "sentinelonecloudproviderinstancesize": cloud_info.get("instanceSize", ""), "sentinelonecloudproviderlocation": cloud_info.get("location", ""), "sentinelonecloudprovidernetwork": cloud_info.get("network", ""), "sentinelonecloudprovidertags": cloud_info.get("tags", []), "sentinelonekubernetescluster": kubernetes_info.get("clusterName", ""), "sentinelonekubernetescontrollerkind": kubernetes_info.get("controllerType", ""), "sentinelonekubernetescontrollerlabels": kubernetes_info.get("controllerLabels", []), "sentinelonekubernetescontrollername": kubernetes_info.get("controllerName", ""), "sentinelonekubernetesnamespacelabels": kubernetes_info.get("namespaceLabels", []), "sentinelonekubernetesnamespace": kubernetes_info.get("namespaceName", ""), "sentinelonekubernetesnodelabels": kubernetes_info.get("nodeLabels", []), "sentinelonekubernetesnode": kubernetes_info.get("nodeName", ""), "sentinelonekubernetespodlabels": kubernetes_info.get("podLabels", []), "sentinelonekubernetespod": kubernetes_info.get("podName", ""), "deviceosversion": asset_info.get("osVersion", ""), "deviceosname": asset_info.get("osType", ""), "agentversion": asset_info.get("agentVersion", ""), "deviceid": asset_info.get("agentUuid", ""), } def main(): """PARSE INTEGRATION PARAMETERS""" global IS_VERSION_2_1 params = demisto.params() token = params.get("credentials", {}).get("password") or params.get("token") if not token: raise ValueError("The API Token parameter is required.") api_version = params.get("api_version", "2.1") server = params.get("url", "").rstrip("/") base_url = urljoin(server, f"/web/api/v{api_version}/") use_ssl = not params.get("insecure", False) proxy = params.get("proxy", False) IS_VERSION_2_1 = api_version == "2.1" fetch_type = params.get("fetch_type", "Threats") first_fetch_time = params.get("fetch_time", "3 days") fetch_severity = params.get("fetch_severity", []) fetch_uam_alert_type = params.get("fetch_uam_alert_type", "") fetch_incidentStatus = params.get("fetch_incidentStatus", ["UNRESOLVED"]) fetch_threat_incident_statuses = params.get("fetch_threat_incident_statuses", ["UNRESOLVED"]) fetch_threat_rank = int(params.get("fetch_threat_rank", 0)) fetch_limit = int(params.get("fetch_limit", 10)) fetch_site_ids = params.get("fetch_site_ids", None) block_site_ids = argToList(params.get("block_site_ids")) or [] mirror_direction = params.get("mirror_direction", None) headers = { "Authorization": "ApiToken " + token if token else "ApiToken", "Content-Type": "application/json", "Accept": "application/json", } commands: Dict[str, Dict[str, Callable]] = { "common": { "sentinelone-get-activities": get_activities_command, "sentinelone-get-threats": get_threats_command, "sentinelone-mitigate-threat": mitigate_threat_command, "sentinelone-get-hash": get_hash_command, "sentinelone-get-white-list": get_white_list_command, "sentinelone-create-white-list-item": create_white_item_command, "sentinelone-get-sites": get_sites_command, "sentinelone-get-site": get_site_command, "sentinelone-reactivate-site": reactivate_site_command, "sentinelone-list-agents": list_agents_command, "sentinelone-get-agent": get_agent_command, "sentinelone-get-agent-mac": get_agent_mac_command, "sentinelone-get-groups": get_groups_command, "sentinelone-move-agent": move_agent_to_group_command, "sentinelone-delete-group": delete_group, "sentinelone-connect-agent": connect_agent_to_network, "sentinelone-disconnect-agent": disconnect_agent_from_network, "sentinelone-broadcast-message": broadcast_message, "sentinelone-get-events": get_events, "sentinelone-create-query": create_query, "sentinelone-get-dv-query-status": get_dv_query_status, "sentinelone-get-processes": get_processes, "sentinelone-shutdown-agent": shutdown_agents, "sentinelone-uninstall-agent": uninstall_agent, "sentinelone-expire-site": expire_site, "sentinelone-fetch-threat-file": fetch_threat_file, "sentinelone-get-installed-applications": get_installed_applications, "sentinelone-initiate-endpoint-scan": initiate_endpoint_scan, "sentinelone-abort-endpoint-scan": abort_endpoint_scan, "sentinelone-endpoint-fetch-logs": endpoint_fetch_logs, "get-modified-remote-data": get_modified_remote_data_command, "update-remote-system": update_remote_system_command, "sentinelone-run-powerquery": run_powerquery, }, "2.0": { "sentinelone-mark-as-threat": mark_as_threat_command, "sentinelone-resolve-threat": resolve_threat_command, "sentinelone-agent-processes": get_agent_processes, }, "2.1": { "sentinelone-threat-summary": get_threat_summary_command, "sentinelone-threat-analysis": get_threat_analysis_command, "sentinelone-update-threats-verdict": update_threat_analyst_verdict, "sentinelone-update-alerts-verdict": update_alert_analyst_verdict, "sentinelone-create-star-rule": create_star_rule, "sentinelone-get-star-rules": get_star_rule, "sentinelone-update-star-rule": update_star_rule, "sentinelone-enable-star-rules": enable_star_rules, "sentinelone-disable-star-rules": disable_star_rules, "sentinelone-delete-star-rule": delete_star_rule, "sentinelone-add-hash-to-blocklist": add_hash_to_blocklist, "sentinelone-remove-hash-from-blocklist": remove_hash_from_blocklist, "sentinelone-get-blocklist": get_blocklist, "sentinelone-fetch-file": fetch_file, "sentinelone-download-fetched-file": download_fetched_file, "sentinelone-write-threat-note": write_threat_note, "sentinelone-get-threat-notes": get_threat_notes, "sentinelone-create-ioc": create_ioc, "sentinelone-create-bulk-ioc": create_bulk_ioc, "sentinelone-delete-ioc": delete_ioc, "sentinelone-get-iocs": get_iocs, "sentinelone-create-power-query": create_power_query, "sentinelone-ping-power-query": ping_power_query, "sentinelone-update-threats-status": update_threat_status, "sentinelone-update-alerts-status": update_alert_status, "sentinelone-get-alerts": get_alerts, "sentinelone-remove-item-from-whitelist": remove_item_from_whitelist, "sentinelone-run-remote-script": run_remote_script_command, "sentinelone-get-accounts": get_accounts, "sentinelone-get-remote-script-task-status": get_remote_script_status, "sentinelone-get-remote-script-task-results": get_remote_script_results, "sentinelone-remote-script-automate-results": remote_script_automate_results, "sentinelone-get-power-query-results": get_power_query_results, "sentinelone-list-installed-singularity-marketplace-applications": list_installed_singu_mark_apps_command, "sentinelone-get-service-users": get_service_users_command, "sentinelone-threat-download-from-cloud": threat_download_from_cloud, "sentinelone-update-uam-alert-status": update_uam_alert_status, "sentinelone-update-uam-alert-verdict": update_uam_alert_analyst_verdict, "sentinelone-export-full-threat-timeline": export_full_threat_timeline, "sentinelone-export-threat-events": export_threat_events, }, "commands_with_params": { "get-remote-data": get_remote_data_command, }, "commands_without_params": { "get-mapping-fields": get_mapping_fields_command, }, } """ COMMANDS MANAGER / SWITCH PANEL """ demisto.info(f"Command being called is {demisto.command()}") command = demisto.command() try: client = Client( base_url=base_url, verify=use_ssl, headers=headers, proxy=proxy, block_site_ids=block_site_ids, ) if command == "test-module": return_results(test_module(client, params.get("isFetch"), first_fetch_time)) elif command == "fetch-incidents": if fetch_type or fetch_uam_alert_type: fetch_dict = { "fetch_type": fetch_type, "fetch_limit": fetch_limit, "first_fetch_time": first_fetch_time, "fetch_threat_rank": fetch_threat_rank, "fetch_site_ids": fetch_site_ids, "fetch_incidentStatus": fetch_incidentStatus, "fetch_threat_incident_statuses": fetch_threat_incident_statuses, "fetch_severity": fetch_severity, "fetch_uam_alert_type": fetch_uam_alert_type, "mirror_direction": mirror_direction, } return_results(fetch_handler(client, fetch_dict)) else: return_results("Please define what type to fetch. Alerts, Threats or UAM Alerts.") else: if command in commands["common"]: return_results(commands["common"][command](client, demisto.args())) elif command in commands[api_version]: return_results(commands[api_version][command](client, demisto.args())) elif command in commands["commands_with_params"]: return_results(commands["commands_with_params"][command](client, demisto.args(), params)) elif command in commands["commands_without_params"]: return_results(commands["commands_without_params"][command]()) else: raise NotImplementedError(f"The {command} command is not supported for API version {api_version}") except Exception as e: demisto.error(traceback.format_exc()) # print the traceback return_error(f"Failed to execute {command} command.\nError:\n{str(e)}") if __name__ in ["__main__", "builtin", "builtins"]: main()