domain-enrichment
Enriches Domain indicators with reputation data from multiple integrations and outputs a consolidated DomainEnrichment object. This script exclusively supports indicators of type Domain and will automatically create the indicator in TIM if it is not already exists.
python · Aggregated Scripts
Details
| ID | domain-enrichment |
|---|---|
| Language | python |
| From Version | 8.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Tags | basescript |
README
Enriches Domain indicators with reputation data from multiple integrations and outputs a consolidated DomainEnrichment object. This script exclusively supports indicators of type Domain and will automatically create the indicator in TIM if it is not already exists.
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 |
|---|---|
| domain_list | Accepts a list of domains to enrich. - From CLI: Provide a comma-separated list. If a Domain contains a comma, wrap the domains in a JSON array. Example: “[\“example.com/search:yellow,red\”, \“example2.com\”]”. - From Context: Pass JSON arrays directly, without modification. |
| 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 core-get-IP-analytics-prevalence, add Cortex Core - IR to the brands list. To see the available brands for the `domain` command, run: `!ProvidesCommand command=domain`. |
| 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: `Name`, `Brand`, `Score`, `Verdict`, `DetectionEngines`, `PositiveDetections`. When set to false, only the standard keys are returned. |
Outputs
| Path | Description | Type |
|---|---|---|
| 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.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 |
| Core.AnalyticsPrevalence.Domain.value | Whether the domain is prevalent or not. | Boolean |
| Core.AnalyticsPrevalence.Domain.data.global_prevalence.value | The global prevalence of the domain. | Number |
| Core.AnalyticsPrevalence.Domain.data.local_prevalence.value | The local prevalence of the domain. | Number |
| Core.AnalyticsPrevalence.Domain.data.prevalence.value | The prevalence of the domain. | Number |
import json import demistomock as demisto import pytest from DomainEnrichment import domain_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 analytics + enrichIndicators (batch data from file) def test_domain_enrichment_script_end_to_end_with_batch_file(mocker): r""" Given: - Two domains. - TIM pages from test_data/mock_domain_tim_results.json (includes per-vendor reliability, and for example.com a manual edit + modifiedTime). - Batch results from test_data/mock_domain_batch_results.json. When: - domain_enrichment_script runs end-to-end (external_enrichment=True). Then: - DomainEnrichment contains both domains. - For example.com: * Results has 3 entries: TIM + brand1 + brand2. - TIM row's Status/ModifiedTime are popped to top-level (TIM row itself should NOT contain them). * MaxScore=3, MaxVerdict=Malicious, TIMScore=3. * Top-level Status == 'Manual' and ModifiedTime matches fixture. * brand1 Reliability == 'High', brand2 Reliability == 'Medium'. - For example2.com: * Results has 2 entries: TIM + brand3 (brand3 Reliability == 'Low'). - Core prevalence mapped under 'Core.AnalyticsPrevalence.Domain'. """ # Load fixtures tim_pages = util_load_json("test_data/mock_domain_tim_results.json")["pages"] batch_data = util_load_json("test_data/mock_domain_batch_results.json") domain_list = ["example.com", "example2.com"] mocker.patch.object(demisto, "args", return_value={"domain_list": ",".join(domain_list)}) mocker.patch("DomainEnrichment.is_xsiam", return_value=True) mocker.patch("DomainEnrichment.is_platform", return_value=False) # extractIndicators -> validates input mocker.patch( "AggregatedCommandApiModule.execute_command", return_value=[{"EntryContext": {"ExtractedIndicators": {"Domain": domain_list}}}], ) # TIM via IndicatorsSearcher 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 mocker.patch.object( demisto, "getModules", return_value={ "m1": {"state": "active", "brand": "brand1"}, "m2": {"state": "active", "brand": "brand2"}, "core": {"state": "active", "brand": "Cortex Core - IR"}, }, ) # Helpers to wrap raw entries -> processed tuples [(entry, hr, err)] 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]] # Batch executor -> map fixtures to command batches 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: core prevalence then enrichIndicators ----- b2_cmds = list_of_batches[1] assert b2_cmds[0].name == "core-get-domain-analytics-prevalence" core_entries = batch_data["batch2_core"] assert len(core_entries) == 1, "expected one core analytics entry" batch2_results = _wrap_each_as_command(core_entries, hr="core-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 = domain_enrichment_script( domain_list=domain_list, external_enrichment=True, verbose=True, enrichment_brands=["brand1", "brand2", "Cortex Core - IR"], additional_fields=False, ) outputs = res.outputs # DomainEnrichment indicators enrichment_key = "DomainEnrichment(val.Value && val.Value == obj.Value)" enrichment_list = outputs.get(enrichment_key, []) enrichment_map = {item["Value"]: item for item in enrichment_list} assert set(enrichment_map.keys()) == set(domain_list) # example.com -> TIM + brand1 + brand2 ex1 = enrichment_map["example.com"] assert {r.get("Brand") for r in ex1["Results"]} == {"TIM", "brand1", "brand2"} assert len(ex1["Results"]) == 3 # vendor rows (reliability present) b1 = next(r for r in ex1["Results"] if r["Brand"] == "brand1") assert b1["Score"] == 2 assert b1["PositiveDetections"] == 5 assert b1.get("Reliability") == "High" b2 = next(r for r in ex1["Results"] if r["Brand"] == "brand2") assert b2["Score"] == 3 assert b2["PositiveDetections"] == 37 assert b2.get("Reliability") == "Medium" # TIM row present but without Status/ModifiedTime (popped to top-level) tim_row_ex1 = next(r for r in ex1["Results"] if r["Brand"] == "TIM") assert "Status" not in tim_row_ex1 assert "ModifiedTime" not in tim_row_ex1 # Max fields + TIMScore + top-level Status/ModifiedTime assert ex1["MaxScore"] == 3 assert ex1["MaxVerdict"] == "Malicious" assert ex1["TIMScore"] == 3 assert ex1.get("Status") == "Manual" assert ex1.get("ModifiedTime") == "2025-09-01T00:00:00Z" # example2.com -> TIM + brand3 (brand3 only via TIM; brand3 not enabled) ex2 = enrichment_map["example2.com"] assert {r.get("Brand") for r in ex2["Results"]} == {"TIM", "brand3"} b3 = next(r for r in ex2["Results"] if r["Brand"] == "brand3") assert b3["Score"] == 1 assert b3.get("Reliability") == "Low" # Core prevalence mapped core_ctx = outputs.get("Core.AnalyticsPrevalence.Domain", []) assert isinstance(core_ctx, list) assert len(core_ctx) == 2 assert {d["Domain"] for d in core_ctx} == {"example.com", "example2.com"} def test_domain_enrichment_uses_builtin_command_on_platform(mocker): """ Given: - Running on the unified Cortex platform (is_xsiam and is_platform both True). When: - domain_enrichment_script builds its command batches. Then: - The prevalence command is the built-in "getDomainAnalyticsPrevalence". - Its command_type is CommandType.BUILTIN (not the legacy INTERNAL core command). """ from AggregatedCommandApiModule import CommandType domain_list = ["example.com"] 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={"domain_list": ",".join(domain_list)}) mocker.patch("DomainEnrichment.is_xsiam", return_value=True) mocker.patch("DomainEnrichment.is_platform", return_value=True) mocker.patch( "AggregatedCommandApiModule.execute_command", return_value=[{"EntryContext": {"ExtractedIndicators": {"Domain": domain_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): domain_enrichment_script( domain_list=domain_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 == "getDomainAnalyticsPrevalence"] assert len(prevalence_cmds) == 1 assert prevalence_cmds[0].command_type == CommandType.BUILTIN