Group-IB Threat Intelligence & Attribution Feed
Use Group-IB Threat Intelligence Feed integration to fetch IOCs from various Group-IB collections.
Data Enrichment & Threat Intelligence · Group-IB Threat Intelligence · Feed
Details
| ID | Group-IB Threat Intelligence & Attribution Feed |
|---|---|
| Provider | Group IB |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.0.0 |
| Docker Image | demisto/vendors-sdk:1.0.0.10120494 |
| Supported Modules | Agentix XSIAM |
README
Group-IB Threat Intelligence Feed
Use Group-IB Threat Intelligence Feed integration to fetch IOCs (Indicators of Compromise) from various Group-IB collections. The integration supports multiple collections - see the Data Collections Overview section below for the complete list with descriptions and recommended date ranges (indicator first fetch).
Prerequisites
- Access Group-IB Threat Intelligence (TI) Web Interface
- Open the Group-IB TI platform at https://tap.group-ib.com
- Generate API Credentials
- In the web interface, click your name in the upper right corner
- Select Profile → Security and Access tab
- Click Personal token and follow the instructions to generate your API token
- Note: The API token serves as your password for authentication
- Network Configuration
- Important: Contact Group-IB support to add your Cortex XSOAR server’s IP address to the allow list
- If you are using a proxy, provide the public IP address of the proxy server instead
- Make sure you have added Group-IB API IPs/URLs to your FW/Proxy rules.
Important Notes
Limit Parameter
The Limit (items per request) parameter specifies the number of records requested per API page. This limit applies to all collections configured in the integration instance.
Important considerations:
- The limit determines how many records are fetched in a single API request. For example, if “Number of requests per collection” is set to 2 and the limit is 100, the integration will make 2 requests per collection, each requesting up to 100 records, resulting in up to 200 records per collection per fetch cycle.
- Different collections may have different optimal limit values based on their data structure and API recommendations. We strongly recommend consulting the official API Limitations documentation for specific limit recommendations for each collection.
- Best practice: Create separate integration instances for different collections or groups of collections that share similar optimal limit values. This allows you to optimize performance for each collection type.
Data Collections Overview
Once the configuration is complete, the following collections become available in Cortex XSOAR. For detailed information about each collection, its structure, and available fields, please refer to the official Collections Details documentation.
Note: If you’re using a POC or partner license, access to data is limited to 30 days. The recommended date ranges below are guidelines and can be adjusted according to your needs.
| Collection | Description | Recommended Date Range |
|---|---|---|
compromised/account_group |
In your compromised accounts, there CNCs in place which can be used as IOCs. Usually included in IOC Common. | 2-4 years |
compromised/bank_card_group |
In your compromised cards, there CNCs in place which can be used as IOCs. Usually included in IOC Common. | 2 years |
compromised/masked_card |
In masked card records, top-level CNC domain and IP values can be used as IOCs similarly to other compromised card collections. | 2 years |
compromised/mule |
Information on compromised accounts used by threat actors for money laundering and fund transfers. Collection is currently deprecated - only legacy information is available | 90 days |
attacks/ddos |
Data on Distributed Denial of Service (DDoS) attacks, including targeted resources and attack durations. | 5-10 days |
attacks/deface |
Records of defacement attacks, highlighting compromised websites and related actors. | 5-10 days |
attacks/phishing_group |
Information on phishing attacks, including URLs of phishing websites. Note: Do not use IPs for detection - it may cause many false positives. Focus only on URLs. | 3-5 days |
attacks/phishing_kit |
Collections of phishing website templates, scripts, and configurations used by attackers. | 30 days |
apt/threat |
IOCs only from APT reports. | 2-4 years |
hi/threat |
IOCs only from Cybercriminals reports. | 2-4 years |
ioc/common |
General indicators of Compromise (IoCs) from threat reports (Cybercriminals and APT) and Malware sections. Consists of Hashes (MD5, SHA1, SHA256), IPs, domains and URLs. Major source of IOCs. Contains: malware/malware, malware/cnc, hi/threat, apt/threat, hi/threat_actor, apt/threat_actor. | 90 days |
malware/cnc |
Information on malware Command-and-Control (C&C) servers used for data exfiltration and command distribution. This feed is also part of IOC Common. | 90 days |
osi/vulnerability |
Information on software vulnerabilities, associated exploits, and available proof-of-concept details. | 90 days |
suspicious_ip/tor_node |
Data about known Tor exit nodes used as anonymity relays. | 5 days |
suspicious_ip/open_proxy |
Information on publicly available proxy servers, including potentially misconfigured proxies. | 5 days |
suspicious_ip/scanner |
IP addresses identified as scanning or probing corporate networks. | 5 days |
suspicious_ip/socks_proxy |
IP addresses of infected hosts configured as SOCKS proxies used for anonymized attacks. | 5 days |
suspicious_ip/vpn |
Information about public and private VPN servers identified as potentially malicious or suspicious. | 5 days |
Configure Group-IB Threat Intelligence Feed in Cortex
| Parameter | Description | Required |
|---|---|---|
| GIB TI URL | The FQDN/IP the integration should connect to (default: https://tap.group-ib.com/api/v2/). |
True |
| Username | Enter the email address you use to log into the web interface. The API token serves as your password for authentication. | True |
| Trust any certificate (not secure) | Whether to allow connections without verifying SSL certificates validity. | False |
| Use system proxy settings | Whether to use XSOAR system proxy settings to connect to the API. | False |
| Fetches indicators | Enable to fetch indicators from the feed (default: enabled). | False |
| Indicator Reputation | Select the default reputation for indicators from this feed (default: Suspicious). Options: Unknown, Benign, Suspicious, Malicious. As an example, it is recommended to use Malicious for IOC common and Suspicious for Suspicious IP collections. | False |
| Source Reliability | Select the reliability rating for the source (required, default: A - Completely reliable). Options: A - Completely reliable, B - Usually reliable, C - Fairly reliable, D - Not usually reliable, E - Unreliable, F - Reliability cannot be judged. | True |
| Feed Fetch Interval | Configure how often to fetch indicators (hours and minutes, default: 1 minute). | False |
| Bypass exclusion list | When enabled, bypasses the exclusion list 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 |
| Indicator collections | Select the collections you want to fetch indicators from. Read more about collections here. | False |
| Indicator first fetch | Specify the date range for initial data fetch (default: “3 days”). | False |
| Number of requests per collection | Number of API requests per collection in each fetch iteration (default: 2). Each request picks up to 100 (limit) objects with different amount of indicators. If you face runtime errors, lower the value. | False |
| Limit (items per request) | Specifies the number of records fetched per API request (default: 100). This limit applies to all collections in the instance. For optimal performance, check the official API Limitations documentation for recommended limit values per collection. Best practice: create separate integration instances for different collections or groups of collections with similar optimal limit values. | False |
| Tags | Enter tags for indicators if needed. | False |
| Traffic Light Protocol Color | Select the Traffic Light Protocol (TLP) designation to apply to indicators fetched from the feed. Options: RED, AMBER, GREEN, WHITE. When Use TLP from source is disabled, this value is applied to all indicators. When Use TLP from source is enabled, this value is used only as a fallback when Group-IB does not provide TLP for an indicator. | False |
| Use TLP from source (per indicator) | When enabled, each indicator gets its TLP from Group-IB when the source provides it (see TLP per indicator for the list of collections). For ioc/common, TLP is always set to AMBER. The Traffic Light Protocol Color setting is then used only as a fallback when the source has no TLP. When disabled, all indicators use the single Traffic Light Protocol Color selected above. | False |
| Indicator Expiration Method | Configure how indicators expire. Options: Time Interval, Never Expire, When removed from the feed. | False |
Additional Resources
For detailed information about collections, their structure, available fields, and recommended date ranges, refer to the official Collections Details documentation.
For step-by-step configuration instructions including classifier and mapper setup, refer to the integration description file.
Traffic Light Protocol (TLP) per indicator
By default, the integration applies a single Traffic Light Protocol Color to all indicators (the one selected in the integration settings). If you want each indicator to keep the TLP value provided by Group-IB for that record, enable Use TLP from source (per indicator).
When Use TLP from source is enabled:
- Collections that receive TLP from Group-IB (when the source provides it):
compromised/account_group,compromised/bank_card_group,compromised/masked_card,attacks/ddos,attacks/deface,attacks/phishing_kit,attacks/phishing_group,apt/threat,hi/threat,osi/vulnerability,osi/git_repository,suspicious_ip/tor_node,suspicious_ip/open_proxy,suspicious_ip/socks_proxy,suspicious_ip/vpn,suspicious_ip/scanner. - ioc/common: TLP is always set to AMBER (Group-IB does not provide TLP for this collection).
- If Group-IB does not provide TLP for an indicator, the Traffic Light Protocol Color setting is used as fallback for that indicator. So the integration-level TLP applies only when the source has no TLP for the given record.
When Use TLP from source is disabled: All indicators receive the same TLP from the Traffic Light Protocol Color setting.
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.
gibti-get-indicators
Get a limited count of indicators for a specified collection and get all indicators from particular events by ID.
Base Command
gibti-get-indicators
Legacy alias
gibtia-get-indicatorsremains available for backward compatibility.
Input
| Argument Name | Description | Required |
|---|---|---|
| collection | GIB Collection to get indicators from. Possible values are: compromised/mule, compromised/masked_card, compromised/imei, attacks/ddos, attacks/deface, attacks/phishing, attacks/phishing_kit, hi/threat, apt/threat, osi/vulnerability, suspicious_ip/tor_node, suspicious_ip/open_proxy, suspicious_ip/socks_proxy, malware/cnc. | Required |
| id | Incident ID to get indicators. If set, all indicators will be provided from the particular incident. | Optional |
| limit | Limit of indicators to display in War Room. Possible values are: 10, 20, 30, 40, 50. Default is 50. | Optional |
Command Example
!gibti-get-indicators collection=ioc/common
Configuration parameters
url— GIB TI URL (required)credentials— Username (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsfeedIncremental— Incremental feedfeed— Fetch indicatorsfeedReputation— Indicator ReputationfeedReliability— Source Reliability (required)feedFetchInterval— Feed Fetch IntervalfeedBypassExclusionList— Bypass exclusion listindicator_collections— Indicator collectionsindicators_first_fetch— Indicator first fetchrequests_count— Number of requests per collectionfeedTags— Tagstlp_color— Traffic Light Protocol Coloruse_tlp_from_source— Use TLP from source (per indicator)limit— Limit (items per request)feedExpirationPolicy—feedExpirationInterval—
Commands (1)
-
gibtia-get-indicatorsGet limited count of indicators for specified collection and get all indicators from particular events by id.
import pytest import os from json import load from typing import Any, cast from GroupIB_TIA_Feed import ( fetch_indicators_command, Client, main, DateHelper, validate_launch_get_indicators_command, _validate_indicator_collections, get_indicators_command, IndicatorBuilding, COMMON_MAPPING, ) import GroupIB_TIA_Feed from CommonServerPython import DemistoException from urllib3.exceptions import InsecureRequestWarning from urllib3 import disable_warnings as urllib3_disable_warnings from cyberintegrations.cyberintegrations import Parser # Disable insecure warnings urllib3_disable_warnings(InsecureRequestWarning) COLLECTION_NAMES = [ "compromised/account_group", "compromised/bank_card_group", "compromised/mule", "attacks/ddos", "attacks/deface", "attacks/phishing_kit", "attacks/phishing_group", "apt/threat", "hi/threat", "suspicious_ip/tor_node", "suspicious_ip/open_proxy", "suspicious_ip/socks_proxy", "suspicious_ip/vpn", "suspicious_ip/scanner", "malware/cnc", "osi/vulnerability", "osi/git_repository", "ioc/common", ] realpath = os.path.join(os.path.dirname(os.path.realpath(__file__))) with open(f"{realpath}/test_data/avalible_collections_example.json") as example: AVALIBLE_COLLECTIONS_RAW_JSON = load(example) with open(f"{realpath}/test_data/main_collections_examples.json") as example: COLLECTIONS_RAW_JSON = load(example) @pytest.fixture(scope="function", params=COLLECTION_NAMES) def session_fixture(request): """ Fixture for setting up a session with a client instance specific to each collection. Given: - COLLECTION_NAMES, a list of collection names representing different data types that the integration handles. When: - Each test function uses this fixture to set up a unique session with a particular collection name. Then: - Returns a tuple containing: - The current collection name as a parameter for test functions that may need it. - An instance of Client configured with the specified base URL, authentication, and necessary headers for the integration. - This fixture allows parameterized tests that run independently for each collection, providing an isolated client setup for each run. """ return request.param, Client( base_url="https://some-url.com", auth=("example@roup-ib.com", "exampleAPI_TOKEN"), verify=True, headers={"Accept": "*/*"}, ) def test_main_error(): """ Test for verifying the error-handling behavior in the main() function. Given: - A main() function configured to raise an exception when calling error_command. When: - The main function invokes error_command(), which is expected to trigger an error. Then: - Ensures that a SystemExit exception is raised as expected. - The test checks that the main function handles errors in a predictable and controlled manner, allowing graceful exits during failure. """ with pytest.raises(SystemExit): main()["error_command"]() # type: ignore def test_common_mapping_contains_masked_card(): mapping = cast(dict[str, Any], COMMON_MAPPING["compromised/masked_card"]) assert mapping["types"]["cnc_domain"] == "Domain" assert mapping["types"]["cnc_ipv4_ip"] == "IP" assert mapping["parser_mapping"]["cnc_domain"] == "cnc.domain" assert mapping["parser_mapping"]["cnc_ipv4_ip"] == "cnc.ipv4.ip" assert mapping["parser_mapping"]["evaluation_tlp"] == "evaluation.tlp" def test_fetch_indicators_command(mocker, session_fixture): """ Test for validating the functionality of fetch_indicators_command with multiple collection types. Given: - A session_fixture that supplies a client instance configured for a specific collection name for each test iteration. - collection_name, the current collection name being tested (e.g., "compromised/mule"). When: - The fetch_indicators_command() function is called with: - An empty last_run dictionary to indicate that this is the initial data fetch. - first_fetch_time set based on specific collection conditions: - For "compromised/mule", first_fetch_time is set to a fixed date of "2023-01-01". - For "attacks/deface", first_fetch_time is set to "2024-10-01". - For all other collections, first_fetch_time is set to "15 days" as a general recent timeframe. - indicator_collections set to a list containing only the current collection_name. - requests_count set to 3, which limits the number of requests per fetch. - common_fields set to an empty dictionary for simplicity, as no specific common fields are required for this test. Then: - Validates that: - "last_fetch" is a key in next_run, indicating that the command updates last_run data with the latest fetch time. - The first indicator in the indicators list contains a "fields" dictionary with a "gibid" key, verifying that each indicator has the expected structure. - This test ensures that fetch_indicators_command retrieves data according to each collection's parameters and formats the output consistently. """ collection_name, client = session_fixture if collection_name == "compromised/mule": first_fetch_time = "2023-01-01" elif collection_name == "attacks/deface": first_fetch_time = "2024-10-01" else: first_fetch_time = "15 days" mocker.patch.object(client, "get_available_collections_cached", return_value=frozenset(AVALIBLE_COLLECTIONS_RAW_JSON)) mocker.patch.object( client, "create_update_generator_proxy_functions", return_value=[Parser(chunk=COLLECTIONS_RAW_JSON[collection_name], keys=[], iocs_keys=[])], ) next_run, indicators = fetch_indicators_command( client=client, last_run={}, first_fetch_time=first_fetch_time, indicator_collections=[collection_name], requests_count=3, common_fields={}, ) assert "last_fetch" in next_run, "Expected 'last_fetch' key in next_run to indicate the last data retrieval time." if len(indicators) > 0: assert "gibid" in indicators[0].get("fields"), ( "Expected 'gibid' field in the first indicator's 'fields' dictionary, ensuring each indicator " "includes unique identifier data." ) def test_integration_test_module_success(mocker): """ Test for verifying successful test_module execution when collections are available. Given: - A client instance with mocked get_available_collections_cached that returns a non-empty frozenset. When: - test_module() is called with the client. Then: - Returns 'ok' indicating successful connection and availability of collections. """ client = Client( base_url="https://some-url.com", auth=("example@group-ib.com", "exampleAPI_TOKEN"), verify=True, headers={"Accept": "*/*"}, ) mocker.patch.object(client, "get_available_collections_cached", return_value=frozenset({"collection1", "collection2"})) result = GroupIB_TIA_Feed.test_module(client) assert result == "ok", "Expected 'ok' when collections are available." def test_integration_test_module_no_collections(mocker): """ Test for verifying test_module behavior when no collections are available. Given: - A client instance with mocked get_available_collections_cached that returns an empty frozenset. When: - test_module() is called with the client. Then: - Returns a message indicating that no collections are available. """ client = Client( base_url="https://some-url.com", auth=("example@group-ib.com", "exampleAPI_TOKEN"), verify=True, headers={"Accept": "*/*"}, ) mocker.patch.object(client, "get_available_collections_cached", return_value=frozenset()) result = GroupIB_TIA_Feed.test_module(client) assert result == "There are no collections available", "Expected message when no collections are available." def test_date_helper_first_time_fetch(): """ Test for verifying DateHelper.handle_first_time_fetch behavior on first fetch. Given: - An empty last_run dictionary indicating first-time fetch. - A valid first_fetch_time string. When: - DateHelper.handle_first_time_fetch() is called with these parameters. Then: - Returns date_from as a formatted date string and seq_update as None. """ last_run: dict[str, Any] = {} collection_name = "compromised/account_group" first_fetch_time = "2023-01-01" date_from, seq_update = DateHelper.handle_first_time_fetch(last_run, collection_name, first_fetch_time) assert date_from == "2023-01-01", "Expected date_from to be formatted as YYYY-MM-DD." assert seq_update is None, "Expected seq_update to be None on first fetch." def test_date_helper_subsequent_fetch(): """ Test for verifying DateHelper.handle_first_time_fetch behavior on subsequent fetches. Given: - A last_run dictionary with existing last_fetch data for the collection. - A first_fetch_time string. When: - DateHelper.handle_first_time_fetch() is called with these parameters. Then: - Returns date_from as None and seq_update as the value from last_run. """ last_run = {"last_fetch": {"compromised/account_group": 12345}} collection_name = "compromised/account_group" first_fetch_time = "15 days" date_from, seq_update = DateHelper.handle_first_time_fetch(last_run, collection_name, first_fetch_time) assert date_from is None, "Expected date_from to be None on subsequent fetch." assert seq_update == 12345, "Expected seq_update to match the value from last_run." def test_date_helper_invalid_first_fetch_time(): """ Test for verifying DateHelper.handle_first_time_fetch raises exception on invalid date format. Given: - An empty last_run dictionary. - An invalid first_fetch_time string that cannot be parsed. When: - DateHelper.handle_first_time_fetch() is called with these parameters. Then: - Raises DemistoException with an appropriate error message. """ last_run: dict[str, Any] = {} collection_name = "compromised/account_group" first_fetch_time = "invalid-date-format" with pytest.raises(DemistoException, match="Inappropriate indicators_first_fetch format"): DateHelper.handle_first_time_fetch(last_run, collection_name, first_fetch_time) def test_validate_launch_get_indicators_command_valid_input(): """ Test for verifying validate_launch_get_indicators_command with valid inputs. Given: - A valid limit (integer between 1 and 50). - A valid collection name that exists in COMMON_MAPPING. When: - validate_launch_get_indicators_command() is called with these parameters. Then: - Does not raise any exception, indicating successful validation. """ limit = 25 collection_name = "compromised/account_group" # Should not raise any exception validate_launch_get_indicators_command(limit, collection_name) def test_validate_launch_get_indicators_command_invalid_limit_type(): """ Test for verifying validate_launch_get_indicators_command raises exception for non-numeric limit. Given: - A limit that is not a number (string that cannot be converted to int). When: - validate_launch_get_indicators_command() is called with this limit. Then: - Raises DemistoException with message "Limit should be a number." """ limit = "not-a-number" collection_name = "compromised/account_group" with pytest.raises(DemistoException, match="Limit should be a number"): validate_launch_get_indicators_command(limit, collection_name) def test_validate_launch_get_indicators_command_limit_too_low(): """ Test for verifying validate_launch_get_indicators_command raises exception for limit <= 0. Given: - A limit that is 0 or negative. When: - validate_launch_get_indicators_command() is called with this limit. Then: - Raises DemistoException with message "Limit should be greater than 0." """ limit = 0 collection_name = "compromised/account_group" with pytest.raises(DemistoException, match="Limit should be greater than 0"): validate_launch_get_indicators_command(limit, collection_name) def test_validate_launch_get_indicators_command_limit_too_high(): """ Test for verifying validate_launch_get_indicators_command raises exception for limit > 50. Given: - A limit that exceeds 50. When: - validate_launch_get_indicators_command() is called with this limit. Then: - Raises DemistoException with message "Limit should be lower than or equal to 50." """ limit = 51 collection_name = "compromised/account_group" with pytest.raises(DemistoException, match="Limit should be lower than or equal to 50"): validate_launch_get_indicators_command(limit, collection_name) def test_validate_launch_get_indicators_command_invalid_collection(): """ Test for verifying validate_launch_get_indicators_command raises exception for invalid collection name. Given: - A collection name that does not exist in COMMON_MAPPING. When: - validate_launch_get_indicators_command() is called with this collection name. Then: - Raises DemistoException with message about incorrect collection name. """ limit = 25 collection_name = "invalid/collection" with pytest.raises(DemistoException, match="Incorrect collection name"): validate_launch_get_indicators_command(limit, collection_name) def test_validate_indicator_collections_success(mocker): """ Test for verifying _validate_indicator_collections succeeds when every requested collection is granted to the API user. Given: - A client whose `get_available_collections_cached` returns a frozenset of granted collections that fully covers the requested list. When: - _validate_indicator_collections() is called with a non-empty subset of the granted collections. Then: - Does not raise any exception. - Calls `get_available_collections_cached` exactly once (no extra round-trips). """ client = Client( base_url="https://some-url.com", auth=("example@group-ib.com", "exampleAPI_TOKEN"), verify=True, headers={"Accept": "*/*"}, ) granted = frozenset({"compromised/account_group", "attacks/ddos", "other/collection"}) cached_mock = mocker.patch.object(client, "get_available_collections_cached", return_value=granted) _validate_indicator_collections(client, ["compromised/account_group", "attacks/ddos"]) assert cached_mock.call_count == 1 def test_validate_indicator_collections_failure(mocker): """ Test for verifying _validate_indicator_collections raises a DemistoException when at least one requested collection is not granted to the API user. Given: - A client whose `get_available_collections_cached` returns a frozenset that does NOT include some of the requested collections. When: - _validate_indicator_collections() is called with a list that contains an unavailable collection. Then: - Raises DemistoException whose message names the offending collection so the operator can immediately fix the instance settings. """ client = Client( base_url="https://some-url.com", auth=("example@group-ib.com", "exampleAPI_TOKEN"), verify=True, headers={"Accept": "*/*"}, ) mocker.patch.object(client, "get_available_collections_cached", return_value=frozenset({"other/collection"})) with pytest.raises(DemistoException, match="unavailable/collection"): _validate_indicator_collections(client, ["unavailable/collection"]) def test_validate_indicator_collections_empty_list_skips_remote_call(mocker): """ Test for verifying _validate_indicator_collections is a no-op for an empty list. Given: - A client whose `get_available_collections_cached` is patched to fail loudly if it is ever invoked. When: - _validate_indicator_collections() is called with an empty list. Then: - Returns silently, never touching the network. This protects fetch flows configured with no collections from triggering an unnecessary `/user/granted_collections` round-trip. """ client = Client( base_url="https://some-url.com", auth=("example@group-ib.com", "exampleAPI_TOKEN"), verify=True, headers={"Accept": "*/*"}, ) cached_mock = mocker.patch.object( client, "get_available_collections_cached", side_effect=AssertionError("get_available_collections_cached must not be called for empty selection"), ) _validate_indicator_collections(client, []) cached_mock.assert_not_called() def test_indicator_building_clean_data(): """ Test for verifying IndicatorBuilding.clean_data removes None, empty values, and flattens nested lists. Given: - A list of dictionaries containing None values, empty strings, empty lists, and nested lists. When: - IndicatorBuilding.clean_data() is called with this data. Then: - Returns cleaned data with None, empty strings, and empty lists removed, and nested lists flattened. """ data = [ {"key1": "value1", "key2": None, "key3": "", "key4": []}, {"key1": ["nested", "list"], "key2": [["deeply", "nested"], "value"]}, {"key1": "value2", "key2": [None, "", "valid"]}, ] cleaned = IndicatorBuilding.clean_data(data) assert len(cleaned) == 3, "Expected all items to be preserved." # clean_data doesn't remove keys with None values, only cleans lists assert cleaned[0]["key2"] is None, "None values are preserved in dict keys." assert cleaned[0]["key4"] == [], "Empty lists are preserved." assert cleaned[1]["key1"] == ["nested", "list"], "Nested lists are flattened." assert cleaned[2]["key2"] == ["valid"], "None and empty values are removed from lists." def test_indicator_building_extract_single_value(): """ Test for verifying IndicatorBuilding.extract_single_value extracts non-empty values from nested structures. Given: - An IndicatorBuilding instance. - Various nested list structures containing None, empty strings, and valid values. When: - extract_single_value() is called with these structures. Then: - Returns the first non-empty, non-None value found, or None if no valid value exists. """ builder = IndicatorBuilding( parsed_json=[], collection_name="test", common_fields={}, collection_mapping={}, ) assert builder.extract_single_value([None, "", "valid"]) == "valid", "Expected first valid value." assert builder.extract_single_value([[None, "nested"], "value"]) == "nested", "Expected nested valid value." assert builder.extract_single_value([None, "", []]) is None, "Expected None when no valid value exists." assert builder.extract_single_value("simple") == "simple", "Expected simple value to be returned as-is." def test_indicator_building_find_iocs_in_feed(): """ Test for verifying IndicatorBuilding.find_iocs_in_feed correctly extracts IOCs from feed data. Given: - An IndicatorBuilding instance with a valid collection mapping. - A feed dictionary containing IOC data matching the collection mapping. When: - find_iocs_in_feed() is called with the feed data. Then: - Returns a list of indicators with proper structure including value, type, rawJSON, and fields. """ collection_name = "ioc/common" mapping = COMMON_MAPPING[collection_name] feed = { "id": "test-id-123", "url": "https://example.com/malicious", "domain": "malicious.example.com", "ip": "192.168.1.1", "dateFirstSeen": "2023-01-01T00:00:00Z", "dateLastSeen": "2023-01-02T00:00:00Z", } builder = IndicatorBuilding( parsed_json=[], collection_name=collection_name, common_fields={"trafficlightprotocol": "RED"}, collection_mapping=mapping, ) indicators = builder.find_iocs_in_feed(feed) assert len(indicators) > 0, "Expected at least one indicator to be extracted." assert all("value" in ind for ind in indicators), "Expected all indicators to have 'value' field." assert all("type" in ind for ind in indicators), "Expected all indicators to have 'type' field." assert all("rawJSON" in ind for ind in indicators), "Expected all indicators to have 'rawJSON' field." assert all("fields" in ind for ind in indicators), "Expected all indicators to have 'fields' field." def test_fetch_indicators_command_with_last_run(mocker, session_fixture): """ Test for verifying fetch_indicators_command behavior when last_run contains previous fetch data. Given: - A session_fixture providing a client and collection name. - A last_run dictionary with existing last_fetch data for the collection. When: - fetch_indicators_command() is called with this last_run data. Then: - Uses seq_update from last_run instead of date_from. - Returns updated next_run with the latest seq_update. """ collection_name, client = session_fixture last_run = {"last_fetch": {collection_name: 12345}} first_fetch_time = "15 days" mocker.patch.object(client, "get_available_collections_cached", return_value=frozenset(AVALIBLE_COLLECTIONS_RAW_JSON)) mock_parser = Parser(chunk=COLLECTIONS_RAW_JSON[collection_name], keys=[], iocs_keys=[]) mock_parser.sequpdate = 67890 mocker.patch.object( client, "create_update_generator_proxy_functions", return_value=[mock_parser], ) next_run, indicators = fetch_indicators_command( client=client, last_run=last_run, first_fetch_time=first_fetch_time, indicator_collections=[collection_name], requests_count=1, common_fields={}, ) assert "last_fetch" in next_run, "Expected 'last_fetch' key in next_run." assert collection_name in next_run["last_fetch"], "Expected collection name in next_run['last_fetch']." assert next_run["last_fetch"][collection_name] == 67890, "Expected seq_update to be updated from parser." def test_fetch_indicators_command_multiple_collections(mocker): """ Test for verifying fetch_indicators_command handles multiple collections correctly. Given: - A client instance. - A list of multiple collection names to fetch. When: - fetch_indicators_command() is called with multiple collections. Then: - Processes all collections and returns next_run with last_fetch for each collection. """ client = Client( base_url="https://some-url.com", auth=("example@group-ib.com", "exampleAPI_TOKEN"), verify=True, headers={"Accept": "*/*"}, ) collections = ["compromised/account_group", "attacks/ddos"] mocker.patch.object(client, "get_available_collections_cached", return_value=frozenset(AVALIBLE_COLLECTIONS_RAW_JSON)) def create_mock_parser(collection_name): """Helper function to create a mock parser for a specific collection.""" mock_parser = Parser(chunk=COLLECTIONS_RAW_JSON[collection_name], keys=[], iocs_keys=[]) mock_parser.sequpdate = 10000 return mock_parser def side_effect_func(**kwargs): """Side effect function that returns appropriate parser based on collection_name.""" collection_name = kwargs.get("collection_name") return [create_mock_parser(collection_name)] mocker.patch.object( client, "create_update_generator_proxy_functions", side_effect=side_effect_func, ) next_run, indicators = fetch_indicators_command( client=client, last_run={}, first_fetch_time="15 days", indicator_collections=collections, requests_count=1, common_fields={}, ) assert "last_fetch" in next_run, "Expected 'last_fetch' key in next_run." assert len(next_run["last_fetch"]) == len(collections), "Expected last_fetch entry for each collection." def test_get_indicators_command_without_id(mocker, session_fixture): """ Test for verifying get_indicators_command behavior when fetching without specific ID. Given: - A session_fixture providing a client and collection name. - Command arguments without an ID parameter. When: - get_indicators_command() is called with these arguments. Then: - Returns a list of CommandResults objects with readable output. """ collection_name, client = session_fixture args = {"collection": collection_name, "limit": 10} mocker.patch.object( client, "create_update_generator_proxy_functions", return_value=[Parser(chunk=COLLECTIONS_RAW_JSON[collection_name], keys=[], iocs_keys=[])], ) results = get_indicators_command(client, args) assert isinstance(results, list), "Expected results to be a list." # Some collections may not have indicators in test data, so we check structure if results exist if len(results) > 0: assert all(hasattr(r, "readable_output") for r in results), "Expected all results to have readable_output."