file-enrichment

Enriches File indicators with reputation data from multiple integrations and outputs a consolidated FileEnrichment object. This script exclusively supports indicators of type File and will automatically create the indicator in TIM if it is not already exists. Note: The script runs core-get-hash-analytics-prevalence on SHA256 values only.

python · Aggregated Scripts

Details

IDfile-enrichment
Languagepython
From Version8.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Tagsbasescript

README

Enriches File indicators with reputation data from multiple integrations and outputs a consolidated FileEnrichment object. This script exclusively supports indicators of type File and will automatically create the indicator in TIM if it is not already exists. Note: The script runs core-get-hash-analytics-prevalence on SHA256 values only.
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

Used In


This script is used in the following playbooks and scripts.

  • FileEnrichment - Test

Inputs


Argument Name Description
file_hash A comma-separated list of File hashes to enrich. Supported types are: MD5, SHA1, SHA256, and SHA512.
external_enrichment “Whether to call external integrations for enrichment:
- ‘true’: enrich using all enabled integrations (e.g., VirusTotal (API v3), AlienVault OTX v2).
- ‘false’: Will enrich using only WildFire-v2 if available; 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.
brands A list of integration brands to run enrichment against.
Example: `“AlienVault OTX v2”, “WildFire-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-hash-analytics-prevalence, add Cortex Core - IR to the brands list (will run only on SHA256 values).
To see the available brands for the `file` command, run: `!ProvidesCommand command=file`.
additional_fields When set to true, the output 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: `MD5`, `SHA1`, `SHA256`, `SHA512`, `CRC32`, `CTPH`, `SSDeep`, `ImpHash`,
`SourceTimeStamp`, `Modified`, `Path`, `Size`, `FileExtension`, `AssociatedFileNames`, `Brand`, `Score`.
When set to false, only the standard keys are returned.

Outputs


Path Description Type
FileEnrichment.Value The File hash from the input Value. String
FileEnrichment.Hashes.MD5 The file MD5 hash if exists. String
FileEnrichment.Hashes.SHA1 The file SHA1 hash if exists. String
FileEnrichment.Hashes.SHA256 The file SHA256 hash if exists. String
FileEnrichment.Hashes.SHA512 The file SHA512 hash if exists. String
FileEnrichment.Hashes.CRC32 The file CRC32 hash if exists. String
FileEnrichment.Hashes.CTPH The file CTPH hash if exists. String
FileEnrichment.Hashes.SSDeep The file SSDeep hash if exists. String
FileEnrichment.Hashes.ImpHash The file ImpHash hash if exists. String
FileEnrichment.MaxScore The max score of all the indicators found. Number
FileEnrichment.MaxVerdict The max verdict of all the indicators found. String
FileEnrichment.Results List of all indicators found for the File. Array
FileEnrichment.TIMScore The TIM score of the File. Number
FileEnrichment.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
FileEnrichment.ModifiedTime The time the indicator was last modified. Date
FileEnrichment.Results.Brand The brand of the indicator. String
FileEnrichment.Results.Score The score of the indicator. Number
FileEnrichment.Results.Verdict The verdict of the indicator. String
FileEnrichment.Results.DetectionEngines The detection engines of the indicator. Number
FileEnrichment.Results.PositiveDetections The positive detections of the indicator. Number
FileEnrichment.Results.MD5 The file MD5 hash if exists. String
FileEnrichment.Results.SHA1 The file SHA1 hash if exists. String
FileEnrichment.Results.SHA256 The file SHA256 hash if exists. String
FileEnrichment.Results.SHA512 The file SHA512 hash if exists. String
FileEnrichment.Results.CRC32 The file CRC32 hash if exists. String
FileEnrichment.Results.CTPH The file CTPH hash if exists. String
FileEnrichment.Results.SSDeep The file SSDeep hash if exists. String
FileEnrichment.Results.ImpHash The file ImpHash hash if exists. String
FileEnrichment.Results.Reliability The reliability of the Brand. String
FileEnrichment.Results.AdditionalFields.Name The name of the file including its extension. String
FileEnrichment.Results.AdditionalFields.EntryID The identifier used to locate the file in the Incident War Room. String
FileEnrichment.Results.AdditionalFields.Actor The threat actor associated with the file, if applicable. String
FileEnrichment.Results.AdditionalFields.behavior.details A brief description of the behavior exhibited by the file. String
FileEnrichment.Results.AdditionalFields.behavior.title A brief description of the behavior exhibited by the file. String
FileEnrichment.Results.AdditionalFields.Campaign The identified campaign associated with the file, if applicable. String
FileEnrichment.Results.AdditionalFields.CommunityNotes.note Community-contributed notes regarding observations or findings related to the file. String
FileEnrichment.Results.AdditionalFields.CommunityNotes.timestamp The timestamp when the community note was added. Date
FileEnrichment.Results.AdditionalFields.Company The name of the company that released a binary. String
FileEnrichment.Results.AdditionalFields.DigitalSignature.Publisher The entity that issued the digital signature of the file. String
FileEnrichment.Results.AdditionalFields.Extension The file extension, indicating the type of file format, for example, ‘exe’. String
FileEnrichment.Results.AdditionalFields.FeedRelatedIndicators.value Shows other indicators associated with the file. String
FileEnrichment.Results.AdditionalFields.FeedRelatedIndicators.type Identifies the types of associated indicators. String
FileEnrichment.Results.AdditionalFields.FeedRelatedIndicators.description Describes the associated indicators providing context or relevance. String
FileEnrichment.Results.AdditionalFields.FirstSeenBySource The first time seen by the source brand. Date
FileEnrichment.Results.AdditionalFields.GlobalPrevalence The global prevalence of the file hash. Number
FileEnrichment.Results.AdditionalFields.Hostname The hostname of the device where the file was found. String
FileEnrichment.Results.AdditionalFields.LastSeenBySource The last time seed by the source brand. Date
FileEnrichment.Results.AdditionalFields.Malicious.Vendor Specifies the vendor that identified the file as malicious. String
FileEnrichment.Results.AdditionalFields.Malicious.Description For malicious files, the reason that the vendor made the decision. Unknown
FileEnrichment.Results.AdditionalFields.Malicious.Detections For malicious files, the total number of detections. Unknown
FileEnrichment.Results.AdditionalFields.Malicious.TotalEngines For malicious files, the total number of engines that checked the file hash. Unknown
FileEnrichment.Results.AdditionalFields.VTVendors.EngineDetections Number of VT vendors that flagged the file as malicious. Unknown
FileEnrichment.Results.AdditionalFields.VTVendors.EngineVendors VT vendors who flagged the file as malicious. Unknown
FileEnrichment.Results.AdditionalFields.VTVendors.EngineDetectionNames VT detection names that flagged the file as malicious. Unknown
FileEnrichment.Results.AdditionalFields.MalwareFamily Names the malware family associated with the file, if known. String
FileEnrichment.Results.AdditionalFields.Organization The organization to which the file is attributed. String
FileEnrichment.Results.AdditionalFields.OrganizationFirstSeen The date and time when the indicator was first seen in the organization. Date
FileEnrichment.Results.AdditionalFields.OrganizationLastSeen The date and time when the indicator was last seen in the organization. Date
FileEnrichment.Results.AdditionalFields.OrganizationPrevalence The number of times the indicator is detected in the organization. Number
FileEnrichment.Results.AdditionalFields.ProductName The file product name. String
FileEnrichment.Results.AdditionalFields.Publications.source Identifies the publishing source of an article relating to the file. String
FileEnrichment.Results.AdditionalFields.Publications.title Identifies the publishing source of an article relating to the file. String
FileEnrichment.Results.AdditionalFields.Publications.link Provides a hyperlink to the full article or publication for detailed information. String
FileEnrichment.Results.AdditionalFields.Publications.timestamp Publications.timestamp Date
FileEnrichment.Results.AdditionalFields.Quarantined Indicates whether the file has been quarantined to prevent potential harm. Bool
FileEnrichment.Results.AdditionalFields.Relationships.EntityA The initiating entity in a relationship involving the file. String
FileEnrichment.Results.AdditionalFields.Relationships.EntityB The recipient or target entity in a relationship involving the file. String
FileEnrichment.Results.AdditionalFields.Relationships.Relationship Defines the type or nature of the relationship between entities. String
FileEnrichment.Results.AdditionalFields.Relationships.EntityAType The type or classification of the initiating entity. String
FileEnrichment.Results.AdditionalFields.Relationships.EntityBType The type or classification of the recipient entity. String
FileEnrichment.Results.AdditionalFields.Signature.Authentihash The Authentihash, a cryptographic hash, used for verifying the file’s authenticity. String
FileEnrichment.Results.AdditionalFields.Signature.Description Describes the file signature data relevant to identification. String
FileEnrichment.Results.AdditionalFields.Signature.FileVersion Indicates the version number of the file. String
FileEnrichment.Results.AdditionalFields.Signature.InternalName The internal name of the file as designated by the creators. String
FileEnrichment.Results.AdditionalFields.Signature.OriginalName The original name of the file before any changes or renames. String
FileEnrichment.Results.AdditionalFields.Tags Tags assigned to the file for categorization or identification. Array
FileEnrichment.Results.AdditionalFields.ThreatTypes Threat types associated with the file. Unknown
FileEnrichment.Results.AdditionalFields.TrafficLightProtocol Specifies the TLP color designation suitable for handling the file. String
FileEnrichment.Results.AdditionalFields.Type The file type determined by libmagic. String
Core.AnalyticsPrevalence.Hash.value Whether the hash is prevalent or not. Boolean
Core.AnalyticsPrevalence.Hash.data.global_prevalence.value The global prevalence of the hash. Number
Core.AnalyticsPrevalence.Hash.data.local_prevalence.value The local prevalence of the hash. Number
Core.AnalyticsPrevalence.Hash.data.prevalence.value The prevalence of the hash. Number
import json

import demistomock as demisto
from FileEnrichment import file_enrichment_script


def util_load_json(path: str):
    with open(path, encoding="utf-8") as f:
        return json.load(f)


def test_file_enrichment_script_end_to_end_with_files(mocker):
    """
    Given:
        - Two file hashes (both SHA256).
        - TIM file results from test_data/mock_file_tim_results.json.
        - Batch results from test_data/mock_file_batch_results.json (create + enrich + core-get-hash-analytics-prevalence).
    When:
        - file_enrichment_script runs end-to-end (external_enrichment=True).
    Then:
        - FileEnrichmentV2 contains both hashes.
        - For file1:
            * Results has 2 entries (TIM + WildFire-v2).
            * Hashes contain MD5 + SHA256.
            * TIMScore=3, MaxScore=3, MaxVerdict=Malicious.
            * Top-level Status == "Manual" (due to manuallyEditedFields.Score).
            * TIM row in Results has NO Status/ModifiedTime (popped to top-level).
        - For file2:
            * Results has 2 entries (TIM + WildFire-v2), reliability Low.
    """
    # ---------- Load fixtures ----------
    tim_pages = util_load_json("test_data/mock_file_tim_results.json")["pages"]
    batch_blob = util_load_json("test_data/mock_file_batch_results.json")

    file_list = [
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",  # file1 SHA256
        "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",  # file2 SHA256
    ]

    mocker.patch.object(demisto, "args", return_value={"file_hash": ",".join(file_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": {"File": file_list}}}]
        return []

    mocker.patch("AggregatedCommandApiModule.execute_command", side_effect=extractIndicators_side_effect)

    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={
            "wf": {"state": "active", "brand": "WildFire-v2"},
            "core": {"state": "active", "brand": "Cortex Core - IR"},
        },
    )
    mocker.patch("FileEnrichment.is_platform", return_value=False)

    # ---------- 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 = []

        create_items = list(batch_blob.get("createNewIndicator", []))
        enrich_items = list(batch_blob.get("enrichIndicators", []))
        core_items = list(batch_blob.get("coreGetHashAnalyticsPrevalence", []))

        # Batch 0: CreateNewIndicatorsOnly
        batch0_cmds = list_of_batches[0]
        batch0_results = []
        for _ in batch0_cmds:
            item = create_items.pop(0) if create_items else {"Type": 1, "EntryContext": {}}
            batch0_results.append([(item, "", "")])
        out.append(batch0_results)

        # Batch 1: enrichIndicators + core-get-hash-analytics-prevalence
        # (a single prevalence command carries a list of SHA256 values)
        batch1_cmds = list_of_batches[1]
        batch1_results = []
        for cmd in batch1_cmds:
            if cmd.name == "enrichIndicators":
                items = enrich_items or [{"Type": 1, "EntryContext": {}, "Metadata": {"brand": "WildFire-v2"}}]
                batch1_results.append([(e, "", "") for e in items])
            elif cmd.name == "core-get-hash-analytics-prevalence":
                items = core_items or [
                    {
                        "Type": 1,
                        "EntryContext": {},
                        "Metadata": {"brand": "Cortex Core - IR"},
                    }
                ]
                batch1_results.append([(item, "", "") for item in items])
            else:
                batch1_results.append([({"Type": 1, "EntryContext": {}}, "", "")])
        out.append(batch1_results)

        return out

    mocker.patch("AggregatedCommandApiModule.BatchExecutor.execute_list_of_batches", _fake_execute_list_of_batches)

    # ---------- Act ----------
    command_results = file_enrichment_script(
        file_list=file_list,
        external_enrichment=True,
        verbose=True,
        enrichment_brands=["WildFire-v2"],
        additional_fields=False,
    )
    outputs = command_results.outputs

    # ---------- Assert: FileEnrichmentV2 indicators ----------
    enrichment_key = "FileEnrichment(val.Value && val.Value == obj.Value)"
    enrichment_list = outputs.get(enrichment_key, [])
    assert len(enrichment_list) == 2

    enrichment_map = {item["Value"]: item for item in enrichment_list}
    # In this scenario, Value will be the canonical TIM "value" (sha256) → same as file_list
    assert set(enrichment_map.keys()) == set(file_list)

    # ---- file1 assertions ----
    f1 = enrichment_map[file_list[0]]
    brands_present_f1 = {r.get("Brand") for r in f1["Results"]}
    assert brands_present_f1 == {"TIM", "WildFire-v2"}
    assert len(f1["Results"]) == 2

    # Hashes aggregated from TIM indicator
    hashes1 = f1.get("Hashes", {})
    assert hashes1.get("MD5") is not None
    assert hashes1.get("SHA256") == file_list[0]

    # TIM row present but without Status/ModifiedTime (popped to top-level)
    tim_row_f1 = next(r for r in f1["Results"] if r["Brand"] == "TIM")
    assert "Status" not in tim_row_f1
    assert "ModifiedTime" not in tim_row_f1

    # Top-level scores & status
    assert f1["TIMScore"] == 3
    assert f1["MaxScore"] == 3
    assert f1["MaxVerdict"] == "Malicious"
    assert f1["Status"] == "Manual"  # due to manuallyEditedFields.Score in TIM IOC

    # ---- file2 assertions ----
    f2 = enrichment_map[file_list[1]]
    brands_present_f2 = {r.get("Brand") for r in f2["Results"]}
    assert brands_present_f2 == {"TIM", "WildFire-v2"}
    assert len(f2["Results"]) == 2

    wf2 = next(r for r in f2["Results"] if r["Brand"] == "WildFire-v2")
    assert wf2["Score"] == 1
    assert wf2.get("Reliability") == "Low"


def _capture_built_commands(mocker, file_list, is_platform=False):
    """Helper: runs file_enrichment_script with mocks and returns the command batches exactly as
    they were built by file_enrichment_script, captured before any brand/type filtering is applied."""
    mocker.patch.object(demisto, "args", return_value={"file_hash": ",".join(file_list)})
    mocker.patch("FileEnrichment.is_platform", return_value=is_platform)

    def extractIndicators_side_effect(cmd, args=None, extract_contents=False, fail_on_error=True):
        if cmd == "extractIndicators":
            return [{"EntryContext": {"ExtractedIndicators": {"File": file_list}}}]
        return []

    mocker.patch("AggregatedCommandApiModule.execute_command", side_effect=extractIndicators_side_effect)

    # Build a minimal TIM page so each input hash is "found" in TIM. This keeps the
    # enrichment flow on the success path (instead of the all-failed path which now
    # raises), while these tests only care about the commands that were built.
    def _build_ioc(file_hash):
        custom_fields = {"sha256": file_hash} if len(file_hash) == 64 else {"md5": file_hash}
        return {
            "value": file_hash,
            "score": 1,
            "modifiedTime": "2025-09-01T00:00:00Z",
            "CustomFields": custom_fields,
        }

    tim_pages = [{"iocs": [_build_ioc(file_hash) for file_hash in file_list]}]

    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))
    mocker.patch.object(
        demisto,
        "getModules",
        return_value={
            "wf": {"state": "active", "brand": "WildFire-v2"},
            "core": {"state": "active", "brand": "Cortex Core - IR"},
        },
    )

    captured = {}

    def _fake_prepare_commands_batches(self, external_enrichment=False):
        # Capture the commands as built (pre-filter) so the assertions are independent of
        # the brand/type filtering performed by the real prepare_commands_batches.
        captured["batches"] = self.commands
        return self.commands

    mocker.patch(
        "AggregatedCommandApiModule.ReputationAggregatedCommand.prepare_commands_batches",
        _fake_prepare_commands_batches,
    )

    def _fake_execute_list_of_batches(self, list_of_batches, brands_to_run=None, verbose=False):
        return [[[({"Type": 1, "EntryContext": {}}, "", "")] for _ in batch] for batch in list_of_batches]

    mocker.patch("AggregatedCommandApiModule.BatchExecutor.execute_list_of_batches", _fake_execute_list_of_batches)

    file_enrichment_script(
        file_list=file_list,
        external_enrichment=True,
        verbose=False,
        enrichment_brands=["WildFire-v2"],
        additional_fields=False,
    )
    return captured["batches"]


def test_prevalence_command_omitted_when_no_sha256(mocker):
    """
    Given:
        - A single MD5 hash (no SHA256) as input.
    When:
        - file_enrichment_script builds the command batches.
    Then:
        - The core-get-hash-analytics-prevalence command is NOT included.
    """
    md5_hash = "d41d8cd98f00b204e9800998ecf8427e"
    batches = _capture_built_commands(mocker, [md5_hash])

    all_command_names = [cmd.name for batch in batches for cmd in batch]
    assert "core-get-hash-analytics-prevalence" not in all_command_names


def test_prevalence_command_included_when_sha256_present(mocker):
    """
    Given:
        - A SHA256 hash as input.
    When:
        - file_enrichment_script builds the command batches.
    Then:
        - A single core-get-hash-analytics-prevalence command is included with the SHA256 value.
    """
    sha256_hash = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    batches = _capture_built_commands(mocker, [sha256_hash])

    prevalence_cmds = [cmd for batch in batches for cmd in batch if cmd.name == "core-get-hash-analytics-prevalence"]
    assert len(prevalence_cmds) == 1
    assert prevalence_cmds[0].args == {"sha256": [sha256_hash]}


def test_file_enrichment_uses_builtin_command_on_platform(mocker):
    """
    Given:
        - A SHA256 hash as input, running on the unified Cortex platform (is_platform True).
    When:
        - file_enrichment_script builds the command batches.
    Then:
        - The prevalence command is the built-in "getHashAnalyticsPrevalence".
        - Its command_type is CommandType.BUILTIN (not the legacy INTERNAL core command).
    """
    from AggregatedCommandApiModule import CommandType

    sha256_hash = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    batches = _capture_built_commands(mocker, [sha256_hash], is_platform=True)

    prevalence_cmds = [cmd for batch in batches for cmd in batch if cmd.name == "getHashAnalyticsPrevalence"]
    assert len(prevalence_cmds) == 1
    assert prevalence_cmds[0].command_type == CommandType.BUILTIN
    assert "core-get-hash-analytics-prevalence" not in [cmd.name for batch in batches for cmd in batch]