IgniteFeedV2
Flashpoint Ignite Feed V2 Integration allows importing indicators of compromise using the V2 API that provides a more concise, context-rich response structure. It includes sightings of IOCs over time and IOC relationships, providing visibility into an IOC's evolution. The indicators of compromise are ingested into Cortex XSOAR and displayed in the War Room.
Data Enrichment & Threat Intelligence · Flashpoint Feed · Feed
Details
| ID | IgniteFeedV2 |
|---|---|
| Provider | Flashpoint |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Flashpoint Ignite Feed V2 Integration allows importing indicators of compromise using the V2 API that provides a more concise, context-rich response structure. It includes sightings of IOCs over time and IOC relationships, providing visibility into an IOC’s evolution. The indicators of compromise are ingested into Cortex XSOAR and displayed in the War Room.
This integration was integrated and tested with API v2 of Flashpoint Ignite.
Fetch Indicators
Fetching the Ignite indicators. The indicators that are created or updated after the provided “First fetch time” will be fetched in the ascending order.
If you are upgrading from a Flashpoint Feed integration, please refer to the Migration Guide for guidance.
Configure Flashpoint Ignite Feed v2 in Cortex
- Navigate to Settings > Integrations > Servers & Services.
- Search for Flashpoint Ignite Feed v2.
-
Click Add instance to create and configure a new integration instance.
Parameter Description Required Server URL Server URL to connect to Ignite. True API Key API key used for secure communication with the Ignite platform. True Types of the indicators to fetch Types of the indicators to fetch. If not specified, it fetches all the indicators. Options: IPv4, IPv6, Domain, URL, File, Extracted Config. False CIDR Range of an IPv4 or IPv6 indicator CIDR range to filter IPv4 or IPv6 indicators.
Note: This parameter is applied only if the “Types of the indicators to fetch” is IPv4 or IPv6.False Maximum Severity Level of an indicator Filter indicators by their maximum severity level. If not specified, it fetches all the indicators. Options: Informational, Suspicious, Malicious. False Minimum Severity Level of an indicator Filter indicators by their minimum severity level. If not specified, it fetches all the indicators. Options: Informational, Suspicious, Malicious. False MITRE ATTACK IDs of an indicator Filter indicators by their MITRE ATTACK IDs.
Example: T1003, T1004.False Tags of an indicator Filter indicators by their tags. Must be exact tag matches.
Example: malware:cobaltstrike, actor:apt.False Actor Tags of an indicator Filter indicators by their actor tags. Must be exact tag matches. Inclusion of the actor: prefix is optional.
Example: actor:apt37, apt1.False Malware Tags of an indicator Filter indicators by their malware tags. Must be exact tag matches. Inclusion of the malware: prefix is optional.
Example: malware:cobaltstrike, amadey.False Source Tags of an indicator Filter indicators by their source tags. Must be exact tag matches. Inclusion of the source: prefix is optional.
Example: source:flashpoint_extraction, flashpoint_detection.False First fetch time Backfill indicators by providing date or relative timestamp. Default is ‘3 days’.
Formats accepted: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ, etc.False Fetch indicators Enable to fetch indicators. 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 Default Indicator Mapping When selected, all the incoming indicators will map to the Ignite Indicator. False Tags Provides the tags to be added to the indicators. Supports CSV values. False feedIncremental To indicate to the Cortex XSOAR server that a feed is incremental. Generally feeds that fetch based on a time range. For example, a daily feed which provides new indicators for the last day or a feed which is immutable and provides indicators from a search date onwards. False feedExpirationPolicy False feedExpirationInterval False Feed Fetch Interval Interval in minutes to fetch indicators. 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 Create relationships Create relationships between indicators as part of Enrichment. False Trust any certificate (not secure) Indicates whether to allow connections without verifying SSL certificate’s validity. False Use system proxy settings Indicates whether to use XSOAR’s system proxy settings to connect to the API. False - Click Test to validate the URLs, token, and connection.
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.
flashpoint-ignite-v2-get-indicators
Retrieves indicators from the Ignite V2 API. It displays the content of the fetch-indicators command.
Base Command
flashpoint-ignite-v2-get-indicators
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of result objects to return. Maximum allowed limit is 500. Default is 10. | Optional |
| updated_since | Only retrieve values after the given timestamp. This parameter operates on the timestamp when an IOC was last modified. Formats accepted: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ, etc. Default is 3 days. |
Optional |
| types | Search by Indicator types. Supports comma separated values. Possible values are: IPv4, IPv6, Domain, URL, File, Extracted Config. | Optional |
| from | Fetch indicators after the given count of indicators. Default is 0. | Optional |
| cidr_range | CIDR range to filter IPv4 or IPv6 indicators. Note: This parameter is applied only if the “types” is IPv4 or IPv6. |
Optional |
| max_severity_level | Filter indicators by their maximum severity level. If not specified, it fetches all the indicators. Possible values are: Informational, Suspicious, Malicious | Optional |
| min_severity_level | Filter indicators by their minimum severity level. If not specified, it fetches all the indicators. Possible values are: Informational, Suspicious, Malicious | Optional |
| mitre_attack_ids | Filter indicators by their MITRE ATTACK IDs. Supports comma-separated values. | Optional |
| tags | Filter indicators by their tags. Must be exact tag matches. Supports comma-separated values. | Optional |
| actor_tags | Filter indicators by their actor tags. Must be exact tag matches. Inclusion of the actor: prefix is optional. Supports comma-separated values. | Optional |
| malware_tags | Filter indicators by their malware tags. Must be exact tag matches. Inclusion of the malware: prefix is optional. Supports comma-separated values. | Optional |
| source_tags | Filter indicators by their source tags. Must be exact tag matches. Inclusion of the source: prefix is optional. Supports comma-separated values. | Optional |
Context Output
There is no context output for this command.
Command example
!flashpoint-ignite-v2-get-indicators limit=2 types=URL updated_since="3 days"
Human Readable Output
Indicator(s)
ID Indicator Type Indicator Value Score Modified At Created At Last Seen At APT Description MITRE Attack IDs Sightings External References Total Sightings dummy-id-1 ipv4 0.0.0.1 malicious 2026-01-01T00:00:00Z 2026-01-01T00:00:00Z 2026-01-01T00:00:00Z N/A - id: T0001
name: Dummy Technique
tactics:
values: Defense-Evasion
tactic: Defense-Evasion
- id: T0002
name: Dummy Discovery
tactics:
values: Discovery
tactic: Discovery- id: dummy-sighting-1
href: https://api.example.com/sightings/dummy-sighting-1
source: dummy_source
sighted_at: 2026-01-01T00:00:00Z
tags:
values: malware:dummy-malware, source:dummy_source
related_iocs:
- id: dummy-related-id-1
type: file
value: dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
href: https://api.example.com/indicators/dummy-related-id-1
mitre_attack_ids:
- id: T0001
name: Dummy Technique
tactics:
values: Defense-Evasion
tactic: Defense-Evasion
- id: T0002
name: Dummy Discovery
tactics:
values: Discovery
tactic: Discovery
apt_description: N/A
malware_description: N/A
description: Observation: dummy-malware [2026-01-01T00:00:00Z]- source_name: Dummy Source
url: https://dummy.example.com1 dummy-id-2 file eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee unknown 2026-01-01T00:00:00Z 2026-01-01T00:00:00Z 2026-01-01T00:00:00Z - id: dummy-sighting-2
href: https://api.example.com/sightings/dummy-sighting-2
source: dummy_source
sighted_at: 2026-01-01T00:00:00Z
tags:
values: malware:dummy-malware-2, source:dummy_source
description: Observation: dummy-malware-2 [2026-01-01T00:00:00Z]1
Migration Guide
Migrated Commands
Some of the previous integration’s commands have been migrated to new commands. Below is the table showing the commands that have been migrated to the new ones.
| Flashpoint Ignite Command | Flashpoint Ignite v2 Command |
|---|---|
| flashpoint-ignite-get-indicators | flashpoint-ignite-v2-get-indicators |
Configuration parameters
url— Server URL (required)credentials— (required)types— Types of the indicators to fetchcidr_range— CIDR Range of an IPv4 or IPv6 indicatormax_score— Maximum Severity Level of an indicatormin_score— Minimum Severity Level of an indicatormitre_attack_ids— MITRE ATTACK IDs of an indicatortags— Tags of an indicatoractor_tags— Actor Tags of an indicatormalware_tags— Malware Tags of an indicatorsource_tags— Source Tags of an indicatorfirst_fetch— First fetch timefeed— Fetch indicatorsfeedReputation— Indicator ReputationfeedReliability— Source Reliability (required)tlp_color— Traffic Light Protocol ColordefaultMap— Default Indicator MappingfeedTags— TagsfeedIncremental—feedExpirationPolicy—feedExpirationInterval—feedFetchInterval— Feed Fetch IntervalfeedBypassExclusionList— Bypass exclusion listcreateRelationship— Create relationshipsinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (1)
-
flashpoint-ignite-v2-get-indicatorsRetrieves indicators from the Ignite V2 API. It displays the content of the fetch-indicators command.
"""Flashpoint Ignite Feed V2 Integration for Cortex XSOAR (aka Demisto)""" """ IMPORTS """ import json # noqa E402 from typing import Any # noqa E402 from copy import deepcopy # noqa E402 import urllib3 # noqa E402 from CommonServerPython import * # noqa # pylint: disable=unused-wildcard-import from CommonServerUserPython import * # noqa # Disable insecure warnings urllib3.disable_warnings() # pylint: disable=no-member """ CONSTANTS """ DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # ISO8601 format with UTC, default in XSOAR LIMIT = 10 MAX_FETCH = 500 DEFAULT_FIRST_FETCH = "3 days" CURRENT_TIME = "now" DEFAULT_OFFSET = 0 DEFAULT_SORT_ORDER = "modified_at:asc" DEFAULT_INDICATOR_TYPE = "Ignite Indicator" TIMEOUT = 60 STATUS_LIST_TO_RETRY = (429, *(status_code for status_code in requests.status_codes._codes if status_code >= 500)) # type: ignore OK_CODES = (200, 201) TOTAL_RETRIES = 4 BACKOFF_FACTOR = 7.5 # Sleep for [0s, 15s, 30s, 60s] between retries. URL_SUFFIX = {"INDICATORS": "/technical-intelligence/v2/indicators"} # Mapping old types to new ioc_types for v2 API IOC_TYPE_MAPPING = { "ip": "ipv4,ipv6", "ipv4": "ipv4", "ipv6": "ipv6", "domain": "domain", "url": "url", "file": "file", "extracted config": "extracted_config", } INDICATOR_TYPE_MAPPING = { "domain": FeedIndicatorType.Domain, "url": FeedIndicatorType.URL, "file": FeedIndicatorType.File, "extracted_config": DEFAULT_INDICATOR_TYPE, } # Score mapping for v2 API SCORE_MAPPING = { "informational": "informational", "suspicious": "suspicious", "malicious": "malicious", } MESSAGES = { "NO_PARAM_PROVIDED": "Please provide the {}.", "LIMIT_ERROR": "{} is an invalid value for limit. Limit must be between 1 and {}.", "NO_INDICATORS_FOUND": "No indicators were found for the given argument(s).", "INVALID_TYPES": "Invalid Types of the indicators provided: {}.", "INVALID_SCORE": "Invalid Severity provided: {}. Valid values are: Informational, Suspicious, Malicious.", "INVALID_SCORE_RANGE": "Minimum Severity Level of an indicator cannot be greater than " "Maximum Severity Level of an indicator.", "FROM_ERROR": "Invalid value for from: {}. From must be a non-negative integer.", } HTTP_ERRORS = { 400: "Bad request [400]: An error occurred while fetching the data.", 401: "Authentication error [401]: Please provide valid API Key.", 403: "Forbidden [403]: Please provide valid API Key.", 404: "Resource not found [404]: Invalid endpoint was called.", 422: "Validation error [422]: Invalid request parameters.", 500: "Internal server error [500]: Please try again after some time.", } INTEGRATION_VERSION = get_pack_version() INTEGRATION_PLATFORM = "Cortex XSOAR" DEFAULT_API_PATH = "https://api.flashpoint.io" DEFAULT_PLATFORM_PATH = "https://app.flashpoint.io" IGNITE_FEED_EVENT_HREF = "https://app.flashpoint.io/cti/malware/iocs/" # Field mapping for v2 API response FLASHPOINT_FEED_MAPPING_V2 = { "flashpointfeedindicatorid": {"path": "id", "type": "str"}, "flashpointfeedindicatortype": {"path": "type", "type": "str"}, "flashpointfeedapi": {"path": "href", "type": "url"}, "flashpointfeedscorevalue": {"path": "score.value", "type": "str"}, "flashpointfeedlastscoredate": {"path": "score.last_scored_at", "type": "date"}, "flashpointfeedmodifieddate": {"path": "modified_at", "type": "date"}, "flashpointfeedcreateddate": {"path": "created_at", "type": "date"}, "flashpointfeedlastseendate": {"path": "last_seen_at", "type": "date"}, "flashpointfeedplatformurl": {"path": "platform_urls.ignite", "type": "url"}, "flashpointfeedaptdescription": {"path": "apt_description", "type": "str"}, "flashpointfeedexternalreferences": {"path": "external_references", "type": "str"}, "md5": {"path": "hashes.md5", "type": "str"}, "sha1": {"path": "hashes.sha1", "type": "str"}, "sha256": {"path": "hashes.sha256", "type": "str"}, "flashpointfeedhtmlmalwaredescription": {"path": "malware_description", "type": "str"}, "flashpointfeedmitreattackids": {"path": "mitre_attack_ids", "type": "str"}, "flashpointfeedsightings": {"path": "sightings", "type": "str"}, "flashpointfeedlatestsighting": {"path": "latest_sighting", "type": "str"}, "flashpointfeedtotalsightings": {"path": "total_sightings", "type": "int"}, } class Client(BaseClient): """Client class to interact with the Flashpoint Ignite V2 API.""" def __init__(self, url, headers, verify, proxy): """Initialize class object. :type url: ``str`` :param url: Base server address with suffix, for example: https://example.com. :type headers: ``Dict`` :param headers: Additional headers to be included in the requests. :type verify: ``bool`` :param verify: Use to indicate secure/insecure http request. :type proxy: ``bool`` :param proxy: The proxy settings to be used. """ self.url = url if DEFAULT_API_PATH in url: self.platform_url = DEFAULT_PLATFORM_PATH else: self.platform_url = url self.headers = headers self.verify = verify self.proxy = proxy super().__init__(base_url=self.url, headers=self.headers, verify=self.verify, proxy=self.proxy) def http_request(self, url_suffix: str, params: dict[str, Any] | None, method: str = "GET", resp_type: str = "json") -> Any: """ Get http response based on url and given parameters. :param url_suffix: url encoded url suffix. :param params: URL parameters to specify the query. :param method: Specify http methods. :param resp_type: Response type to be returned. :return: http response on json. """ resp = self._http_request( method=method, url_suffix=url_suffix, params=params, ok_codes=OK_CODES, error_handler=self.handle_errors, status_list_to_retry=STATUS_LIST_TO_RETRY, retries=TOTAL_RETRIES, backoff_factor=BACKOFF_FACTOR, timeout=TIMEOUT, resp_type=resp_type, ) return resp def check_indicator_type(self, indicator_value: str, indicator_type: str, default_map: bool = False) -> str: """ Set the type of the indicator. :param indicator_value: Value of the indicator. :param indicator_type: Type of the indicator. :param default_map: To enable the default mapper setting for the indicator. :return: Type of the indicator. """ ind_type = DEFAULT_INDICATOR_TYPE if not default_map: if indicator_type in INDICATOR_TYPE_MAPPING: ind_type = INDICATOR_TYPE_MAPPING.get(indicator_type, DEFAULT_INDICATOR_TYPE) else: ind_type = auto_detect_indicator_type(indicator_value) return ind_type or DEFAULT_INDICATOR_TYPE def create_relationship(self, entity_a: str, entity_a_type: str, entity_b_data: list) -> list: """ Create a list of relationships objects from the tags. :param entity_a: the entity a of the relation which is the current indicator. :param entity_a_type: the entity a type which is the type of the current indicator. :param entity_b_data: list of entity_b_data returned from the API. :return: list of EntityRelationship objects containing all the relationships. """ relationships = [] for entity_b in entity_b_data: if entity_b: # operations for entity_b entity_b_value = entity_b.get("value", "") res_entity_b_type = entity_b.get("type", "") entity_b_type = self.check_indicator_type( indicator_value=entity_b_value, indicator_type=res_entity_b_type, ) obj = EntityRelationship( name=EntityRelationship.Relationships.INDICATOR_OF, entity_a=entity_a, entity_a_type=entity_a_type, entity_b=entity_b_value, entity_b_type=entity_b_type, ) obj = obj.to_indicator() relationships.append(obj) return relationships def map_indicator_fields(self, resp: dict, indicator_obj: dict) -> None: """ Map fields of indicators from the v2 API response. :param resp: raw response of indicator. :param indicator_obj: created indicator object. :return: None. """ for key, value in FLASHPOINT_FEED_MAPPING_V2.items(): true_value = None path = value.get("path", "") true_value = self._get_nested_value(resp, path) indicator_obj["fields"][key] = true_value def _get_nested_value(self, data: dict, path: str) -> Any: """ Get nested value from dictionary using dot notation path. :param data: Dictionary to extract value from. :param path: Dot notation path (e.g., "parent.child.value"). :return: Value at the path or None if not found. """ if not path: return None paths = path.split(".") value = data try: for p in paths: if isinstance(value, dict): value = value.get(p) # type: ignore else: return None except (AttributeError, TypeError): return None return value def create_indicators_from_response(self, response: Any, params: dict) -> list: """ Create indicators from the v2 API response. :param response: response received from the API (items array). :param params: dictionary of parameters. :return: List of indicators. """ indicators = [] feed_tags = argToList(params.get("feedTags")) tlp_color = params.get("tlp_color") relationship = params.get("createRelationship", False) default_map = params.get("defaultMap", False) for resp in response: indicator_value = resp.get("value", "") res_indicator_type = resp.get("type", "") indicator_type = self.check_indicator_type( indicator_value=indicator_value, indicator_type=res_indicator_type, default_map=default_map, ) indicator_obj = { "value": indicator_value, "type": indicator_type, "rawJSON": resp, "fields": { "tags": feed_tags if feed_tags else [], }, } if tlp_color: indicator_obj["fields"]["trafficlightprotocol"] = tlp_color if relationship: iocs_data = resp.get("relationships", {}).get("iocs", []) iocs_relationships = self.create_relationship(indicator_value, indicator_type, iocs_data) related_iocs_data = [] sightings_data = deepcopy(resp.get("sightings", [])) for sighting in sightings_data: related_iocs_data.extend(sighting.get("related_iocs", [])) related_iocs_data.extend(resp.get("latest_sighting", {}).get("related_iocs", [])) related_iocs_relationships = self.create_relationship(indicator_value, indicator_type, related_iocs_data) all_relationships = iocs_relationships + related_iocs_relationships # Deduplicate relationships using JSON string comparison seen = set() unique_relationships = [] for rel in all_relationships: rel_key = json.dumps(rel, sort_keys=True) if rel_key not in seen: seen.add(rel_key) unique_relationships.append(rel) indicator_obj["relationships"] = unique_relationships self.map_indicator_fields(resp, indicator_obj) indicators.append(indicator_obj) return indicators def fetch_indicators(self, params: dict, resp_type: str = "json") -> Any: """ Fetch the list of indicators based on specified arguments. :param params: Parameters to be sent with API call. :param resp_type: Response type to be returned. :return: API response. """ response = self.http_request(url_suffix=URL_SUFFIX["INDICATORS"], params=params, method="GET", resp_type=resp_type) return response @staticmethod def handle_errors(resp) -> None: """Handle http errors.""" status = resp.status_code # Handle 422 validation errors with detailed message if status == 422: error_message = Client.parse_validation_error(resp) raise DemistoException(error_message) if status in HTTP_ERRORS: raise DemistoException(HTTP_ERRORS[status]) else: resp.raise_for_status() @staticmethod def parse_validation_error(resp) -> str: """ Parse 422 validation error response and return a meaningful error message. :param resp: HTTP response object. :return: Formatted error message string. """ try: error_data = resp.json() errors = error_data.get("errors", []) if errors: error_messages = [] for error in errors: loc = error.get("loc", []) # Get the parameter name (last element in loc array) param_name = loc[-1] if loc else "unknown" msg = error.get("msg", "validation failed") input_value = error.get("input", "") if input_value: error_messages.append(f"{param_name}: {msg} (input: {input_value})") else: error_messages.append(f"{param_name}: {msg}") return f"Validation Error [422]: {'; '.join(error_messages)}" # Fallback to detail message if no errors array return f"Validation Error [422]: {error_data}" except Exception: return HTTP_ERRORS[422] """ HELPER FUNCTIONS """ def remove_space_from_args(args): """ Remove space from args. :param args: Arguments. :return: Argument"s dictionary without spaces. """ for key in args: if isinstance(args[key], str): args[key] = args[key].strip() return args def validate_params(params: dict): """ Validate the parameters. :param params: Params to validate. """ if not params.get("url"): raise DemistoException(MESSAGES["NO_PARAM_PROVIDED"].format("Server URL")) if not str(params.get("credentials", {}).get("password", "")).strip(): raise DemistoException(MESSAGES["NO_PARAM_PROVIDED"].format("API Key")) def convert_types_to_ioc_types(types_list: list[str]) -> str: """ Convert old type values to v2 ioc_types format. :param types_list: List of types from configuration. :return: Comma-separated string of ioc_types. """ if not types_list: return "" ioc_types = [] for t in types_list: t_lower = t.lower() if t_lower in IOC_TYPE_MAPPING: ioc_types.append(IOC_TYPE_MAPPING[t_lower]) return ",".join(ioc_types) def validate_get_indicators_args(args: dict) -> dict: """ Validate the argument list for get indicators. :param args: Dictionary of arguments. :return: Updated dictionary of arguments for v2 API. """ cidr_range = args.get("cidr_range") min_score = args.get("min_severity_level") max_score = args.get("max_severity_level") validated_min, validated_max = validate_score_params(min_score, max_score) mitre_attack_ids = argToList(args.get("mitre_attack_ids", [])) tags = argToList(args.get("tags", [])) actor_tags = argToList(args.get("actor_tags", [])) malware_tags = argToList(args.get("malware_tags", [])) source_tags = argToList(args.get("source_tags", [])) fetch_params = assign_params( cidr_range=cidr_range, min_score=validated_min, max_score=validated_max, mitre_attack_ids=",".join(mitre_attack_ids), tags=",".join(tags), actors=",".join(actor_tags), malware=",".join(malware_tags), sources=",".join(source_tags), embed="all", sort=DEFAULT_SORT_ORDER, ) _from = arg_to_number(args.get("from", 0)) if _from < 0: # type: ignore raise ValueError(MESSAGES["FROM_ERROR"].format(_from)) fetch_params["from"] = _from limit = arg_to_number(args.get("limit", LIMIT)) if limit < 1 or limit > MAX_FETCH: # type: ignore raise ValueError(MESSAGES["LIMIT_ERROR"].format(limit, MAX_FETCH)) fetch_params["size"] = limit # Convert types to ioc_types for v2 API types = args.get("types", "") if types: types_list = argToList(types) fetch_params["ioc_types"] = convert_types_to_ioc_types(types_list) # type: ignore if types_list and not fetch_params.get("ioc_types"): raise ValueError(MESSAGES["INVALID_TYPES"].format(types_list)) first_fetch = arg_to_datetime(args.get("updated_since", DEFAULT_FIRST_FETCH)) fetch_params["modified_after"] = first_fetch.strftime(DATE_FORMAT) # type: ignore remove_nulls_from_dictionary(fetch_params) return fetch_params def prepare_hr_for_indicators(indicators: list) -> str: """ Prepare human-readable response. :param indicators: List of indicators. :return: Indicators in human-readable format. """ hr = [] for indicator in indicators: raw_json = indicator.get("rawJSON", {}) data = { "ID": f"[{raw_json.get('id', '')}]({raw_json.get('platform_urls', {}).get('ignite', '')})", "Indicator Type": raw_json.get("type", ""), "Indicator Value": raw_json.get("value", ""), "Score": raw_json.get("score", {}).get("value", ""), "Modified At": raw_json.get("modified_at", ""), "Created At": raw_json.get("created_at", ""), "Last Seen At": raw_json.get("last_seen_at", ""), "APT Description": raw_json.get("apt_description", ""), "MITRE Attack IDs": raw_json.get("mitre_attack_ids", ""), "Sightings": raw_json.get("sightings", ""), "External References": raw_json.get("external_references", ""), "Total Sightings": raw_json.get("total_sightings", ""), } hr.append(data) headers = [ "ID", "Indicator Type", "Indicator Value", "Score", "Modified At", "Created At", "Last Seen At", "APT Description", "MITRE Attack IDs", "Sightings", "External References", "Total Sightings", ] return tableToMarkdown( name="Indicator(s)", t=hr, headers=headers, removeNull=True, json_transform_mapping={ "MITRE Attack IDs": JsonTransformer(is_nested=True), "Sightings": JsonTransformer(is_nested=True), "External References": JsonTransformer(is_nested=True), }, ) def validate_score_params(min_score: str | None, max_score: str | None) -> tuple[str | None, str | None]: """ Validate and convert score parameters. :param min_score: Minimum score value. :param max_score: Maximum score value. :return: Tuple of validated (min_score, max_score) for API. """ score_order = ["informational", "suspicious", "malicious"] validated_min = None validated_max = None if min_score: min_lower = min_score.lower() if min_lower not in SCORE_MAPPING: raise ValueError(MESSAGES["INVALID_SCORE"].format(min_score)) validated_min = SCORE_MAPPING[min_lower] if max_score: max_lower = max_score.lower() if max_lower not in SCORE_MAPPING: raise ValueError(MESSAGES["INVALID_SCORE"].format(max_score)) validated_max = SCORE_MAPPING[max_lower] # Validate that min_score is not greater than max_score if validated_min and validated_max and score_order.index(validated_min) > score_order.index(validated_max): raise ValueError(MESSAGES["INVALID_SCORE_RANGE"]) return validated_min, validated_max def validate_fetch_indicators_params(params: dict, last_run: dict[str, Any]) -> dict: """ Validate the parameter list for fetch indicators. :param params: Dictionary of parameters. :param last_run: last run object obtained from demisto.getLastRun(). :return: Updated dictionary of parameters for v2 API. """ # Convert types to ioc_types types_list = params.get("types", []) ioc_types = convert_types_to_ioc_types(types_list) if types_list else "" if types_list and not ioc_types: raise ValueError(MESSAGES["INVALID_TYPES"].format(types_list)) cidr_range = params.get("cidr_range") min_score = params.get("min_score") max_score = params.get("max_score") validated_min, validated_max = validate_score_params(min_score, max_score) mitre_attack_ids = argToList(params.get("mitre_attack_ids", [])) tags = argToList(params.get("tags", [])) actor_tags = argToList(params.get("actor_tags", [])) malware_tags = argToList(params.get("malware_tags", [])) source_tags = argToList(params.get("source_tags", [])) first_fetch = arg_to_datetime(params.get("first_fetch", DEFAULT_FIRST_FETCH)).strftime(DATE_FORMAT) # type: ignore # If available then take modified_after from last_run. modified_after = last_run.get("next_modified_after", first_fetch) current_time = arg_to_datetime(CURRENT_TIME).strftime(DATE_FORMAT) # type: ignore # If available then take modified_before from last_run. modified_before = last_run.get("next_modified_before", current_time) offset = last_run.get("offset", DEFAULT_OFFSET) fetch_params = assign_params( size=MAX_FETCH, embed="all", ioc_types=ioc_types, cidr_range=cidr_range, min_score=validated_min, max_score=validated_max, mitre_attack_ids=",".join(mitre_attack_ids), tags=",".join(tags), actors=",".join(actor_tags), malware=",".join(malware_tags), sources=",".join(source_tags), modified_after=modified_after, modified_before=modified_before, sort=DEFAULT_SORT_ORDER, ) fetch_params["from"] = offset remove_nulls_from_dictionary(fetch_params) return fetch_params """Command functions""" def test_module(client: Client) -> str: """ Tests the indicators from the feed. :param client: Client object. :return: "ok" if test passed, anything else will fail the test. """ params = demisto.params() is_fetch = params.get("feed", False) if is_fetch: fetch_indicators_command(client=client, params=params, last_run={}, is_test=True) else: client.fetch_indicators(params={"size": 1}) return "ok" def fetch_indicators_command(client: Client, params: dict, last_run: dict[str, Any], is_test: bool = False) -> tuple[list, dict]: """ Fetch the indicators from v2 API. :param client: Client object. :param params: Dictionary of parameters. :param last_run: last run object obtained from demisto.getLastRun(). :param is_test: If test_module called fetch_incident. :return: List of indicators and Dict of last run object. """ next_run: dict = {} fetch_params = validate_fetch_indicators_params(params=params, last_run=last_run) resp = client.fetch_indicators(params=fetch_params, resp_type="json") items = resp.get("items", []) items = remove_empty_elements(items) if is_test: return [], {} # Creating new last_run according to response. if len(items) < MAX_FETCH: # Updating modified_after equal to previous modified_before. next_run["next_modified_after"] = last_run.get("next_modified_before", fetch_params["modified_before"]) else: next_run = last_run.copy() next_run["next_modified_after"] = fetch_params["modified_after"] next_run["next_modified_before"] = fetch_params["modified_before"] # Set only offset equal to previous offset + max_fetch. next_run["offset"] = next_run.get("offset", DEFAULT_OFFSET) + MAX_FETCH indicators = client.create_indicators_from_response(response=items, params=params) demisto.debug(f"Set the last Run for indicators: {next_run}") return indicators, next_run def flashpoint_ignite_v2_get_indicators_command(client: Client, params: dict, args: dict) -> CommandResults: """ Get limited number of indicators from v2 API. :param client: Client object. :param params: Dictionary of parameters. :param args: Dictionary of arguments. :return: Standard Command Result. """ fetch_params = validate_get_indicators_args(args=args) response = client.fetch_indicators(params=fetch_params) items = response.get("items", []) items = remove_empty_elements(items) indicators = client.create_indicators_from_response(response=items, params=params) if not indicators: return CommandResults(readable_output=MESSAGES["NO_INDICATORS_FOUND"]) readable_output = prepare_hr_for_indicators(indicators=indicators) return CommandResults( readable_output=readable_output, outputs=items, outputs_key_field="id", outputs_prefix="FlashpointIgniteFeedV2.Indicator", raw_response=indicators, ) """Main Function""" def main(): """Parse params and runs command functions.""" params = remove_space_from_args(demisto.params()) remove_nulls_from_dictionary(params) # Get the service API url base_url = params.get("url", DEFAULT_API_PATH) api_key = str(params.get("credentials", {}).get("password", "")).strip() # Default configuration parameters for handling proxy and SSL Certificate validation. insecure = not argToBoolean(params.get("insecure", False)) proxy = argToBoolean(params.get("proxy", False)) command = demisto.command() demisto.debug(f"[Ignite Feed V2] Command being called is {command}") try: validate_params(params=params) headers: dict = { "Authorization": f"Bearer {api_key}", "X-FP-IntegrationPlatform": INTEGRATION_PLATFORM, "X-FP-IntegrationPlatformVersion": get_demisto_version_as_str(), "X-FP-IntegrationVersion": INTEGRATION_VERSION, } client = Client(verify=insecure, proxy=proxy, url=base_url, headers=headers) args = demisto.args() if command == "test-module": return_results(test_module(client=client)) elif command == "fetch-indicators": last_run = demisto.getLastRun() indicators, next_run = fetch_indicators_command(client=client, params=params, last_run=last_run) demisto.setLastRun(next_run) demisto.createIndicators(indicators) elif command == "flashpoint-ignite-v2-get-indicators": return_results(flashpoint_ignite_v2_get_indicators_command(client=client, params=params, args=args)) else: raise NotImplementedError(f"Command {command} is not implemented.") # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {command} command.\nError:\n{str(e)}") if __name__ in ["__main__", "builtin", "builtins"]: # pragma: no cover main()