url-enrichment
Enriches URL indicators with reputation data from multiple integrations and outputs a consolidated URLEnrichment object. This script exclusively supports indicators of type URL and will automatically create the indicator in TIM if it is not already exists.
python · Aggregated Scripts
Details
| ID | url-enrichment |
|---|---|
| Language | python |
| From Version | 8.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Tags | basescript |
README
Enriches URL indicators with reputation data from multiple integrations and outputs a consolidated URLEnrichment object. This script exclusively supports indicators of type URL 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 |
|---|---|
| url_list | Accepts a list of URLs to enrich. - From CLI: Provide a comma-separated list. If a URL contains a comma, wrap the URLs in a JSON array. Example: “[\“https://example.com/search?tags=red,yellow,green\”, \“https://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). - ‘false’: use only existing TIM data; skip external integrations. If the ‘brands’ argument is provided, this flag is ignored and enrichment is 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. To see the available brands for the `url` command, run: `!ProvidesCommand command=url`. |
| 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: `Data`, `DetectionEngines`, `PositiveDetections`, `Score`, and `Brand`. When set to false, only the standard keys are returned. |
Outputs
| Path | Description | Type |
|---|---|---|
| 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.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 |
import json import demistomock as demisto from URLEnrichment import url_enrichment_script def util_load_json(path: str): with open(path, encoding="utf-8") as f: return json.load(f) def test_url_enrichment_script_end_to_end_with_files(mocker): """ Given: - Input is Two URLs: https://example.com, https://example2.com. - TIM results from test_data/mock_tim_results.json. - Batch results from test_data/mock_batch_results.json (only create + enrichIndicators). When: - url_enrichment_script runs end-to-end (external_enrichment=True). Then: - URLEnrichment contains both URLs. - For https://example.com: * Results has 3 entries (TIM + brand1 + brand2). - TIM is summarized via top-level TIMScore/Status/ModifiedTime. - TIM row in Results has NO Status/ModifiedTime (they're popped). * MaxScore=3, MaxVerdict=Malicious, TIMScore=3. * Vendor rows include All standard output fields. * Top-level Status == "Manual" (due to manuallyEditedFields.Score). - For https://example2.com: * Results has 2 entries (TIM + brand3), and brand3 Reliability is Low. """ # ---------- Load fixtures ---------- tim_pages = util_load_json("test_data/mock_tim_results.json")["pages"] batch_blob = util_load_json("test_data/mock_batch_results.json") url_list = ["https://example.com", "https://example2.com"] # demisto.args() passthrough (used by ctor) mocker.patch.object(demisto, "args", return_value={"url_list": ",".join(url_list)}) # ---------- Mock execute_command ONLY for extractIndicators ---------- def extractIndicators_side_effect(cmd, args=None, extract_contents=False, fail_on_error=True): if cmd == "extractIndicators": return [{"EntryContext": {"ExtractedIndicators": {"URL": url_list}}}] return [] mocker.patch("AggregatedCommandApiModule.execute_command", side_effect=extractIndicators_side_effect) # ---------- TIM search 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 modules/brands (BrandManager) ---------- mocker.patch.object( demisto, "getModules", return_value={ "m1": {"state": "active", "brand": "brand1"}, "m2": {"state": "active", "brand": "brand2"}, # brand3 not enabled; still appears via TIM context (not enrichIndicators) }, ) # ---------- Mock BatchExecutor.execute_list_of_batches using JSON ---------- def _fake_execute_list_of_batches(self, list_of_batches, brands_to_run=None, verbose=False): out = [] # Batch 0: CreateNewIndicatorsOnly -> one command in the script batch0_cmds = list_of_batches[0] batch0_results = [] create_items = list(batch_blob.get("createNewIndicator", [])) for _ in batch0_cmds: item = create_items[0] if create_items else {"Type": 1, "EntryContext": {}} batch0_results.append([(item, "", "")]) out.append(batch0_results) # Batch 1: enrichIndicators only (single command with 2 entries for 2 URLs) enrich_items = list(batch_blob.get("enrichIndicators", [])) batch1_cmds = list_of_batches[1] batch1_results = [] for cmd in batch1_cmds: assert cmd.name == "enrichIndicators" items = enrich_items if enrich_items else [{"Type": 1, "EntryContext": {}}] batch1_results.append([(e, "", "") for e in items]) out.append(batch1_results) return out mocker.patch("AggregatedCommandApiModule.BatchExecutor.execute_list_of_batches", _fake_execute_list_of_batches) # ---------- Act ---------- command_results = url_enrichment_script( url_list=url_list, external_enrichment=True, verbose=True, enrichment_brands=["brand1", "brand2"], additional_fields=False, ) outputs = command_results.outputs # ---------- Assert: URLEnrichment indicators ---------- enrichment_key = "URLEnrichment(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(url_list) # https://example.com should have TIM + brand1 + brand2 ex1 = enrichment_map["https://example.com"] brands_present_ex1 = {r.get("Brand") for r in ex1["Results"]} assert brands_present_ex1 == {"TIM", "brand1", "brand2"} assert len(ex1["Results"]) == 3 # vendor rows with reliability 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" # https://example2.com should have TIM + brand3 (from TIM; brand3 not enabled) ex2 = enrichment_map["https://example2.com"] brands_present_ex2 = {r.get("Brand") for r in ex2["Results"]} assert brands_present_ex2 == {"TIM", "brand3"} b3 = next(r for r in ex2["Results"] if r["Brand"] == "brand3") assert b3["Score"] == 1 assert b3.get("Reliability") == "Low"