ip-enrichment
Enriches IP indicators with reputation data from multiple integrations and outputs a consolidated IPEnrichment object. This script exclusively supports indicators of type IPv4 (IPv6 is not supported) and will automatically create the indicator in TIM if it is not already exists. Note: If enabled, get-endpoint-data is executed only for internal IP addresses.
python · Aggregated Scripts
Details
| ID | ip-enrichment |
|---|---|
| Language | python |
| From Version | 8.0.0 |
| Docker Image | demisto/python3:3.12.13.10404775 |
| Tags | basescript |
README
Enriches IP indicators with reputation data from multiple integrations and outputs a consolidated IPEnrichment object. This script exclusively supports indicators of type IPv4 (IPv6 is not supported) and will automatically create the indicator in TIM if it is not already exists. Note: If enabled, get-endpoint-data is executed only for internal IP addresses.
Note: This script is supported in Cortex XSOAR 8.0 and later, and in Cortex XSIAM.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Tags | basescript |
| Cortex XSOAR Version | 8.0.0 |
Inputs
| Argument Name | Description |
|---|---|
| ip_list | A comma-separated list of IPv4 addresses to enrich. |
| 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. |
| verbose | Retrieve a human-readable entry for each command; if false, only the final result is summarized and errors are suppressed. |
| brands | A list of integration brands to run enrichment against. Example: `“VirusTotal (API v3), AlienVault OTX v2”`. - If provided, only the selected brands are used. - If left empty, the script runs enrichment on all enabled integrations, depending on the `external_enrichment` flag. - In order to run get-endpoint-data add Core to the brands list. - In order to run core-get-IP-analytics-prevalence, add Cortex Core - IR to the brands list. To see the available brands for the `ip` command, run: `!ProvidesCommand command=ip`. |
| additional_fields | When set to true, the output includes an `AdditionalFields` object for each of the indicator result. `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. |
Outputs
| Path | Description | Type |
|---|---|---|
| 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.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 |
| 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 |
import json import demistomock as demisto import pytest from IPEnrichment import ip_enrichment_script def util_load_json(path: str): with open(path, encoding="utf-8") as f: return json.load(f) # End-to-end: TIM + Core lookups + enrichIndicators (batch data from file) def test_ip_enrichment_script_end_to_end_with_batch_file(mocker): """ Given: - Two IPs. - TIM returns: * 1.1.1.1 with TIM score 3, brand2 score 3 (Medium reliability), brand1 score 2 (High reliability). (IOC marked as manually edited → top-level Status should be "Manual") * 8.8.8.8 with TIM score 1, brand3 score 1 (Low reliability) - Batches: * createNewIndicator (no-op outputs) * core-get-IP-analytics-prevalence (Core.AnalyticsPrevalence.Ip) * enrichIndicators per IP (DBotScore only → not surfaced by current mapping) When: - ip_enrichment_script runs end-to-end. Then: - IPEnrichment contains both IPs. - 1.1.1.1 has 3 Results items (TIM + brand1 + brand2). - MaxScore=3, MaxVerdict=Malicious, TIMScore=3 for 1.1.1.1. - Vendor Results include Reliability propagated from TIM score cache. - TIM row in Results does NOT contain Status/ModifiedTime (popped to top-level). - Core prevalence are mapped/preserved. """ tim_pages = util_load_json("test_data/mock_ip_tim_results.json")["pages"] batch_data = util_load_json("test_data/mock_ip_batch_results.json") ip_list = ["1.1.1.1", "8.8.8.8"] mocker.patch.object(demisto, "args", return_value={"ip_list": ",".join(ip_list)}) # is_xsiam mocker.patch("IPEnrichment.is_xsiam", return_value=True) mocker.patch("IPEnrichment.is_platform", return_value=False) # extractIndicators -> validates input mocker.patch( "AggregatedCommandApiModule.execute_command", return_value=[{"EntryContext": {"ExtractedIndicators": {"IP": ip_list}}}], ) # IndicatorsSearcher -> yield TIM pages class _MockSearcher: def __init__(self, pages): self.pages = pages def __iter__(self): return iter(self.pages) mocker.patch("AggregatedCommandApiModule.IndicatorsSearcher", return_value=_MockSearcher(tim_pages)) # Enabled brands/instances mocker.patch.object( demisto, "getModules", return_value={ "m1": {"state": "active", "brand": "brand1"}, "m2": {"state": "active", "brand": "brand2"}, "core": {"state": "active", "brand": "Core"}, "coreir": {"state": "active", "brand": "Cortex Core - IR"}, }, ) # Helpers to shape BatchExecutor output into (result, hr, err) tuples def _wrap_each_as_command(entries, hr=""): return [[(e, hr, "")] for e in entries] def _wrap_all_in_one_command(entries, hr=""): return [[(e, hr, "") for e in entries]] # Patch BatchExecutor.execute_list_of_batches to return our fixtures aligned to commands def _fake_execute_list_of_batches(self, list_of_batches, brands_to_run=None, verbose=False): out = [] # ----- Batch 1: createNewIndicator ----- b1_cmds = list_of_batches[0] b1_entries = batch_data["batch1_createNewIndicator"] if len(b1_cmds) == 1: out.append(_wrap_all_in_one_command(b1_entries)) else: assert len(b1_entries) == len(b1_cmds), "batch1 size mismatch vs fixture" out.append(_wrap_each_as_command(b1_entries)) # ----- Batch 2: prevalence, enrichIndicators ----- b2_cmds = list_of_batches[1] assert "get-endpoint-data" not in [c.name for c in b2_cmds] assert b2_cmds[0].name == "core-get-IP-analytics-prevalence" batch2_results = [] prevalence_entries = batch_data["batch2_core_prevalence"] assert len(prevalence_entries) == 1 batch2_results.extend(_wrap_each_as_command(prevalence_entries, hr="prevalence-hr")) enrich_entries = batch_data["batch2_enrichIndicators"] enrich_cmds_count = sum(1 for c in b2_cmds if c.name == "enrichIndicators") if enrich_cmds_count == 1: batch2_results.extend(_wrap_all_in_one_command(enrich_entries)) else: assert len(enrich_entries) == enrich_cmds_count, "enrichIndicators size mismatch vs fixture" batch2_results.extend(_wrap_each_as_command(enrich_entries)) out.append(batch2_results) return out mocker.patch("AggregatedCommandApiModule.BatchExecutor.execute_list_of_batches", _fake_execute_list_of_batches) # Act res = ip_enrichment_script( ip_list=ip_list, external_enrichment=True, verbose=True, enrichment_brands=["brand1", "brand2", "Core", "Cortex Core - IR"], additional_fields=False, ) outputs = res.outputs assert outputs is not None # IPEnrichment indicators enrichment_list = outputs.get("IPEnrichment(val.Value && val.Value == obj.Value)", []) enrichment_map = {item["Value"]: item for item in enrichment_list} assert set(enrichment_map.keys()) == set(ip_list) # 1.1.1.1 should have TIM + brand1 + brand2 ip1 = enrichment_map["1.1.1.1"] brands_present = {r.get("Brand") for r in ip1["Results"]} assert brands_present == {"TIM", "brand1", "brand2"} assert len(ip1["Results"]) == 3 # Vendor results with reliability b1 = next(r for r in ip1["Results"] if r["Brand"] == "brand1") assert b1["Score"] == 2 assert b1["PositiveDetections"] == 5 assert b1.get("Reliability") == "High" b2 = next(r for r in ip1["Results"] if r["Brand"] == "brand2") assert b2["Score"] == 3 assert b2["PositiveDetections"] == 37 assert b2.get("Reliability") == "Medium" # TIM line present but without Status/ModifiedTime (popped to top-level) tim_row = next(r for r in ip1["Results"] if r["Brand"] == "TIM") assert "Status" not in tim_row assert "ModifiedTime" not in tim_row # Max fields and TIMScore assert ip1["MaxScore"] == 3 assert ip1["MaxVerdict"] == "Malicious" assert ip1["TIMScore"] == 3 # Optional: we set the IOC "manuallyEditedFields" → top-level Status should be Manual assert ip1.get("Status") == "Manual" # Core prevalence mapped prevalence_ctx = outputs.get("Core.AnalyticsPrevalence.Ip", []) assert isinstance(prevalence_ctx, list) assert len(prevalence_ctx) == 2 assert {d["Ip"] for d in prevalence_ctx} == {"1.1.1.1", "8.8.8.8"} def test_ip_enrichment_script_with_internal_ip(mocker): """ Given: - One internal IP. When: - ip_enrichment_script runs. Then: - get-endpoint-data command is executed. - EndpointData is mapped and preserved. """ batch_data = util_load_json("test_data/mock_ip_batch_results.json") ip_list = ["192.168.1.1"] mocker.patch.object(demisto, "args", return_value={"ip_list": ",".join(ip_list)}) mocker.patch("IPEnrichment.is_xsiam", return_value=True) mocker.patch("IPEnrichment.is_platform", return_value=False) mocker.patch( "AggregatedCommandApiModule.execute_command", return_value=[{"EntryContext": {"ExtractedIndicators": {"IP": ip_list}}}], ) mocker.patch("AggregatedCommandApiModule.IndicatorsSearcher", return_value=iter([])) mocker.patch.object( demisto, "getModules", return_value={ "core": {"state": "active", "brand": "Core"}, "coreir": {"state": "active", "brand": "Cortex Core - IR"}, }, ) def _wrap_each_as_command(entries, hr=""): return [[(e, hr, "")] for e in entries] def _wrap_all_in_one_command(entries, hr=""): return [[(e, hr, "") for e in entries]] def _fake_execute_list_of_batches(self, list_of_batches, brands_to_run=None, verbose=False): out = [] b1_cmds = list_of_batches[0] b1_entries = batch_data["batch1_createNewIndicator"] if len(b1_cmds) == 1: out.append(_wrap_all_in_one_command(b1_entries)) else: assert len(b1_entries) == len(b1_cmds), "batch1 size mismatch vs fixture" out.append(_wrap_each_as_command(b1_entries)) b2_cmds = list_of_batches[1] assert b2_cmds[0].name == "get-endpoint-data" batch2_results = [] endpoint_entries = batch_data["batch2_core_endpoint_data"] assert len(endpoint_entries) == 1 batch2_results.extend(_wrap_each_as_command(endpoint_entries, hr="endpoint-hr")) prevalence_entries = batch_data["batch2_core_prevalence"] assert len(prevalence_entries) == 1 batch2_results.extend(_wrap_each_as_command(prevalence_entries, hr="prevalence-hr")) enrich_entries = batch_data["batch2_enrichIndicators"] enrich_cmds_count = sum(1 for c in b2_cmds if c.name == "enrichIndicators") if enrich_cmds_count == 1: batch2_results.extend(_wrap_all_in_one_command(enrich_entries)) else: assert len(enrich_entries) == enrich_cmds_count, "enrichIndicators size mismatch vs fixture" batch2_results.extend(_wrap_each_as_command(enrich_entries)) out.append(batch2_results) return out mocker.patch("AggregatedCommandApiModule.BatchExecutor.execute_list_of_batches", _fake_execute_list_of_batches) res = ip_enrichment_script( ip_list=ip_list, external_enrichment=True, verbose=True, enrichment_brands=["Core", "Cortex Core - IR"], additional_fields=False, ) outputs = res.outputs assert outputs is not None endpoint_ctx = outputs.get( "EndpointData" "(val.Brand && val.Brand == obj.Brand && val.ID && val.ID == obj.ID && val.Hostname && val.Hostname == obj.Hostname)", [], ) assert isinstance(endpoint_ctx, list) assert len(endpoint_ctx) == 2 assert {e["Brand"] for e in endpoint_ctx} == {"Core"} assert {e["Hostname"] for e in endpoint_ctx} == {"host-1", "host-2"} def test_ip_enrichment_uses_builtin_command_on_platform(mocker): """ Given: - Running on the unified Cortex platform (is_xsiam and is_platform both True). When: - ip_enrichment_script builds its command batches. Then: - The prevalence command is the built-in "getIPAnalyticsPrevalence". - Its command_type is CommandType.BUILTIN (not the legacy INTERNAL core command). """ from AggregatedCommandApiModule import CommandType ip_list = ["1.1.1.1"] captured_batches: dict = {} class _StopAfterCapture(Exception): pass def _capture_batches(self, list_of_batches, brands_to_run=None, verbose=False): captured_batches["batches"] = list_of_batches raise _StopAfterCapture mocker.patch.object(demisto, "args", return_value={"ip_list": ",".join(ip_list)}) mocker.patch("IPEnrichment.is_xsiam", return_value=True) mocker.patch("IPEnrichment.is_platform", return_value=True) mocker.patch( "AggregatedCommandApiModule.execute_command", return_value=[{"EntryContext": {"ExtractedIndicators": {"IP": ip_list}}}], ) mocker.patch("AggregatedCommandApiModule.IndicatorsSearcher", return_value=iter([])) mocker.patch.object( demisto, "getModules", return_value={"coreir": {"state": "active", "brand": "Cortex Core - IR"}}, ) mocker.patch("AggregatedCommandApiModule.BatchExecutor.execute_list_of_batches", _capture_batches) with pytest.raises(_StopAfterCapture): ip_enrichment_script( ip_list=ip_list, external_enrichment=True, verbose=True, enrichment_brands=["Cortex Core - IR"], additional_fields=False, ) b2_cmds = captured_batches["batches"][1] prevalence_cmds = [c for c in b2_cmds if c.name == "getIPAnalyticsPrevalence"] assert len(prevalence_cmds) == 1 assert prevalence_cmds[0].command_type == CommandType.BUILTIN