ThreatMon Threat Feed
Fetches Indicators of Compromise (IOCs) from the ThreatMon IOC platform and ingests them into Cortex as indicators.
Data Enrichment & Threat Intelligence · ThreatMon Threat Feed · Feed
Details
| ID | ThreatMon Threat Feed |
|---|---|
| Provider | ThreatMon |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10404775 |
| Supported Modules | Agentix XSIAM |
README
Fetches Indicators of Compromise (IOCs) from the ThreatMon IOC platform and ingests them into Cortex as indicators.
This integration was integrated and tested with the ThreatMon IOC API.
Configure ThreatMon Threat Feed in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | The ThreatMon IOC API base URL. | True |
| API Token | The ThreatMon API token used to authenticate against the IOC API. | True |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Fetch indicators | False | |
| Data Type to Fetch | The type of IOC data to fetch from ThreatMon. | False |
| Maximum number of indicators per fetch | The maximum number of indicators to fetch in a single run. | False |
| Collection IDs | A comma-separated list of ThreatMon collection IDs to filter the fetch by. Leave empty to fetch from all collections. | False |
| Indicator Reputation | Indicators from this integration instance will be marked with this reputation. | False |
| Source Reliability | Reliability of the source providing the intelligence data. | True |
| Traffic Light Protocol Color | The Traffic Light Protocol (TLP) designation to apply to indicators fetched from the feed. | False |
| Feed Fetch Interval | False | |
| Tags | Supports CSV values. | False |
| Bypass exclusion list | When selected, the exclusion list is ignored for indicators from this feed. This means that if an indicator from this feed is on the exclusion list, the indicator might still be added to the system. | False |
Fetched Indicators
The feed maps the ThreatMon ioc_type field to Cortex indicator types as follows.
| ThreatMon ioc_type | Cortex indicator type |
|---|---|
Contains ip |
IP |
Contains domain |
Domain |
Contains url |
URL |
Contains file or hash |
File |
For IP indicators, the extracted_ip field is preferred over ioc_value, and any port suffix is stripped, so that 1.2.3.4:8080 is ingested as 1.2.3.4.
The following indicator fields are populated from the ThreatMon response.
| Indicator field | ThreatMon field |
|---|---|
| description | A summary built from source, confidence level, severity, status, ISP, resolved IPs, categories, tags, timestamp and score |
| tags | tags, categories and source, merged with the ThreatMon tag |
| modified | updated_at, falling back to timestamp and then created_at |
| confidence | confidence_level |
| threatseverity | severity |
| status | status |
| isp | isp |
| resolvedips | resolved_ips |
| geolocation | geo_location |
| trafficlightprotocol | The Traffic Light Protocol Color parameter |
The feed is incremental. On every run it stores the newest indicator timestamp it has seen, and on subsequent runs it skips indicators that are not newer than that timestamp.
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
threatmon-get-indicators
Gets a sample of indicators from the ThreatMon feed. This command is used mainly for testing and debugging, and does not create indicators in the system.
Base Command
threatmon-get-indicators
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of indicators to return. Default is 10. | Optional |
Context Output
There is no context output for this command.
Indicators are also ingested automatically according to the Feed Fetch Interval parameter.
Troubleshooting
- Test failed - Verify the API token and that the Server URL is reachable from the Cortex engine.
- No indicators fetched - Verify that the configured Collection IDs contain data, and that Data Type to Fetch is not filtering out the indicators you expect. Because the feed is incremental, a run returns nothing when the API has not published indicators newer than the previous run.
<~PLATFORM>
License Requirements
The following configuration parameters require one of these licenses: Cortex XSIAM or Agentix:
- Fetch indicators
- Incremental Feed
- Bypass exclusion list
</~PLATFORM>
Configuration parameters
url— Server URL (required)credentials— (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsfeed— Fetch indicatorsdata_type— Data Type to Fetchlimit— Maximum number of indicators per fetchcollection_ids— Collection IDsfeedReputation— Indicator ReputationfeedReliability— Source Reliability (required)tlp_color— Traffic Light Protocol ColorfeedIncremental— Incremental FeedfeedExpirationPolicy—feedExpirationInterval—feedFetchInterval— Feed Fetch IntervalfeedBypassExclusionList— Bypass exclusion listfeedTags— Tags
Commands (1)
-
threatmon-get-indicatorsGets a sample of indicators from the ThreatMon feed. This command is used mainly for testing and debugging, and does not create indicators in the system.
import pytest from ThreatMonThreatFeed import ( build_indicators, calculate_verdict, fetch_indicators_command, get_indicators_command, map_indicator_type, parse_indicator, strip_port, ) from ThreatMonThreatFeed import test_module as run_test_module class MockClient: """ A minimal stand-in for the Client class, so the tests do not perform HTTP calls. """ def __init__(self, response=None, exception=None): self.response = response or {} self.exception = exception self.calls: list = [] def get_daily_iocs(self, data_type="all", size=500, collection_ids=None): self.calls.append({"data_type": data_type, "size": size, "collection_ids": collection_ids}) if self.exception: raise self.exception return self.response IOC_IP = { "ioc_value": "1.2.3.4:8080", "ioc_type": "ip", "extracted_ip": "1.2.3.4", "source": ["ThreatmonIntel"], "tags": ["c2"], "categories": ["malware"], "confidence_level": 90, "severity": "high", "status": "active", "isp": "Example ISP", "resolved_ips": ["1.2.3.4"], "geo_location": "TR", "score": 95, "created_at": "2026-01-02T00:00:00Z", } IOC_DOMAIN = { "ioc_value": "malicious.example.com", "ioc_type": "domain", "created_at": "2026-01-03T00:00:00Z", } @pytest.mark.parametrize( "ioc_type, expected", [ ("ip", "IP"), ("IPv4", "IP"), ("domain", "Domain"), ("url", "URL"), ("file_hash", "File"), ("md5 hash", "File"), ], ) def test_map_indicator_type(ioc_type, expected): """ Given: A ThreatMon ioc_type value of a supported indicator type. When: Mapping it to an XSOAR indicator type. Then: The matching FeedIndicatorType is returned. """ assert map_indicator_type(ioc_type) == expected @pytest.mark.parametrize("ioc_type", ["", None, "something-else"]) def test_map_indicator_type_unknown_returns_none(ioc_type): """ Given: A missing or unrecognized ioc_type. When: Mapping it to an XSOAR indicator type. Then: None is returned so the caller can skip the indicator. """ assert map_indicator_type(ioc_type) is None @pytest.mark.parametrize( "value, expected", [ ("1.2.3.4:8080", "1.2.3.4"), ("1.2.3.4", "1.2.3.4"), ("[2001:db8::1]:443", "2001:db8::1"), ("2001:db8::1", "2001:db8::1"), ], ) def test_strip_port(value, expected): """ Given: An indicator value that may contain a port, including IPv6 forms. When: Stripping the port. Then: The port is removed for IPv4 and bracketed IPv6, and unbracketed IPv6 is left intact. """ assert strip_port(value) == expected def test_parse_indicator_ip_strips_port(): """ Given: An IP IOC whose value contains a port and which has an extracted_ip field. When: Parsing it into an XSOAR indicator. Then: The indicator value is the bare IP, and the rawJSON and service keys are present. """ indicator = parse_indicator(IOC_IP, feed_tags=["ThreatMon"], tlp_color="GREEN") assert indicator["value"] == "1.2.3.4" assert indicator["type"] == "IP" assert indicator["service"] == "ThreatMon" assert indicator["rawJSON"] == IOC_IP def test_parse_indicator_builds_fields_and_tags(): """ Given: An IOC containing metadata, tags, categories and source. When: Parsing it into an XSOAR indicator. Then: The tags are merged without duplicates and the metadata is mapped to indicator fields. """ indicator = parse_indicator(IOC_IP, feed_tags=["ThreatMon", "c2"], tlp_color="AMBER") fields = indicator["fields"] assert fields["tags"] == ["c2", "malware", "ThreatmonIntel", "ThreatMon"] assert fields["confidence"] == 90 assert fields["threatseverity"] == "high" assert fields["isp"] == "Example ISP" assert fields["geolocation"] == "TR" assert fields["trafficlightprotocol"] == "AMBER" assert "Severity: high" in fields["description"] assert "API Score: 95" in fields["description"] def test_parse_indicator_without_tlp_color(): """ Given: An IOC and an empty TLP color. When: Parsing it into an XSOAR indicator. Then: The trafficlightprotocol field is not set. """ indicator = parse_indicator(IOC_DOMAIN, feed_tags=[], tlp_color="") assert indicator["value"] == "malicious.example.com" assert indicator["type"] == "Domain" assert "trafficlightprotocol" not in indicator["fields"] def test_parse_indicator_skips_untyped_and_valueless(): """ Given: An IOC that has no value, or an unrecognized type. When: Parsing it into an XSOAR indicator. Then: None is returned so the caller can skip it, instead of raising. """ assert parse_indicator({"ioc_type": "ip"}, feed_tags=[], tlp_color=None) is None assert parse_indicator({"ioc_value": "something", "ioc_type": "unknown-type"}, feed_tags=[], tlp_color=None) is None @pytest.mark.parametrize( "reputation, expected", [ ("None", 0), ("Good", 1), ("Suspicious", 2), ("Bad", 3), ("Malicious", 3), ("Unrecognized value", 3), ], ) def test_calculate_verdict(reputation, expected): """ Given: A feedReputation configuration value. When: Calculating the DBot score for the fetched indicators. Then: The matching DBot score is returned, defaulting to Bad (3). """ assert calculate_verdict(reputation) == expected def test_run_test_module_success(): """ Given: An API that responds successfully. When: Running the test-module command. Then: 'ok' is returned and only a single IOC is requested. """ client = MockClient(response={"iocs": [IOC_IP]}) assert run_test_module(client, {"data_type": "ip"}) == "ok" assert client.calls == [{"data_type": "ip", "size": 1, "collection_ids": None}] def test_run_test_module_failure(): """ Given: An API that raises an error. When: Running the test-module command. Then: A descriptive failure message is returned instead of raising. """ client = MockClient(exception=Exception("Unauthorized")) assert run_test_module(client, {}) == "Test failed: Unauthorized" def test_build_indicators_applies_feed_tags(): """ Given: A feedTags parameter configured by the user. When: Building indicators. Then: The user tags are applied to the indicators alongside the ThreatMon source tag. """ client = MockClient(response={"iocs": [IOC_DOMAIN]}) indicators, _ = build_indicators(client, {"feedTags": "internal,priority"}, limit=100) assert indicators[0]["fields"]["tags"] == ["ThreatMon", "internal", "priority"] def test_fetch_indicators_first_run(): """ Given: An empty last run and an API returning two IOCs. When: Fetching indicators. Then: Both IOCs are returned, scored, and the newest timestamp is stored in the last run. """ client = MockClient(response={"iocs": [IOC_IP, IOC_DOMAIN]}) params = {"data_type": "all", "tlp_color": "RED", "feedReputation": "Bad", "collection_ids": "abc,def"} indicators, last_run = fetch_indicators_command(client, params, limit=100, last_run={}) assert len(indicators) == 2 assert {indicator["value"] for indicator in indicators} == {"1.2.3.4", "malicious.example.com"} assert all(indicator["score"] == 3 for indicator in indicators) assert last_run == {"last_timestamp": "2026-01-03T00:00:00Z"} assert client.calls == [{"data_type": "all", "size": 100, "collection_ids": "abc,def"}] def test_fetch_indicators_skips_already_fetched(): """ Given: A last run holding a timestamp newer than one of the returned IOCs. When: Fetching indicators. Then: The already fetched IOC is skipped and only the newer one is returned. """ client = MockClient(response={"iocs": [IOC_IP, IOC_DOMAIN]}) indicators, last_run = fetch_indicators_command(client, {}, limit=500, last_run={"last_timestamp": "2026-01-02T00:00:00Z"}) assert len(indicators) == 1 assert indicators[0]["value"] == "malicious.example.com" assert last_run == {"last_timestamp": "2026-01-03T00:00:00Z"} def test_fetch_indicators_empty_response(): """ Given: An API returning no IOCs. When: Fetching indicators. Then: No indicators are returned and the previous last run is preserved. """ client = MockClient(response={"iocs": []}) indicators, last_run = fetch_indicators_command(client, {}, limit=500, last_run={"last_timestamp": "2026-01-02T00:00:00Z"}) assert indicators == [] assert last_run == {"last_timestamp": "2026-01-02T00:00:00Z"} def test_fetch_indicators_raises_on_api_error(capfd): """ Given: An API that raises an error. When: Fetching indicators. Then: The error is propagated so the fetch run is marked as failed. """ client = MockClient(exception=Exception("Connection error")) with capfd.disabled(), pytest.raises(Exception, match="Connection error"): fetch_indicators_command(client, {}, limit=500, last_run={}) def test_get_indicators_command_returns_table(): """ Given: An API returning IOCs. When: Running the get-indicators command with a limit. Then: A CommandResults with a markdown table and the raw response is returned, limited in size. """ client = MockClient(response={"iocs": [IOC_IP, IOC_DOMAIN]}) result = get_indicators_command(client, {}, {"limit": "1"}) assert "Indicators from ThreatMon" in result.readable_output assert len(result.raw_response) == 1 def test_get_indicators_command_no_results(): """ Given: An API returning no IOCs. When: Running the get-indicators command. Then: A friendly "no indicators" message is returned. """ client = MockClient(response={"iocs": []}) result = get_indicators_command(client, {}, {}) assert "No indicators were found" in result.readable_output