indicator-enrichment
Enriches indicators from a provided list or a block of free text. This script detects the indicator type and runs the correct underlying enrichment script. Currently supports: IP, URL, Domain, CVE, and File.
python · Aggregated Scripts
Details
| ID | indicator-enrichment |
|---|---|
| Language | python |
| From Version | 8.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Tags | basescript enrichment |
README
Enriches indicators from a provided list or a block of free text. This script detects the indicator type and runs the correct underlying enrichment script. Currently supports: IP, URL, Domain, CVE, and File.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Tags | basescript, enrichment |
| Cortex XSOAR Version | 6.10.0 |
Inputs
| Argument Name | Description |
|---|---|
| indicator_list | Accepts a list of indicators to enrich. - From CLI: Provide a comma-separated list. If an indicator contains a comma, wrap the indicators in a JSON array. Example: “[\“https://example.com/search?tags=red,yellow,green\”, \“https://example2.com\”]”. - From Context: Pass JSON arrays directly, without modification. |
| text | Free text to be parsed for indicators. The script will run !extractIndicators to retrieve the underlying indicators within the text. |
| external_enrichment | Whether to call external integrations for enrichment: - ‘true’: enrich using enabled external integrations (e.g., VirusTotal (API v3), AlienVault OTX v2) and run internal commands. - ‘false’: use only existing TIM data and run internal commands; skip external integrations. If the ‘brands’ argument is provided, this flag is ignored and enrichment/internal commands will run only on the brands provided. |
| brands | A comma separated list of integration brands to run enrichment against. Example: `“VirusTotal (API v3), AlienVault OTX v2”`. - If provided, only the selected brands are used. Specifying brands will force enable external_enrichment. - If left empty, the script runs enrichment on all enabled integrations. |
| additional_fields | When set to true, the output for each enrichment command includes an `AdditionalFields` object for each of the indicator results. `AdditionalFields` contains all fields returned by TIM or the integrations that are not part of the standard output keys: `Address`, `DetectionEngines`, `PositiveDetections`, `Score`, and `Brand`. When set to false, only the standard keys are returned. |
| raw_context | If true, will also append the underlying enrichment command outputs to the context. (For backwards compatibility). |
| ignore_indicator_limit | By default, the script enforces a 100 indicator limit to maintain performance. If more than 100 unique indicators are found, the script will stop and return an error. Set this argument to true to bypass this limit and process all found indicators. Note: Bypassing the limit is not recommended as it may impact performance. |
Outputs
| Path | Description | Type |
|---|---|---|
| IndicatorEnrichment.Status | The overall status of the script execution. “Success”, “Failed”. | String |
| IndicatorEnrichment.Message | A success message if the command runs successfully, otherwise a message that contains the error. | String |
| IndicatorEnrichment.Results | A list of all indicators found. | Array |
| IndicatorEnrichment.Results.Type | The detected indicator type (e.g., “IP”, “URL”) that triggered this enrichment. | String |
| IndicatorEnrichment.Results.Value | The indicator value. (e.g, IP, URL). | string |
| IndicatorEnrichment.Results.MaxScore | The max score of all the indicators found. | number |
| IndicatorEnrichment.Results.MaxVerdict | The max verdict of all the indicators found. | string |
| IndicatorEnrichment.Results.TIMScore | The TIM score of the indicator. | number |
| IndicatorEnrichment.TIMCVSS | The max CVSS of the indicator. | number |
| IndicatorEnrichment.Results.Status | The status of the indicator: “Manual” if the score was changed manually, “Fresh” if modified within the last week, “Stale” if modified more than a week ago, and “None” if never modified. | string |
| IndicatorEnrichment.Results.ModifiedTime | The time the indicator was last modified. | Date |
| IndicatorEnrichment.Results.Results | A list of all the underlying script results for the valid indicators. | array |
| IndicatorEnrichment.Results.Results.Source | The source of the indicator. | string |
| IndicatorEnrichment.Results.Results.Brand | The brand of the indicator. | string |
| IndicatorEnrichment.Results.Results.DetectionEngines | The detection engines of the indicator. | number |
| IndicatorEnrichment.Results.Results.PositiveDetections | The positive detections of the indicator. | number |
| IndicatorEnrichment.Results.Results.ASOwner | Registered owner of the Autonomous System announcing the IP prefix. | string |
| IndicatorEnrichment.Results.Results.Score | The score of the indicator. | number |
| IndicatorEnrichment.Results.Results.Verdict | The verdict of the indicator. | string |
| IndicatorEnrichment.Results.Results.Address | The IP address of the indicator. | string |
| IndicatorEnrichment.Results.Data | The URL of the indicator. | string |
| IndicatorEnrichment.Results.Name | The Domain of the indicator. | string |
| IndicatorEnrichment.Results.Results.Reliability | The reliability of the Brand. | string |
| IndicatorEnrichment.Results.Results.AdditionalFields | All fields extracted from the indicator other then the main keys (“Brand”, “Score”, “Verdict”, “DetectionEngines”, “PositiveDetections”, “Address”). | list |
| IndicatorEnrichment.Results.Results.AdditionalFields.Relationships.EntityA | The source of the relationship. | string |
| IndicatorEnrichment.Results.Results.AdditionalFields.Relationships.EntityB | The destination of the relationship. | string |
| IndicatorEnrichment.Results.Results.AdditionalFields.Relationships.Relationship | The name of the relationship. | string |
| IndicatorEnrichment.Results.Results.AdditionalFields.Relationships.EntityAType | The type of the source of the relationship. | string |
| IndicatorEnrichment.Results.Results.AdditionalFields.Relationships.EntityBType | The type of the destination of the relationship. | string |
| IndicatorEnrichment.Results.Results.AdditionalFields.Category | The category associated with the indicator. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.ASN | The autonomous system name for the IP address, for example: “AS8948”. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Hostname | The hostname that is mapped to this IP address. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.DNS | A list of IP objects resolved by DNS. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Geo.Location | The geolocation where the IP address or Domain is located, in the format: latitude:longitude. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Geo.Country | The country in which the IP address is located. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Geo.Description | Additional information about the location. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Malicious.Vendor | The vendor reporting the IP address as malicious. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Malicious.Description | A description explaining why the IP address was reported as malicious. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Tags | (List) Tags of the Indicator. | Unknown |
| IndicatorEnrichment.Results.Results.AdditionalFields.FeedRelatedIndicators.value | Indicators that are associated with the given indicator value. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.FeedRelatedIndicators.type | The type of the indicators that are associated with the given indicator value. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.FeedRelatedIndicators.description | The description of the indicators that are associated with the given indicator value. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.MalwareFamily | The malware family associated with the given indicator. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Organization.Name | The organization of the IP. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Organization.Type | The organization type of the IP. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Region | The region in which the IP is located. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Port | Ports that are associated with the Indicator. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Internal | Whether or not the indicator is internal or external. | Bool |
| IndicatorEnrichment.Results.Results.AdditionalFields.UpdatedDate | The date that the indicator was last updated. | Date |
| IndicatorEnrichment.Results.Results.AdditionalFields.Billing | Billing address of the domain. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Tech.Country | The country of the domain technical contact. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Tech.Name | The name of the domain technical contact. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Tech.Organization | The organization of the domain technical contact. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Tech.Email | The email address of the domain technical contact. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.DomainIDNName | The internationalized domain name (IDN) of the domain. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.ExpirationDate | The expiration date of the domain. | Date |
| IndicatorEnrichment.Results.Results.AdditionalFields.DomainStatus | The status of the domain. | Date |
| IndicatorEnrichment.Results.Results.AdditionalFields.NameServers | (List<String>) Name servers of the domain. | Unknown |
| IndicatorEnrichment.Results.Results.AdditionalFields.Organization | The organization of the domain. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Subdomains | (List<String>) Subdomains of the domain. | Unknown |
| IndicatorEnrichment.Results.Results.AdditionalFields.Admin.Country | The country of the domain administrator. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Admin.Email | The email address of the domain administrator. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Admin.Name | The name of the domain administrator. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Admin.Phone | The phone number of the domain administrator. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Registrant.Country | The country of the registrant. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Registrant.Email | The email address of the registrant. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Registrant.Name | The name of the registrant. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Registrant.Phone | The phone number to receive abuse reports. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.NameServers | (List<String>) Name servers of the domain. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.CreationDate | The date that the domain was created. | Date |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.UpdatedDate | The date that the domain was last updated. | Date |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.ExpirationDate | The expiration date of the domain. | Date |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.Registrant.Name | The name of the registrant. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.Registrant.Email | The email address of the registrant. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.Registrant.Phone | The phone number of the registrant. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.Registrar.Name | The name of the registrar, for example, GoDaddy. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.Registrar.AbuseEmail | The email address of the contact to report abuse. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.Registrar.AbusePhone | The phone number of the contact to report abuse. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.Admin.Name | The name of the domain administrator. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.Admin.Email | The email address of the domain administrator. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.Admin.Phone | The phone number of the domain administrator. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.WHOIS.History | List of Whois objects. | String |
| IndicatorEnrichment.Results.Results.CVSS | The CVSS of the indicator. | number |
| IndicatorEnrichment.Results.Results.Description | The description of the indicator. | string |
| IndicatorEnrichment.Results.Results.Published | The published date of the indicator. | string |
| IndicatorEnrichment.Results.Results.AdditionalFields.CreationDate | The date when the domain was created. | Date |
| IndicatorEnrichment.Results.Results.AdditionalFields.ExpirationDate | The expiration date of the domain. | Date |
| IndicatorEnrichment.Results.Results.AdditionalFields.Registrar.Abuse.Name | The name of the contact for reporting abuse. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Registrar.Abuse.Address | The address of the contact for reporting abuse. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Registrar.Abuse.Country | The country of the contact for reporting abuse. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Registrar.Abuse.Network | The network of the contact for reporting abuse. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Registrar.Abuse.Phone | The phone number of the contact for reporting abuse. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Registrar.Abuse.Email | The email address of the contact for reporting abuse. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Campaign | The campaign associated with the Indicator. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.TrafficLightProtocol | The Traffic Light Protocol (TLP) color that is suitable for the IP/Domain. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.CommunityNotes.note | Notes on the IP that were given by the community. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.CommunityNotes.timestamp | The time in which the note was published. | Date |
| IndicatorEnrichment.Results.Results.AdditionalFields.Publications.source | The source in which the article was published. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Publications.title | The name of the article. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Publications.link | A link to the original article. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.Publications.timestamp | The time in which the article was published. | Date |
| IndicatorEnrichment.Results.Results.AdditionalFields.ThreatTypes.threatcategory | The threat category associated to this indicator by the source vendor, for example, Phishing, Control, TOR, etc. | String |
| IndicatorEnrichment.Results.Results.AdditionalFields.ThreatTypes.threatcategoryconfidence | Threat Category Confidence is the confidence level provided by the vendor for the threat type category. For example, a confidence level of 90 for the ‘malware’ threat type category means that the vendor is confident that its 90% malware. | String |
| Core.AnalyticsPrevalence.Ip.value | Whether the IP address is prevalent or not. | Boolean |
| Core.AnalyticsPrevalence.Ip.data.global_prevalence.value | The global prevalence of the IP. | Number |
| Core.AnalyticsPrevalence.Ip.data.local_prevalence.value | The local prevalence of the IP. | Number |
| Core.AnalyticsPrevalence.Ip.data.prevalence.value | The prevalence of the IP. | Number |
| EndpointData.Hostname.value | The endpoint’s hostname. | String |
| EndpointData.Hostname.source | The vendor from which the hostname of this endpoint was retrieved. | String |
| EndpointData.EntityA.value | The source of the relationship. | String |
| EndpointData.EntityA.source | The vendor from which EntityA of this endpoint was retrieved. | String |
| EndpointData.EntityB.value | The destination of the relationship. | String |
| EndpointData.EntityB.source | The vendor from which EntityB of this endpoint was retrieved. | String |
| EndpointData.Relationship.value | The name of the relationship. | String |
| EndpointData.Relationship.source | The vendor from which the relationship of this endpoint was retrieved. | String |
| EndpointData.EntityAType.value | The type of the source of the relationship. | String |
| EndpointData.EntityAType.source | The vendor from which the type of the source of the relationship of this endpoint was retrieved. | String |
| EndpointData.EntityBType.value | The type of the destination of the relationship. | String |
| EndpointData.EntityBType.source | The vendor from which the type of the destination of the relationship of this endpoint was retrieved. | String |
| EndpointData.ID.value | The endpoint’s ID. | String |
| EndpointData.ID.source | The vendor from which the ID of this endpoint was retrieved. | String |
| EndpointData.IPAddress | The endpoint’s IP address. | String |
| EndpointData.Domain.value | The endpoint’s domain. | String |
| EndpointData.Domain.source | The vendor from which the domain of this endpoint was retrieved. | String |
| EndpointData.MACAddress.value | The endpoint’s MAC address. | String |
| EndpointData.MACAddress.source | The vendor from which the MAC address of this endpoint was retrieved. | String |
| EndpointData.DHCPServer.value | The DHCP server of the endpoint. | String |
| EndpointData.DHCPServer.source | The vendor from which the DHCP server of this endpoint was retrieved. | String |
| EndpointData.OS.value | The endpoint’s operating system. | String |
| EndpointData.OS.source | The vendor from which the operating system of this endpoint was retrieved. | String |
| EndpointData.OSVersion.value | The endpoint’s operating system version. | String |
| EndpointData.OSVersion.source | The vendor from which the operating system version of this endpoint was retrieved. | String |
| EndpointData.BIOSVersion.value | The endpoint’s BIOS version. | String |
| EndpointData.BIOSVersion.source | The vendor from which the BIOS version of this endpoint was retrieved. | String |
| EndpointData.Model.value | The model of the machine or device. | String |
| EndpointData.Model.source | The vendor from which the model of this endpoint was retrieved. | String |
| EndpointData.Memory.value | Amount of memory on this endpoint. | Integer |
| EndpointData.Memory.source | The vendor from which the amount of memory of this endpoint was retrieved. | String |
| EndpointData.Processors.value | The number of processors. | Integer |
| EndpointData.Processors.source | The vendor from which the processors of this endpoint was retrieved. | String |
| EndpointData.Processor.value | The model of the processor. | String |
| EndpointData.Processor.source | The vendor from which the processor of this endpoint was retrieved. | String |
| EndpointData.IsIsolated.value | The endpoint’s isolation status. | String |
| EndpointData.IsIsolated.source | The vendor from which the isolation of this endpoint was retrieved. | String |
| EndpointData.Status.value | The endpoint’s status. | String |
| EndpointData.Status.source | The vendor from which the status of this endpoint was retrieved. | String |
| EndpointData.Vendor.value | The integration name of the endpoint vendor. | String |
| EndpointData.Vendor.source | The vendor from which the Vendor of this endpoint was retrieved. | String |
| IndicatorEnrichment.Failed | Audit log of all the inputs/indicators that were not successfully sent to an enrichment script. Weather the type is unsupported, unknown or some internal fatal error. | Array |
| IndicatorEnrichment.Failed.Value | The indicator value that failed. | String |
| IndicatorEnrichment.Failed.Type | The detected type of the indicator that failed. | String |
| IndicatorEnrichment.Failed.Error | The reason for the failure (e.g., “Unsupported indicator type”). | String |
| IPEnrichment.Value | The IP address. | string |
| IPEnrichment.MaxScore | The max score of all the indicators found. | number |
| IPEnrichment.MaxVerdict | The max verdict of all the indicators found. | string |
| IPEnrichment.TIMScore | The TIM score of the IP address. | number |
| IPEnrichment.Results | A list of all indicators found for the IP address. | array |
| IPEnrichment.Status | The status of the indicator: “Manual” if the score was changed manually, “Fresh” if modified within the last week, “Stale” if modified more than a week ago, and “None” if never modified. | string |
| IPEnrichment.ModifiedTime | The time the indicator was last modified. | Date |
| IPEnrichment.Results.Source | The source of the indicator. | string |
| IPEnrichment.Results.Brand | The brand of the indicator. | string |
| IPEnrichment.Results.DetectionEngines | The detection engines of the indicator. | number |
| IPEnrichment.Results.PositiveDetections | The positive detections of the indicator. | number |
| IPEnrichment.Results.ASOwner | Registered owner of the Autonomous System announcing the IP prefix. | string |
| IPEnrichment.Results.Score | The score of the indicator. | number |
| IPEnrichment.Results.Verdict | The verdict of the indicator. | string |
| IPEnrichment.Results.Address | The IP address of the indicator. | string |
| IPEnrichment.Results.Reliability | The reliability of the Brand. | string |
| IPEnrichment.Results.AdditionalFields | All fields extracted from the indicator other then the main keys (“Brand”, “Score”, “Verdict”, “DetectionEngines”, “PositiveDetections”, “Address”). | list |
| IPEnrichment.Results.AdditionalFields.Relationships.EntityA | The source of the relationship. | string |
| IPEnrichment.Results.AdditionalFields.Relationships.EntityB | The destination of the relationship. | string |
| IPEnrichment.Results.AdditionalFields.Relationships.Relationship | The name of the relationship. | string |
| IPEnrichment.Results.AdditionalFields.Relationships.EntityAType | The type of the source of the relationship. | string |
| IPEnrichment.Results.AdditionalFields.Relationships.EntityBType | The type of the destination of the relationship. | string |
| IPEnrichment.Results.AdditionalFields.ASN | The autonomous system name for the IP address, for example: “AS8948”. | String |
| IPEnrichment.Results.AdditionalFields.Hostname | The hostname that is mapped to this IP address. | String |
| IPEnrichment.Results.AdditionalFields.Geo.Location | The geolocation where the IP address is located, in the format: latitude:longitude. | String |
| IPEnrichment.Results.AdditionalFields.Geo.Country | The country in which the IP address is located. | String |
| IPEnrichment.Results.AdditionalFields.Geo.Description | Additional information about the location. | String |
| IPEnrichment.Results.AdditionalFields.Malicious.Vendor | The vendor reporting the IP address as malicious. | String |
| IPEnrichment.Results.AdditionalFields.Malicious.Description | A description explaining why the IP address was reported as malicious. | String |
| IPEnrichment.Results.AdditionalFields.Tags | (List) Tags of the IP. | Unknown |
| IPEnrichment.Results.AdditionalFields.FeedRelatedIndicators.value | Indicators that are associated with the IP. | String |
| IPEnrichment.Results.AdditionalFields.FeedRelatedIndicators.type | The type of the indicators that are associated with the IP. | String |
| IPEnrichment.Results.AdditionalFields.FeedRelatedIndicators.description | The description of the indicators that are associated with the IP. | String |
| IPEnrichment.Results.AdditionalFields.MalwareFamily | The malware family associated with the IP. | String |
| IPEnrichment.Results.AdditionalFields.Organization.Name | The organization of the IP. | String |
| IPEnrichment.Results.AdditionalFields.Organization.Type | The organization type of the IP. | String |
| IPEnrichment.Results.AdditionalFields.Region | The region in which the IP is located. | String |
| IPEnrichment.Results.AdditionalFields.Port | Ports that are associated with the IP. | String |
| IPEnrichment.Results.AdditionalFields.Internal | Whether or not the IP is internal or external. | Bool |
| IPEnrichment.Results.AdditionalFields.UpdatedDate | The date that the IP was last updated. | Date |
| IPEnrichment.Results.AdditionalFields.Registrar.Abuse.Name | The name of the contact for reporting abuse. | String |
| IPEnrichment.Results.AdditionalFields.Registrar.Abuse.Address | The address of the contact for reporting abuse. | String |
| IPEnrichment.Results.AdditionalFields.Registrar.Abuse.Country | The country of the contact for reporting abuse. | String |
| IPEnrichment.Results.AdditionalFields.Registrar.Abuse.Network | The network of the contact for reporting abuse. | String |
| IPEnrichment.Results.AdditionalFields.Registrar.Abuse.Phone | The phone number of the contact for reporting abuse. | String |
| IPEnrichment.Results.AdditionalFields.Registrar.Abuse.Email | The email address of the contact for reporting abuse. | String |
| IPEnrichment.Results.AdditionalFields.Campaign | The campaign associated with the IP. | String |
| IPEnrichment.Results.AdditionalFields.TrafficLightProtocol | The Traffic Light Protocol (TLP) color that is suitable for the IP. | String |
| IPEnrichment.Results.AdditionalFields.CommunityNotes.note | Notes on the IP that were given by the community. | String |
| IPEnrichment.Results.AdditionalFields.CommunityNotes.timestamp | The time in which the note was published. | Date |
| IPEnrichment.Results.AdditionalFields.Publications.source | The source in which the article was published. | String |
| IPEnrichment.Results.AdditionalFields.Publications.title | The name of the article. | String |
| IPEnrichment.Results.AdditionalFields.Publications.link | A link to the original article. | String |
| IPEnrichment.Results.AdditionalFields.Publications.timestamp | The time in which the article was published. | Date |
| IPEnrichment.Results.AdditionalFields.ThreatTypes.threatcategory | The threat category associated to this indicator by the source vendor, for example, Phishing, Control, TOR, etc. | String |
| IPEnrichment.Results.AdditionalFields.ThreatTypes.threatcategoryconfidence | Threat Category Confidence is the confidence level provided by the vendor for the threat type category. For example, a confidence level of 90 for the ‘malware’ threat type category means that the vendor is confident that its 90% malware. | String |
| DomainEnrichment.Value | The Domain. | string |
| DomainEnrichment.MaxScore | The max score of all the indicators found. | number |
| DomainEnrichment.MaxVerdict | The max verdict of all the indicators found. | string |
| DomainEnrichment.Results | List of all indicators found for the domain. | array |
| DomainEnrichment.TIMScore | The TIM score of the domain. | number |
| DomainEnrichment.Status | The status of the indicator: “Manual” if the score was changed manually, “Fresh” if modified within the last week, “Stale” if modified more than a week ago, and “None” if never modified. | string |
| DomainEnrichment.ModifiedTime | The time the indicator was last modified. | Date |
| DomainEnrichment.Results.Brand | The brand of the indicator. | string |
| DomainEnrichment.Results.Score | The score of the indicator. | number |
| DomainEnrichment.Results.Verdict | The verdict of the indicator. | string |
| DomainEnrichment.Results.DetectionEngines | The detection engines of the indicator. | number |
| DomainEnrichment.Results.PositiveDetections | The positive detections of the indicator. | number |
| DomainEnrichment.Results.Name | The Domain. | string |
| DomainEnrichment.Results.Reliability | The reliability of the Brand. | string |
| DomainEnrichment.Results.AdditionalFields | All fields extracted from the indicator other then the main keys (“Brand”, “Score”, “Verdict”, “DetectionEngines”, “PositiveDetections”, “Name”). | Object |
| DomainEnrichment.Results.AdditionalFields.Relationships.EntityA | The source of the relationship. | string |
| DomainEnrichment.Results.AdditionalFields.Relationships.EntityB | The destination of the relationship. | string |
| DomainEnrichment.Results.AdditionalFields.Relationships.Relationship | The name of the relationship. | string |
| DomainEnrichment.Results.AdditionalFields.Relationships.EntityAType | The type of the source of the relationship. | string |
| DomainEnrichment.Results.AdditionalFields.Relationships.EntityBType | The type of the destination of the relationship. | string |
| DomainEnrichment.Results.AdditionalFields.DNS | A list of IP objects resolved by DNS. | String |
| DomainEnrichment.Results.AdditionalFields.CreationDate | The date when the domain was created. | Date |
| DomainEnrichment.Results.AdditionalFields.UpdatedDate | The date when the domain was last updated. | String |
| DomainEnrichment.Results.AdditionalFields.ExpirationDate | The expiration date of the domain. | Date |
| DomainEnrichment.Results.AdditionalFields.DomainStatus | The status of the domain. | Date |
| DomainEnrichment.Results.AdditionalFields.NameServers | (List<String>) Name servers of the domain. | Unknown |
| DomainEnrichment.Results.AdditionalFields.Organization | The organization of the domain. | String |
| DomainEnrichment.Results.AdditionalFields.Subdomains | (List<String>) Subdomains of the domain. | Unknown |
| DomainEnrichment.Results.AdditionalFields.Admin.Country | The country of the domain administrator. | String |
| DomainEnrichment.Results.AdditionalFields.Admin.Email | The email address of the domain administrator. | String |
| DomainEnrichment.Results.AdditionalFields.Admin.Name | The name of the domain administrator. | String |
| DomainEnrichment.Results.AdditionalFields.Admin.Phone | The phone number of the domain administrator. | String |
| DomainEnrichment.Results.AdditionalFields.Registrant.Country | The country of the registrant. | String |
| DomainEnrichment.Results.AdditionalFields.Registrant.Email | The email address of the registrant. | String |
| DomainEnrichment.Results.AdditionalFields.Registrant.Name | The name of the registrant. | String |
| DomainEnrichment.Results.AdditionalFields.Registrant.Phone | The phone number to receive abuse reports. | String |
| DomainEnrichment.Results.AdditionalFields.Tags | (List) Tags of the domain. | Unknown |
| DomainEnrichment.Results.AdditionalFields.FeedRelatedIndicators.value | Indicators that are associated with the domain. | String |
| DomainEnrichment.Results.AdditionalFields.FeedRelatedIndicators.type | The type of the indicators that are associated with the domain. | String |
| DomainEnrichment.Results.AdditionalFields.FeedRelatedIndicators.description | The description of the indicators that are associated with the domain. | String |
| DomainEnrichment.Results.AdditionalFields.MalwareFamily | The malware family associated with the domain. | String |
| DomainEnrichment.Results.AdditionalFields.WHOIS.DomainStatus | The status of the domain. | String |
| DomainEnrichment.Results.AdditionalFields.WHOIS.NameServers | (List<String>) Name servers of the domain. | String |
| DomainEnrichment.Results.AdditionalFields.WHOIS.CreationDate | The date that the domain was created. | Date |
| DomainEnrichment.Results.AdditionalFields.WHOIS.UpdatedDate | The date that the domain was last updated. | Date |
| DomainEnrichment.Results.AdditionalFields.WHOIS.ExpirationDate | The expiration date of the domain. | Date |
| DomainEnrichment.Results.AdditionalFields.WHOIS.Registrant.Name | The name of the registrant. | String |
| DomainEnrichment.Results.AdditionalFields.WHOIS.Registrant.Email | The email address of the registrant. | String |
| DomainEnrichment.Results.AdditionalFields.WHOIS.Registrant.Phone | The phone number of the registrant. | String |
| DomainEnrichment.Results.AdditionalFields.WHOIS.Registrar.Name | The name of the registrar, for example, GoDaddy. | String |
| DomainEnrichment.Results.AdditionalFields.WHOIS.Registrar.AbuseEmail | The email address of the contact to report abuse. | String |
| DomainEnrichment.Results.AdditionalFields.WHOIS.Registrar.AbusePhone | The phone number of the contact to report abuse. | String |
| DomainEnrichment.Results.AdditionalFields.WHOIS.Admin.Name | The name of the domain administrator. | String |
| DomainEnrichment.Results.AdditionalFields.WHOIS.Admin.Email | The email address of the domain administrator. | String |
| DomainEnrichment.Results.AdditionalFields.WHOIS.Admin.Phone | The phone number of the domain administrator. | String |
| DomainEnrichment.Results.AdditionalFields.WHOIS.History | List of Whois objects. | String |
| DomainEnrichment.Results.AdditionalFields.Malicious.Vendor | The vendor reporting the domain as malicious. | String |
| DomainEnrichment.Results.AdditionalFields.Malicious.Description | Reason the domain was reported as malicious. | String |
| DomainEnrichment.Results.AdditionalFields.DomainIDNName | The internationalized domain name (IDN) of the domain. | String |
| DomainEnrichment.Results.AdditionalFields.Port | Ports associated with the domain. | String |
| DomainEnrichment.Results.AdditionalFields.Internal | Whether the domain is internal or external. | Bool |
| DomainEnrichment.Results.AdditionalFields.Category | The category associated with the indicator. | String |
| DomainEnrichment.Results.AdditionalFields.Campaign | The campaign associated with the domain. | String |
| DomainEnrichment.Results.AdditionalFields.TrafficLightProtocol | The Traffic Light Protocol (TLP) color that is suitable for the domain. | String |
| DomainEnrichment.Results.AdditionalFields.ThreatTypes.threatcategory | The threat category associated to this indicator by the source vendor, for example, Phishing, Control, TOR, etc. | String |
| DomainEnrichment.Results.AdditionalFields.ThreatTypes.threatcategoryconfidence | Threat Category Confidence is the confidence level provided by the vendor for the threat type category. For example, a confidence of 90 for the threat type category ‘malware’ means the vendor estimates a 90% likelihood that it is malware. | String |
| DomainEnrichment.Results.AdditionalFields.Geo.Location | The geolocation where the domain address is located, in the format: latitude:longitude. | String |
| DomainEnrichment.Results.AdditionalFields.Geo.Country | The country in which the domain address is located. | String |
| DomainEnrichment.Results.AdditionalFields.Geo.Description | Additional information about the location. | String |
| DomainEnrichment.Results.AdditionalFields.Tech.Country | The country of the domain technical contact. | String |
| DomainEnrichment.Results.AdditionalFields.Tech.Name | The name of the domain technical contact. | String |
| DomainEnrichment.Results.AdditionalFields.Tech.Organization | The organization of the domain technical contact. | String |
| DomainEnrichment.Results.AdditionalFields.Tech.Email | The email address of the domain technical contact. | String |
| DomainEnrichment.Results.AdditionalFields.CommunityNotes.note | Notes on the domain that were given by the community. | String |
| DomainEnrichment.Results.AdditionalFields.CommunityNotes.timestamp | Time the note was published. | Date |
| DomainEnrichment.Results.AdditionalFields.Publications.source | The source where the article was published. | String |
| DomainEnrichment.Results.AdditionalFields.Publications.title | The name of the article. | String |
| DomainEnrichment.Results.AdditionalFields.Publications.link | A link to the original article. | String |
| DomainEnrichment.Results.AdditionalFields.Publications.timestamp | Time the article was published. | Date |
| DomainEnrichment.Results.AdditionalFields.Billing | Billing address of the domain. | String |
| URLEnrichment.Value | The URL. | string |
| URLEnrichment.MaxScore | The max score of all the indicators found. | number |
| URLEnrichment.MaxVerdict | The max verdict of all the indicators found. | string |
| URLEnrichment.Results | List of all indicators found for the URL. | array |
| URLEnrichment.TIMScore | The TIM score of the URL. | number |
| URLEnrichment.Status | The status of the indicator: “Manual” if the score was changed manually, “Fresh” if modified within the last week, “Stale” if modified more than a week ago, and “None” if never modified. | string |
| URLEnrichment.ModifiedTime | The time the indicator was last modified. | Date |
| URLEnrichment.Results.Brand | The brand of the indicator. | string |
| URLEnrichment.Results.Score | The score of the indicator. | number |
| URLEnrichment.Results.Verdict | The verdict of the indicator. | string |
| URLEnrichment.Results.DetectionEngines | The detection engines of the indicator. | number |
| URLEnrichment.Results.PositiveDetections | The positive detections of the indicator. | number |
| URLEnrichment.Results.Data | The URL it self. | string |
| URLEnrichment.Results.Reliability | The reliability of the Brand. | string |
| URLEnrichment.Results.AdditionalFields | All fields extracted from the indicator other then the main keys (“Brand”, “Score”, “Verdict”, “DetectionEngines”, “PositiveDetections”, “Data”). | Object |
| URLEnrichment.Results.AdditionalFields.Relationships.EntityA | The source of the relationship. | string |
| URLEnrichment.Results.AdditionalFields.Relationships.EntityB | The destination of the relationship. | string |
| URLEnrichment.Results.AdditionalFields.Relationships.Relationship | The name of the relationship. | string |
| URLEnrichment.Results.AdditionalFields.Relationships.EntityAType | The type of the source of the relationship. | string |
| URLEnrichment.Results.AdditionalFields.Relationships.EntityBType | The type of the destination of the relationship. | string |
| URLEnrichment.Results.AdditionalFields.Category | The category associated with the indicator. | String |
| URLEnrichment.Results.AdditionalFields.Malicious.Vendor | The vendor reporting the URL as malicious. | String |
| URLEnrichment.Results.AdditionalFields.Malicious.Description | A description of the malicious URL. | String |
| URLEnrichment.Results.AdditionalFields.Tags | (List) Tags of the URL. | Unknown |
| URLEnrichment.Results.AdditionalFields.FeedRelatedIndicators.value | Indicators that are associated with the URL. | String |
| URLEnrichment.Results.AdditionalFields.FeedRelatedIndicators.type | The type of the indicators that are associated with the URL. | String |
| URLEnrichment.Results.AdditionalFields.FeedRelatedIndicators.description | The description of the indicators that are associated with the URL. | String |
| URLEnrichment.Results.AdditionalFields.MalwareFamily | The malware family associated with the URL. | String |
| URLEnrichment.Results.AdditionalFields.Port | Ports that are associated with the URL. | String |
| URLEnrichment.Results.AdditionalFields.Internal | Whether or not the URL is internal or external. | Bool |
| URLEnrichment.Results.AdditionalFields.Campaign | The campaign associated with the URL. | String |
| URLEnrichment.Results.AdditionalFields.TrafficLightProtocol | The Traffic Light Protocol (TLP) color that is suitable for the URL. | String |
| URLEnrichment.Results.AdditionalFields.ThreatTypes.threatcategory | The threat category associated to this indicator by the source vendor. For example, Phishing, Control, TOR, etc. | String |
| URLEnrichment.Results.AdditionalFields.ThreatTypes.threatcategoryconfidence | Threat Category Confidence is the confidence level provided by the vendor for the threat type category For example a confidence of 90 for threat type category ‘malware’ means that the vendor rates that this is 90% confidence of being a malware. | String |
| URLEnrichment.Results.AdditionalFields.ASN | The autonomous system name for the URL, for example: ‘AS8948’. | String |
| URLEnrichment.Results.AdditionalFields.ASOwner | The autonomous system owner of the URL. | String |
| URLEnrichment.Results.AdditionalFields.GeoCountry | The country in which the URL is located. | String |
| URLEnrichment.Results.AdditionalFields.Organization | The organization of the URL. | String |
| URLEnrichment.Results.AdditionalFields.CommunityNotes.note | Notes on the URL that were given by the community. | String |
| URLEnrichment.Results.AdditionalFields.CommunityNotes.timestamp | The time in which the note was published. | Date |
| URLEnrichment.Results.AdditionalFields.Publications.source | The source in which the article was published. | String |
| URLEnrichment.Results.AdditionalFields.Publications.title | The name of the article. | String |
| URLEnrichment.Results.AdditionalFields.Publications.link | A link to the original article. | String |
| URLEnrichment.Results.AdditionalFields.Publications.timestamp | The time in which the article was published. | Date |
| CVEEnrichment.Value | The CVE. | string |
| CVEEnrichment.TIMCVSS | The max CVSS of the indicator. | number |
| CVEEnrichment.Results | List of all indicators found for the CVE. | array |
| CVEEnrichment.Status | The status of the indicator. | string |
| CVEEnrichment.Results.Brand | The brand of the indicator. | string |
| CVEEnrichment.Results.CVSS | The CVSS of the indicator. | number |
| CVEEnrichment.Results.Description | The description of the indicator. | string |
| CVEEnrichment.Results.Published | The published date of the indicator. | string |
| CVEEnrichment.Results.Status | The status of the indicator: “Manual” if the score was changed manually, “Fresh” if modified within the last week, “Stale” if modified more than a week ago, and “None” if never modified. | string |
| CVEEnrichment.Results.ModifiedTime | The time the indicator was last modified. | Date |
| CVEEnrichment.Results.AdditionalFields | All fields extracted from the indicator other then the main keys (“ID”, “Brand”, “CVSS”, “Description”, “Published”, “CVSS”). | Object |
| CVEEnrichment.Results.AdditionalFields.Relationships.EntityA | The source of the relationship. | string |
| CVEEnrichment.Results.AdditionalFields.Relationships.EntityB | The destination of the relationship. | string |
| CVEEnrichment.Results.AdditionalFields.Relationships.Relationship | The name of the relationship. | string |
| CVEEnrichment.Results.AdditionalFields.Relationships.EntityAType | The type of the source of the relationship. | string |
| CVEEnrichment.Results.AdditionalFields.Relationships.EntityBType | The type of the destination of the relationship. | string |
| CVEEnrichment.Results.AdditionalFields.Modified | The timestamp of when the CVE was last modified. | Date |
import pytest from collections import defaultdict from IndicatorEnrichment import ( IndicatorType, EnrichmentRequest, EnrichmentResult, ResponseFormatter, EnrichmentService, UnsupportedIndicator, AllExecutionsFailedError, EnrichmentRequestBuilder, ValidationError, GracefulExit, main, ) class TestIndicatorType: """ Tests the configuration enum, ensuring aliases map correctly to strict types. """ @pytest.mark.parametrize( "input_str, expected_enum", [ ("ip", IndicatorType.IP), ("IPv4", IndicatorType.IP), ("url", IndicatorType.URL), ("Domain", IndicatorType.DOMAIN), ("file", IndicatorType.FILE), ("cve", IndicatorType.CVE), ], ids=["lower_case_ip", "camel_case_ipv4", "simple_url", "capitalized_domain", "file", "simple_cve"], ) def test_resolve_valid_types(self, input_str, expected_enum): """ Scenario: Resolving valid aliases. Given: A string representing a supported indicator type (various casings). When: IndicatorType.resolve_from_string is called. Then: The correct Enum member is returned. """ assert IndicatorType.resolve_from_string(input_str) == expected_enum @pytest.mark.parametrize( "invalid_input", ["garbage_type", "email", " ", None], ids=["unknown_string", "unsupported_type", "whitespace_only", "none_value"], ) def test_resolve_invalid_types(self, invalid_input): """ Scenario: Resolving unsupported inputs. Given: An input string that does not match any configured alias. When: IndicatorType.resolve_from_string is called. Then: None is returned. """ assert IndicatorType.resolve_from_string(invalid_input) is None class TestResponseFormatter: """ Tests the transformation of internal results into XSOAR Output formats (Markdown and Context). """ def test_format_markdown_happy_path(self): """ Scenario: Standard successful enrichment. Given: A Result object with one Markdown section and no errors. When: format() is called. Then: The output contains the section and NO error table or duplicate notes. """ # Arrange result = EnrichmentResult() result.markdown_sections.append("### IP Results\n|IP|Geo|\n|---|---|\n|1.1.1.1|US|") request = EnrichmentRequest( valid_indicators_by_type=defaultdict(list), unsupported_items=[], unknown_items=[], duplicates_removed_count=0, # No duplicates sub_command_arguments={}, include_raw_context=False, ) # Act formatter = ResponseFormatter() cmd_results = formatter.format(result, request) # Assert assert cmd_results.readable_output == "### IP Results\n|IP|Geo|\n|---|---|\n|1.1.1.1|US|" def test_format_markdown_with_duplicates_and_errors(self): """ Scenario: Enrichment with mixed issues. Given: A Request with 2 duplicates removed and 1 unknown item. When: format() is called. Then: The output includes the Duplicate Note AND the Error Table. """ # Arrange result = EnrichmentResult() # Empty result logic for this test request = EnrichmentRequest( valid_indicators_by_type=defaultdict(list), unsupported_items=[], unknown_items=["bad_hash"], duplicates_removed_count=2, sub_command_arguments={}, include_raw_context=False, ) # Act formatter = ResponseFormatter() cmd_results = formatter.format(result, request) # Assert assert cmd_results.readable_output == ( "Note: Removed 2 duplicate indicator occurrences before enrichment.\n" "\n" "### Invalid or unsupported indicators\n" "|Type|Value|Status|Message|\n" "|---|---|---|---|\n" "| Unknown | bad_hash | Error | Not a valid indicator. |\n" ) def test_context_filtering_default(self): """ Scenario: Default context filtering (raw_context=False). Given: Context containing 'IPEnrichment' (raw) and 'DBotScore' (side-effect). When: format() is called with include_raw_context=False. Then: 'IPEnrichment' is removed, 'DBotScore' is preserved. """ # Arrange result = EnrichmentResult() result.raw_context = { "IPEnrichment": {"data": "raw"}, # Should be removed "IndicatorEnrichment": [{"Value": "1.1.1.1", "Type": "IP"}], "Core": {"Score": 3}, # Should be kept "EndpointData": {"Name": "X"}, # Should be kept } request = EnrichmentRequest( valid_indicators_by_type=defaultdict(list), unsupported_items=[], unknown_items=[], duplicates_removed_count=0, sub_command_arguments={}, include_raw_context=False, # <--- Default behavior ) # Act formatter = ResponseFormatter() cmd_results = formatter.format(result, request) ctx = cmd_results.outputs # Assert assert ctx == { "Core": {"Score": 3}, "EndpointData": {"Name": "X"}, "IndicatorEnrichment": [{"Type": "IP", "Value": "1.1.1.1"}], } def test_context_filtering_include_raw(self): """ Scenario: raw_context=True. Given: Context containing 'IPEnrichment' (raw). When: format() is called with include_raw_context=True. Then: 'IPEnrichment' is preserved in the final output. """ # Arrange result = EnrichmentResult() result.raw_context = { "IPEnrichment": {"data": "raw"}, # Should be removed "IndicatorEnrichment": [{"Value": "1.1.1.1", "Type": "IP"}], "Core": {"Score": 3}, # Should be kept "EndpointData": {"Name": "X"}, # Should be kept } request = EnrichmentRequest( valid_indicators_by_type=defaultdict(list), unsupported_items=[], unknown_items=[], duplicates_removed_count=0, sub_command_arguments={}, include_raw_context=True, # <--- Explicit inclusion ) # Act formatter = ResponseFormatter() cmd_results = formatter.format(result, request) # Assert assert "IPEnrichment" in cmd_results.outputs def test_unified_context_creation(self): """ Scenario: Unified context generation. Given: A result with enriched data and a request with an error item. When: format() is called. Then: 'IndicatorEnrichment' list contains both the success entry and the error entry. """ # Arrange result = EnrichmentResult() result.enriched_data = [{"Value": "1.1.1.1", "Type": "IP"}] # Success item request = EnrichmentRequest( valid_indicators_by_type=defaultdict(list), unsupported_items=[UnsupportedIndicator("IPv6", "2001::1")], # Error item unknown_items=[], duplicates_removed_count=0, sub_command_arguments={}, include_raw_context=False, ) # Act formatter = ResponseFormatter() cmd_results = formatter.format(result, request) unified_list = cmd_results.outputs["IndicatorEnrichment"] # Assert assert len(unified_list) == 2 # Check Success Item assert unified_list[0]["Value"] == "1.1.1.1" assert unified_list[0]["Type"] == "IP" # Check Error Item assert unified_list[1]["Value"] == "2001::1" assert unified_list[1]["Type"] == "IPv6" assert unified_list[1]["Status"] == "Error" def test_format_happy_path(self): """ Scenario: Standard successful execution. Given: A Result object with enriched data and markdown. When: format() is called. Then: It returns a CommandResults object with populated readable_output and outputs. """ # Arrange result = EnrichmentResult() result.enriched_data = [{"Value": "8.8.8.8", "Type": "IP", "Geo": "US"}] result.markdown_sections = ["### IP Results\n|IP|Geo|\n|---|---|\n|8.8.8.8|US|"] result.raw_context = {"IPEnrichment": [{"Value": "8.8.8.8"}]} # Should be filtered out by default request = EnrichmentRequest( valid_indicators_by_type=defaultdict(list), unsupported_items=[], unknown_items=[], duplicates_removed_count=0, sub_command_arguments={}, include_raw_context=False, ) # Act formatter = ResponseFormatter() cmd_results = formatter.format(result, request) # Assert # 1. Verify Markdown assert cmd_results.readable_output == "### IP Results\n|IP|Geo|\n|---|---|\n|8.8.8.8|US|" # 2. Verify Context # Ensure the unified key is present and correct, and no raw context assert cmd_results.outputs == {"IndicatorEnrichment": [{"Geo": "US", "Type": "IP", "Value": "8.8.8.8"}]} def test_format_fatal_execution_error(self): """ Scenario: All commands failed (Fatal Error). Given: A Result object with 0 enriched data rows and >0 execution errors. When: format() is called. Then: A ValidationError is raised containing the specific error details. """ # Arrange result = EnrichmentResult() result.enriched_data = [] # Crucial: No success data result.execution_errors = ["Error 1: Timeout", "Error 2: API Limit"] # Dummy request (content irrelevant for this specific check) request = EnrichmentRequest( valid_indicators_by_type=defaultdict(list), unsupported_items=[], unknown_items=[], duplicates_removed_count=0, sub_command_arguments={}, include_raw_context=False, ) # Act & Assert formatter = ResponseFormatter() with pytest.raises(AllExecutionsFailedError) as excinfo: formatter.format(result, request) # Verify the exception message matches the template and includes the errors error_msg = str(excinfo.value) assert error_msg == ( "Fatal error while executing enrichment. View the logs or try again.\n" "Error 1: Timeout\n" "Error 2: API Limit" ) class TestEnrichmentService: """ Tests the orchestration of child commands and result parsing. """ @pytest.fixture def mock_batch_executor(self, mocker): mock_executor = mocker.patch("IndicatorEnrichment.BatchExecutor") mock_instance = mock_executor.return_value return mock_instance def test_execute_empty_request(self, mock_batch_executor): request = EnrichmentRequest( valid_indicators_by_type=defaultdict(list), unsupported_items=[], unknown_items=[], duplicates_removed_count=0, sub_command_arguments={}, include_raw_context=False, ) service = EnrichmentService(request) result = service.execute() assert len(result.enriched_data) == 0 assert len(result.execution_errors) == 0 mock_batch_executor.execute_batch.assert_not_called() def test_execute_happy_path(self, mock_batch_executor): """ Scenario: Standard execution. Mock Structure: List (Tasks) -> List (Batch Results) -> Tuple (Entry, HR, Err) """ # Arrange Request valid_map = defaultdict(list) valid_map[IndicatorType.IP] = ["8.8.8.8"] request = EnrichmentRequest( valid_indicators_by_type=valid_map, unsupported_items=[], unknown_items=[], duplicates_removed_count=0, sub_command_arguments={}, include_raw_context=False, ) # Arrange Mock Output mock_entry_dict = {"Type": 1, "EntryContext": {"IPEnrichment": [{"Address": "8.8.8.8", "Geo": "US"}]}} # Structure: [ [ (Entry, HR, Err) ] ] mock_batch_executor.execute_batch.return_value = [[(mock_entry_dict, "### IP Info", None)]] # Act service = EnrichmentService(request) result = service.execute() # Assert assert len(result.enriched_data) == 1 assert result.enriched_data[0]["Address"] == "8.8.8.8" assert "### IP Info" in result.markdown_sections[0] def test_execute_with_command_error(self, mock_batch_executor): """ Scenario: Child command fails. Mock Structure: [ [ (None, None, ErrorString) ] ] """ # Arrange Request valid_map = defaultdict(list) valid_map[IndicatorType.URL] = ["bad.com"] request = EnrichmentRequest( valid_indicators_by_type=valid_map, unsupported_items=[], unknown_items=[], duplicates_removed_count=0, sub_command_arguments={}, include_raw_context=False, ) # Arrange Mock Output mock_batch_executor.execute_batch.return_value = [[(None, None, "Error: Timeout connecting to API")]] # Act service = EnrichmentService(request) result = service.execute() # Assert assert len(result.enriched_data) == 0 assert len(result.execution_errors) == 1 assert "Timeout connecting to API" in result.execution_errors[0] def test_execute_mixed_results(self, mock_batch_executor): """ Scenario: Two commands run; one succeeds (IP), one fails (URL). Mock Structure: [ [(SuccessTuple)], [(FailureTuple)] ] """ # Arrange Request with 2 types valid_map = defaultdict(list) valid_map[IndicatorType.IP] = ["1.1.1.1"] valid_map[IndicatorType.URL] = ["bad.com"] request = EnrichmentRequest( valid_indicators_by_type=valid_map, unsupported_items=[], unknown_items=[], duplicates_removed_count=0, sub_command_arguments={}, include_raw_context=False, ) mock_ip_entry = {"EntryContext": {"IPEnrichment": [{"Address": "1.1.1.1"}]}} # Two items in the main list (one per command task) mock_batch_executor.execute_batch.return_value = [ [(mock_ip_entry, "IP Table", None)], # Result for Command 1 (IP) [(None, None, "404 Not Found")], # Result for Command 2 (URL) ] # Act service = EnrichmentService(request) result = service.execute() # Assert # Success Check assert len(result.enriched_data) == 1 assert result.enriched_data[0]["Address"] == "1.1.1.1" # Failure Check assert len(result.execution_errors) == 1 assert "404 Not Found" in result.execution_errors[0] class TestEnrichmentRequestBuilder: """ Tests input parsing, deduplication, and validation logic. """ @pytest.fixture def mock_execute_command(self, mocker): """Mocks the Demisto execute_command for text extraction.""" # Update 'IndicatorEnrichment' to your actual file name return mocker.patch("IndicatorEnrichment.execute_command") @pytest.fixture def mock_arg_to_list(self, mocker): """Mocks the CommonServerPython argToList helper.""" return mocker.patch( "IndicatorEnrichment.argToList", side_effect=lambda x, transform=None: [y.strip() for y in x.split(",")] if x else [] ) @pytest.fixture def mock_auto_detect(self, mocker): """Mocks the auto_detect_indicator_type function.""" return mocker.patch("IndicatorEnrichment.auto_detect_indicator_type") def test_build_and_validate_happy_path_list(self, mock_arg_to_list, mock_auto_detect): """ Scenario: Standard list input with one IP and one Domain. """ raw_args = {"indicator_list": "1.1.1.1, example.com"} mock_arg_to_list.side_effect = lambda arg, transform=None: ["1.1.1.1", "example.com"] mock_auto_detect.side_effect = lambda x: "IP" if "1.1.1.1" in x else "Domain" builder = EnrichmentRequestBuilder(raw_args) request = builder.build_and_validate() assert request.total_valid_count == 2 assert "1.1.1.1" in request.valid_indicators_by_type[IndicatorType.IP] assert "example.com" in request.valid_indicators_by_type[IndicatorType.DOMAIN] def test_build_and_validate_deduplication_text_vs_list(self, mock_execute_command, mock_arg_to_list, mock_auto_detect): """ Scenario: The same IP appears in 'text' and 'indicator_list'. """ raw_args = {"text": "check 8.8.8.8", "indicator_list": "8.8.8.8"} mock_execute_command.return_value = [{"EntryContext": {"ExtractedIndicators": {"IP": ["8.8.8.8"]}}}] mock_arg_to_list.return_value = ["8.8.8.8"] mock_auto_detect.return_value = "IP" builder = EnrichmentRequestBuilder(raw_args) request = builder.build_and_validate() assert request.total_valid_count == 1 assert request.valid_indicators_by_type[IndicatorType.IP] == ["8.8.8.8"] assert request.duplicates_removed_count == 1 def test_build_and_validate_mixed_valid_and_invalid(self, mock_arg_to_list, mock_auto_detect): """ Scenario: Input contains mixed types: Valid (IP), Unsupported (IPv6), and Unknown. Goal: Verify bucketing logic works. (Must have at least 1 valid to succeed). """ raw_args = {"indicator_list": "1.1.1.1, 2001:db8::1, bad_hash"} # Return all 3 items mock_arg_to_list.return_value = ["1.1.1.1", "2001:db8::1", "bad_hash"] # Define behavior: 1.1.1.1 is IP, 2001 is IPv6, bad_hash is None def auto_detect_side_effect(val): if "1.1.1.1" in val: return "IP" if "2001" in val: return "IPv6" return None mock_auto_detect.side_effect = auto_detect_side_effect builder = EnrichmentRequestBuilder(raw_args) request = builder.build_and_validate() # Verify Valid assert request.total_valid_count == 1 assert "1.1.1.1" in request.valid_indicators_by_type[IndicatorType.IP] # Verify Unsupported (IPv6) assert len(request.unsupported_items) == 1 assert request.unsupported_items[0].type == "IPv6" assert request.unsupported_items[0].value == "2001:db8::1" # Verify Unknown (bad_hash) assert len(request.unknown_items) == 1 assert request.unknown_items[0] == "bad_hash" def test_validation_only_invalid_indicators(self, mock_arg_to_list, mock_auto_detect): """ Scenario: Input contains *only* invalid/unsupported items (no valid ones). Goal: Verify that this raises ValidationError (because total_valid == 0). """ raw_args = {"indicator_list": "2001:db8::1, bad_hash"} mock_arg_to_list.return_value = ["2001:db8::1", "bad_hash"] def auto_detect_side_effect(val): if "2001" in val: return "IPv6" return None mock_auto_detect.side_effect = auto_detect_side_effect builder = EnrichmentRequestBuilder(raw_args) # Must raise because we strictly require at least 1 valid indicator with pytest.raises(ValidationError) as excinfo: builder.build_and_validate() assert "No valid indicators provided" in str(excinfo.value) def test_validation_no_inputs(self, mock_arg_to_list): """ Scenario: No text, no list provided. """ raw_args = {} mock_arg_to_list.return_value = [] builder = EnrichmentRequestBuilder(raw_args) with pytest.raises(ValidationError) as excinfo: builder.build_and_validate() assert "No valid indicators provided" in str(excinfo.value) def test_graceful_exit_text_only_nothing_found(self, mock_execute_command, mock_arg_to_list): """ Scenario: Text provided, but extractIndicators finds nothing. """ raw_args = {"text": "hello world"} mock_execute_command.return_value = [{"EntryContext": {}}] mock_arg_to_list.return_value = [] builder = EnrichmentRequestBuilder(raw_args) with pytest.raises(GracefulExit) as excinfo: builder.build_and_validate() assert "No valid indicators provided" in str(excinfo.value) def test_limit_enforcement(self, mock_arg_to_list, mock_auto_detect): """ Scenario: More than 100 valid indicators found. """ many_ips = [f"1.1.1.{i}" for i in range(101)] raw_args = {"indicator_list": ",".join(many_ips)} mock_arg_to_list.return_value = many_ips mock_auto_detect.return_value = "IP" builder = EnrichmentRequestBuilder(raw_args) with pytest.raises(ValidationError) as excinfo: builder.build_and_validate() assert "Indicator limit exceeded" in str(excinfo.value) def test_limit_bypass(self, mock_arg_to_list, mock_auto_detect): """ Scenario: 101 IPs found, but ignore_indicator_limit is True. """ many_ips = [f"1.1.1.{i}" for i in range(101)] raw_args = {"indicator_list": ",".join(many_ips), "ignore_indicator_limit": "true"} mock_arg_to_list.return_value = many_ips mock_auto_detect.return_value = "IP" builder = EnrichmentRequestBuilder(raw_args) request = builder.build_and_validate() assert request.total_valid_count == 101 class TestMain: """ Tests the entry point execution, ensuring the correct flow between Builder -> Service -> Formatter, and correct handling of exceptions. """ @pytest.fixture def mock_demisto_args(self, mocker): return mocker.patch("IndicatorEnrichment.demisto.args") @pytest.fixture def mock_return_results(self, mocker): return mocker.patch("IndicatorEnrichment.return_results") @pytest.fixture def mock_return_error(self, mocker): return mocker.patch("IndicatorEnrichment.return_error") @pytest.fixture def mock_components(self, mocker): """Mocks the classes instantiated inside main().""" mocks = { "builder_cls": mocker.patch("IndicatorEnrichment.EnrichmentRequestBuilder"), "service_cls": mocker.patch("IndicatorEnrichment.EnrichmentService"), "formatter_cls": mocker.patch("IndicatorEnrichment.ResponseFormatter"), } return mocks def test_main_happy_path(self, mock_demisto_args, mock_return_results, mock_return_error, mock_components): """ Scenario: Successful execution. Flow: Args -> Build -> Execute -> Format -> return_results. """ # Arrange mock_demisto_args.return_value = {"indicator_list": "1.1.1.1"} # Setup Component Mocks mock_builder_instance = mock_components["builder_cls"].return_value mock_builder_instance.build_and_validate.return_value = "dummy_request" mock_service_instance = mock_components["service_cls"].return_value mock_service_instance.execute.return_value = "dummy_result" mock_formatter_instance = mock_components["formatter_cls"].return_value mock_formatter_instance.format.return_value = "final_command_results" # Act main() # Assert # 1. Builder Check mock_components["builder_cls"].assert_called_once_with({"indicator_list": "1.1.1.1"}) # 2. Service Check # Verify the *Constructor* received the request (from the builder mock) mock_components["service_cls"].assert_called_once_with("dummy_request") # Verify execute() was called with NO arguments mock_service_instance.execute.assert_called_once_with() # 3. Formatter Check mock_formatter_instance.format.assert_called_once_with("dummy_result", "dummy_request") # 4. Final Output Check mock_return_results.assert_called_once_with("final_command_results") mock_return_error.assert_not_called() def test_main_graceful_exit(self, mock_demisto_args, mock_return_results, mock_return_error, mock_components): """ Scenario: Builder raises GracefulExit (e.g. text input with no indicators). Flow: Args -> Build -> GracefulExit -> return_results(info_message). """ # Arrange mock_demisto_args.return_value = {"text": "nothing here"} # Make builder raise the exception mock_builder_instance = mock_components["builder_cls"].return_value mock_builder_instance.build_and_validate.side_effect = GracefulExit("No indicators found.") # Act main() # Assert # Should call return_results (Green check), NOT return_error assert mock_return_results.call_count == 1 args = mock_return_results.call_args[0][0] assert args.readable_output == "No indicators found." mock_return_error.assert_not_called() def test_main_validation_error(self, mock_demisto_args, mock_return_results, mock_return_error, mock_components): """ Scenario: Builder raises ValidationError (e.g. limit exceeded or garbage list). Flow: Args -> Build -> ValidationError -> return_error. """ # Arrange mock_demisto_args.return_value = {"indicator_list": "garbage"} # Make builder raise exception mock_builder_instance = mock_components["builder_cls"].return_value mock_builder_instance.build_and_validate.side_effect = ValidationError("Invalid input.") # Act main() # Assert # Should call return_error (Red X) mock_return_error.assert_called_once_with("Invalid input.") mock_return_results.assert_not_called() def test_main_fatal_execution_error(self, mock_demisto_args, mock_return_results, mock_return_error, mock_components): """ Scenario: Formatter raises ValidationError (Fatal Execution Error). Flow: Args -> Build -> Execute -> Format -> ValidationError -> return_error. """ # Arrange mock_builder_instance = mock_components["builder_cls"].return_value # We need this to return a value so main() proceeds to the service execution mock_builder_instance.build_and_validate.return_value = "req" mock_service_instance = mock_components["service_cls"].return_value mock_service_instance.execute.return_value = "res" # Make formatter raise the fatal error mock_formatter_instance = mock_components["formatter_cls"].return_value mock_formatter_instance.format.side_effect = ValidationError("Fatal execution error.") # Act main() # Assert mock_return_error.assert_called_once_with("Fatal execution error.") def test_main_system_exception(self, mock_demisto_args, mock_return_results, mock_return_error, mock_components, mocker): """ Scenario: Unexpected crash (e.g. NoneType error). Flow: Args -> Build -> Exception -> return_error (caught by generic try/except). """ # Arrange mock_demisto_error = mocker.patch("IndicatorEnrichment.demisto.error") # To verify logging # Simulate a crash in the builder mock_components["builder_cls"].side_effect = RuntimeError("Unexpected Crash") # Act main() # Assert # 1. return_error is called with a generic wrapper message mock_return_error.assert_called_once() error_msg = mock_return_error.call_args[0][0] assert "Failed to execute !indicator-enrichment" in error_msg assert "Unexpected Crash" in error_msg # 2. demisto.error is called to log the stack trace mock_demisto_error.assert_called()