Dataminr Pulse ReGenAI
Dataminr Pulse's AI-powered, real-time intelligence integrates into Cortex XSOAR workflows for faster detection and response.
Data Enrichment & Threat Intelligence · Dataminr Pulse
Details
| ID | Dataminr Pulse ReGenAI |
|---|---|
| Provider | Dataminr |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Overview
Dataminr Pulse brings the most advanced AI-powered real-time intelligence into Cortex XSOAR, easily fitting into your workflows and enabling rapid identification and mitigation of emerging threats so you can deliver faster time to detection and response.
Swiftly Close The Loop From Insight to Response
Effectively detect, prioritize and manage risk to protect your physical and digital assets with the fastest real-time alerting to discover threats as they unfold.
Broad Global Threat Coverage at Unmatched AI Speed
Dataminr has been the global leader in AI for risk detection since 2009. Dataminr Pulse is relied on by two thirds of Fortune 100 companies to inform their physical and cybersecurity operations. Every day, the Dataminr multi-modal AI platform analyzes billions of public data inputs in 105 languages from over 500K global sources including the deep and dark web, enabling you to:
- Gain real-time visibility into deep and dark web sources including markets, forums, paste sites, and ransomware group sites.
- Get first notice on emerging attacks impacting your network and third parties affecting your business.
- Detect risk at a global scale and track the emergence and global spread of vulnerabilities so you can proactively mitigate risk.
- Identify new ransomware groups and track attacks as they happen, giving you visibility of attacks impacting your or third party risk perspective.
Pulse for Cyber Risk Key Use Cases
- Cyber-Physical Convergence: Gain real-time intelligence on converged cyber and physical threats, including physical threats to IT and OT infrastructure, network and power outages, disasters, and emerging geopolitical risks.
- Vulnerability Prioritization: Prioritize patching with visibility to the entire lifecycle of a vulnerability, from pre-CVE to exploitation, while surfacing relevant vulnerabilities in your infrastructure.
- External Attack Intelligence: Mitigate risk by tracking threats to your company, subsidiaries, and 3rd parties across ransomware, APT groups, leaks, breaches, DDoS, defacement, and malware activity.
- Digital Risk Detection: Get early warnings of risk to digital assets, including leaked credentials and data, account and domain impersonation, and mentions across the surface deep and dark web.
Accelerate and Enrich SOC Workflows
- Accelerate, enrich and trigger triage with contextual intelligence
- Activate playbooks
- Improve incident investigation and response
- Support analysis and threat hunting workflows
- Determine threat identification, scoring and classification by type, severity and status
Use cases
-
Alert Ingestion
Fetches the Dataminr Alerts as an XSOAR Incident based on the configuration parameters. This will have three filters available in place.
1) Watchlist Names
2) Query
3) Alert type (severity) -
Alert Enrichment
Use playbook
Retrieve Alerts For IOCs - Dataminr Pulseto enrich XSOAR incidents using Dataminr Alerts.- This playbook requires three parameters:
1) Text to enrich
2) Number of alerts to retrieve for each indicator
3) A boolean to use configured watchlist names - This playbook will extract indicators from given text (default will be entire incident context).
- After that it will retrieve alerts for each indicator and will store those alerts into context with key
RetrievedDataminrAlerts.
- This playbook requires three parameters:
Configure Dataminr Pulse - ReGenAI in Cortex
- Navigate to Settings > Integrations > Servers & Services.
- Search for Dataminr Pulse - ReGenAI.
- Click Add instance to create and configure a new integration instance.
| Parameter | Description | Required |
|---|---|---|
| Client ID | The Client ID required to authenticate to the service. | True |
| Client Secret | The Client Secret required to authenticate to the service. | True |
| Watchlist Names | Provide the watchlist names from which to fetch the alerts. If not provided, alerts will be fetched from all available watchlists on the platform. | False |
| Query | Terms to search within Dataminr Alerts. | False |
| Alert Type | Filters the incoming alerts with the provided alert type. Default All. | False |
| Max Fetch | The maximum number of alerts to fetch each time. If the value is greater than 100, it will be considered as 100. The maximum is 100. | False |
| Source Reliability | Reliability of the source providing the intelligence data. | False |
| Create relationships | Create relationships between indicators as part of enrichment. | False |
| Trust any certificate (not secure) | Indicates whether to allow connections without verifying the SSL certificate’s validity. | False |
| Use system proxy settings | Indicates whether to use XSOAR’s system proxy settings to connect to the API. | False |
| Incident type | False | |
| Fetch incidents | False | |
| Incidents Fetch Interval | The incident fetch interval. | False |
| First Fetch Time (not supported) | This parameter is not supported as Dataminr Pulse API doesn’t have time based filtering for fetching of alerts. | False |
- Click Test to validate the URLs, token, and connection.
(Optional) Set up Google Maps in Cortex XSOAR to Display Alert Locations in the Incident Layout
-
In Google Cloud Platform, do the following:
- Create a Google Cloud Project.
- Enable APIs and Services (API & Services>Dashboard> ENABLE APIS AND SERVICES).
- Enable Maps JavaScript API.
- Create the Maps JavaScript API key ( Credentials> CREATE CREDENTIALS>API key).
- Copy the Maps JavaScript API key.
-
Add the Maps JavaScript API key to Cortex XSOAR.
- For XSOAR 6: Select Settings > ABOUT > Troubleshooting > Add Server Configuration.
For XSOAR 8: Select Settings & Info > Settings > Server Settings > Add Server Configuration. -
Add the following key and value:
Key Value ui.google.api.key<Maps JavaScript API key> - Click Save.
- For XSOAR 6: Select Settings > ABOUT > Troubleshooting > Add Server Configuration.
Note
- If you detach the out-of-the-box mapper and make changes to it, the pack does not automatically get updates.
- If you are using a custom incident type, you also need to create custom corresponding incoming mappers.
Troubleshooting
Known Issue: Custom CVE Indicators being overridden by the default CVE Type in XSOAR 8
We created a custom indicator type “Dataminr Pulse Vulnerability Indicator” similar to “CVE” so we can show the additional fields in our customized layout.
These indicators are extracted from Dataminr Pulse ReGenAI Alerts, but some of them are still being assigned the default “CVE” indicator type instead of our custom “Dataminr Pulse Vulnerability Indicator”.
Tips for Handling the Issue
Manually edit the indicators type from CVE to Dataminr Pulse Vulnerability Indicator that were enriched by Dataminr Pulse ReGenAI Alert.
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.
dataminrpulse-watchlists-get
Retrieves the Watchlists configured on the Dataminr platform.
Base Command
dataminrpulse-watchlists-get
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| DataminrPulse.WatchLists.id | Number | An unique identifier for an individual list. This ID is needed to retrieve alerts for a given list. |
| DataminrPulse.WatchLists.type | String | The type of list. Can be one of the Topic, Company, or Custom. |
| DataminrPulse.WatchLists.name | String | The name of list as specified in Dataminr platform. |
| DataminrPulse.WatchLists.subType | String | The sub type of list as specified in Dataminr platform. |
Command example
#### Context Example
```json
{
"DataminrPulse": {
"WatchLists": [
{
"id": 1,
"name": "Attack Vendor",
"type": "TOPIC",
"subType": "CYBER"
},
{
"id": 2,
"name": "Cyber-Physical",
"type": "TOPIC",
"subType": "VULNERABILITY"
}
]
}
}
Human Readable Output
Watchlists
Watchlist ID Watchlist Name Watchlist Type Watchlist Sub Type 1 Attack Vendor TOPIC CYBER 2 Cyber-Physical TOPIC VULNERABILITY
dataminrpulse-alerts-get
Retrieves the alerts as per the provided watchlist_ids or query or configured watchlist_names parameter in integration.
Note: The “from” and “to” arguments should not be included on the first execution, there will not be any “from” or “to” cursor to reference. Only subsequent calls should contain those parameters.
Base Command
dataminrpulse-alerts-get
Input
| Argument Name | Description | Required |
|---|---|---|
| watchlist_ids | Comma-separated set of watchlist IDs. | Optional |
| query | Terms to search within Dataminr Alerts. | Optional |
| from | It points to a cursor that you want any alerts after. Note that only one of “from” or “to” can be included per request. | Optional |
| to | It points to a cursor that you want any alerts before. Note that only one of “from” and “to” can be included per request. | Optional |
| num | Maximum number of alerts to return. 100 is maximum value. Default is 40. | Optional |
| use_configured_watchlist_names | A Boolean indicating that If user does not provide watchlist IDs then it should use configured watchlist names with query parameter. Possible values are: yes, no. Default is yes. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| DataminrPulse.Alerts.alertId | String | The unique identifier of the alert. |
| DataminrPulse.Alerts.alertTimestamp | String | The timestamp when the alert was generated. |
| DataminrPulse.Alerts.alertType.name | String | The type of alert. |
| DataminrPulse.Alerts.alertTopics.id | String | The topic ID associated with the alert. |
| DataminrPulse.Alerts.alertTopics.name | String | The topic name associated with the alert. |
| DataminrPulse.Alerts.alertCompanies.name | String | The company name is related to the alert. |
| DataminrPulse.Alerts.alertCompanies.ticker | String | The stock ticker symbol of the company. |
| DataminrPulse.Alerts.alertSectors.name | String | The sector name associated with the alert. |
| DataminrPulse.Alerts.headline | String | The main headline or summary of the alert. |
| DataminrPulse.Alerts.subHeadline.title | String | The subheadline title. |
| DataminrPulse.Alerts.subHeadline.content | String | The subheadline detailed content. |
| DataminrPulse.Alerts.publicPost.timestamp | String | The timestamp of the original public post. |
| DataminrPulse.Alerts.publicPost.href | String | The source URL of the public post. |
| DataminrPulse.Alerts.publicPost.text | String | The text of the public post. |
| DataminrPulse.Alerts.publicPost.channels | String | The channels or platforms where the post appeared. |
| DataminrPulse.Alerts.publicPost.media.type | String | The type of attached media. |
| DataminrPulse.Alerts.publicPost.media.href | String | The media hyperlink. |
| DataminrPulse.Alerts.publicPost.englishText | String | The english text of the public post. |
| DataminrPulse.Alerts.eventCorroboration.timestamp | String | The timestamp of the event corroboration. |
| DataminrPulse.Alerts.eventCorroboration.summary.title | String | The title of the event corroboration summary. |
| DataminrPulse.Alerts.eventCorroboration.summary.content | String | The content of the event corroboration summary. |
| DataminrPulse.Alerts.estimatedEventLocation.name | String | The estimated name or location of the event. |
| DataminrPulse.Alerts.estimatedEventLocation.coordinates | Number | The coordinates of the estimated event location. |
| DataminrPulse.Alerts.estimatedEventLocation.probabilityRadius | Number | The probability radius of the estimated location. |
| DataminrPulse.Alerts.assetsMatched.locationAssets.name | String | The name of the customer location asset. |
| DataminrPulse.Alerts.assetsMatched.locationAssets.lng | Number | The longitude of the asset. |
| DataminrPulse.Alerts.assetsMatched.locationAssets.lat | Number | The latitude of the asset. |
| DataminrPulse.Alerts.assetsMatched.locationAssets.distanceFromEventLocation | Number | The distance between the asset and the alert’s estimated event location. |
| DataminrPulse.Alerts.assetsMatched.locationAssets.locationGroups.name | String | The name of the location group. |
| DataminrPulse.Alerts.assetsMatched.thirdPartyAssets.name | String | The name of the third-party asset. |
| DataminrPulse.Alerts.assetsMatched.thirdPartyAssets.customerProvidedId | String | The customer-provided unique ID for the asset. |
| DataminrPulse.Alerts.assetsMatched.travelSegments.name | String | The name of the travel segment location or asset. |
| DataminrPulse.Alerts.assetsMatched.travelSegments.lng | Number | The longitude of the asset. |
| DataminrPulse.Alerts.assetsMatched.travelSegments.lat | Number | The latitude of the asset. |
| DataminrPulse.Alerts.assetsMatched.travelSegments.distanceFromEventLocation | Number | The distance between the asset and the alert’s estimated event location. |
| DataminrPulse.Alerts.assetsMatched.travelSegments.travelType | String | The category of travel segment, such as HOTEL or FLIGHT. |
| DataminrPulse.Alerts.intelAgents.summary.type | String | The type of the intelligence agent summary. |
| DataminrPulse.Alerts.intelAgents.summary.title | String | The title of the intelligence agent summary. |
| DataminrPulse.Alerts.intelAgents.summary.content | String | The content of the intelligence agent summary. |
| DataminrPulse.Alerts.intelAgents.version | String | The version of the intelligence agent. |
| DataminrPulse.Alerts.intelAgents.timestamp | String | The timestamp of the intelligence agent. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.name | String | The name of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.type | String | The type of discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.summary | String | The summary of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.publishedDate | String | The published date of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.products.productName | String | The product name of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.products.productVendor | String | The product vendor of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.products.productVersion | String | The product version of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.knownExploitedDate | String | The known exploited date of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.epssScore | String | The EPSS score of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.cvss | String | The CVSS score of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.exploitable | String | The exploitability of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.exploitPocLinks | String | The exploit proof of concept links of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.affectedOperatingSystems | String | The affected operating systems of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.yaraRules | String | The YARA rules of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.aliases | String | The aliases of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.ttps.topLevelTechniqueName | String | The top-level technique name of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.ttps.techniqueName | String | The technique name of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.ttps.techniqueId | String | The technique ID of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.ttps.tacticName | String | The tactic name of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.countryOfOrigin | String | The country of origin of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.threatActors | String | The threat actors related to malware of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.websiteUrl | String | The website URL associated with the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.locations.address | String | The address of the location associated with the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.industry | String | The industry in which the discovered entity operates. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.primaryLocation.address | String | The primary address of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.organizationType | String | The type of organization associated with the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.headOfOrganization | String | The individual who heads the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.executives | String | The executives associated with the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.operatingRegions | String | The regions in which the discovered entity operates. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.address | String | The address information of the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.ownedBy | String | The entity or individual that owns the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.operatedBy | String | The entity or individual that operates the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.country | String | The country in which the discovered entity is located. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.timezone.utcOffsetSeconds | Number | The UTC offset in seconds for the entity’s timezone. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.timezone.displayName | String | The display name of the entity’s timezone. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.stateOrProvince | String | The state or province where the discovered entity is located. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.population | String | The population associated with the discovered entity’s location. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.travelAdvisories.issuingCountry | String | The country issuing the travel advisory. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.travelAdvisories.issuedDate | String | The date when the travel advisory was issued. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.travelAdvisories.issuedCountry | String | The country for which the travel advisory was issued. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.travelAdvisories.advisoryText | String | The text content of the travel advisory. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.president | String | The president associated with the discovered entity’s country or organization. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.officialLanguages | String | The official languages spoken in the entity’s location. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.capital | String | The capital city associated with the discovered entity’s country. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.locationType | String | The type of location represented by the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.managedBy | String | The entity or individual responsible for managing the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.nationalities | String | The nationalities associated with the discovered entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.pastRoles.tenureStartDate | String | The start date of the entity’s past role. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.pastRoles.tenureEndDate | String | The end date of the entity’s past role. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.pastRoles.roleName | String | The name of the past role associated with the entity. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.currentRoles.tenureStartDate | String | The start date of the entity’s current role. |
| DataminrPulse.Alerts.intelAgents.discoveredEntities.currentRoles.roleName | String | The name of the current role associated with the entity. |
| DataminrPulse.Alerts.liveBrief.summary | String | The live brief summary. |
| DataminrPulse.Alerts.liveBrief.version | String | The live brief version. |
| DataminrPulse.Alerts.liveBrief.timestamp | String | The live brief timestamp. |
| DataminrPulse.Alerts.dataminrAlertUrl | String | The Dataminr alert detail URL. |
| DataminrPulse.Alerts.alertReferenceTerms.text | String | The reference keywords or terms for the alert. |
| DataminrPulse.Alerts.metadata.cyber.vulnerabilities.id | String | The vulnerability ID. |
| DataminrPulse.Alerts.metadata.cyber.vulnerabilities.publishedDate | String | The published date of the vulnerability. |
| DataminrPulse.Alerts.metadata.cyber.vulnerabilities.knownExploitedDate | String | The known exploited date of the vulnerability. |
| DataminrPulse.Alerts.metadata.cyber.vulnerabilities.epssScore | String | The EPSS score of the vulnerability. |
| DataminrPulse.Alerts.metadata.cyber.vulnerabilities.cvss | Number | The CVSS score of the vulnerability. |
| DataminrPulse.Alerts.metadata.cyber.vulnerabilities.products.productName | String | The vulnerable product name. |
| DataminrPulse.Alerts.metadata.cyber.vulnerabilities.products.productVendor | String | The vulnerable product vendor. |
| DataminrPulse.Alerts.metadata.cyber.vulnerabilities.products.productVersion | String | The vulnerable product version. |
| DataminrPulse.Alerts.metadata.cyber.vulnerabilities.exploitPocLinks | String | The exploit PoC links for the vulnerability. |
| DataminrPulse.Alerts.metadata.cyber.URL.name | String | The related URL name. |
| DataminrPulse.Alerts.metadata.cyber.addresses.ip | String | The IP address involved. |
| DataminrPulse.Alerts.metadata.cyber.addresses.port | Number | The port number involved. |
| DataminrPulse.Alerts.metadata.cyber.addresses.version | String | The protocol or software version of the address. |
| DataminrPulse.Alerts.metadata.cyber.addresses.type | String | The type of the address. |
| DataminrPulse.Alerts.metadata.cyber.malware.name | String | The malware name. |
| DataminrPulse.Alerts.metadata.cyber.malware.affectedOperatingSystems | String | The affected operating systems. |
| DataminrPulse.Alerts.metadata.cyber.threatActors.name | String | The threat actor name. |
| DataminrPulse.Alerts.metadata.cyber.threatActors.aliases | String | The threat actor aliases. |
| DataminrPulse.Alerts.metadata.cyber.threatActors.countriesOfOrigin | String | The countries of origin of the threat actor. |
| DataminrPulse.Alerts.metadata.cyber.asOrgs.asn | String | The autonomous system number. |
| DataminrPulse.Alerts.metadata.cyber.asOrgs.asOrg | String | The autonomous system organization. |
| DataminrPulse.Alerts.metadata.cyber.hashValues.value | String | The hash value. |
| DataminrPulse.Alerts.metadata.cyber.hashValues.type | String | The hash type. |
| DataminrPulse.Alerts.listsMatched.id | String | The matched list ID. |
| DataminrPulse.Alerts.listsMatched.name | String | The matched list name. |
| DataminrPulse.Alerts.listsMatched.subType | String | The subtype of the matched list. |
| DataminrPulse.Alerts.listsMatched.topicIds | String | The topic IDs of the matched list. |
| DataminrPulse.Alerts.linkedAlerts.count | Number | The count of linked alerts. |
| DataminrPulse.Alerts.linkedAlerts.parentAlertId | String | The parent alert ID of the linked alert. |
| DataminrPulse.Cursor.from | String | The “from” points to a cursor that specifies the alerts you want after it. |
| DataminrPulse.Cursor.to | String | The “to” points to a cursor that specifies the alerts you want before it. |
Command example
!dataminrpulse-alerts-get num=1
Context Example
{
"DataminrPulse": {
"Alerts": [
{
"alertId": "DUMMY_ALERT_ID",
"alertTimestamp": "2025-07-07T19:19:00.397Z",
"alertType": {
"name": "Alert"
},
"alertTopics": [
{
"id": "DUMMY_TOPIC_ID",
"name": "DUMMY_TOPIC_NAME"
}
],
"alertCompanies": [
{
"name": "DUMMY_COMPANY",
"ticker": "DUMMY_TICKER"
}
],
"alertSectors": [
{
"name": "DUMMY_SECTOR"
}
],
"headline": "Spike detected in discussion related to threat actor DUMMY_ACTOR.",
"subHeadline": {
"title": "DUMMY_TITLE",
"content": [
"DUMMY_CONTENT"
]
},
"publicPost": {
"timestamp": "2025-01-01T00:00:00.000Z",
"href": "DUMMY_URL",
"channels": [
"DUMMY_CHANNEL"
],
"media": [
{
"type": "photo",
"href": "DUMMY_IMAGE_URL"
}
]
},
"estimatedEventLocation": {
"name": "DUMMY_LOCATION",
"coordinates": [
0,
0
],
"probabilityRadius": 0
},
"intelAgents": [
{
"summary": [
{
"type": [
"CYBER"
],
"title": "Background Information",
"content": [
"This is a placeholder description for background information related to the issue."
]
},
{
"type": [
"CYBER"
],
"title": "Current Status",
"content": [
"This is a placeholder description for the current status of the issue."
]
},
{
"type": [
"CYBER"
],
"title": "Impact",
"content": [
"This is a placeholder description for the potential impact of the issue."
]
}
],
"version": "prior",
"timestamp": "2025-01-01T00:00:00.000Z",
"discoveredEntities": [
{
"name": "DUMMY_ENTITY",
"type": "threatActor",
"aliases": [
"DUMMY_ALIAS"
]
},
{
"name": "DUMMY_ENTITY02",
"type": "malware",
"affectedOperatingSystems": [
"DUMMY_OS"
]
},
{
"name": "DUMMY_ENTITY03",
"type": "vulnerability",
"publishedDate": "2025-01-01T00:00:00.000Z",
"epssScore": 2.0,
"cvss": 2.5,
"products": [
{
"productName": "DUMMY_PRODUCT",
"productVendor": "DUMMY_VENDOR",
"productVersion": "DUMMY_VERSION"
}
],
"exploitPocLinks": [
"DUMMY_LINK"
]
}
]
}
],
"liveBrief": [
{
"summary": "DUMMY_LIVEBRIEF",
"version": "prior",
"timestamp": "2025-01-01T00:00:00.000Z"
}
],
"dataminrAlertUrl": "https://app.dataminr.com/#alertDetail/DUMMY",
"alertReferenceTerms": [
{
"text": "DUMMY_REF_TERM"
}
],
"metadata": {
"cyber": {
"vulnerabilities": [
{
"id": "DUMMY_VULN",
"publishedDate": "2025-01-01T00:00:00.000Z",
"epssScore": 2.0,
"cvss": 2.5,
"products": [
{
"productName": "DUMMY_PRODUCT",
"productVendor": "DUMMY_VENDOR",
"productVersion": "DUMMY_VERSION"
}
],
"exploitPocLinks": [
"DUMMY_LINK"
]
}
],
"URL": [
{
"name": "DUMMY_URL"
}
],
"addresses": [
{
"ip": "0.0.0.0",
"port": 22,
"version": "DUMMY_VERSION"
}
],
"malware": [
{
"name": "DUMMY_MALWARE"
}
],
"threatActors": [
{
"name": "DUMMY_ACTOR"
}
],
"asOrgs": [
{
"asn": "DUMMY_ASN",
"asOrg": "DUMMY_ORG"
}
],
"hashValues": [
{
"value": "DUMMY_HASH",
"type": "DUMMY_HASH_TYPE"
}
]
}
},
"listsMatched": [
{
"id": 3,
"name": "Data Security",
"subType": "CYBER",
"topicIds": [
"DUMMY_TOPIC_ID"
]
}
],
"linkedAlerts": [
{
"count": 4,
"parentAlertId": "DUMMY_PARENT_ID"
}
]
}
],
"Cursor": {
"from": "DUMMY_CURSOR02",
"to": "DUMMY_CURSOR01"
}
}
}
Human Readable Output
Alerts
Alert Type Alert ID Alert Name Intel Agents Summary Intel Agents Discovered Entities Live Brief Watchlist Name Alert Time Alert Location Post Link Alert Topics Alert DUMMY_ALERT_ID Spike detected in discussion related to threat actor DUMMY_ACTOR. - type: CYBER
title: Background Information
content: This is a placeholder description for background information related to the issue.
- type: CYBER
title: Current Status
content: This is a placeholder description for the current status of the issue.
- type: CYBER
title: Impact
content: This is a placeholder description for the potential impact of the issue.- name: DUMMY_ENTITY
type: threatActor
aliases: DUMMY_ALIAS
- name: DUMMY_ENTITY02
type: malware
affectedOperatingSystems: DUMMY_OS
- name: DUMMY_ENTITY03
type: vulnerability
publishedDate: 2025-01-01T00:00:00.000Z
epssScore: 2.0
cvss: 2.5
products:
- productName: DUMMY_PRODUCT
productVendor: DUMMY_VENDOR
productVersion: DUMMY_VERSION
exploitPocLinks: DUMMY_LINK- summary: DUMMY_LIVEBRIEF
version: prior
timestamp: 2025-01-01T00:00:00.000ZData Security 2025-07-07T19:19:00.397Z DUMMY_LOCATION DUMMY_URL - id: DUMMY_TOPIC_ID
name: DUMMY_TOPIC_NAME
Cursor for pagination
from to DUMMY_CURSOR02 DUMMY_CURSOR01
Migration Guide
Migrated Commands
Below is the list of commands that have been migrated from the “Dataminr Pulse” integration to the “Dataminr Pulse - ReGenAI” integration.
- dataminrpulse-alerts-get
- dataminrpulse-watchlists-get
Deprecated Commands
The following command from the previous integration has been deprecated from the Dataminr Pulse API side with no replacement.
- dataminrpulse-related-alerts-get
Configuration parameters
credentials— Client ID (required)watchlist_names— Watchlist Namesquery— Queryalert_type— Alert Typemax_fetch— Max FetchintegrationReliability— Source Reliabilitycreate_relationships— Create relationshipsinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsincidentType— Incident typeisFetch— Fetch incidentsincidentFetchInterval— Incidents Fetch Intervalfirst_fetch— First Fetch Time (not supported)
Commands (5)
-
dataminrpulse-alerts-getRetrieves the alerts as per the provided watchlist_ids or query or configured watchlist_names parameter in integration. Note: The "from" and "to" arguments should not be included on the first execution, there will not be any "from" or "to" cursor to reference. Only subsequent calls should contain those parameters.
-
dataminrpulse-malware-enrichEnrich the "Dataminr Pulse Malware Indicator" custom indicator with relevant data.
-
dataminrpulse-threat-actor-enrichEnrich the "Dataminr Pulse Threat Actor Indicator" custom indicator with relevant data.
-
dataminrpulse-vulnerability-enrichEnrich the "Dataminr Pulse Vulnerability Indicator" custom indicator with relevant data.
-
dataminrpulse-watchlists-getRetrieves the Watchlists configured on the Dataminr platform.
"""Dataminr Pulse - ReGenAI Integration for Cortex XSOAR (aka Demisto).""" from functools import reduce from operator import concat from urllib.parse import urlparse, parse_qs from collections.abc import Callable from copy import deepcopy import demistomock as demisto import urllib3 from CommonServerPython import * # noqa # pylint: disable=unused-wildcard-import from CommonServerUserPython import * # noqa # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ # Base URL for the Dataminr Pulse ReGenAI API. BASE_URL = "https://api.dataminr.com" ENDPOINTS = { # Authentication endpoint for the Dataminr Pulse API. "AUTH_ENDPOINT": "/auth/v1/token", # Watchlists endpoint for the Dataminr Pulse API. "WATCHLISTS_ENDPOINT": "/pulse/v1/lists", # Alerts endpoint for the Dataminr Pulse API. "ALERTS_ENDPOINT": "/pulse/v1/alerts", } DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # ISO8601 format with UTC, default in XSOAR # Date format for displaying date in human-readable format in war room. HR_DATE_FORMAT = "%d %b %Y, %I:%M %p UTC" APPLICATION_NAME = "palo_alto_cortex_xsoar" OK_CODES = (200, 201, 401) DEFAULT_NUMBER_OF_ALERTS_TO_RETRIEVE = 40 MAX_NUMBER_OF_ALERTS_TO_RETRIEVE = 100 STATUS_LIST_TO_RETRY = (429, *(status_code for status_code in requests.status_codes._codes if status_code >= 500)) # type: ignore EARLY_EXPIRY_TIME = 30000 # in milliseconds DEFAULT_ALERT_TYPE = "All" VENDOR_NAME = "DataminrPulse" CUSTOM_OUTPUT_PREFIX = "DataminrPulse.{}" ERRORS = { "INVALID_JSON_OBJECT": "Status code: {}. Failed to parse json object from response: {}.", "UNAUTHORIZED_REQUEST": "Status code: {}. Unauthorized request: {}.", "GENERAL_AUTH_ERROR": "Status code: {}. Error occurred while creating an authorization token. " "Please check the Client ID, Client Secret {}.", "NOT_MATCHED_WATCHLIST_NAMES": "No matching watchlist data was found for the {} watchlist names configured in the instance.", "INVALID_NUM": '{} is invalid value for "{}". Value should be greater than 0 and less than or equal to {}.', "ATMOST_ONE_ALLOWED": 'At most one argument, either "{}" or "{}" is allowed.', "JSON_DECODE": "Failed to parse '{}' JSON string.", "REQUIRED_ARG": "Required argument '{}' is missing.", } ALERT_TYPE_TO_INCIDENT_SEVERITY = {"Alert": 1, "Urgent": 3, "Flash": 4} DEFAULT_RELIABILITY = "A - Completely reliable" # Output prefix for the alerts. OUTPUT_PREFIX_ALERTS = "DataminrPulse.Alerts" # Output prefix for the lists. OUTPUT_PREFIX_WATCHLISTS = "DataminrPulse.WatchLists" # Output prefix for the cursor. OUTPUT_PREFIX_CURSOR = "DataminrPulse.Cursor" """ CLIENT CLASS """ class DataminrPulseReGenAIClient(BaseClient): """DataminrPulseClient class to interact with the Dataminr Pulse API.""" def __init__(self, client_id: str = "", client_secret: str = "", verify: bool = False, proxy: bool = False): """ Constructor for the DataminrPulseClient class. :type client_id: ``str`` :param client_id: Client ID to be used for authentication. :type client_secret: ``str`` :param client_secret: Client Secret to be used for authentication. :type verify: ``bool`` :param verify: Whether the request should verify the SSL certificate. :type proxy: ``bool`` :param proxy: Whether to run the integration using the system proxy. """ self.client_id = client_id self.client_secret = client_secret self.base_url = BASE_URL super().__init__(base_url=self.base_url, verify=verify, proxy=proxy, headers={}) def http_request( self, method, url_suffix, params=None, status_list_to_retry=STATUS_LIST_TO_RETRY, backoff_factor=30, retries=3, internal_retries=3, **kwargs, ) -> Optional[dict]: """ Method to override private _http_request of BaseClient to handle specific status code. :type method: ``str`` :param method: The HTTP method, for example: GET, POST, and so on. :type url_suffix: ``str`` :param url_suffix: The API endpoint. :type params: ``dict`` :param params: URL parameters to specify the query. :type status_list_to_retry: ``iterable`` :param status_list_to_retry: A set of integer HTTP status codes that we should force a retry on. :type backoff_factor ``float`` :param backoff_factor: A backoff factor to apply between attempts :type retries: ``int`` :param retries: How many retries should be made in case of a failure. :type internal_retries: ``int`` :param internal_retries: How many retries should be made in case of an auth failure. :return: Response dict. :rtype: ``Optional[dict]`` """ # Adds a valid authentication token to the headers. dma_token = self.get_dma_token(use_existing_token=True) headers = { "Authorization": f"Bearer {dma_token}", "X-Application-Name": APPLICATION_NAME, } self._headers.update(headers) res = self._http_request( method=method, url_suffix=url_suffix, params=params, status_list_to_retry=status_list_to_retry, backoff_factor=backoff_factor, retries=retries, resp_type="response", ok_codes=OK_CODES, **kwargs, ) res_status_code = res.status_code try: json_data = res.json() except ValueError as exception: raise DemistoException(ERRORS["INVALID_JSON_OBJECT"].format(res_status_code, res.content), exception) # If the success response is received, then return it. if res_status_code in [200, 201]: return json_data # If authentication failure happens. if res_status_code in [401]: if internal_retries > 0: dma_token = self.get_dma_token(use_existing_token=False) headers = { "Authorization": f"Bearer {dma_token}", "X-Application-Name": APPLICATION_NAME, } self._headers.update(headers) internal_retries = internal_retries - 1 return self.http_request( method=method, url_suffix=url_suffix, params=params, status_list_to_retry=status_list_to_retry, backoff_factor=backoff_factor, retries=retries, internal_retries=internal_retries, ) try: err_msg = ERRORS["UNAUTHORIZED_REQUEST"].format(res_status_code, str(res.json())) except ValueError: err_msg = ERRORS["UNAUTHORIZED_REQUEST"].format(res_status_code, str(res)) raise DemistoException(err_msg) return None def get_dma_token(self, use_existing_token: bool = True) -> Optional[str]: """ Get a DMA token that was previously created if it is still valid, else, generate a new authorization token from the client id, client secret and refresh token. :type use_existing_token ``bool`` :param use_existing_token: Use existing token if it is still valid. :return: DMA token. :rtype: ``Optional[str]`` """ integration_context: dict = get_integration_context() previous_token: dict = integration_context.get("token", {}) # Check if there is existing valid authorization token. if ( previous_token.get("dmaToken") and use_existing_token and previous_token.get("expire") > datetime.now(timezone.utc).timestamp() * 1000 # type: ignore ): demisto.debug("Got authentication token from the integration context.") return previous_token.get("dmaToken") # type: ignore demisto.debug("Trying to generate a new authentication token.") data = {"client_id": self.client_id, "client_secret": self.client_secret, "grant_type": "api_key"} headers = { "Content-Type": "application/x-www-form-urlencoded", "X-Application-Name": APPLICATION_NAME, } res = super()._http_request( method="POST", url_suffix=ENDPOINTS["AUTH_ENDPOINT"], resp_type="response", headers=headers, data=data, ok_codes=OK_CODES, status_list_to_retry=STATUS_LIST_TO_RETRY, ) res_status_code = res.status_code try: res = res.json() except ValueError as exception: raise DemistoException(ERRORS["INVALID_JSON_OBJECT"].format(res_status_code, res.content), exception) if "errors" in res: raise DemistoException(ERRORS["GENERAL_AUTH_ERROR"].format(res_status_code, str(res))) if res.get("dmaToken"): expiry_time = res.get("expire", 0) - EARLY_EXPIRY_TIME demisto.debug( f"Setting the expiry time of the authentication token to {timestamp_to_datestring(expiry_time, is_utc=True)}." ) new_token = {"dmaToken": res.get("dmaToken"), "expire": expiry_time} integration_context.update({"token": new_token}) set_integration_context(integration_context) return res.get("dmaToken") return None def get_alerts( self, watchlist_ids: Optional[list] = None, query: Optional[str] = None, _from: Optional[str] = None, to: Optional[str] = None, page_size: int = DEFAULT_NUMBER_OF_ALERTS_TO_RETRIEVE, ) -> Optional[dict]: """ Retrieves the alerts stored on the Dataminr platform. :type watchlist_ids ``Optional[list]`` :param watchlist_ids: List of watchlist id. :type query ``Optional[str]`` :param query: Terms to search within Dataminr Alerts. :type _from ``Optional[str]`` :param _from: Provide cursor value to get alerts after that. :type to ``Optional[str]`` :param to: Provide cursor value to get alerts before that. :type page_size ``int`` :param page_size: Maximum number of alerts to return. :return: A dictionary of alerts. :rtype: ``Optional[dict]`` """ params = { "pageSize": page_size, "from": _from, "to": to, "query": query, } remove_nulls_from_dictionary(params) if watchlist_ids: params["lists"] = ",".join(map(str, watchlist_ids)) # type: ignore return self.http_request(method="GET", url_suffix=ENDPOINTS["ALERTS_ENDPOINT"], params=params) def get_watchlists(self) -> Optional[dict]: """Retrieves the watchlists stored on the Dataminr platform. :return: A dictionary of Watchlists grouped by their types. :rtype: ``Optional[Dict]`` """ return self.http_request(method="GET", url_suffix=ENDPOINTS["WATCHLISTS_ENDPOINT"]) """ HELPER FUNCTIONS """ def validate_params_for_alerts_get( watchlist_ids: Optional[list] = None, watchlist_names: Optional[list] = None, page_size: int = DEFAULT_NUMBER_OF_ALERTS_TO_RETRIEVE, use_configured_watchlist_names: bool = False, is_fetch: bool = False, _from: Optional[str] = None, to: Optional[str] = None, ): """ To validate arguments for the alerts get. :type watchlist_ids ``Optional[List]`` :param watchlist_ids: List of watchlist id. :type watchlist_names ``Optional[List]`` :param watchlist_names: Watchlist names. :type page_size ``int`` :param page_size: Maximum number of alerts to return. :type use_configured_watchlist_names ``bool`` :param use_configured_watchlist_names: Use configured watchlist names. :type is_fetch ``bool`` :param is_fetch: Function is called by fetch_incident method. :type _from ``Optional[str]`` :param _from: Start cursor for the alerts. :type to ``Optional[str]`` :param to: End cursor for the alerts. """ if is_fetch and (page_size < 1 or page_size > MAX_NUMBER_OF_ALERTS_TO_RETRIEVE): raise ValueError(ERRORS["INVALID_NUM"].format(page_size, "Max Fetch", MAX_NUMBER_OF_ALERTS_TO_RETRIEVE)) elif page_size < 1 or page_size > MAX_NUMBER_OF_ALERTS_TO_RETRIEVE: raise ValueError(ERRORS["INVALID_NUM"].format(page_size, "num", MAX_NUMBER_OF_ALERTS_TO_RETRIEVE)) if (is_fetch or use_configured_watchlist_names) and (watchlist_names and not watchlist_ids): raise ValueError(ERRORS["NOT_MATCHED_WATCHLIST_NAMES"].format(watchlist_names)) if _from and to: raise ValueError(ERRORS["ATMOST_ONE_ALLOWED"].format("from", "to")) def transform_watchlists_data(watchlists_data: Optional[dict] = None) -> list: """ Transform watchlist data from dictionary to single List. :type watchlists_data ``Optional[dict]`` :param watchlists_data: Response to be converted in single list. :return: List of response. :rtype: ``list`` """ list_of_watchlists = watchlists_data.get("lists", {}).values() # type: ignore # The returned object is a dictionary where the lists are grouped by their type. # So, modifying this object and creating a list of all list objects as the object itself contains # a property named "type" in it, which defines the type of the list. list_of_watchlists = list(list_of_watchlists) # The created list will be the list of lists. So, flattening the list is required. list_of_watchlists = reduce(concat, list_of_watchlists) return list_of_watchlists def get_watchlist_ids(client: DataminrPulseReGenAIClient, watchlist_names: Optional[list] = None) -> list: """ Get watchlist IDs as per the given watchlist names using integration context and get_watchlists method from client. :type client ``DataminrPulseReGenAIClient`` :param client: DataminrPulseReGenAIClient to get watchlists data. :type watchlist_names ``Optional[List]`` :param watchlist_names: Watchlist names. :return: Watchlist IDs. :rtype: ``list`` """ watchlists_data: list = transform_watchlists_data(client.get_watchlists()) watchlist_names_in_lower = [watchlist_name.lower() for watchlist_name in watchlist_names] if watchlist_names else [] filtered_watchlists_data: list = ( list( filter( lambda watchlist_data: watchlist_data.get("name").lower() in watchlist_names_in_lower, # type: ignore watchlists_data, ) ) if watchlist_names else watchlists_data ) if not filtered_watchlists_data: demisto.debug( f'No matching watchlist data was found for the "{watchlist_names}" watchlist names configured in the instance.' ) return [] watchlist_ids: list = [watchlist_data.get("id") for watchlist_data in filtered_watchlists_data] watchlist_ids: list = list(filter(None, watchlist_ids)) return watchlist_ids def create_media_html(alert: dict) -> str: """ Create HTML content for the media. :type alert: ``dict`` :param alert: Alert data. :rtype: ``str`` :return: HTML content for the media. """ media = alert.get("publicPost", {}).get("media", []) if not media: return "" photo_media = list(filter(lambda photo: photo.get("type") == "photo" or photo.get("type") == "image", media)) if not photo_media: return "" html_content = [] for photo in photo_media: html_content.append(f'<img src="{photo.get("href", "")}" alt="Photo">') media_html = "\n".join(html_content) return media_html def remove_empty_elements_for_hr(d: Any) -> Any: """ Recursively remove empty lists, empty dicts, or None elements from a dictionary or list. :param d: Input dictionary or list. :return: Dictionary or list with all empty lists, and empty dictionaries removed. """ if not isinstance(d, dict | list): return str(d) if isinstance(d, int | float) else d elif isinstance(d, list): return [v for v in (remove_empty_elements_for_hr(v) for v in d) if not check_empty(v)] return {k: v for k, v in ((k, remove_empty_elements_for_hr(v)) for k, v in d.items()) if not check_empty(v)} def remove_empty_elements_for_fetch(d: Any) -> Any: """ Recursively remove empty lists, empty dicts, or None elements from a dictionary or list. :param d: Input dictionary or list. :return: Dictionary or list with all empty lists, and empty dictionaries removed. """ if not isinstance(d, dict | list): return d elif isinstance(d, list): return [v for v in (remove_empty_elements_for_fetch(v) for v in d) if not check_empty(v)] return {k: v for k, v in ((k, remove_empty_elements_for_fetch(v)) for k, v in d.items()) if not check_empty(v)} def check_empty(x: Any) -> bool: """ Check if input is empty (None, empty dict, empty list, or empty string). :param x: Input to check. :type x: Any :return: True if x is empty, False otherwise. :rtype: bool """ return x is None or x == {} or x == [] or x == "" def trim_spaces_from_args(args: Dict) -> Dict: """Trim spaces from values of the args Dict. :type args: ``Dict`` :param args: Dict to trim spaces from. :rtype: ``Dict`` :return: Arguments after trim spaces. """ for key, val in args.items(): if isinstance(val, str): args[key] = val.strip() elif isinstance(val, dict): args[key] = trim_spaces_from_args(val) elif isinstance(val, list): for i in range(len(val)): if isinstance(val[i], str): val[i] = val[i].strip() elif isinstance(val[i], dict): val[i] = trim_spaces_from_args(val[i]) return args def prepare_hr_for_watchlists_get(watchlists: List) -> str: """ Prepare human-readable string for war room entry. :type watchlists: ``List`` :param watchlists: List of watchlists. :return: Human-readable output. :rtype: ``str`` """ # The title of the table. title = "Watchlists" # Data dictionary for the table. hr_outputs = [ { "Watchlist ID": wl.get("id", ""), "Watchlist Name": wl.get("name", ""), "Watchlist Type": wl.get("type", ""), "Watchlist Sub Type": wl.get("subType", ""), } for wl in watchlists ] # Table headers. headers = ["Watchlist ID", "Watchlist Name", "Watchlist Type", "Watchlist Sub Type"] return tableToMarkdown(title, hr_outputs, headers, removeNull=True) def prepare_hr_for_alerts(alerts: List) -> str: """Prepare Human Readable output for alerts. :type alerts: ``List`` :param alerts: Response from the API. :rtype: ``str`` :return: Human readable output. """ # The title of the table. title = "Alerts" # This will store the data dictionaries for the table. hr_outputs = [] for alert in alerts: # List of watchlist names matched in the alert. watchlist_names = [watchlist.get("name") for watchlist in alert.get("listsMatched", [])] intel_agents = deepcopy(alert.get("intelAgents", [])) intel_agents_summary = [] discovered_entities = [] for intel_agent in intel_agents: intel_agents_summary.extend(intel_agent.get("summary", [])) discovered_entities.extend(intel_agent.get("discoveredEntities", [])) for summary in intel_agents_summary: summary["type"] = ", ".join(summary.get("type", [])) summary["content"] = "\n".join(summary.get("content", [])) for discovered_entity in discovered_entities: if discovered_entity.get("type") == "threatActor" and discovered_entity.get("aliases"): discovered_entity["aliases"] = ", ".join(discovered_entity.get("aliases", [])) elif discovered_entity.get("type") == "malware" and discovered_entity.get("affectedOperatingSystems"): discovered_entity["affectedOperatingSystems"] = ", ".join(discovered_entity.get("affectedOperatingSystems", [])) elif discovered_entity.get("type") == "vulnerability" and discovered_entity.get("exploitPocLinks"): discovered_entity["exploitPocLinks"] = ", ".join(discovered_entity.get("exploitPocLinks", [])) hr_outputs.append( { "Alert Type": alert.get("alertType", {}).get("name", ""), "Alert ID": f"[{alert.get('alertId', '')}]({alert.get('dataminrAlertUrl', '')})", "Alert Name": alert.get("headline", ""), "Intel Agents Summary": intel_agents_summary if intel_agents_summary else "", "Intel Agents Discovered Entities": discovered_entities if discovered_entities else "", "Live Brief": alert.get("liveBrief", ""), "Watchlist Name": ", ".join(watchlist_names), "Alert Time": alert.get("alertTimestamp", ""), "Alert Location": alert.get("estimatedEventLocation", {}).get("name", ""), "Post Link": alert.get("publicPost", {}).get("href", ""), "Alert Topics": alert.get("alertTopics", ""), } ) # Table headers. headers = [ "Alert Type", "Alert ID", "Alert Name", "Intel Agents Summary", "Intel Agents Discovered Entities", "Live Brief", "Watchlist Name", "Alert Time", "Alert Location", "Post Link", "Alert Topics", ] return tableToMarkdown( title, hr_outputs, headers, removeNull=True, url_keys=["Post Link"], json_transform_mapping={ "Alert Topics": JsonTransformer(is_nested=True), "Intel Agents Summary": JsonTransformer(is_nested=True), "Intel Agents Discovered Entities": JsonTransformer(is_nested=True), "Live Brief": JsonTransformer(is_nested=True), }, ) def prepare_hr_for_cursor(cursor: Dict) -> str: """Prepare Human Readable output for cursor. :type cursor: ``Dict`` :param cursor: Contains from and to parameter. :rtype: ``str`` :return: Human readable output. """ # The title of the table. title = "Cursor for pagination" # This will store the data dictionaries for the table. _from = cursor.get("from") if _from: _from = re.escape(_from) to = cursor.get("to") if to: to = re.escape(to) hr_outputs = [{"from": _from, "to": to}] # Table headers. headers = ["from", "to"] return tableToMarkdown(title, hr_outputs, headers, removeNull=True) def create_vulnerability_indicators(alert: dict) -> list: """ Create vulnerability indicators for the alert. :type alert: ``dict`` :param alert: Alert data. :rtype: ``list`` :return: List of vulnerability indicators. """ vulnerability_intel_agents_list = [] vulnerability_metadata_list = alert.get("metadata", {}).get("cyber", {}).get("vulnerabilities", []) intel_agents = alert.get("intelAgents", []) for vulnerability in vulnerability_metadata_list: if "epssScore" in vulnerability: vulnerability["epssScore"] = str(vulnerability.get("epssScore", "0")) + "%" for intel_agent in intel_agents: discovered_entities = intel_agent.get("discoveredEntities", []) for discovered_entity in discovered_entities: if discovered_entity.get("type", "") == "vulnerability": discovered_entity["id"] = discovered_entity.get("name", "") if "epssScore" in discovered_entity: discovered_entity["epssScore"] = str(discovered_entity.get("epssScore", "0")) + "%" vulnerability_intel_agents_list.append(discovered_entity) merged = {} for vuln in vulnerability_intel_agents_list: vid = vuln.get("id") if vid is not None: merged[vid] = deepcopy(vuln) for vuln in vulnerability_metadata_list: vid = vuln.get("id") if vid is None: continue if vid in merged: merged[vid] = merge_dicts(merged[vid], vuln) else: merged[vid] = deepcopy(vuln) return list(merged.values()) def create_malware_indicators(alert: dict) -> list: """ Create malware indicators for the alert. :type alert: ``dict`` :param alert: Alert data. :rtype: ``list`` :return: List of malware indicators. """ malware_intel_agents_list = [] malware_metadata_list = alert.get("metadata", {}).get("cyber", {}).get("malware", []) intel_agents = alert.get("intelAgents", []) for malware in malware_metadata_list: malware["name"] = "Malware: [" + malware.get("name", "") + "]" for intel_agent in intel_agents: discovered_entities = intel_agent.get("discoveredEntities", []) for discovered_entity in discovered_entities: if discovered_entity.get("type", "") == "malware": discovered_entity["name"] = "Malware: [" + discovered_entity.get("name", "") + "]" malware_intel_agents_list.append(discovered_entity) merged = {} for malware in malware_intel_agents_list: mid = malware.get("name") if mid is not None: merged[mid] = deepcopy(malware) for malware in malware_metadata_list: mid = malware.get("name") if mid is None: continue if mid in merged: merged[mid] = merge_dicts(merged[mid], malware) else: merged[mid] = deepcopy(malware) return list(merged.values()) def create_threat_actors_indicators(alert: dict) -> list: """ Create threat actors indicators for the alert. :type alert: ``dict`` :param alert: Alert data. :rtype: ``list`` :return: List of threat actors indicators. """ threat_actor_intel_agents_list = [] threat_actor_metadata_list = alert.get("metadata", {}).get("cyber", {}).get("threatActors", []) intel_agents = alert.get("intelAgents", []) for threat_actor in threat_actor_metadata_list: threat_actor["name"] = "Threat Actor: [" + threat_actor.get("name", "") + "]" for intel_agent in intel_agents: discovered_entities = intel_agent.get("discoveredEntities", []) for discovered_entity in discovered_entities: if discovered_entity.get("type", "") == "threatActor": discovered_entity["name"] = "Threat Actor: [" + discovered_entity.get("name", "") + "]" threat_actor_intel_agents_list.append(discovered_entity) merged = {} for actor in threat_actor_intel_agents_list: name = actor.get("name") if name: merged[name] = deepcopy(actor) for actor in threat_actor_metadata_list: name = actor.get("name") if not name: continue if name in merged: merged[name] = merge_dicts(merged[name], actor) else: merged[name] = deepcopy(actor) return list(merged.values()) def create_flight_data(alert: dict) -> dict: """ Create flight data for the alert. :type alert: ``dict`` :param alert: Alert data. :rtype: ``dict`` :return: Flight data. """ sub_headline_content = alert.get("subHeadline", {}).get("content", []) flight_data = {} for item in sub_headline_content: # Skip empty or whitespace-only lines if not item.strip(): continue # Check if it contains a colon (valid key-value format) if ":" in item: key, value = item.split(":", 1) key, value = key.strip(), value.strip() # Only add if both key and value are non-empty if key and value: flight_data[key] = value return flight_data def calculate_dbot_score(cvss: float) -> int: if cvss >= 4.0: return Common.DBotScore.BAD elif cvss > 0.0: return Common.DBotScore.SUSPICIOUS else: return Common.DBotScore.NONE def merge_dicts(d1: dict, d2: dict) -> dict: """ Merge two dictionaries recursively and handle duplicate values. Works for dict, list, string, number, etc. """ merged = dict(d1) # start with a copy of d1 for key, value in d2.items(): if key not in merged: # New key, just add merged[key] = value return merged def create_relationship_for_malware(malware: dict) -> list: """ Create a list of relationships objects from the malware and threat actor. :type malware: ``dict`` :param malware: Malware of API. :return: List of EntityRelationship objects containing all the relationships. :rtype: ``List`` """ relationships = [] source_malware = malware.get("name", "") integration_reliability = demisto.params().get("integrationReliability", DEFAULT_RELIABILITY) for threat_actor in malware.get("threatActors", []): threat_actor_name = "Threat Actor: [" + threat_actor.get("name", "") + "]" relationships.append( EntityRelationship( name=EntityRelationship.Relationships.ORIGINATED_FROM, entity_a=source_malware, entity_a_type="Dataminr Pulse Malware Indicator", entity_b=threat_actor_name, entity_b_type="Dataminr Pulse Threat Actor Indicator", source_reliability=integration_reliability, brand=VENDOR_NAME, ) ) return relationships """ COMMAND FUNCTIONS """ def test_module(client: DataminrPulseReGenAIClient) -> str: """Tests API connectivity and authentication' Returning 'ok' indicates that the integration works like it is supposed to. Connection to the service is successful. Raises: exceptions if something goes wrong. :type client: ``DataminrPulseReGenAIClient`` :param client: DataminrPulseReGenAIClient to be used. :rtype: ``str`` :return: 'ok' if test passed, anything else will fail the test. """ params = demisto.params() is_fetch = params.get("isFetch", False) if is_fetch: fetch_incidents(client, {}, params, is_test=True) else: client.get_alerts(page_size=1) # Return OK, indicating the connection to the platform is successful. return "ok" def fetch_incidents( client: DataminrPulseReGenAIClient, last_run: dict[str, Any], params: dict[str, Any], is_test: bool = False ) -> tuple[dict, list]: """Fetch alerts as an incidents. :type client: ``DataminrPulseReGenAIClient`` :param client: DataminrPulseReGenAIClient to be used. :type last_run: ``dict[str, Any]`` :param last_run: last run object obtained from demisto.getLastRun(). :type params: `dict[str, Any]`` :param params: Arguments to be used for fetch incident. :type is_test: ``bool`` :param is_test: If test_module called fetch_incident. :rtype: ``tuple[dict, list]`` :return: Tuple of last run object and list of fetched incidents. """ page_size: int = arg_to_number( params.get("max_fetch", DEFAULT_NUMBER_OF_ALERTS_TO_RETRIEVE), # type: ignore arg_name="Max Fetch", ) watchlist_names = argToList(params.get("watchlist_names"), transform=lambda s: s.strip()) query = params.get("query") alert_type: str = params.get("alert_type", DEFAULT_ALERT_TYPE) if query: query = query.strip() _from = last_run.get("from") last_watchlist_names = last_run.get("last_watchlist_names", []) last_query = last_run.get("last_query") found_alert_ids = last_run.get("found_alert_ids", []) watchlist_names_lower = [] last_watchlist_names_lower = [] if watchlist_names and isinstance(watchlist_names, list): watchlist_names_lower = [name.lower() for name in watchlist_names] watchlist_names_lower.sort() if last_watchlist_names and isinstance(last_watchlist_names, list): last_watchlist_names_lower = [name.lower() for name in last_watchlist_names] last_watchlist_names_lower.sort() watchlist_ids = get_watchlist_ids(client=client, watchlist_names=watchlist_names) if last_watchlist_names_lower != watchlist_names_lower or last_query != query: demisto.debug("Watchlist names or query changed in configuration, so fetching incident from start") _from = None if not is_test and page_size > 100: page_size = 100 validate_params_for_alerts_get( watchlist_ids=watchlist_ids, watchlist_names=watchlist_names, page_size=page_size, use_configured_watchlist_names=False, is_fetch=True, ) response = client.get_alerts(watchlist_ids=watchlist_ids, query=query, _from=_from, page_size=page_size) alert_response, _from, to = [], "", "" if response: alert_response = response.get("alerts", []) _from = response.get("previousPage", "") to = response.get("nextPage", "") alert_valid_response = remove_empty_elements_for_fetch(alert_response) from_url = urlparse(_from) from_params = parse_qs(from_url.query) from_value = from_params.get("to", [None])[0] # type: ignore to_url = urlparse(to) to_params = parse_qs(to_url.query) to_value = to_params.get("from", [None])[0] # type: ignore cursor_response = {"from": from_value, "to": to_value} cursor_valid_response = remove_empty_elements(cursor_response) if is_test: return {}, [] next_run = last_run.copy() incidents = [] duplicate_alert_ids_in_current_fetch = [] new_alert_ids_in_current_fetch = [] for alert in alert_valid_response: alert_type_name = alert.get("alertType", {}).get("name", "") if alert_type != "All" and (not alert_type_name or alert_type_name.lower() != alert_type.lower()): continue alert_id = alert.get("alertId") if alert_id in found_alert_ids: demisto.debug(f"Found existing alert. Alert ID: {alert_id}") duplicate_alert_ids_in_current_fetch.append(alert_id) continue media_html = create_media_html(alert) alert["media_html"] = media_html vulnerability_indicators = create_vulnerability_indicators(alert) alert["vulnerability_indicators"] = vulnerability_indicators malware_indicators = create_malware_indicators(alert) alert["malware_indicators"] = malware_indicators threat_actors_indicators = create_threat_actors_indicators(alert) alert["threat_actors_indicators"] = threat_actors_indicators flight_data = create_flight_data(alert) alert["flight_data"] = flight_data occurred_date = alert.get("alertTimestamp", "") incident_name = alert.get("headline", "") incidents.append( { "name": "✨ " + incident_name if alert.get("intelAgents", []) else incident_name, "occurred": occurred_date, "rawJSON": json.dumps(alert), "severity": ALERT_TYPE_TO_INCIDENT_SEVERITY.get(alert_type_name, 0), } ) found_alert_ids.append(alert_id) new_alert_ids_in_current_fetch.append(alert_id) next_run["found_alert_ids"] = found_alert_ids if alert_valid_response and cursor_valid_response: next_run["from"] = cursor_valid_response.get("to") next_run["last_watchlist_names"] = watchlist_names next_run["last_query"] = query demisto.debug(f"Total alerts fetch as an incident: {len(incidents)}") demisto.debug(f"New alert IDs in current fetch: {new_alert_ids_in_current_fetch}") demisto.debug(f"Duplicate alert IDs in current fetch: {duplicate_alert_ids_in_current_fetch}") demisto.debug(f"All Alert IDs store in last run: {next_run['found_alert_ids']}") demisto.debug(f"Next page cursor: {next_run['from']}") return next_run, incidents def dataminrpulse_watchlists_get_command(client: DataminrPulseReGenAIClient, args: Dict[str, Any]) -> CommandResults: """Retrieve the Watchlist stored on the Dataminr platform. :type client: ``DataminrPulseReGenAIClient`` :param client: DataminrPulseReGenAIClient to be used. :type args: ``Dict[str, Any]`` :param args: Arguments provided by user. :rtype: ``CommandResults`` :return: Standard command result. """ # Retrieve the lists stored on the Dataminr platform. raw_lists_resp = client.get_watchlists() list_of_watchlists = transform_watchlists_data(raw_lists_resp) # Create a human-readable output for the war room entry. hr_output = prepare_hr_for_watchlists_get(list_of_watchlists) # Create and return a CommandResults object to return_results function. return CommandResults( outputs_prefix=OUTPUT_PREFIX_WATCHLISTS, outputs_key_field="id", outputs=list_of_watchlists, readable_output=hr_output, raw_response=raw_lists_resp, ) def dataminrpulse_alerts_get(client: DataminrPulseReGenAIClient, args: Dict[str, Any]) -> List[CommandResults]: """Retrieve the list of the alerts that meet the specified filter criteria. :type client: ``DataminrPulseReGenAIClient`` :param client: DataminrPulseReGenAIClient to be used. :type args: ``Dict[str, Any]`` :param args: Arguments provided by user. :rtype: ``List[CommandResults]`` :return: Standard command results. """ watchlist_names: List = argToList(args.get("watchlist_names", "")) watchlist_ids: List = argToList(args.get("watchlist_ids", "")) query: str = args.get("query", "") _from: str = args.get("from", "") to: str = args.get("to", "") num: int = arg_to_number(args.get("num", DEFAULT_NUMBER_OF_ALERTS_TO_RETRIEVE), arg_name="num") # type: ignore use_configured_watchlist_names: bool = argToBoolean(args.get("use_configured_watchlist_names", "yes")) if use_configured_watchlist_names and not watchlist_ids: watchlist_ids = get_watchlist_ids(client, watchlist_names) validate_params_for_alerts_get( watchlist_ids=watchlist_ids, watchlist_names=watchlist_names, page_size=num, use_configured_watchlist_names=use_configured_watchlist_names, _from=_from, to=to, ) response = client.get_alerts(watchlist_ids, query, _from, to, num) alert_response = response.get("alerts", []) # type: ignore alert_valid_response = remove_empty_elements(alert_response) hr_output_for_alerts = prepare_hr_for_alerts(alert_valid_response) _from = response.get("previousPage", "") # type: ignore to = response.get("nextPage", "") # type: ignore from_url = urlparse(_from) from_params = parse_qs(from_url.query) from_value = from_params.get("to", [None])[0] # type: ignore to_url = urlparse(to) to_params = parse_qs(to_url.query) to_value = to_params.get("from", [None])[0] # type: ignore cursor_response = {"from": from_value, "to": to_value} cursor_valid_response = remove_empty_elements(cursor_response) hr_output_for_cursor = prepare_hr_for_cursor(cursor_valid_response) alert_results = CommandResults( outputs_prefix=OUTPUT_PREFIX_ALERTS, outputs_key_field="alertId", outputs=alert_valid_response, readable_output=hr_output_for_alerts, raw_response=response, ) cursor_results = CommandResults( outputs_prefix=OUTPUT_PREFIX_CURSOR, outputs_key_field=["from", "to"], outputs=cursor_valid_response, readable_output=hr_output_for_cursor, raw_response=cursor_response, ) return [alert_results, cursor_results] def dataminrpulse_vulnerability_enrich_command(client: DataminrPulseReGenAIClient, args: Dict[str, Any]) -> list[CommandResults]: """ Enrich the "Dataminr Pulse Vulnerability Indicator" custom indicator with relevant data. :type client: ``DataminrPulseReGenAIClient`` :param client: DataminrPulseReGenAIClient to be used. :type args: ``Dict[str, Any]`` :param args: Arguments provided by user. :rtype: ``CommandResults`` :return: Standard command result. """ vulnerability_json_data: Any = args.get("vulnerability_json_data") vulnerability_list: list = [] if not vulnerability_json_data: raise ValueError(ERRORS["REQUIRED_ARG"].format("vulnerability_json_data")) try: vulnerability_list = json.loads(vulnerability_json_data) except json.JSONDecodeError: raise ValueError(ERRORS["JSON_DECODE"].format("vulnerability_json_data")) results = [] if not vulnerability_list: return CommandResults(readable_output="No vulnerabilities found.") # type: ignore for vulnerability in vulnerability_list: indicator_value = vulnerability.get("id", "") dbot_score = Common.DBotScore( indicator=indicator_value, indicator_type=DBotScoreType.CUSTOM, integration_name=VENDOR_NAME, score=calculate_dbot_score(float(vulnerability.get("cvss", Common.DBotScore.NONE))), reliability=demisto.params().get("integrationReliability", DEFAULT_RELIABILITY), ) custom_indicator = Common.CustomIndicator( value=indicator_value, indicator_type="Dataminr Pulse Vulnerability Indicator", data=remove_empty_elements(vulnerability), context_prefix="DataminrPulseVulnerabilityIndicator", dbot_score=dbot_score, ) hr = tableToMarkdown("Vulnerability", remove_empty_elements_for_hr(vulnerability), is_auto_json_transform=True) results.append( CommandResults( outputs=remove_empty_elements(vulnerability), outputs_prefix=CUSTOM_OUTPUT_PREFIX.format("Vulnerability"), outputs_key_field="id", indicator=custom_indicator, raw_response=vulnerability, readable_output=hr, ) ) return results def dataminrpulse_malware_enrich_command(client: DataminrPulseReGenAIClient, args: Dict[str, Any]) -> list[CommandResults]: """ Enrich the "Dataminr Pulse Malware Indicator" custom indicator with relevant data. :type client: ``DataminrPulseReGenAIClient`` :param client: DataminrPulseReGenAIClient to be used. :type args: ``Dict[str, Any]`` :param args: Arguments provided by user. :rtype: ``CommandResults`` :return: Standard command result. """ malware_json_data: Any = args.get("malware_json_data") malware_list: list = [] if not malware_json_data: raise ValueError(ERRORS["REQUIRED_ARG"].format("malware_json_data")) try: malware_list = json.loads(malware_json_data) except json.JSONDecodeError: raise ValueError(ERRORS["JSON_DECODE"].format("malware_json_data")) results = [] if not malware_list: return CommandResults(readable_output="No malware found.") # type: ignore for malware in malware_list: indicator_value = malware.get("name", "") dbot_score = Common.DBotScore( indicator=indicator_value, indicator_type=DBotScoreType.CUSTOM, integration_name=VENDOR_NAME, score=Common.DBotScore.BAD, reliability=demisto.params().get("integrationReliability", DEFAULT_RELIABILITY), ) relationships = [] if demisto.params().get("create_relationships", True): relationships = create_relationship_for_malware(malware) custom_indicator = Common.CustomIndicator( value=indicator_value, indicator_type="Dataminr Pulse Malware Indicator", data=remove_empty_elements(malware), context_prefix="DataminrPulseMalwareIndicator", dbot_score=dbot_score, relationships=relationships, ) hr = tableToMarkdown("Malware", remove_empty_elements_for_hr(malware), is_auto_json_transform=True) results.append( CommandResults( outputs=remove_empty_elements(malware), outputs_prefix=CUSTOM_OUTPUT_PREFIX.format("Malware"), outputs_key_field="name", indicator=custom_indicator, raw_response=malware, readable_output=hr, ) ) return results def dataminrpulse_threat_actor_enrich_command(client: DataminrPulseReGenAIClient, args: Dict[str, Any]) -> list[CommandResults]: """ Enrich the "Dataminr Pulse Threat Actor Indicator" custom indicator with relevant data. :type client: ``DataminrPulseReGenAIClient`` :param client: DataminrPulseReGenAIClient to be used. :type args: ``Dict[str, Any]`` :param args: Arguments provided by user. :rtype: ``CommandResults`` :return: Standard command result. """ threat_actor_json_data: Any = args.get("threat_actor_json_data") threat_actor_list: list = [] if not threat_actor_json_data: raise ValueError(ERRORS["REQUIRED_ARG"].format("threat_actor_json_data")) try: threat_actor_list = json.loads(threat_actor_json_data) except json.JSONDecodeError: raise ValueError(ERRORS["JSON_DECODE"].format("threat_actor_json_data")) results = [] if not threat_actor_list: return CommandResults(readable_output="No threat actors found.") # type: ignore for threat_actor in threat_actor_list: indicator_value = threat_actor.get("name", "") dbot_score = Common.DBotScore( indicator=indicator_value, indicator_type=DBotScoreType.CUSTOM, integration_name=VENDOR_NAME, score=Common.DBotScore.BAD, reliability=demisto.params().get("integrationReliability", DEFAULT_RELIABILITY), ) custom_indicator = Common.CustomIndicator( value=indicator_value, indicator_type="Dataminr Pulse Threat Actor Indicator", data=remove_empty_elements(threat_actor), context_prefix="DataminrPulseThreatActorIndicator", dbot_score=dbot_score, ) hr = tableToMarkdown("Threat Actor", remove_empty_elements_for_hr(threat_actor), is_auto_json_transform=True) results.append( CommandResults( outputs=remove_empty_elements(threat_actor), outputs_prefix=CUSTOM_OUTPUT_PREFIX.format("ThreatActor"), outputs_key_field="name", indicator=custom_indicator, raw_response=threat_actor, readable_output=hr, ) ) return results def main(): """main function, parses params and runs command functions""" # Retrieve the configuration parameters. params = trim_spaces_from_args(demisto.params()) remove_nulls_from_dictionary(params) # Credentials for connecting with the Dataminr Pulse API. client_id = params.get("credentials", {}).get("identifier") client_secret = params.get("credentials", {}).get("password") # Default configuration parameters for handling proxy and SSL Certificate validation. verify_certificate = not argToBoolean(params.get("insecure", False)) proxy = argToBoolean(params.get("proxy", False)) # Parameters for fetch incident mechanism watchlist_names = params.get("watchlist_names") # Retrieve the name of the command being called. command = demisto.command() demisto.debug(f"The command being called is {command}.") demisto_commands: Dict[str, Callable] = { "dataminrpulse-watchlists-get": dataminrpulse_watchlists_get_command, "dataminrpulse-alerts-get": dataminrpulse_alerts_get, "dataminrpulse-vulnerability-enrich": dataminrpulse_vulnerability_enrich_command, "dataminrpulse-malware-enrich": dataminrpulse_malware_enrich_command, "dataminrpulse-threat-actor-enrich": dataminrpulse_threat_actor_enrich_command, } try: client = DataminrPulseReGenAIClient( client_id=client_id, client_secret=client_secret, proxy=proxy, verify=verify_certificate ) # Execute the respective command function based on the command name got from the Demisto. if command == "test-module": return_results(test_module(client)) elif command == "fetch-incidents": last_run = demisto.getLastRun() next_run, incidents = fetch_incidents(client, last_run, params) demisto.info(f"Fetched {len(incidents)} new incidents") demisto.incidents(incidents) demisto.setLastRun(next_run) elif command in demisto_commands: args = demisto.args() if command == "dataminrpulse-alerts-get": args.update({"watchlist_names": watchlist_names}) remove_nulls_from_dictionary(trim_spaces_from_args(args)) return_results(demisto_commands[command](client, args)) else: raise NotImplementedError(f"The command {command} is not implemented.") # Log exceptions and return errors 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"): # pragma: no cover main()