Github Feed
This is the Feed GitHub integration for getting started with your feed integration.
Data Enrichment & Threat Intelligence · GitHub Feed · Feed
Details
| ID | Github Feed |
|---|---|
| Provider | Microsoft |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.8.0 |
| Docker Image | demisto/taxii2:1.0.0.11040999 |
| Supported Modules | Agentix XSIAM |
README
This is the Feed GitHub integration for getting started with your feed integration.
This integration was integrated and tested with version 1.0.0 of Github Feed.
Configure Github Feed in Cortex
| Parameter | Description | Required |
|---|---|---|
| Fetch indicators | False | |
| Base URL | The URL to the GitHub API. | True |
| API Token | False | |
| Incremental Feed | When enabled (default), only new/modified files since the last fetch are reported (legacy behavior). Disable to fetch the full current set on every cycle so the “When removed from the feed” (suddenDeath) expiration policy can expire indicators removed from the source. | False |
| Trust any certificate (not secure) | False | |
| Owner | Username of the repository owner | True |
| Repository / Path to fetch | The name of the repository. | True |
| Feed type | Predefined list of indicator types: - YARA: Parses YARA rules from the feed. The “Yara” pack is required for this type. - STIX: Parses STIX data from the feed. - IOCs: Parses Indicators of Compromise (IOCs) using regex patterns. |
True |
| Branch name | The name of the main branch to which to compare. | True |
| Files extensions to fetch | The extension of the file names to target. | True |
| 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 |
| First fetch time | First commit date of first published indicators to bring. e.g., “1 min ago”,”2 weeks ago”,”3 months ago”. | False |
| Feed Fetch Interval | 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 |
| Use system proxy settings | False | |
| Feed Expiration Policy | Controls when indicators expire. Choose “suddenDeath” together with disabling the *Incremental Feed* option to expire indicators that were removed from the source repository. | False |
| Feed Expiration Interval | Only used when *Feed Expiration Policy* is set to “interval”. Ignored for the “suddenDeath”, “never” and “indicatorType” policies. | False |
| Tags | Supports CSV values. | False |
| Enrichment Excluded | Select this option to exclude the fetched indicators from the enrichment process. | False |
| Indicator Reputation | Indicators from this integration instance will be marked with this reputation |
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.
github-get-indicators
Retrieves indicators from the feed within a specified date range and up to a maximum limit.
Base Command
github-get-indicators
Input
| Argument Name | Description | Required |
|---|---|---|
| since | The start date from which to fetch indicators. Accepts date strings like “7 days ago”, “2 weeks ago”, etc. Default is 7 days. | Optional |
| until | The end date until which to fetch indicators. Accepts date strings like “now”, “2023-05-19”, etc. | Optional |
| limit | The maximum number of results to return. Default is 50. | Optional |
Context Output
There is no context output for this command.
Troubleshooting
- HTTP 404 Error:
No common ancestor between {commit} and {branch}- Typically occurs when a “force push” or a “commit squash” overwrites previously pushed commits on the configured branch.
- Resolution: To resolve this issue, reset indicator fetching of the affected integration instance.
- Prevention: To avoid this error, refrain from force pushing or squashing commits on the branch configured in the integration instance.
Configuration parameters
feed— Fetch indicatorsurl— Base URL (required)api_token—feedIncremental— Incremental Feedinsecure— Trust any certificate (not secure)owner— Owner (required)repo— Repository / Path to fetch (required)feedType— Feed type (required)branch_head— Branch name (required)extensions_to_fetch— Files extensions to fetch (required)feedReliability— Source Reliability (required)tlp_color— Traffic Light Protocol Colorfetch_since— First fetch timefeedFetchInterval— Feed Fetch IntervalfeedBypassExclusionList— Bypass exclusion listproxy— Use system proxy settingsfeedExpirationPolicy—feedExpirationInterval— Feed Expiration IntervalfeedTags— TagsenrichmentExcluded— Enrichment ExcludedfeedReputation— Indicator Reputation
Commands (1)
-
github-get-indicatorsRetrieves indicators from the feed within a specified date range and up to a maximum limit.
import demistomock as demisto import plyara import plyara.utils import tldextract from CommonServerPython import * from TAXII2ApiModule import * CONTEXT_PREFIX = "GITHUB" RAW_RESPONSE = [] class Client(BaseClient): def __init__(self, base_url: str, verify: bool, proxy: bool, owner: str, repo: str, headers: dict): base_url = urljoin(base_url, f"/repos/{owner}/{repo}") super().__init__(base_url=base_url, verify=verify, proxy=proxy, headers=headers) def get_commits_between_dates(self, since, until) -> list: """ Retrieves the SHA of the base commit of the repository. This function fetches all commits of the repository and returns the SHA of the latest commit, which represents the base commit of the repository. Returns: str: The SHA of the base commit of the repository. """ parsed_since = dateparser.parse(since) parsed_until = dateparser.parse(until) if not (parsed_since and parsed_until): response = self._http_request("GET", full_url=f"{self._base_url}/commits", resp_type="response") else: params = { "since": parsed_since.isoformat(), "until": parsed_until.isoformat(), } response = self._http_request("GET", full_url=f"{self._base_url}/commits", params=params, resp_type="response") demisto.debug(f"The base get_base_head_commits_sha() raw response: {response}") return [commit.get("sha") for commit in self._extract_commits(response)] def _extract_commits(self, response) -> list: all_commits = [] if "next" not in response.links: all_commits.extend(response.json()) while "next" in response.links: data = response.json() all_commits.extend(data) response = self._http_request("GET", full_url=response.links["next"]["url"], resp_type="response") all_commits.extend(response.json()) demisto.debug(f"There are many comites currently bringing them all..., currently exist:{response}") return all_commits def get_files_between_commits( self, base: str, head: str, include_base_commit: bool ) -> tuple[list[dict[str, str]], str]: # pragma: no cover # noqa: E501 """ Retrieves the list of files changed between two commits and the SHA of the base commit. This function compares two commits in a repository to determine the files that have changed between them. Depending on the `include_base_commit` flag, it adjusts the comparison to include the base commit or not. If the comparison fails due to a "Not Found" error, the function handles this specific case by fetching the indicators including the first commit in the repository. :type base: ``str`` :param base: The SHA of the base commit. :type head: ``str`` :param head: The SHA of the head commit. :type include_base_commit: ``bool`` :param include_base_commit: Flag to indicate if the base commit should be included in the comparison. :return: A tuple containing a list of files changed between the commits and the SHA of the base commit. :rtype: ``tuple[list, str]`` :raises Exception: If an error occurs during the HTTP request. """ url_suffix = f"/compare/{base}...{head}" if not include_base_commit else f"/compare/{base}^...{head}" try: response = self._http_request("GET", url_suffix) except Exception as e: if "Not Found" in str(e): demisto.debug("in get_files_between_commits func: Case: fetch indicators including the first commit in the repo") response = self._http_request("GET", f"/compare/{base}...{head}") response["files"] += self._http_request("GET", f"/commits/{base}")["files"] else: demisto.error(f"in get_files_between_commits func error message: {e}") raise demisto.debug(f"The full response from 'get base...head' :{response}") if len(response["commits"]) == 0: base_sha = response["base_commit"]["sha"] else: base_sha = response["commits"][-1].get("sha") return response["files"], base_sha def filter_out_files_by_status(commits_files: list, statuses: tuple[str, ...] = ("added", "modified")) -> list[dict]: """ Filters files from a list of commit files based on their GIT status. Args: commits_files (list): A list of dictionaries representing commit files. statuses (tuple): A list of GIT statuses to filter files by. Returns: list[dict]: A list of dictionaries containing information about files that match the GIT statuses. """ return [commit_file for commit_file in commits_files if commit_file["status"] in statuses] def get_content_files_from_repo(client: Client, relevant_files: list[dict], params: dict) -> list: """ Retrieves content of relevant files based on specified extensions. Args: client (Client): An instance of the client used for HTTP requests. relevant_files (list): A list of dictionaries containing information about files. Returns: list: A list of file contents fetched via HTTP requests. """ global RAW_RESPONSE extensions_to_fetch = argToList(params.get("extensions_to_fetch") or []) skipped_file_paths: list[str] = [] raw_data_files: list[dict] = [] for relevant_file in relevant_files: file_path = relevant_file["filename"] if not file_path.endswith(tuple(extensions_to_fetch)): skipped_file_paths.append(file_path) continue file_commit = relevant_file["sha"] file_url = relevant_file["contents_url"] demisto.debug(f"Getting contents of file: {file_path} in commit: {file_commit} using URL: {file_url}.") file_contents = client._http_request(method="GET", full_url=file_url)["content"] raw_data_files.append({file_path: base64.b64decode(file_contents).decode("utf-8")}) demisto.debug(f"Skipped {len(skipped_file_paths)} files: {skipped_file_paths}.") demisto.debug(f"List of all files raw data: {raw_data_files}.") RAW_RESPONSE = [list(raw_data_file.values()) for raw_data_file in raw_data_files] return raw_data_files def get_commits_files(client: Client, base_commit, head_commit, is_first_fetch: bool) -> tuple[list[dict], str]: """ Retrieves relevant files modified between commits and the current repository head. Args: client (Client): An instance of the client used for interacting with the repository. last_commit_fetch (str): The SHA of the last fetched commit. Returns: tuple[list[dict], str]: A tuple containing a list of relevant file information and the SHA of the current repository head. """ all_commits_files, current_repo_head_sha = client.get_files_between_commits(base_commit, head_commit, is_first_fetch) relevant_files = filter_out_files_by_status(all_commits_files) return relevant_files, current_repo_head_sha def parse_and_map_yara_content(content_item: dict[str, str]) -> list: """ Parses YARA rules from a given content item and maps their attributes. Args: content_item (str): A string containing one or more YARA rules. Returns: list: A list of dictionaries representing parsed and mapped YARA rules. Each dictionary contains attributes such as rule name, description, author, etc. """ text_content = list(content_item.values())[0] file_path = list(content_item.keys())[0] current_time = datetime.now().isoformat() parser = plyara.Plyara() parsed_rules = [] try: raw_rules = parser.parse_string(text_content) for parsed_rule in raw_rules: metadata = {key: value for d in parsed_rule["metadata"] for key, value in d.items()} value_ = parsed_rule["rule_name"] type_ = "YARA Rule" mapper = { "value": value_, "description": metadata.get("description", ""), "author": metadata.get("author", ""), "rulereference": metadata.get("reference", ""), "sourcetimestamp": metadata.get("date", ""), "ruleid": metadata.get("id", ""), "rulestrings": make_grid_layout(parsed_rule.get("strings", {})), "condition": " ".join(parsed_rule["condition_terms"]), "references": file_path, "rawrule": f"```\n {plyara.utils.rebuild_yara_rule(parsed_rule)} \n```", } indicator_obj = { "value": value_, "type": type_, "service": "github", "fields": mapper, "score": Common.DBotScore.NONE, "firstseenbysource": current_time, "rawJSON": {"value": value_, "type": type_}, } parsed_rules.append(indicator_obj) except Exception as e: demisto.error(f"File: {file_path!r} cannot be processed. Error Message: {e}") return parsed_rules def make_grid_layout(list_dict): return [ {"index": d.get("name"), "string": d.get("value"), "type": d.get("type"), "modifiers": d.get("modifiers")} for d in list_dict ] def get_yara_indicators(content: list[dict]): """ Retrieves YARA indicators from a list of content items. Args: content (list): A list of strings containing YARA rules. Returns: list: A list of dictionaries representing parsed and mapped YARA rules for each content item. """ return [rule for item in content for rule in parse_and_map_yara_content(item)] def detect_domain_type(domain: str): """ Detects the type of an indicator (e.g., Domain, DomainGlob) using tldextract library. Args: domain (str): The indicator value to be analyzed. Returns: Optional[FeedIndicatorType]: The type of the indicator, or None if detection fails. """ try: no_cache_extract = tldextract.TLDExtract(cache_dir=False, suffix_list_urls=None) # type: ignore if no_cache_extract(domain).suffix: if "*" in domain: return FeedIndicatorType.DomainGlob return FeedIndicatorType.Domain except Exception: demisto.debug(f"tldextract failed to detect indicator type. indicator value: {domain}") return None ipv4Regex = ( r"(?P<ipv4>(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))[:]?(?P<port>\d+)?" ) ipv4cidrRegex = r"([0-9]{1,3}\.){3}[0-9]{1,3}(\/([1-2][0-9]|3[0-2]|[0-9]))(?!\d)" ipv6Regex = r"(?:(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:(?:(:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))" # noqa: E501 ipv6cidrRegex = r"(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,7}:|(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2}|(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3}|(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4}|(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5}|[0-9A-Fa-f]{1,4}:(?::[0-9A-Fa-f]{1,4}){1,6}|:(?::[0-9A-Fa-f]{1,4}){1,7}|::|(?:[0-9A-Fa-f]{1,4}:){6}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}|::(?:ffff(?::0{1,4})?:)?(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}|(?:[0-9A-Fa-f]{1,4}:){1,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})(?:\/(?:12[0-8]|1[0-1]\d|[1-9]\d|[0-9]))(?!\d)" # noqa: E501 regex_indicators = [ (ipv4cidrRegex, FeedIndicatorType.CIDR), (ipv6Regex, FeedIndicatorType.IPv6), (ipv6cidrRegex, FeedIndicatorType.IPv6CIDR), (emailRegex, FeedIndicatorType.Email), (re.compile(cveRegex, re.M), FeedIndicatorType.CVE), (md5Regex, FeedIndicatorType.File), (sha1Regex, FeedIndicatorType.File), (sha256Regex, FeedIndicatorType.File), (sha512Regex, FeedIndicatorType.File), ] regex_with_groups = [ (ipv4Regex, FeedIndicatorType.IP, "ipv4"), (urlRegex, FeedIndicatorType.URL, "url_with_path"), (domainRegex, detect_domain_type, "fqdn"), ] def extract_text_indicators(content: dict[str, str], params): """ Extracts indicators from text content using predefined regular expressions. Args: content (str): The text content to extract indicators from. Returns: list: A list of dictionaries representing extracted indicators. Each dictionary contains the indicator value and its type. """ text_content = list(content.values())[0] file_path = list(content.keys())[0] text_content = text_content.replace("[.]", ".").replace("[@]", "@") # Refang indicator prior to checking indicators = [] for regex, type_ in regex_indicators: matches = re.finditer(regex, text_content) # type: ignore if matches: indicators += [{"value": match.group(0), "type": type_} for match in matches] for regex, type_, group_name in regex_with_groups: matches = re.finditer(regex, text_content) # type: ignore if matches: for match in matches: if regex in (ipv4Regex, urlRegex): indicators.append({"value": match.group(group_name), "type": type_}) elif regex == domainRegex: regex_type = type_(match.group(group_name)) if callable(type_) else type_ if regex_type: indicators.append({"value": match.group(group_name), "type": regex_type}) indicators_to_xsoar = arrange_iocs_indicator_to_xsoar(file_path, indicators, params) return indicators_to_xsoar def arrange_iocs_indicator_to_xsoar(file_path: str, parsed_indicators: list, params: dict): res = [] owner = params.get("owner", "") repo = params.get("repo", "") current_time = datetime.now().isoformat() for indicator in parsed_indicators: value_ = indicator.get("value") type_ = indicator.get("type") raw_data = {"value": value_, "type": type_} indicator_obj = { "value": value_, "type": type_, "service": "github", "fields": {"references": file_path, "tags": {"owner": owner, "repo": repo}, "firstseenbysource": current_time}, "rawJSON": raw_data, } res.append(indicator_obj) return res def get_stix_indicators(repo_files_content): stix_client = STIX2XSOARParser({}) generator_stix_files = create_stix_generator(repo_files_content) indicators = stix_client.load_stix_objects_from_envelope(generator_stix_files) # type: ignore return indicators def identify_json_structure(json_data) -> Any: """ Identifies the structure of JSON data based on its content. Args: json_data : The JSON data to identify its structure. Returns: Union[str, Dict[str, Any], None]: The identified structure of the JSON data. Possible values are: "Bundle", "Envelope", or a dictionary with the key "objects". Returns None if the structure cannot be identified. """ if isinstance(json_data, dict) and json_data.get("bundle"): return "Bundle" if isinstance(json_data, dict) and json_data.get("objects"): return "Envelope" if isinstance(json_data, dict) and all([json_data.get("type"), json_data.get("id")]): return "Envelope" if isinstance(json_data, list) and all([json_data[0].get("type"), json_data[0].get("id")]): return {"objects": json_data} return None def filtering_stix_files(file_names: list, file_contents: list) -> list: """ Filters a list of content files, returning only those in STIX format. Args: content_files (list): A list of JSON files or dictionaries representing STIX content. Returns: list: A list of STIX files or dictionaries found in the input list. """ stix_files = [] for file_name, file_content in zip(file_names, file_contents): try: json_data = json.loads(file_content) except json.JSONDecodeError as e: demisto.debug(f"Invalid JSON data in {file_name!r}. Error: {str(e)}.") continue file_type = identify_json_structure(json_data) if file_type in ("Envelope", "Bundle"): demisto.debug(f"Identified STIX {file_type} object in {file_name!r}.") stix_files.append(json_data) elif isinstance(file_type, dict): demisto.debug(f"Constructed STIX object in {file_name!r}.") stix_files.append(file_type) else: demisto.debug(f"Could not identify STIX objects in {file_name!r}.") return stix_files def create_stix_generator(content_files: list[dict]): """ Create a generator for iterating over STIX files. This function takes a list of JSON files, filters them to include only STIX files, and then creates a generator that yields each STIX file or object one at a time. Args: content_files (list): A list of JSON files. Returns: Generator: A generator that yields each STIX file from the filtered list, one at a time. """ file_names = [list(content_file.keys())[0] for content_file in content_files] file_contents = [list(content_file.values())[0] for content_file in content_files] yield from filtering_stix_files(file_names=file_names, file_contents=file_contents) def test_module(client: Client, params) -> str: """Builds the iterator to check that the feed is accessible. Args: client: Client object. Returns: Outputs. """ try: dateparser.parse(params.get("fetch_since")) except Exception as e: return str(f"error in 'First fetch time' parameter: {e}") try: client._http_request("GET", full_url=client._base_url) except Exception as e: if "Not Found" in str(e): return f"Not Found error please check the 'Owner / Repo' names The error massage:{e}" elif "Bad credentials" in str(e): return f"Bad credentials error please check the API Token The error massage:{e}" return str(f"{e}") return "ok" def fetch_indicators( client: Client, last_commit_fetch, params, tlp_color: Optional[str] = None, feed_tags: List = [], limit: int = -1, enrichment_excluded: bool = False, ) -> List[Dict]: """ Fetches indicators from a GitHub repository using the provided client. Args: client (Client): The GitHub client used to fetch indicators. last_commit_fetch: The last commit fetched from the repository. Used as the base SHA only when the feed is configured as incremental (``feedIncremental`` = true). When non-incremental, the base is always recomputed from the configured "First fetch time" so every cycle returns the full current indicator set. tlp_color (Optional[str]): The Traffic Light Protocol (TLP) color to assign to the fetched indicators. feed_tags (List): Tags to associate with the fetched indicators. limit (int): The maximum number of indicators to fetch. Default is -1 (fetch all). Returns: List[Dict]: A list of dictionaries representing the fetched indicators. """ demisto.debug(f"Before fetch command last commit sha run: {last_commit_fetch}") since = params.get("fetch_since", "90 days ago") until = "now" is_incremental = argToBoolean(params.get("feedIncremental", True)) if is_incremental and last_commit_fetch: base_commit_sha = last_commit_fetch is_first_fetch = False else: base_commit_sha = client.get_commits_between_dates(since, until)[-1] is_first_fetch = True demisto.debug( f"Fetch mode: {'incremental' if is_incremental else 'full'}; " f"base_commit_sha={base_commit_sha}; is_first_fetch={is_first_fetch}" ) head_commit = params.get("branch_head", "") iterator, last_commit_info = get_indicators(client, params, base_commit_sha, head_commit, is_first_fetch) indicators = [] if limit > 0: iterator = iterator[:limit] for item in iterator: if not item.get("fields"): item["fields"] = {} if feed_tags: item["fields"]["tags"] = feed_tags if tlp_color: item["fields"]["trafficlightprotocol"] = tlp_color if enrichment_excluded: item["enrichmentExcluded"] = enrichment_excluded indicators.append(item) demisto.debug(f"After fetch command last run: {last_commit_info}") if last_commit_info: demisto.setLastRun({"last_commit": last_commit_info}) return indicators def get_indicators(client: Client, params, base_commit_sha, head_commit, is_first_fetch: bool = True): relevant_files, last_commit_info = get_commits_files(client, base_commit_sha, head_commit, is_first_fetch) feed_type = params.get("feedType", "") repo_files_content = get_content_files_from_repo(client, relevant_files, params) try: if feed_type == "YARA": indicators = get_yara_indicators(repo_files_content) elif feed_type == "STIX": indicators = get_stix_indicators(repo_files_content) elif feed_type == "IOCs": indicators = [] for file in repo_files_content: indicators += extract_text_indicators(file, params) except Exception as err: demisto.error(str(err)) raise ValueError(f"Could not parse returned data as indicator. \n\nError massage: {err}") demisto.debug(f"Fetched {len(indicators)} indicators with values: {[indicator.get('value') for indicator in indicators]}.") return indicators, last_commit_info def get_indicators_command(client: Client, params: dict, args: dict = {}) -> CommandResults: """Wrapper for retrieving indicators from the feed to the war-room. Args: client: Client object with request params: demisto.params() Returns: Outputs. """ limit = arg_to_number(args.get("limit")) enrichment_excluded = argToBoolean(params.get("enrichmentExcluded", False)) or ( params.get("tlp_color") == "RED" and is_xsiam_or_xsoar_saas() ) indicators: list = [] try: if limit and limit <= 0: raise ValueError("Limit must be a positive number.") since = args.get("since", "7 days ago") until = args.get("until", "now") all_commits = client.get_commits_between_dates(since, until) if not all_commits: indicators = [] human_readable = "#### No commits were found in the given time range" demisto.debug("No commits were found in the given time range") else: base_commit_sha = all_commits[-1] head_commit_sha = all_commits[0] indicators, _ = get_indicators(client, params, base_commit_sha, head_commit_sha) hr_indicators = [] if limit and limit > 0: indicators = indicators[:limit] for indicator in indicators: if enrichment_excluded: indicator["enrichmentExcluded"] = enrichment_excluded hr_indicators.append( { "Value": indicator.get("value"), "Type": indicator.get("type"), } ) human_readable = tableToMarkdown( "Indicators from GitHubFeed:", hr_indicators, headers=["Type", "Value"], removeNull=True ) if not indicators: human_readable = "#### There are no indicators in the given timeframe" demisto.debug(f"human_readable for request indicators is: {human_readable}") demisto.debug(f"indicators: {indicators}") return CommandResults( outputs_prefix=CONTEXT_PREFIX + ".Indicators", outputs_key_field="githubfeed", raw_response=RAW_RESPONSE, outputs=indicators, readable_output=human_readable, ) except Exception as err: demisto.error(str(err)) raise ValueError(f"get_indicators_command return with error. \n\nError massage: {err}") def fetch_indicators_command(client: Client, params: dict, args) -> list[dict]: """Wrapper for fetching indicators from the feed to the Indicators tab. Args: client: Client object with request params: demisto.params() Returns: Indicators. """ feed_tags = argToList(params.get("feedTags", "")) tlp_color = params.get("tlp_color") enrichment_excluded = argToBoolean(params.get("enrichmentExcluded", False)) or ( params.get("tlp_color") == "RED" and is_xsiam_or_xsoar_saas() ) limit = int(params.get("limit", -1)) last_commit_fetch = demisto.getLastRun().get("last_commit") indicators = fetch_indicators( client, last_commit_fetch, params, tlp_color=tlp_color, feed_tags=feed_tags, limit=limit, enrichment_excluded=enrichment_excluded, ) return indicators def main(): # pragma: no cover params = demisto.params() args = demisto.args() command = demisto.command() demisto.debug(f"Command being called is: {command}") base_url = str(params.get("url")) verify_certificate = not params.get("insecure", False) proxy = params.get("proxy", False) owner = params.get("owner", "") repo = params.get("repo", "") api_token = (params.get("api_token") or {}).get("password", "") headers = ( {"Accept": "application/vnd.github+json", "Authorization": f"Bearer {api_token}"} if api_token else {"Accept": "application/vnd.github+json"} ) # noqa: E501 try: client = Client( base_url=base_url, verify=verify_certificate, proxy=proxy, owner=owner, repo=repo, headers=headers, ) if command == "test-module": return_results(test_module(client, params)) elif command == "github-get-indicators": return_results(get_indicators_command(client, params, args)) elif command == "fetch-indicators": indicators = fetch_indicators_command(client, params, args) for iter_ in batch(indicators, batch_size=2000): demisto.createIndicators(iter_) else: raise NotImplementedError(f"Command {command} is not implemented.") except Exception as e: demisto.error(traceback.format_exc()) return_error(f"Failed to execute {command} command.\nError:\n{e!s}") if __name__ in ("__main__", "__builtin__", "builtins"): main()