CyCognito

The CyCognito integration fetches issues discovered by the CyCognito platform, thereby providing users with a view of their organization's internet-exposed attack surface. These issues include identification, prioritization, and recommendations for remediation of the risks faced by the organization. The integration contains commands to query assets and issues detected by the CyCognito platform, and includes a rich dashboard and layout with issue management capability.

Vulnerability Management · CyCognito

Details

IDCyCognito
ProviderCyCognito
CategoryVulnerability Management
From Version6.2.0
Docker Imagedemisto/pycountry:1.0.0.10120494
Supported ModulesAgentix XSIAM

README

The CyCognito integration fetches issues discovered by the CyCognito platform, thereby providing users with a view of their organization’s internet-exposed attack surface. These issues include identification, prioritization, and recommendations for remediation of the risks faced by the organization. The integration contains commands to query assets and issues detected by the CyCognito platform, and includes a rich dashboard and layout with issue management capability.
This integration was integrated and tested with CyCognito V1 API.

Configure CyCognito in Cortex

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for CyCognito.
  3. Click Add instance to create and configure a new integration instance.
Parameter Description Required
API Key The API Key required to authenticate to the service. True
Region Select the region based on the location of the CyCognito instance. If the region is not listed in the dropdown, choose the “Other” option and specify the region in the “Other Region” text field. False
Other Region Specify the region based on the location of the CyCognito instance. Only applicable if the “Other” option is selected in the Region dropdown. False
Incident type Incident type to map if no classifier is provided. False
Incident Mirroring Direction The mirroring direction in which to mirror the incident. You can mirror only in (from CyCognito to XSOAR), out (from XSOAR to CyCognito), or in both directions. False
Fetch incidents Indicates whether to fetch incident from the instance. False
First Fetch Time The date or relative timestamp from which to begin fetching incidents.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ

For example: 01 Mar 2021, 01 Feb 2021 04:45:33, 2022-04-17T14:05:44Z
False
Max Fetch The maximum number of incidents to fetch every time. The maximum value is ‘1000’. False
Issue Type The type of issue to fetch. By default, all types of issues will be fetched. Multiple selection is supported. False
Locations Filters incidents according to the geographic locations in which the issue is found. Multiple selection is supported. False
Severity The severity levels of the issues to fetch from CyCognito. By default, all the severity levels will be fetched, Multiple selection is supported. False
Investigation Status The investigation status of the issues to fetch from CyCognito. By default, it fetches uninvestigated issues. False
Advanced Filter Applies a filter to the list of issues based on a JSON-specific query.

Format:
[{
“field”: “issue-type”,
“op”: “in”,
“values”: [
“Unsafe Authentication”,
“Vulnerable Software”
]
},
{
“op”: “not-in”,
“field”: “severity-score”,
“values”: [10, 9]
}]

Note: When using several filtering options (e.g., ‘Issue Type’ and ‘Advanced Filter’), Advanced Filter parameters will take precedence over other parameters.
For a complete reference to the CyCognito fields and operations, please refer to the CyCognito API V0 documentation at
https://docs.cycognito.com/reference/query-issues
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
Incidents Fetch Interval Time interval for fetching incidents. False
  1. 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.

cycognito-issue-get


Retrieves information about an issue associated with a particular instance based on the provided issue instance ID.

Base Command

cycognito-issue-get

Input

Argument Name Description Required
issue_instance_id Unique issue ID of the instance.

Example: 0.0.0.0-cyc-auth-default-credentials,
example.com-cyc-sql-injection, 0.0.0.0-cyc-exposed-bucket-with-data.

Note: Users can retrieve the list of issue instance IDs by executing the “cycognito-issues-list” command.
Required

Context Output

Path Type Description
CyCognito.Issue.id String Unique ID of the issue.
CyCognito.Issue.references Unknown Issue reference.
CyCognito.Issue.potential_threat String The threat that the issue might cause.
CyCognito.Issue.tags Unknown Tags of the issue.
CyCognito.Issue.organizations Unknown Organizations of the instance.
CyCognito.Issue.issue_id String Unique ID of the issue.
CyCognito.Issue.summary String A brief description that summarizes the issue.
CyCognito.Issue.resolved_at String Date/time when the issue was resolved.
CyCognito.Issue.investigation_status String Investigation status of the issue.
CyCognito.Issue.locations Unknown The geographic location of the instance.
CyCognito.Issue.detection_complexity String Measures the difficulty at which a vulnerable asset can be detected by a potential attacker.
CyCognito.Issue.title String Title of the issue.
CyCognito.Issue.exploitation_score Number Exploitation score of the issue.
CyCognito.Issue.issue_type String Type of the issue.
CyCognito.Issue.comment String Comment associated with the issue.
CyCognito.Issue.severity String Severity of the issue.
CyCognito.Issue.remediation_steps Unknown A list of actions that describe how to resolve the issue.
CyCognito.Issue.potential_impact Unknown A list of categories that describe what might happen if the issue is exploited.
CyCognito.Issue.exploitation_method String Exploitation method of the issue.
CyCognito.Issue.affected_asset String The unique ID of the asset with which the issue is associated.
CyCognito.Issue.severity_score Number The numeric severity of the issue is in the range of 0 (not severe) through 10 (severe).
CyCognito.Issue.last_detected Date The time at which the issue was last detected.
CyCognito.Issue.first_detected Date The time at which the issue was first detected.
CyCognito.Issue.issue_status String Status of the issue found.
CyCognito.Issue.evidence Unknown Provides a reason or proof of why the issue was indeed detected by CyCognito.

Command example

!cycognito-issue-get issue_instance_id=127.0.0.1-cve-2019-00000

Context Example

{
    "CyCognito": {
        "Issue": {
            "affected_asset": "ip/127.0.0.1",
            "detection_complexity": "Service Detection",
            "exploitation_method": "Metasploit",
            "exploitation_score": 3,
            "first_detected": "2022-03-31T03:39:22.568Z",
            "id": "127.0.0.1-cve-2019-00000",
            "investigation_status": "investigating",
            "issue_id": "CVE-2019-00000",
            "issue_status": "new",
            "issue_type": "Vulnerable Software",
            "last_detected": "2022-03-31T03:39:22.568Z",
            "locations": [
                "IND"
            ],
            "organizations": [
                "Acme Interior Design",
                "Acme Corporation"
            ],
            "potential_impact": [
                "Loss of integrity",
                "Loss of confidentiality",
                "Data compromise"
            ],
            "references": [],
            "remediation_steps": [
                "Patch the Pulse Secure VPN to the latest version."
            ],
            "severity": "critical",
            "severity_score": 10,
            "summary": "| The Pulse Secure VPN has been confirmed to be vulnerable to an arbitrary file reading vulnerability. | Unauthenticated remote attackers can send the asset a specially crafted URI and thereby access arbitrary sensitive files. | Attackers can leverage the harvested information to perform further attacks.",
            "tags": [
                "Vulnerable Software",
                "Pulse Secure",
                "network vulnerabilities"
            ],
            "potential_threat": "Information Disclosure",
            "title": "Pulse Secure Arbitrary File Reading"
        }
    }
}

Human Readable Output

Issue detail

ID: 127.0.0.1-test

Title Affected Asset Detection Complexity Investigation Status Exploitation Score First Detected Last Detected Organizations Locations Potential Threat Severity Issue Type Issue Status Remediation Steps Potential Impact Tags Summary Link to Platform
Pulse Secure Arbitrary File Reading ip/127.0.0.1 Service Detection investigating 3 31 Mar 2022, 03:39 AM 31 Mar 2022, 03:39 AM Acme Interior Design, Acme Corporation India Information Disclosure critical Vulnerable Software new Patch the Pulse Secure VPN to the latest version. Loss of integrity, Loss of confidentiality, Data compromise Vulnerable Software, Pulse Secure, network vulnerabilities The Pulse Secure VPN has been confirmed to be vulnerable to an arbitrary file reading vulnerability. Unauthenticated remote attackers can send the asset a specially crafted URI and thereby access arbitrary sensitive files. Attackers can leverage the harvested information to perform further attacks. Click Here

cycognito-asset-get


Retrieves information about a specific asset according to the specified asset type and asset ID.

Base Command

cycognito-asset-get

Input

Argument Name Description Required
asset_type The type of asset.

Supported values: ‘ip’, ‘domain’, ‘cert’, ‘webapp’, ‘iprange’
Required
asset_id The unique asset identifier.

Note: The asset ID value can be found by executing the “cycognito-assets-list” command.
Required

Context Output

Path Type Description
CyCognito.Asset.alive Boolean Whether the port is alive or not.
CyCognito.Asset.comment String Comment associated with the asset.
CyCognito.Asset.id String Unique identifier of the asset.
Note: The asset ID is derived from the asset_id input field.
CyCognito.Asset.type String The type of asset.
CyCognito.Asset.business_units Unknown The business units of the asset.
CyCognito.Asset.signature String The identifier of the certificate.
CyCognito.Asset.closed_ports.status String Status of the closed ports object associated with the asset.
CyCognito.Asset.closed_ports.port Number Port of the closed ports object associated with the asset.
CyCognito.Asset.closed_ports.protocol String Protocol associated with the asset.
CyCognito.Asset.created Date Creation time of the asset.
CyCognito.Asset.domain String Domain of the asset.
CyCognito.Asset.domains Unknown List of domains associated with the asset.
CyCognito.Asset.domain_names Unknown List of domain names associated with the asset.
CyCognito.Asset.expiration Date The date and time at which the asset expires.
CyCognito.Asset.first_seen Date The time and date at which the asset was first discovered.
CyCognito.Asset.hosting_type String Hosting type of the asset.
CyCognito.Asset.ip String IP address of the asset.
CyCognito.Asset.ip_addresses Unknown IP name of the asset.
CyCognito.Asset.issuer_alt_names Unknown List of alternative names of the issuers.
CyCognito.Asset.issuer_common_name String Common name of the Issuer.
CyCognito.Asset.issuer_country String Country of the issuer.
CyCognito.Asset.issuer_locality String Locality of the issuer.
CyCognito.Asset.issuer_organization String The issuer’s organization.
CyCognito.Asset.issuer_organization_unit String The issuer’s organization unit.
CyCognito.Asset.issuer_state String The state of the issuer.
CyCognito.Asset.issues_count Number Count of the issues.
CyCognito.Asset.last_seen Date The time and date at which the asset was last seen.
CyCognito.Asset.locations Unknown Location of the asset.
CyCognito.Asset.open_ports.status String Status of the open ports object associated with the asset.
CyCognito.Asset.open_ports.port Number Port of the open ports object associated with the asset.
CyCognito.Asset.open_ports.protocol String Protocol associated with the asset.
CyCognito.Asset.organizations Unknown Organizations of the asset.
CyCognito.Asset.status String Status of the asset.
CyCognito.Asset.security_grade String Security rating of the asset.
CyCognito.Asset.severe_issues Number The number of severe issues associated with the asset.
CyCognito.Asset.signature_algorithm String Signature algorithm of the asset.
CyCognito.Asset.sub_domains Unknown Subdomains of the asset.
CyCognito.Asset.subject_alt_names Unknown List of alternate subject names.
CyCognito.Asset.subject_common_name String Common name of the subject.
CyCognito.Asset.subject_country String Subject’s country.
CyCognito.Asset.subject_locality String Locality of the subject.
CyCognito.Asset.subject_organization String Subject’s Organization.
CyCognito.Asset.subject_organization_unit String The organization unit of the subject.
CyCognito.Asset.subject_state String State of the subject.
CyCognito.Asset.tags Unknown Tags of the asset.
CyCognito.Asset.dynamically_resolved String Whether the asset has a rotating IP address.
CyCognito.Asset.investigation_status String Investigation status of the asset.
CyCognito.Asset.discoverability String Quantifies an asset’s level of exposure.

Command example

!cycognito-asset-get asset_type=ip asset_id=127.0.0.1

Context Example

{
    "CyCognito": {
        "Asset": {
            "alive": true,
            "closed_ports": [
                {
                    "port": 8080,
                    "protocol": "tcp",
                    "status": "closed"
                },
                {
                    "port": 102,
                    "protocol": "tcp",
                    "status": "closed"
                },
                {
                    "port": 445,
                    "protocol": "tcp",
                    "status": "closed"
                },
                {
                    "port": 161,
                    "protocol": "tcp",
                    "status": "closed"
                },
                {
                    "port": 4040,
                    "protocol": "tcp",
                    "status": "closed"
                },
                {
                    "port": 7070,
                    "protocol": "tcp",
                    "status": "closed"
                },
                {
                    "port": 1723,
                    "protocol": "tcp",
                    "status": "closed"
                }
            ],
            "comment": {
                "content": "A grade",
                "last_update": "2022-05-06T05:19:05.931Z"
            },
            "dynamically_resolved": "no",
            "first_seen": "2022-01-20T03:58:36.696Z",
            "hosting_type": "owned",
            "id": "127.0.0.1",
            "investigation_status": "investigated",
            "ip": "127.0.0.1",
            "issues_count": 1,
            "last_seen": "2022-03-31T03:39:22.568Z",
            "locations": [
                "IND"
            ],
            "open_ports": [
                {
                    "port": 9999,
                    "protocol": "tcp",
                    "status": "open"
                },
                {
                    "port": 2000,
                    "protocol": "tcp",
                    "status": "open"
                }
            ],
            "organizations": [
                "Acme Interior Design",
                "Acme Corporation"
            ],
            "security_grade": "B",
            "severe_issues": 0,
            "status": "new",
            "tags": [
                "Gateways",
                "ACME"
            ],
            "type": "ip"
        }
    }
}

Human Readable Output

Asset Details

Asset ID Asset Type Hosting Type Alive Locations First Seen Last Seen Status Security Grade Tags Organizations Severe Issues Investigation Status Open Ports
127.0.0.1 ip owned true India 20 Jan 2022, 03:58 AM 31 Mar 2022, 03:39 AM new B Gateways,
ACME
Acme Interior Design, Acme Corporation 0 investigated TCP - 9999, TCP - 2000

cycognito-asset-investigation-status-change


Modifies the investigation status of the specified asset.

Base Command

cycognito-asset-investigation-status-change

Input

Argument Name Description Required
asset_type The type of asset.

Supported values: ‘ip’, ‘domain’, ‘cert’, ‘webapp’, ‘iprange’
Required
asset_id The unique asset identifier.

Note: The asset ID value can be found by executing the “cycognito-assets-list” command.
Required
investigation_status The investigation status of the asset.

Supported values: ‘uninvestigated’, ‘investigating’, ‘investigated’.
Required

Context Output

Path Type Description
CyCognito.Asset.type String The type of the asset.
CyCognito.Asset.id String Unique identifier of the asset.
Note: The asset ID is derived from the asset_ID input field.
CyCognito.Asset.investigation_status String Investigation status of the Asset.
CyCognito.Asset.action_status String Whether the status update is successful or failed.

Command example

!cycognito-asset-investigation-status-change asset_type=ip asset_id=127.0.0.1 investigation_status=investigated

Context Example

{
    "CyCognito": {
        "Asset": {
            "action_status": "Success",
            "asset_type": "ip",
            "id": "127.0.0.1",
            "investigation_status": "investigated"
        }
    }
}

Human Readable Output

Investigation Status has been successfully updated for 127.0.0.1

Asset Type Asset ID Investigation Status Action Status
ip 127.0.0.1 investigated Success

cycognito-issue-investigation-status-change


Modifies the investigation status of the specified issue.

Base Command

cycognito-issue-investigation-status-change

Input

Argument Name Description Required
issue_instance_id The unique issue ID of the instance whose investigation status is to be changed.

Example: 0.0.0.0-cyc-auth-default-credentials,
example.com-cyc-sql-injection, 0.0.0.0-cyc-exposed-bucket-with-data.

Note: Users can retrieve the list of issue instance IDs by executing the “cycognito-issues-list” command.
Required
investigation_status The investigation status of the issue.

Supported values: ‘uninvestigated’, ‘investigating’, ‘investigated’
Required

Context Output

Path Type Description
CyCognito.Issue.id String Unique ID of the issue.
CyCognito.Issue.investigation_status String Investigation status of the issue.
CyCognito.Issue.action_status String Whether the update is successful or failed.

Command example

!cycognito-issue-investigation-status-change issue_instance_id=127.0.0.1-cve-2019-00000 investigation_status=investigated

Context Example

{
    "CyCognito": {
        "Issue": {
            "action_status": "Success",
            "id": "127.0.0.1-cve-2019-00000",
            "investigation_status": "investigated"
        }
    }
}

Human Readable Output

Investigation Status has been successfully updated for 127.0.0.1-cve-2019-00000

Issue ID Investigation Status Action Status
127.0.0.1-cve-2019-00000 investigated Success

cycognito-issues-list


Retrieves the list of the issues that meet the specified filter criteria.

Base Command

cycognito-issues-list

Input

Argument Name Description Required
count The number of results to retrieve.

Maximum value is ‘1000’. Default is 50.
Optional
offset Sets the starting index for the returned results. By specifying offset, you retrieve a subset of records starting with the offset value.

Note: If a negative value is provided then the default value of 0 will be used. Default is 0.
Optional
search An Advanced Search parameter to query the response.

Note: Retrieves all the occurrences that are included in the string.
Optional
first_detected The date and time at which CyCognito first discovered and attributed the asset to the organization.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ

For example: 01 Mar 2021, 01 Feb 2021 04:45:33, 2022-04-17T14:05:44Z
Optional
last_detected The date and time at which CyCognito most recently attributed the asset to the organization.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ

For example: 01 Mar 2021, 01 Feb 2021 04:45:33, 2022-04-17T14:05:44Z
Optional
organizations Filters the issues according to the provided organizations. Supports comma-separated values. Optional
locations The geographical locations in which the issue is found. Supported values contain the three-letter ISO country code for the respective countries–e.g., IND, USA. Optional
issue_type Filters the records according to the issue type. Supports comma-separated values.

Supported values: “Abandoned Asset”, “Certificate Validity”, “Cryptographic Vulnerability”, “E-mail Security”, “Exposed Asset”, “Exposed Data”, “Exposed Dev Environment”, “Information Gathering”, “Phishing Threat”, “Potential Imposter Asset”, “Security Hygiene”, “Unmaintained Asset”, “Unsafe Authentication”, “Vulnerable Software”, “Weak Encryption”, “XSS”
Optional
sort_by The name of the field by which to sort the results. The response fields available for sorting the data are found in the following documentation:
https://docs.cycognito.com/reference/reference-getting-started.
Optional
sort_order Specifies whether to sort the results in either ascending or descending order.

Supported values: ‘asc’, ‘desc’
Optional
advanced_filter Applies a filter to the list of issues based on a JSON-specific query.

Format:
[{
“field”: “issue-type”,
“op”: “in”,
“values”: [
“Unsafe Authentication”,
“Vulnerable Software”
]
},
{
“op”: “not-in”,
“field”: “severity-score”,
“values”: [10, 9]
}]

Note: When using several filtering options (e.g., ‘Issue Type’ and ‘Advanced Filter’), advance_json parameters will take precedence over other parameters.
For a complete reference to the CyCognito fields and operations, please refer to the CyCognito API V0 documentation at
https://docs.cycognito.com/reference/query-issues.
Optional

Context Output

Path Type Description
CyCognito.Issue.id String Unique ID of the issue.
CyCognito.Issue.references Unknown Reference of the issue.
CyCognito.Issue.potential_threat String The threat that the issue might cause.
CyCognito.Issue.tags Unknown Tags of the issue.
CyCognito.Issue.organizations Unknown Organizations of the instance.
CyCognito.Issue.issue_id String Unique ID of the issue.
CyCognito.Issue.summary String A brief description that summarizes the issue.
CyCognito.Issue.resolved_at String Date/time when the issue was resolved.
CyCognito.Issue.investigation_status String Investigation status of the issue.
CyCognito.Issue.locations Unknown The geographic location of the instance.
CyCognito.Issue.detection_complexity String Measures the difficulty at which a vulnerable asset can be detected by a potential attacker.
CyCognito.Issue.title String Title of the issue.
CyCognito.Issue.exploitation_score Number Exploitation score of the issue.
CyCognito.Issue.issue_type String Type of the issue.
CyCognito.Issue.comment String Comment associated with the issue.
CyCognito.Issue.severity String Severity of the issue.
CyCognito.Issue.remediation_steps Unknown A list of actions that describe how to resolve the issue.
CyCognito.Issue.potential_impact Unknown A list of categories that describe what might happen if the issue is exploited.
CyCognito.Issue.exploitation_method String Exploitation method of the issue.
CyCognito.Issue.affected_asset String The unique ID of the asset with which the issue is associated.
CyCognito.Issue.severity_score Number The numeric severity of the issue is in the range of 0 (not severe) through 10 (severe).
CyCognito.Issue.last_detected Date The time at which the issue was last detected.
CyCognito.Issue.first_detected Date The time at which the issue was first detected.
CyCognito.Issue.issue_status String Status of the issue found.
CyCognito.Issue.evidence Unknown Provides a reason or proof of why the issue was indeed detected by CyCognito.

Command example

!cycognito-issues-list count=2

Context Example

{
    "CyCognito": {
        "Issue": [
            {
                "affected_asset": "ip/127.0.0.1",
                "detection_complexity": "Service Detection",
                "exploitation_method": "Metasploit",
                "exploitation_score": 3,
                "first_detected": "2022-03-31T03:39:22.568Z",
                "id": "issue/127.0.0.1-cve-2019-00000",
                "investigation_status": "investigating",
                "issue_id": "CVE-2019-00000",
                "issue_status": "new",
                "issue_type": "Vulnerable Software",
                "last_detected": "2022-03-31T03:39:22.568Z",
                "locations": [
                    "USA"
                ],
                "organizations": [
                    "ACME Ticketing",
                    "ACME Cleantech Solutions",
                    "Acme Holdings"
                ],
                "potential_impact": [
                    "Loss of integrity",
                    "Loss of confidentiality",
                    "Loss of availability",
                    "Data compromise",
                    "Network breach"
                ],
                "references": [],
                "remediation_steps": [
                    "Patch the NetScaler to the latest version.",
                    "If a patch is not feasible, perform \"work-around\" mitigations per Citrix's instructions."
                ],
                "severity": "critical",
                "severity_score": 10,
                "summary": "| The NetScaler has been confirmed to be vulnerable to CVE-2019-00000 (first made public in December 2019). | Due to improper handling of the path names, CVE-2019-00000 enables attackers to perform directory traversal and unauthenticated, remote arbitrary code execution via specially crafted HTTP requests. | As NetScalers serve as entry-points to organization networks, attackers can exploit this vulnerability to breach organization networks and leverage the NetScaler for further attacks. | This vulnerability has been exploited \"in the wild\" by unknown attackers.",
                "tags": [
                    "Pulse Secure"
                ],
                "potential_threat": "Remote Code Execution",
                "title": "CVE-2019-00000 (Unauthenticated Remote Directory Traversal & Code Execution)"
            },
            {
                "affected_asset": "ip/127.0.0.2",
                "comment": {
                    "content": "hello",
                    "last_update": "2022-06-14T06:42:20.952Z"
                },
                "detection_complexity": "Handshake",
                "exploitation_method": "Man-in-the-Middle",
                "exploitation_score": 4,
                "first_detected": "2022-03-20T18:48:33.528Z",
                "id": "issue/127.0.0.2-cyc-tls-hsts-dummy",
                "investigation_status": "investigating",
                "issue_id": "CYC-TLS-HSTS-DUMMY",
                "issue_status": "new",
                "issue_type": "Cryptographic Vulnerability",
                "last_detected": "2022-03-20T18:48:33.528Z",
                "locations": [
                    "USA"
                ],
                "organizations": [
                    "Acme Homes"
                ],
                "potential_impact": [
                    "Loss of integrity",
                    "Loss of confidentiality"
                ],
                "remediation_steps": [
                    "Enable an HSTS policy of at least 180 days."
                ],
                "severity": "critical",
                "severity_score": 10,
                "summary": "The server's HSTS policy is either too short or non-existent. | HTTP Strict Transport Security is an optional HTTP header that instructs browsers to only communicate with the server using HTTPS (and not HTTP) for a certain period of time, thus helping prevent \"SSL-stripping\" attacks.",
                "potential_threat": "Trust",
                "title": "Insecure HSTS"
            }
        ]
    }
}

Human Readable Output

Issues

ID Title Severity Score Severity Issue Type Issue Status Organizations Investigation Status First Detected Last Detected Locations
127.0.0.1-cve-2019-00000 Pulse Secure Arbitrary File Reading 10.0 critical Vulnerable Software new Acme Interior Design, Acme Corporation investigating 31 Mar 2022, 03:39 AM 31 Mar 2022, 03:39 AM India
127.0.0.2-cve-2019-00000 CVE-2019-00000 (Unauthenticated Remote Directory Traversal & Code Execution) 10.0 critical Vulnerable Software new ACME Ticketing, ACME Cleantech Solutions, Acme Holdings uninvestigated 31 Mar 2022, 03:39 AM 31 Mar 2022, 03:39 AM United States

cycognito-assets-list


Retrieves the list of assets that meet specified filter criteria.

Base Command

cycognito-assets-list

Input

Argument Name Description Required
asset_type The type of asset.

Supported values: ‘ip’, ‘domain’, ‘cert’, ‘webapp’, ‘iprange’
Required
count The number of results to be retrieved in a response.

Maximum value is ‘1000’. Default is 50.
Optional
offset Sets the starting index for the returned results. By specifying offset, you retrieve a subset of records starting with the offset value.

Note: If a negative value is provided then the default value of 0 will be used. Default is 0.
Optional
search An Advanced Search parameter to query the response.

Note: Retrieves all the occurrences that are included in the string.
Optional
status Filters the assets according to the selected status. Supports comma-separated values.

Supported values: ‘changed’, ‘new’, ‘normal’
Optional
organizations Filters the assets according to the provided organizations. Supports comma-separated values. Optional
security_grade Filters the assets according to the provided security ratings. Supports comma-separated values.

Supported values: ‘A’, ‘B’, ‘C’, ‘D’, ‘F’

Where:
A = Very strong
B = Strong
C = Less vulnerable
D = Vulnerable
F = Highly vulnerable
Optional
locations The geographical locations in which the asset is found. Supported values contain the three-letter ISO country code for the respective countries’e.g., IND, USA.
Locations are available only for IP, Domain, and Certificate asset types.
Optional
first_seen The date and time at which CyCognito first discovered and attributed the asset to the organization.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ

For example: 01 Mar 2021, 01 Feb 2021 04:45:33, 2022-04-17T14:05:44Z
Optional
last_seen The date and time at which CyCognito most recently attributed the asset to the organization.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ

For example: 01 Mar 2021, 01 Feb 2021 04:45:33, 2022-04-17T14:05:44Z
Optional
sort_by Specifies the field by which to sort.

Note: The response fields available for sorting the data are found in the following documentation:
https://docs.cycognito.com/reference/query-assets.
Optional
sort_order Specifies whether to sort the results in either ascending or descending order.

Supported values: ‘asc’, ‘desc’. Default is desc.
Optional
advanced_filter Applies a filter to the list of assets based on a JSON-specific query.

Format:
[{
“field”: “status”,
“op”: “in”,
“values”: [
“new”,
“changed”
]
},
{
“op”: “not-in”,
“field”: “security-rating”,
“values”: [“A”]
}]

Note: For a complete reference to the CyCognito fields and operations, please refer to the CyCognito API V0 documentation at https://docs.cycognito.com/reference/query-assets.
Optional

Context Output

Path Type Description
CyCognito.Asset.alive Boolean Whether the port is alive or not.
CyCognito.Asset.comment String Comments related to the asset.
CyCognito.Asset.id String Unique identifier of the asset.
Note: Asset ID is derived from the asset_id input field.
CyCognito.Asset.type String Type of the asset.
CyCognito.Asset.business_units Unknown Business units of the asset.
CyCognito.Asset.signature String The identifier of the certificate.
CyCognito.Asset.closed_ports.status String Status of the closed ports object associated with the asset.
CyCognito.Asset.closed_ports.port Number Port of the closed ports object associated with the asset.
CyCognito.Asset.closed_ports.protocol String Protocol associated with the asset.
CyCognito.Asset.created Date Date and time at which the asset was created.
CyCognito.Asset.domain String Domain name of the asset.
CyCognito.Asset.domains Unknown Domain of the asset.
CyCognito.Asset.domain_names Unknown List of domain names associated with the asset.
CyCognito.Asset.expiration Date Date and time at which the asset is expired.
CyCognito.Asset.first_seen Date Time at which an asset was first discovered and attributed to the organization.
CyCognito.Asset.hosting_type String Hosting type of the asset.
CyCognito.Asset.investigation_status String Investigation status of the asset.
CyCognito.Asset.ip String IP of the asset.
CyCognito.Asset.ip_addresses Unknown List of IP associated with the asset.
CyCognito.Asset.issuer_alt_names Unknown List of alternate issuer names.
CyCognito.Asset.issuer_common_name String Common name of the Issuer.
CyCognito.Asset.issuer_country String Country of Issuer.
CyCognito.Asset.issuer_locality String Locality of issuer.
CyCognito.Asset.issuer_organization String Organization of the issuer.
CyCognito.Asset.issuer_organization_unit String The organization unit of the issuer.
CyCognito.Asset.issuer_state String State of the issuer.
CyCognito.Asset.issues_count Number Count of issues associated with the asset.
CyCognito.Asset.last_seen Date Time at which an asset was discovered and attributed to the organization.
CyCognito.Asset.locations Unknown List of geographic locations with which an asset might be associated.
CyCognito.Asset.open_ports.status String Status of the open ports object associated with the asset.
CyCognito.Asset.open_ports.port Number Port of the open ports object associated with the asset.
CyCognito.Asset.open_ports.protocol String Protocol associated with the asset.
CyCognito.Asset.organizations Unknown List of organizations associated with the asset.
CyCognito.Asset.status String Last status of the asset.
CyCognito.Asset.security_grade String Security rating of the asset based on the number and severity of the associated issues.
CyCognito.Asset.severe_issues Number The number of severe issues associated with the asset.
CyCognito.Asset.signature_algorithm String Signature algorithm associated with the asset.
CyCognito.Asset.sub_domains Unknown List of subdomains associated with the asset.
CyCognito.Asset.subject_alt_names Unknown List of alternate subject names.
CyCognito.Asset.subject_common_name String Common name of the subject.
CyCognito.Asset.subject_country String Subject’s country.
CyCognito.Asset.subject_locality String Locality of the subject.
CyCognito.Asset.subject_organization String Subject’s organization.
CyCognito.Asset.subject_organization_unit String The organization unit of the subject.
CyCognito.Asset.subject_state String State of the subject.
CyCognito.Asset.tags Unknown List of tags associated with the asset.
CyCognito.Asset.dynamically_resolved String Whether the asset has a rotating IP address.
CyCognito.Asset.discoverability String Quantifies an asset’s level of exposure.

Command example

!cycognito-assets-list asset_type=ip count=2

Context Example

{
    "CyCognito": {
        "Asset": [
            {
                "alive": true,
                "closed_ports": [
                    {
                        "port": 6001,
                        "protocol": "tcp",
                        "status": "closed"
                    },
                    {
                        "port": 47808,
                        "protocol": "tcp",
                        "status": "closed"
                    },
                    {
                        "port": 5900,
                        "protocol": "tcp",
                        "status": "closed"
                    },
                    {
                        "port": 111,
                        "protocol": "tcp",
                        "status": "closed"
                    },
                    {
                        "port": 9200,
                        "protocol": "tcp",
                        "status": "closed"
                    },
                    {
                        "port": 11211,
                        "protocol": "tcp",
                        "status": "closed"
                    },
                    {
                        "port": 1723,
                        "protocol": "tcp",
                        "status": "closed"
                    }
                ],
                "dynamically_resolved": "no",
                "first_seen": "2022-03-23T12:36:17.808Z",
                "hosting_type": "owned",
                "id": "ip/127.0.0.1",
                "investigation_status": "investigated",
                "ip": "127.0.0.1",
                "issues_count": 1,
                "last_seen": "2022-03-31T03:39:22.568Z",
                "locations": [
                    "MYS"
                ],
                "open_ports": [
                    {
                        "port": 465,
                        "protocol": "tcp",
                        "status": "open"
                    },
                    {
                        "port": 993,
                        "protocol": "tcp",
                        "status": "open"
                    },
                    {
                        "port": 80,
                        "protocol": "tcp",
                        "status": "open"
                    },
                    {
                        "port": 53,
                        "protocol": "udp",
                        "status": "open"
                    }
                ],
                "organizations": [
                    "Acme Corporation"
                ],
                "security_grade": "F",
                "severe_issues": 1,
                "status": "new",
                "tags": [
                    "Vulnerable Software",
                    "Red Hat"
                ],
                "type": "ip"
            },
            {
                "alive": true,
                "closed_ports": [
                    {
                        "port": 3389,
                        "protocol": "tcp",
                        "status": "closed"
                    },
                    {
                        "port": 8888,
                        "protocol": "tcp",
                        "status": "closed"
                    },
                    {
                        "port": 110,
                        "protocol": "tcp",
                        "status": "closed"
                    },
                    {
                        "port": 548,
                        "protocol": "tcp",
                        "status": "closed"
                    },
                    {
                        "port": 23,
                        "protocol": "tcp",
                        "status": "closed"
                    },
                    {
                        "port": 11211,
                        "protocol": "tcp",
                        "status": "closed"
                    },
                    {
                        "port": 1723,
                        "protocol": "tcp",
                        "status": "closed"
                    }
                ],
                "dynamically_resolved": "no",
                "first_seen": "2022-03-23T12:27:04.354Z",
                "hosting_type": "owned",
                "id": "ip/127.0.0.2",
                "investigation_status": "investigated",
                "ip": "127.0.0.2",
                "issues_count": 2,
                "last_seen": "2022-03-31T03:39:22.568Z",
                "locations": [
                    "MYS"
                ],
                "open_ports": [
                    {
                        "port": 587,
                        "protocol": "tcp",
                        "status": "open"
                    },
                    {
                        "port": 21,
                        "protocol": "tcp",
                        "status": "open"
                    },
                    {
                        "port": 22,
                        "protocol": "tcp",
                        "status": "open"
                    },
                    {
                        "port": 465,
                        "protocol": "tcp",
                        "status": "open"
                    }
                ],
                "organizations": [
                    "Acme Corporation"
                ],
                "security_grade": "F",
                "severe_issues": 1,
                "status": "new",
                "tags": [
                    "Block Cipher"
                ],
                "type": "ip"
            }
        ]
    }
}

Human Readable Output

Asset List

Assets Type: IP

Asset ID Security Grade Status Organizations Investigation Status Severe Issues First Seen Last Seen Hosting Type Locations
127.0.0.1 F new Acme Corporation investigated 1 23 Mar 2022, 12:27 PM 31 Mar 2022, 03:39 AM owned Malaysia
127.0.0.2 F new Acme Corporation investigated 1 23 Mar 2022, 12:27 PM 31 Mar 2022, 03:39 AM owned Malaysia

Configuration parameters

  • api_key — API Key (required)
  • region — Region
  • other_region — Other Region
  • incidentType — Incident type
  • mirror_direction — Incident Mirroring Direction
  • isFetch — Fetch incidents
  • first_fetch — First Fetch Time
  • max_fetch — Max Fetch
  • issue_type — Issue Type
  • locations — Locations
  • severity_filter — Severity
  • investigation_status — Investigation Status
  • advanced_filter — Advanced Filter
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • incidentFetchInterval — Incidents Fetch Interval

Commands (6)

  • cycognito-asset-get

    Retrieves information about a specific asset according to the specified asset type and asset ID.

  • cycognito-asset-investigation-status-change

    Modifies the investigation status of the specified asset.

  • cycognito-assets-list

    Retrieves the list of assets that meet specified filter criteria.

  • cycognito-issue-get

    Retrieves information about an issue associated with a particular instance based on the provided issue instance ID.

  • cycognito-issue-investigation-status-change

    Modifies the investigation status of the specified issue.

  • cycognito-issues-list

    Retrieves the list of the issues that meet the specified filter criteria.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401

"""Main Integration file for CyCognito."""

import pycountry
import urllib3
from requests import Response

from CommonServerUserPython import *  # noqa

# Disable insecure warnings
urllib3.disable_warnings()

""" CONSTANTS """

DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"  # ISO8601 format with UTC, default in XSOAR
API_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
HR_DATE_FORMAT = "%d %b %Y, %I:%M %p"
BASE_URL = "https://api.{}platform.cycognito.com/v1"
MAX_PAGE_SIZE = 1000
DEFAULT_COUNT = 50
DEFAULT_SORT_ORDER = "desc"
DEFAULT_OFFSET = 0
VALID_INVESTIGATION_STATUS = ["uninvestigated", "investigating", "investigated"]
VALID_ASSET_TYPES = ["ip", "domain", "cert", "webapp", "iprange"]
VALID_SEVERITY = ["low", "medium", "high", "critical"]
VALID_ISSUE_TYPES = [
    "Abandoned Asset",
    "Certificate Validity",
    "Cryptographic Vulnerability",
    "E-mail Security",
    "Exposed Asset",
    "Exposed Data",
    "Exposed Dev Environment",
    "Information Gathering",
    "Phishing Threat",
    "Potential Imposter Asset",
    "Security Hygiene",
    "Unmaintained Asset",
    "Unsafe Authentication",
    "Vulnerable Software",
    "Weak Encryption",
    "XSS",
]
VALID_OPERATORS = [
    "is",
    "not",
    "in",
    "not-in",
    "key-of",
    "not-key-of",
    "between",
    "not-between",
    "within-range",
    "not-within-range",
    "contains",
]
AVAILABLE_SECURITY_GRADE = ["a", "b", "c", "d", "f"]
AVAILABLE_STATUS_TYPES = ["new", "changed", "normal"]
MIRROR_DIRECTION = {"None": None, "Incoming": "In", "Outgoing": "Out", "Incoming and Outgoing": "Both"}
ERRORS = {
    "NO_RECORDS": "No Record Found.",
    "INVALID_SINGLE_SELECT_PARAM": "{} is an invalid value for {}. Possible values are: {}.",
    "INVALID_MULTI_SELECT_PARAM": "Invalid value for {}. Possible comma separated values are {}.",
    "INVALID_PAGE_SIZE": "{} is invalid value for count. Value must be in 1 to 1000.",
    "INVALID_ADVANCED_FILTER": "{} is in invalid format for advanced_filter. "
    "Please find the accepted format in argument's help text.",
    "INVALID_ARGUMENT": "Invalid argument(s) provided. Please find accepted formats in argument's help text.",
    "INVALID_REQUIRED_PARAMETER": "{} is a required field. Please provide correct input.",
    "INVALID_OPERATOR": "{} is invalid value for operator. Possible Values of operators are: {}.",
    "INVALID_COUNTRY_ERROR": "{} is an invalid country name.",
}
INVESTIGATION_STATUS_HR = "Investigation Status"
AFFECTED_ASSET_HR = "Affected Asset"
FIRST_DETECTED_HR = "First Detected"
LAST_DETECTED_HR = "Last Detected"
ISSUE_TYPE_HR = "Issue Type"
ISSUE_STATUS_HR = "Issue Status"
ASSET_ID_HR = "Asset ID"
ASSET_TYPE_HR = "Asset Type"
HOSTING_TYPE_HR = "Hosting Type"
ASSET_OUTPUT_PREFIX = "CyCognito.Asset"
ISSUE_OUTPUT_PREFIX = "CyCognito.Issue"
REGIONS = {"General": "", "US": "us-"}

""" CLIENT CLASS """


class CyCognitoClient(BaseClient):
    """CyCognitoClient class to interact with the service API."""

    def __init__(self, params: dict, verify, proxy):
        """
        :type params: ``dict``
        :param params: Integration parameters.

        :type verify: ``bool``
        :param verify: Verify SSL certificate.

        :type proxy: ``bool``
        :param proxy: Use proxy.
        """
        api_key = params.pop("api_key")
        headers = {"Authorization": api_key}

        region = params.get("region", "")
        other_region = params.get("other_region", "").strip()
        region = self.get_region(region=region, other_region=other_region)

        super().__init__(base_url=BASE_URL.format(region), verify=verify, proxy=proxy, ok_codes=(404, 200, 400), headers=headers)

    def get_region(self, region: str, other_region: str) -> str:
        """Get region to use.

        :type region: ``str``
        :param region: Region to use.

        :type other_region: ``str``
        :param other_region: Other region to use.

        :rtype: ``str``
        :return: Region to use.
        """
        if region:
            if other_region and other_region[-1] != "-":
                other_region = f"{other_region}-"
            return REGIONS[region] if region.lower() != "other" else other_region
        else:
            return REGIONS["General"]

    def get_issue(self, issue_instance_id: str) -> Response:
        """Get an issue from Cycognito.

        :type issue_instance_id: ``str``
        :param issue_instance_id: Unique issue ID of the instance.

        :rtype: ``Response``
        :return: Response of API.
        """
        return self._http_request(method="GET", url_suffix=f"/issues/issue/{issue_instance_id}", resp_type="response")

    def get_asset(self, asset_type: str, asset_id: str):
        """Get an asset from Cycognito.

        :type asset_type: str
        :param asset_type: Asset type to use

        :type asset_id: str
        :param asset_id: ID of the asset

        :return: Asset details.
        """
        return self._http_request(method="GET", url_suffix=f"/assets/{asset_type}/{asset_id}", resp_type="response")

    def change_issue_investigation_status(self, params: Dict[str, Any]) -> Response:
        """Change investigation status of an issue.

        :type params: ``Dict[str, Any]``
        :param params: Params passed by user.

        :rtype: ``Response``
        :return: Response of API.
        """
        json_data = {"investigation_status": params["investigation_status"]}
        return self._http_request(
            method="PUT",
            url_suffix=f"/issues/issue/{params['issue_instance_id']}/investigation-status",
            json_data=json_data,
            resp_type="response",
        )

    def change_asset_investigation_status(self, params: Dict[str, Any]):
        """Change investigation status of an asset.

        :type params: ``Dict[str, Any]``
        :param params: Params passed by user.

        :rtype: ``Response``
        :return: Response of API.
        """
        return self._http_request(
            method="PUT",
            url_suffix=f"/assets/{params['asset_type']}/{params['asset_id']}/investigation-status",
            resp_type="response",
            json_data={"investigation_status": params["investigation_status"]},
        )

    def list_issues(
        self,
        filters: List,
        count: int = None,
        offset: int = None,
        sort_order: str = None,
        sort_by: str = None,
        search: str = None,
    ) -> Response:
        """List issues from Cycognito.

        :type count: ``int``
        :param count: Number of the issues to fetch.

        :type offset: ``int``
        :param offset: Number of pages to skip.

        :type sort_order: ``str``
        :param sort_order: Order in which to sort result.

        :type sort_by: ``str``
        :param sort_by: field on which to sort result.

        :type search: ``str``
        :param search: Search string to perform plain text search.

        :type filters: ``List``
        :param filters: Body parameters for filter result.

        :rtype: ``Response``
        :return: Response of API.
        """
        query_params = {"count": count, "q": search, "offset": offset, "sort-by": sort_by, "sort-order": sort_order}
        remove_nulls_from_dictionary(query_params)
        return self._http_request(
            method="POST", url_suffix="/issues", params=query_params, json_data=filters, resp_type="response"
        )

    def list_assets(
        self,
        filters: List,
        asset_type: str,
        count: int = None,
        search: str = None,
        offset: int = None,
        sort_by: str = None,
        sort_order: str = None,
    ) -> Any:
        """List assets from Cycognito.

        :type asset_type: ``str``
        :param asset_type: Type of the asset.

        :type count: ``int``
        :param count: Number of the issues to fetch.

        :type offset: ``int``
        :param offset: Number of pages to skip.

        :type sort_order: ``str``
        :param sort_order: Order in which to sort result.

        :type sort_by: ``str``
        :param sort_by: field on which to sort result.

        :type search: ``str``
        :param search: Search string to perform plain text search.

        :type filters: ``List``
        :param filters: Body parameters for filter result.

        :rtype: ``Response``
        :return: Response of API.
        """
        query_params = {"count": count, "q": search, "offset": offset, "sort-by": sort_by, "sort-order": sort_order}
        remove_nulls_from_dictionary(query_params)
        return self._http_request(
            method="POST", url_suffix=f"assets/{asset_type}", params=query_params, json_data=filters, resp_type="response"
        )


""" HELPER FUNCTIONS """


def validate_response(response: Response) -> Dict:
    """Validate Response Status Code.

    :type response: ``Response``
    :param response: Response from API.

    :rtype: ``Dict``
    :return: Response JSON.
    """
    if response.status_code == 404:
        raise DemistoException(ERRORS["NO_RECORDS"])
    if response.status_code == 400:
        raise DemistoException(ERRORS["INVALID_ARGUMENT"])
    return remove_empty_elements(response.json())


def validate_arguments_for_asset_get(asset_type: str, asset_id: str):
    """Validate arguments for cycognito-asset-get command.

    :type asset_type: ``str``
    :param asset_type: Type of the asset.

    :type asset_id: ``str``
    :param asset_id: ID of the asset.
    """
    if not asset_type:
        raise ValueError(ERRORS["INVALID_REQUIRED_PARAMETER"].format("asset_type"))

    if not asset_id:
        raise ValueError(ERRORS["INVALID_REQUIRED_PARAMETER"].format("asset_id"))

    if asset_type not in VALID_ASSET_TYPES:
        raise ValueError(ERRORS["INVALID_SINGLE_SELECT_PARAM"].format(asset_type, "asset_type", VALID_ASSET_TYPES))


def trim_spaces_from_args(args: Dict) -> Dict:
    """Trim spaces from values of the args dict.

    :type args: ``Dict``
    :param args: Dict to trim spaces from.

    :rtype: ``Dict``
    :return: Arguments after trim spaces.
    """
    for key, val in args.items():
        if isinstance(val, str):
            args[key] = val.strip()

    return args


def convert_country_names_to_alpha_3_codes(locations: List[str]):
    """Convert location into alpha-3 codes.

    :type locations: List[str]
    :param locations: Locations to be converted into alpha-3 codes.

    :rtype: List[str]
    :returns: List of locations converted into alpha-3 codes.
    """
    if not locations:
        return []

    converted_locations = []
    for location in filter(None, locations):
        try:
            converted_locations.append(pycountry.countries.search_fuzzy(location)[0].alpha_3)  # type: ignore[attr-defined]
        except LookupError:
            raise ValueError(ERRORS["INVALID_COUNTRY_ERROR"].format(location))

    return converted_locations


def convert_alpha_3_codes_to_country_names(locations: List[str]):
    """Convert alpha-3 code location to country name.

    :type locations: List[str]
    :param locations: Locations to be converted into country name

    :rtype: List[str]
    :returns: List of locations converted into country name.
    """
    if not locations:
        return []

    converted_locations = []
    for location in filter(None, locations):
        try:
            converted_locations.append(pycountry.countries.search_fuzzy(location)[0].name)  # type: ignore[attr-defined]
        except LookupError:
            raise ValueError(ERRORS["INVALID_COUNTRY_ERROR"].format(location))

    return converted_locations


def validate_params_for_issue_investigation_status_change(args: Dict[str, Any]) -> Dict[str, Any]:
    """Validate arguments for cycognito-issue-investigation-status-change command.

    :type args: ``Dict[str, Any]``
    :param args: Arguments provided by user.

    :rtype: ``Dict[str, Any]``
    :return: Validated argument.
    """
    issue_instance_id = args.get("issue_instance_id", "").lower()
    investigation_status = args.get("investigation_status", "").lower()
    if investigation_status not in VALID_INVESTIGATION_STATUS:
        raise ValueError(
            ERRORS["INVALID_SINGLE_SELECT_PARAM"].format(investigation_status, "investigation_status", VALID_INVESTIGATION_STATUS)
        )

    return assign_params(issue_instance_id=issue_instance_id, investigation_status=investigation_status)


def validate_params_for_asset_investigation_status_change(args: Dict[str, Any]) -> Dict[str, Any]:
    """Validate arguments for cycognito-asset-investigation-status-change command.

    :type args: ``Dict[str, Any]``
    :param args: Argument provided by user.

    :rtype: ``Dict[str, Any]``
    :return: Validated argument.
    """
    asset_id = args.get("asset_id", "").lower()
    asset_type = args.get("asset_type", "").lower()
    investigation_status = args.get("investigation_status", "").lower()
    if investigation_status not in VALID_INVESTIGATION_STATUS:
        raise ValueError(
            ERRORS["INVALID_SINGLE_SELECT_PARAM"].format(investigation_status, "investigation_status", VALID_INVESTIGATION_STATUS)
        )
    if not asset_type:
        raise ValueError(ERRORS["INVALID_REQUIRED_PARAMETER"].format("asset_type"))

    if not asset_id:
        raise ValueError(ERRORS["INVALID_REQUIRED_PARAMETER"].format("asset_id"))

    if asset_type not in VALID_ASSET_TYPES:
        raise ValueError(ERRORS["INVALID_SINGLE_SELECT_PARAM"].format(asset_type, "asset_type", VALID_ASSET_TYPES))

    return assign_params(asset_type=asset_type, asset_id=asset_id, investigation_status=investigation_status)


def prepare_hr_for_issue_get(response: Dict) -> str:
    """Prepare Human Readable output for cycognito-issue-get command.

    :type response: ``Dict``
    :param response: Response from the API.

    :rtype: ``str``
    :return: Human readable output.
    """
    link = f"https://platform.cycognito.com/issues/issue/{response.get('id', '').replace('issue/', '')}/info"  # noqa: E231
    hr_output = [
        {
            "Title": response.get("title"),
            AFFECTED_ASSET_HR: response.get("affected_asset"),
            "Detection Complexity": response.get("detection_complexity"),
            INVESTIGATION_STATUS_HR: response.get("investigation_status"),
            "Exploitation Score": response.get("exploitation_score"),
            FIRST_DETECTED_HR: None
            if not response.get("first_detected")
            else arg_to_datetime(response.get("first_detected")).strftime(HR_DATE_FORMAT),  # type: ignore
            LAST_DETECTED_HR: None
            if not response.get("last_detected")
            else arg_to_datetime(response.get("last_detected")).strftime(HR_DATE_FORMAT),  # type: ignore
            "Organizations": ", ".join(response.get("organizations", [])),
            "Locations": ", ".join(convert_alpha_3_codes_to_country_names(response.get("locations", []))),
            "Potential Threat": response.get("potential_threat"),
            "Severity": response.get("severity"),
            ISSUE_TYPE_HR: response.get("issue_type"),
            ISSUE_STATUS_HR: response.get("issue_status"),
            "Remediation Steps": " ".join(response.get("remediation_steps", [])),
            "Potential Impact": ", ".join(response.get("potential_impact", [])),
            "Tags": ", ".join(response.get("tags", [])),
            "References": response.get("references"),
            "Summary": None if not response.get("summary") else response.get("summary").replace("| ", ""),  # type: ignore
            "Link to Platform": f"[Click Here]({link})",
        }
    ]

    headers = [
        "Title",
        AFFECTED_ASSET_HR,
        "Detection Complexity",
        INVESTIGATION_STATUS_HR,
        "Exploitation Score",
        FIRST_DETECTED_HR,
        LAST_DETECTED_HR,
        "Organizations",
        "Locations",
        "Potential Threat",
        "Severity",
        ISSUE_TYPE_HR,
        ISSUE_STATUS_HR,
        "Remediation Steps",
        "Potential Impact",
        "Tags",
        "References",
        "Summary",
        "Link to Platform",
    ]

    heading = f"Issue detail: \n#### ID: {response['id'].replace('issue/', '')}"

    return tableToMarkdown(heading, hr_output, headers=headers, removeNull=True)


def prepare_hr_for_asset_get(response) -> str:
    """Prepare Human Readable output for asset-get command.

    :type response: ``Dict``
    :param response: Response from the API.

    :rtype: ``str``
    :return: Human readable output.
    """
    first_seen, last_seen, expiration = "", "", ""
    if response.get("first_seen"):
        first_seen = arg_to_datetime(response.get("first_seen")).strftime(HR_DATE_FORMAT)  # type: ignore

    if response.get("last_seen"):
        last_seen = arg_to_datetime(response.get("last_seen")).strftime(HR_DATE_FORMAT)  # type: ignore

    if response.get("expiration"):
        expiration = arg_to_datetime(response.get("expiration")).strftime(HR_DATE_FORMAT)  # type: ignore

    hr_output_asset_details = [
        {
            ASSET_ID_HR: response.get("id").split("/", 1)[-1],
            ASSET_TYPE_HR: response.get("type"),
            HOSTING_TYPE_HR: response.get("hosting_type"),
            "Alive": response.get("alive"),
            "Locations": ", ".join(convert_alpha_3_codes_to_country_names(response.get("locations", []))),
            "IP Addresses": response.get("ip_addresses"),
            "First Seen": first_seen,
            "Last Seen": last_seen,
            "Status": response.get("status"),
            "Security Grade": response.get("security_grade"),
            "Tags": response.get("tags"),
            "Sub Domains": response.get("sub_domains"),
            "Domain Names": response.get("domain_names"),
            "Organizations": ", ".join(response.get("organizations", [])),
            INVESTIGATION_STATUS_HR: response.get("investigation_status"),
            "Severe Issues": response.get("severe_issues"),
            "Open Ports": ", ".join(
                [
                    f"{port_info.get('protocol', '').upper()} - {port_info.get('port')}"
                    for port_info in response.get("open_ports", [])
                ]
            ),
        }
    ]

    hr_output_certificate_details = [
        {
            "Certificate Signature": response.get("id", "").split("/", 1)[-1],
            "Expiration Time": expiration,
            "Subject Organization Unit": response.get("subject_organization_unit"),
            "Subject Common Name": response.get("subject_common_name"),
            "Subject Locality": response.get("subject_locality"),
            "Subject Organization": response.get("subject_organization"),
            "Subject Country": response.get("subject_country"),
            "Issuer Organization Unit": response.get("issuer_organization_unit"),
            "Issuer Common Name": response.get("issuer_common_name"),
            "Issuer Locality": response.get("issuer_common_name"),
            "Issuer Organization": response.get("issuer_organization"),
            "Issuer Country": response.get("issuer_country"),
        }
    ]

    headers_asset = [
        ASSET_ID_HR,
        ASSET_TYPE_HR,
        HOSTING_TYPE_HR,
        "Alive",
        "Locations",
        "IP Addresses",
        "First Seen",
        "Last Seen",
        "Status",
        "Security Grade",
        "Tags",
        "Sub Domains",
        "Domain Names",
        "Organizations",
        "Severe Issues",
        INVESTIGATION_STATUS_HR,
        "Open Ports",
    ]

    headers_certificate = [
        "Certificate Signature",
        "Expiration Time",
        "Subject Organization Unit",
        "Subject Common Name",
        "Subject Locality",
        "Subject Organization",
        "Subject Country",
        "Issuer Organization Unit",
        "Issuer Common Name",
        "issuer Locality",
        "Issuer Organization",
        "Issuer Country",
    ]

    if response.get("type") == "cert":
        return (
            tableToMarkdown("Asset Details:", hr_output_asset_details, headers=headers_asset, removeNull=True)
            + "\n"
            + tableToMarkdown("Certificate Details:", hr_output_certificate_details, headers=headers_certificate, removeNull=True)
        )
    else:
        return tableToMarkdown("Asset Details:", hr_output_asset_details, headers=headers_asset, removeNull=True)


def prepare_context_for_issue_investigation_status_change(
    valid_response: Dict[str, Any], params: Dict[str, Any]
) -> Dict[str, Any]:
    """Prepare Context output for cycognito-issue-investigation-status-change command.

    :type valid_response: ``Dict[str, Any]``
    :param valid_response: Validated Response.

    :type params: ``Dict[str, Any]``
    :param params: Validated argument.

    :rtype: ``Dict[str, Any]``
    :return: Context Output.
    """
    response = {"id": f"issue/{params['issue_instance_id']}"}
    if valid_response.get("updated"):
        response["investigation_status"] = params["investigation_status"]
        response["action_status"] = "Success"
    else:
        response["action_status"] = "Failure"

    return response


def prepare_context_for_asset_investigation_status(valid_response: Dict[str, Any], params: Dict[str, Any]) -> Dict[str, Any]:
    """Prepare Context output for cycognito-asset-investigation-status-change command.

    :type valid_response: ``Dict[str, Any]``
    :param valid_response: Validated Response.

    :type params: ``Dict[str, Any]``
    :param params: Validated argument.

    :rtype: ``Dict[str, Any]``
    :return: Context Output.
    """
    response = {"id": f"{params['asset_type']}/{params['asset_id']}"}
    if valid_response.get("updated"):
        response["asset_type"] = params["asset_type"]
        response["investigation_status"] = params["investigation_status"]
        response["action_status"] = "Success"
    else:
        response["action_status"] = "Failure"

    return response


def prepare_hr_for_issue_investigation_status_change(valid_response: Dict[str, Any], params: Dict[str, Any]) -> str:
    """Prepare Human Readable output for cycognito-issue-investigation-status-change command.

    :type valid_response: ``Dict[str, Any]``
    :param valid_response: Validated Response.

    :type params: ``Dict[str, Any]``
    :param params: Validated argument.

    :rtype: ``str``
    :return: Human readable output.
    """
    if valid_response.get("updated"):
        action_status = "Success"
        heading = f"Investigation status for {params.get('issue_instance_id')} has been successfully updated."
    else:
        action_status = "Failure"
        heading = f"Investigation status for {params.get('issue_instance_id')} has failed to update."

    hr_output = [
        {
            "Issue ID": params.get("issue_instance_id"),
            INVESTIGATION_STATUS_HR: params.get("investigation_status", "").title(),
            "Action Status": action_status,
        }
    ]

    headers = ["Issue ID", INVESTIGATION_STATUS_HR, "Action Status"]

    return tableToMarkdown(heading, hr_output, headers=headers, removeNull=True)


def prepare_hr_for_asset_investigation_status(valid_response: Dict[str, Any], params: Dict[str, Any]) -> str:
    """Prepare Human Readable output for asset-change-investigation-status command.

    :type valid_response: ``Dict[str, Any]``
    :param valid_response: Validated Response.

    :type params: ``Dict[str, Any]``
    :param params: Validated argument.

    :rtype: ``str``
    :return: Human readable output.
    """
    if valid_response.get("updated"):
        action_status = "Success"
        heading = f"Investigation status for {params.get('asset_id')} has been successfully updated."
    else:
        action_status = "Failure"
        heading = f"Investigation status for {params.get('asset_id')} has failed to update."

    hr_output = [
        {
            ASSET_TYPE_HR: params.get("asset_type"),
            ASSET_ID_HR: params.get("asset_id"),
            "Investigation Status": params.get("investigation_status", "").title(),
            "Action Status": action_status,
        }
    ]

    headers = [ASSET_TYPE_HR, ASSET_ID_HR, INVESTIGATION_STATUS_HR, "Action Status"]

    return tableToMarkdown(heading, hr_output, headers=headers, removeNull=True)


def validate_params_for_list_issues(
    count: Optional[int] = None,
    sort_order: str = None,
    severity: List = None,
    investigation_status: str = None,
    issue_type: List = None,
):
    """Validate arguments for cycognito-issues-list command.

    :type count: ``Optional[int]``
    :param count: Number of the issues to fetch.

    :type sort_order: ``str``
    :param sort_order: Order in which to sort result.

    :type severity: ``List``
    :param severity: Severity of issues.

    :type investigation_status: ``str``
    :param investigation_status: Investigation status of the issue.

    :type issue_type: ``List``
    :param issue_type: Type of the issue.
    """
    if count and (count < 1 or count > MAX_PAGE_SIZE):
        raise ValueError(ERRORS["INVALID_PAGE_SIZE"].format(count))

    if sort_order and sort_order not in ["asc", "desc"]:
        raise ValueError(ERRORS["INVALID_SINGLE_SELECT_PARAM"].format(sort_order, "sort_order", ["asc", "desc"]))

    if severity and not set(severity).issubset(VALID_SEVERITY):  # type: ignore
        raise ValueError(ERRORS["INVALID_MULTI_SELECT_PARAM"].format("Severity", VALID_SEVERITY))

    if investigation_status and investigation_status not in VALID_INVESTIGATION_STATUS:
        raise ValueError(
            ERRORS["INVALID_SINGLE_SELECT_PARAM"].format(
                investigation_status, INVESTIGATION_STATUS_HR, VALID_INVESTIGATION_STATUS
            )
        )

    if issue_type and not set(issue_type).issubset(VALID_ISSUE_TYPES):  # type: ignore
        raise ValueError(ERRORS["INVALID_MULTI_SELECT_PARAM"].format("Issue Type", VALID_ISSUE_TYPES))


def validate_params_for_list_assets(
    asset_type: str = None,
    count: Optional[int] = None,
    sort_order: str = None,
    security_grade: List[str] = None,
    status: List[str] = None,
) -> None:
    """Validate arguments for cycognito-assets-list command.

    :type asset_type: ``str``
    :param asset_type: Type of the asset.

    :type count: ``Optional[int]``
    :param count: Number of the assets to fetch.

    :type sort_order: ``str``
    :param sort_order: Order in which to sort result.

    :type security_grade: ``str``
    :param count: Security rating of the asset.

    :type status: ``str``
    :param status: status of the asset.
    """
    if not asset_type:
        raise ValueError(ERRORS["INVALID_REQUIRED_PARAMETER"].format("asset_type"))

    if asset_type not in VALID_ASSET_TYPES:
        raise ValueError(ERRORS["INVALID_SINGLE_SELECT_PARAM"].format(asset_type, "asset_type", VALID_ASSET_TYPES))

    if count and (count < 1 or count > MAX_PAGE_SIZE):
        raise ValueError(ERRORS["INVALID_PAGE_SIZE"].format(count))

    if sort_order and sort_order not in ["asc", "desc"]:
        raise ValueError(ERRORS["INVALID_SINGLE_SELECT_PARAM"].format(sort_order, "sort_order", ["asc", "desc"]))

    if not set(security_grade).issubset(AVAILABLE_SECURITY_GRADE):  # type: ignore
        raise ValueError(
            ERRORS["INVALID_MULTI_SELECT_PARAM"].format("security_grade", [x.upper() for x in AVAILABLE_SECURITY_GRADE])
        )

    if not set(status).issubset(AVAILABLE_STATUS_TYPES):  # type: ignore
        raise ValueError(ERRORS["INVALID_MULTI_SELECT_PARAM"].format("status", AVAILABLE_STATUS_TYPES))


def prepare_body_filters_for_list_issues(
    advanced_filter: List,
    organizations: List[str] = None,
    locations: List[str] = None,
    first_detected: str = None,
    last_detected: str = None,
    issue_type: List[str] = None,
) -> List[Dict]:
    """Prepare body params for cycognito-issues-list command.

    :type organizations: ``List[str]``
    :param organizations: To retrieve issues related to specific organizations.

    :type locations: ``List[str]``
    :param locations: To retrieve issues related to specific locations.

    :type first_detected: ``str``
    :param first_detected: To retrieve issues from a specific first detected time.

    :type last_detected: ``str``
    :param last_detected: To retrieve issues from a specific last detected time.

    :type issue_type: ``List[str]``
    :param issue_type: To retrieve issues of a specific issue type.

    :type advanced_filter: ``List``
    :param advanced_filter: To filter data by any response field.

    :rtype: ``List[Dict]``
    :return: Body parameters for filter result.
    """
    filter_fields = []

    for filter_dict in advanced_filter:
        filter_fields.append(filter_dict.get("field"))

        if filter_dict.get("op") not in VALID_OPERATORS:
            raise ValueError(ERRORS["INVALID_OPERATOR"].format(filter_dict.get("op"), VALID_OPERATORS))

    fields_with_in_operator = ["organizations", "locations", "issue_type"]
    values_with_in_operator = [organizations, locations, issue_type]

    for field, value in zip(fields_with_in_operator, values_with_in_operator):
        if value and field not in filter_fields:
            advanced_filter.append({"field": field, "op": "in", "values": value})

    if first_detected and first_detected not in filter_fields:
        advanced_filter.append(
            {
                "field": "first_detected",
                "op": "between",
                "values": [
                    [first_detected, arg_to_datetime(time.time()).strftime(DATE_FORMAT)]  # type: ignore
                ],
            }
        )

    if last_detected and last_detected not in filter_fields:
        advanced_filter.append(
            {
                "field": "last_detected",
                "op": "between",
                "values": [
                    [last_detected, arg_to_datetime(time.time()).strftime(DATE_FORMAT)]  # type: ignore
                ],
            }
        )

    return advanced_filter


def prepare_body_filters_for_list_assets(
    asset_type: str,
    organizations: List[str],
    locations: List[str],
    first_seen: str,
    last_seen: str,
    security_grade: List[str],
    status: List[Any],
    advanced_filter: List,
) -> List[Dict]:
    """Prepare body params for cycognito-assets-list command.

    :type asset_type: ``str``
    :param asset_type: To retrieve assets related to specific asset type.

    :type organizations: ``List[str]``
    :param organizations: To retrieve assets related to specific organizations.

    :type locations: ``List[str]``
    :param locations: To retrieve assets related to specific locations.

    :type first_seen: ``str``
    :param first_seen: To retrieve assets from a specific first seen time.

    :type last_seen: ``str``
    :param last_seen: To retrieve assets from a specific last seen time.

    :type security_grade: ``List[str]``
    :param security_grade: To retrieve assets of a specific security rating.

    :type status: ``List[str]``
    :param status: To retrieve assets of a specific status.

    :type advanced_filter: ``List``
    :param advanced_filter: To filter data by any response field.

    :rtype: ``List[Dict]``
    :return: Body parameters for filter result.
    """
    filter_fields = []
    for filter_dict in advanced_filter:
        filter_fields.append(filter_dict.get("field"))
        if filter_dict.get("op") not in VALID_OPERATORS:
            raise ValueError(ERRORS["INVALID_OPERATOR"].format(filter_dict.get("op"), VALID_OPERATORS))

    fields_with_in_operator = ["organizations", "locations", "security_grade", "status"]
    values_with_in_operator = [organizations, locations, security_grade, status]

    for field, value in zip(fields_with_in_operator, values_with_in_operator):
        if value and field not in filter_fields:
            advanced_filter.append({"field": field, "op": "in", "values": value})

    if first_seen and first_seen not in filter_fields:
        advanced_filter.append(
            {
                "field": "first_seen",
                "op": "between",
                "values": [
                    [first_seen, arg_to_datetime(time.time()).strftime(DATE_FORMAT)]  # type: ignore
                ],
            }
        )

    if last_seen and last_seen not in filter_fields:
        advanced_filter.append(
            {
                "field": "last_seen",
                "op": "between",
                "values": [
                    [last_seen, arg_to_datetime(time.time()).strftime(DATE_FORMAT)]  # type: ignore
                ],
            }
        )

    return advanced_filter


def prepare_hr_for_list_issues(response: Dict) -> str:
    """Prepare Human Readable output for cycognito-issues-list command.

    :type response: ``Dict``
    :param response: Response from API.

    :rtype: ``str``
    :return: Human readable output.
    """
    hr_output = []

    for issue in response:
        hr_output.append(
            {
                "ID": issue.get("id", "").replace("issue/", ""),
                "Title": issue.get("title"),
                AFFECTED_ASSET_HR: issue.get("affected_asset"),
                FIRST_DETECTED_HR: None
                if not issue.get("first_detected")
                else arg_to_datetime(issue.get("first_detected")).strftime(HR_DATE_FORMAT),  # type: ignore
                LAST_DETECTED_HR: None
                if not issue.get("last_detected")
                else arg_to_datetime(issue.get("last_detected")).strftime(HR_DATE_FORMAT),  # type: ignore
                "Organizations": ", ".join(issue.get("organizations", [])),
                "Locations": ", ".join(convert_alpha_3_codes_to_country_names(issue.get("locations", []))),
                ISSUE_STATUS_HR: issue.get("issue_status"),
                ISSUE_TYPE_HR: issue.get("issue_type"),
                INVESTIGATION_STATUS_HR: issue.get("investigation_status"),
                "Severity": issue.get("severity"),
                "Severity Score": issue.get("severity_score"),
            }
        )

    headers = [
        "ID",
        "Title",
        "Severity Score",
        "Severity",
        AFFECTED_ASSET_HR,
        ISSUE_TYPE_HR,
        ISSUE_STATUS_HR,
        "Organizations",
        INVESTIGATION_STATUS_HR,
        FIRST_DETECTED_HR,
        LAST_DETECTED_HR,
        "Locations",
    ]

    return tableToMarkdown("Issues:", hr_output, headers=headers, removeNull=True)


def prepare_hr_for_list_assets(response: Dict, asset_type: str) -> str:
    """Prepare Human Readable output for cycognito-assets-list command.

    :type response: ``Dict``
    :param response: Response from API.

    :type asset_type: ``str``
    :param asset_type: Type of the asset.

    :rtype: ``str``
    :return: Human readable output.
    """
    hr_outputs = []
    for asset in response:
        first_seen = arg_to_datetime(asset.get("first_seen"), arg_name="first_seen")  # type: ignore
        last_seen = arg_to_datetime(asset.get("last_seen"), arg_name="last_seen")  # type: ignore
        if first_seen:
            first_seen: str = first_seen.strftime(HR_DATE_FORMAT)  # type: ignore
        if last_seen:
            last_seen: str = last_seen.strftime(HR_DATE_FORMAT)  # type: ignore

        hr_outputs.append(
            {
                ASSET_ID_HR: asset.get("id", "").split("/", 1)[-1],
                HOSTING_TYPE_HR: asset.get("hosting_type"),
                "First Seen": first_seen,
                "Last Seen": last_seen,
                "Domain Names": ", ".join(asset.get("domain_names", [])),
                "Domains": ", ".join(asset.get("domains", [])),
                "Security Grade": asset.get("security_grade"),
                "Common Name": asset.get("subject_common_name"),
                "Status": asset.get("status"),
                "Organizations": ", ".join(asset.get("organizations", [])),
                "Locations": ", ".join(convert_alpha_3_codes_to_country_names(asset.get("locations", []))),
                INVESTIGATION_STATUS_HR: asset.get("investigation_status"),
                "Severe Issues": asset.get("severe_issues"),
            }
        )

    headers = [
        ASSET_ID_HR,
        "Security Grade",
        "Status",
        "Organizations",
        INVESTIGATION_STATUS_HR,
        "Severe Issues",
        "Common Name",
        "Domain Names",
        "Domains",
        "First Seen",
        "Last Seen",
        HOSTING_TYPE_HR,
        "Locations",
    ]

    return tableToMarkdown(
        f"Asset List: \n Assets Type: {asset_type.title() if asset_type != 'ip' else asset_type.upper()}",
        hr_outputs,
        headers=headers,
        removeNull=True,
    )


def prepare_advance_filter_for_fetch_incident(severity: List, investigation_status: str, advanced_filter: List) -> List:
    """Prepare advance json for fetch incident.

    :type severity: ``List``
    :param severity: Valid severity list to filter response.

    :type investigation_status: ``str``
    :param investigation_status: To filter response according investigation status.

    :type advanced_filter: ``List``
    :param advanced_filter: Advance json to filter response.

    :rtype: ``List``
    :return: Advance json for fetch incidents.
    """
    if severity:
        advanced_filter.append({"field": "severity", "op": "in", "values": severity})

    if investigation_status:
        advanced_filter.append({"field": "investigation_status", "op": "in", "values": [investigation_status]})

    return advanced_filter


""" COMMAND FUNCTIONS """


def test_module(client: CyCognitoClient) -> str:
    """Tests API connectivity and authentication.

    Returning 'ok' indicates that the integration works like it is supposed to.
    Connection to the service is successful.
    Raises:
     exceptions if something goes wrong.

    :type client: ``CyCognitoClient``
    :param client: CyCognitoClient to be used.

    :rtype: ``str``
    :return: 'ok' if test passed, anything else will fail the test.
    """
    params = demisto.params()
    is_fetch = params.get("isFetch", False)
    if is_fetch:
        fetch_incidents(client, {}, params, is_test=True)
    else:
        client.list_issues(count=1, filters=[])  # body is required
    return "ok"


def fetch_incidents(
    client: CyCognitoClient, last_run: Dict[str, Any], params: Dict[str, Any], is_test: bool = False
) -> tuple[Dict, List]:
    """Fetch issues list incidents.

    :type client: ``CyCognitoClient``
    :param client: CyCognitoClient to be used.

    :type last_run: ``Dict[str, Any]``
    :param last_run: last run object obtained from demisto.getLastRun().

    :type params: `Dict[str, Any]``
    :param params: Arguments to be used for fetch incident.

    :type is_test: ``bool``
    :param is_test: If test_module called fetch_incident.

    :rtype: ``Tuple[Dict, List]``
    :return: Tuple of last run object and list of fetched incidents.
    """
    count = arg_to_number(params.get("max_fetch"), arg_name="Max Fetch")  # type: ignore
    first_fetch = arg_to_datetime(params.get("first_fetch", "3 days"), arg_name="First Fetch").strftime(  # type: ignore
        DATE_FORMAT
    )
    offset = last_run.get("offset", 0)
    first_detected = last_run.get("last_fetch", first_fetch)

    issue_type = argToList(params.get("issue_type"))
    locations = convert_country_names_to_alpha_3_codes(argToList(params.get("locations", "")))
    severity = argToList(params.get("severity_filter"))
    investigation_status = params.get("investigation_status", "uninvestigated")
    advanced_filter = params.get("advanced_filter")

    if not count:
        raise ValueError(ERRORS["INVALID_REQUIRED_PARAMETER"].format("Max Fetch"))

    if not advanced_filter:
        advanced_filter = "[]"

    try:
        advanced_filter = json.loads(advanced_filter)
        if isinstance(advanced_filter, dict):
            advanced_filter = [advanced_filter]
    except json.JSONDecodeError:
        raise ValueError(ERRORS["INVALID_ADVANCED_FILTER"].format(advanced_filter))

    validate_params_for_list_issues(
        count=count, severity=severity, investigation_status=investigation_status, issue_type=issue_type
    )

    advanced_filter = prepare_advance_filter_for_fetch_incident(
        severity=severity, investigation_status=investigation_status, advanced_filter=advanced_filter
    )

    filters = prepare_body_filters_for_list_issues(
        locations=locations, issue_type=issue_type, first_detected=first_detected, advanced_filter=advanced_filter
    )

    response = client.list_issues(filters=filters, count=count, offset=offset, sort_by="first_detected", sort_order="asc")
    valid_response = validate_response(response)

    if is_test:
        return {}, []

    next_run = last_run.copy()

    incidents = []

    demisto.debug(f"\n\n[+] CyCognito: Params -> {params}\n\n")

    for issue in valid_response:
        issue.update(
            {
                "mirror_direction": MIRROR_DIRECTION.get(params.get("mirror_direction", "None")),
                "mirror_instance": demisto.integrationInstance(),
            }
        )
        occurred_date = dateparser.parse(issue.get("first_detected"))  # type: ignore
        incidents.append(
            {
                "name": f"{issue.get('id').replace('issue/', '')} {issue.get('title')}",
                "occurred": occurred_date.strftime(DATE_FORMAT),  # type: ignore
                "rawJSON": json.dumps(issue),
            }
        )
    demisto.debug(f"Total {len(incidents)} incident fetched.")
    if valid_response:
        if len(valid_response) < count:  # type: ignore
            last_fetch_time = dateparser.parse(valid_response[-1].get("first_detected")) + timedelta(  # type: ignore
                milliseconds=1
            )
            next_run["last_fetch"] = last_fetch_time.strftime(API_DATE_FORMAT)  # type: ignore
            next_run["offset"] = 0
        else:
            next_run["offset"] = offset + 1
            next_run["last_fetch"] = first_detected

    return next_run, incidents


def get_remote_data_command(client: CyCognitoClient, args: Dict[str, Any]):
    """Get updated remote data.

    :type client: ``CyCognitoClient``
    :param client: CyCognitoClient to be used.

    :type args: `Dict[str, Any]``
    :param args: Arguments to be used for get-remote-data command.
    """
    parsed_args = GetRemoteDataArgs(args)

    demisto.debug(f"\n\n[+] CyCognito: Running get_remote_data_command for incident {parsed_args.remote_incident_id}")
    responded_incident = client.get_issue(parsed_args.remote_incident_id)
    demisto.debug(f"[+] CyCognito: response {responded_incident.json()}")
    return GetRemoteDataResponse(responded_incident.json(), [])


def update_remote_system_command(client: CyCognitoClient, args: Dict[str, Any]):
    """Update investigation status change to remote system.

    :type client: ``CyCognitoClient``
    :param client: CyCognitoClient to be used.

    :type args: `Dict[str, Any]``
    :param args: Arguments to be used for update-remote-system command.
    """
    parsed_args = UpdateRemoteSystemArgs(args)

    demisto.debug(
        f"\n\n[+] CyCognito: Update - Running update_remote_system_command for incident {parsed_args.remote_incident_id}"
    )
    demisto.debug(f"[+] CyCognito: parsed_args -> {parsed_args}")
    if parsed_args.delta:
        demisto.debug(f"[+] CyCognito: Delta keys for incident {parsed_args.remote_incident_id} are {parsed_args.delta!s}")
    if not parsed_args.delta.get("cycognitoinvestigationstatus"):
        demisto.debug(f"[+] CyCognito: No Update necessary for incident {parsed_args.remote_incident_id}")
        return parsed_args.remote_incident_id

    demisto.debug(f"[+] CyCognito: Delta keys -> {list(parsed_args.delta.keys())!s}")

    params = {
        "investigation_status": parsed_args.delta["cycognitoinvestigationstatus"],
        "issue_instance_id": parsed_args.remote_incident_id,
    }
    demisto.debug(f"[+] CyCognito: params -> {params}")
    updated_incident = client.change_issue_investigation_status(params)
    updated_incident = prepare_context_for_issue_investigation_status_change(updated_incident.json(), params)
    return updated_incident.get("id", "").replace("issue/", "")


def cycognito_issue_get_command(client: CyCognitoClient, args: Dict[str, Any]) -> CommandResults:
    """Retrieve information about an issue associated with a particular asset.

    :type client: ``CyCognitoClient``
    :param client: CyCognitoClient to be used.

    :type args: ``Dict[str, Any]``
    :param args: Arguments provided by user.

    :rtype: ``CommandResults``
    :return: Standard command result.
    """
    issue_instance_id = args.get("issue_instance_id", "").lower()

    response = client.get_issue(issue_instance_id)
    valid_response = validate_response(response)

    valid_response["id"] = f"issue/{issue_instance_id}"

    hr_output = prepare_hr_for_issue_get(valid_response)

    return CommandResults(
        outputs_prefix=ISSUE_OUTPUT_PREFIX,
        outputs_key_field="id",
        outputs=valid_response,
        readable_output=hr_output,
        raw_response=response.json(),
    )


def cycognito_asset_get_command(client: CyCognitoClient, args: Dict[str, Any]):
    """Retrieve a particular asset based on the specified type.

    :type client: ``CyCognitoClient``
    :param client: CyCognitoClient to use

    :type args: ``dict``
    :param args: arguments obtained from demisto.args()

    :return: CommandResult object
    """
    asset_id = args.get("asset_id", "").lower()
    asset_type = args.get("asset_type", "").lower()
    validate_arguments_for_asset_get(asset_type, asset_id)
    response = client.get_asset(asset_type, asset_id)
    validated_response = validate_response(response)
    readable_output = prepare_hr_for_asset_get(validated_response)

    return CommandResults(
        outputs_prefix=ASSET_OUTPUT_PREFIX,
        outputs_key_field="id",
        outputs=validated_response,
        readable_output=readable_output,
        raw_response=response.json(),
    )


def cycognito_issue_investigation_status_change_command(client: CyCognitoClient, args: Dict[str, Any]) -> CommandResults:
    """Modify the investigation status of the specified issue.

    :type client: ``CyCognitoClient``
    :param client: CyCognitoClient to be used.

    :type args: ``Dict[str, Any]``
    :param args: Arguments provided by user.

    :rtype: ``CommandResults``
    :return: Standard command result.
    """
    params = validate_params_for_issue_investigation_status_change(args)

    response = client.change_issue_investigation_status(params)
    valid_response = validate_response(response)

    context_data = prepare_context_for_issue_investigation_status_change(valid_response, params)
    hr_output = prepare_hr_for_issue_investigation_status_change(valid_response, params)

    return CommandResults(
        outputs_prefix=ISSUE_OUTPUT_PREFIX,
        outputs_key_field="id",
        outputs=context_data,
        readable_output=hr_output,
        raw_response=response.json(),
    )


def cycognito_asset_investigation_status_change_command(client: CyCognitoClient, args: Dict[str, Any]):
    """Modify the investigation status of the asset.

    :type client: ``CyCognitoClient``
    :param client: CyCognitoClient to use

    :type args: ``dict``
    :param args: arguments obtained from demisto.args()

    :return: CommandResult object
    """
    params = validate_params_for_asset_investigation_status_change(args)
    response = client.change_asset_investigation_status(params)
    validated_response = validate_response(response)
    context_data = prepare_context_for_asset_investigation_status(validated_response, params)
    readable_output = prepare_hr_for_asset_investigation_status(validated_response, params)

    return CommandResults(
        outputs_prefix=ASSET_OUTPUT_PREFIX,
        outputs_key_field="id",
        outputs=context_data,
        readable_output=readable_output,
        raw_response=response.json(),
    )


def cycognito_issues_list_command(client: CyCognitoClient, args: Dict[str, Any]) -> CommandResults:
    """Retrieve the list of the issues that meet the specified filter criteria.

    :type client: ``CyCognitoClient``
    :param client: CyCognitoClient to be used.

    :type args: ``Dict[str, Any]``
    :param args: Arguments provided by user.

    :rtype: ``CommandResults``
    :return: Standard command result.
    """
    count = arg_to_number(args.get("count", DEFAULT_COUNT), arg_name="count")
    offset = max(arg_to_number(args.get("offset", DEFAULT_OFFSET), arg_name="offset"), 0)  # type: ignore
    search = args.get("search", "")
    first_detected = args.get("first_detected", "")
    last_detected = args.get("last_detected", "")
    organizations = argToList(args.get("organizations", "").lower())
    locations = argToList(args.get("locations", ""))
    issue_type = argToList(args.get("issue_type", ""))
    sort_by = args.get("sort_by", "").lower()
    sort_order = args.get("sort_order", DEFAULT_SORT_ORDER).lower()
    advanced_filter = args.get("advanced_filter", "[]")

    try:
        advanced_filter = json.loads(advanced_filter)
        if isinstance(advanced_filter, dict):
            advanced_filter = [advanced_filter]
    except json.JSONDecodeError:
        raise ValueError(ERRORS["INVALID_ADVANCED_FILTER"].format(advanced_filter))

    if first_detected:
        first_detected = arg_to_datetime(first_detected, arg_name="first_detected").strftime(  # type: ignore
            DATE_FORMAT
        )
    if last_detected:
        last_detected = arg_to_datetime(last_detected, arg_name="last_detected").strftime(DATE_FORMAT)  # type: ignore

    validate_params_for_list_issues(count=count, sort_order=sort_order)

    filters = prepare_body_filters_for_list_issues(
        organizations=organizations,
        locations=locations,
        first_detected=first_detected,
        last_detected=last_detected,
        issue_type=issue_type,
        advanced_filter=advanced_filter,
    )

    response = client.list_issues(
        filters=filters, count=count, offset=offset, sort_order=sort_order, sort_by=sort_by, search=search
    )

    valid_response = validate_response(response)

    hr_output = prepare_hr_for_list_issues(valid_response)

    return CommandResults(
        outputs_prefix=ISSUE_OUTPUT_PREFIX,
        outputs_key_field="id",
        outputs=valid_response,
        readable_output=hr_output,
        raw_response=response.json(),
    )


def cycognito_assets_list_command(client: CyCognitoClient, args: Dict[str, Any]) -> CommandResults:
    """Retrieve the list of the assets that meet the specified filter criteria.

    :type client: ``CyCognitoClient``
    :param client: CyCognitoClient to be used.

    :type args: ``Dict[str, Any]``
    :param args: Arguments provided by user.

    :rtype: ``CommandResults``
    :return: Standard command result.
    """
    asset_type = args.get("asset_type", "").lower()
    count = arg_to_number(args.get("count", DEFAULT_COUNT), arg_name="count")
    offset = max(arg_to_number(args.get("offset", DEFAULT_OFFSET), arg_name="offset"), 0)  # type: ignore
    search = args.get("search", "")
    first_seen = args.get("first_seen", "")
    last_seen = args.get("last_seen", "")
    organizations = argToList(args.get("organizations", "").lower())
    security_grade = argToList(args.get("security_grade", "").lower())
    status = argToList(args.get("status", "").lower())
    locations = argToList(args.get("locations", ""))
    sort_by = args.get("sort_by", "").lower()
    sort_order = args.get("sort_order", DEFAULT_SORT_ORDER).lower()
    advanced_filter = args.get("advanced_filter", "[]")

    try:
        advanced_filter = json.loads(advanced_filter)
        if isinstance(advanced_filter, dict):
            advanced_filter = [advanced_filter]
    except json.JSONDecodeError:
        raise ValueError(ERRORS["INVALID_ADVANCED_FILTER"].format(advanced_filter))

    if first_seen:
        first_seen = arg_to_datetime(first_seen, arg_name="first_seen").strftime(  # type: ignore
            DATE_FORMAT
        )
    if last_seen:
        last_seen = arg_to_datetime(last_seen, arg_name="last_seen").strftime(DATE_FORMAT)  # type: ignore

    validate_params_for_list_assets(
        asset_type=asset_type, count=count, sort_order=sort_order, status=status, security_grade=security_grade
    )

    filters = prepare_body_filters_for_list_assets(
        asset_type=asset_type,
        organizations=organizations,
        locations=locations,
        security_grade=security_grade,
        status=status,
        first_seen=first_seen,
        last_seen=last_seen,
        advanced_filter=advanced_filter,
    )
    response = client.list_assets(
        filters=filters, asset_type=asset_type, count=count, offset=offset, search=search, sort_order=sort_order, sort_by=sort_by
    )
    valid_response = validate_response(response)

    hr_output = prepare_hr_for_list_assets(valid_response, asset_type)

    return CommandResults(
        outputs_prefix=ASSET_OUTPUT_PREFIX,
        outputs_key_field="id",
        outputs=valid_response,
        readable_output=hr_output,
        raw_response=response.json(),
    )


def main():
    """Parse params and runs command functions."""
    params = demisto.params()

    verify_certificate = not params.get("insecure", False)
    proxy = argToBoolean(params.get("proxy", False))

    command = demisto.command()
    demisto.debug(f"Command being called is {command}")

    try:
        client = CyCognitoClient(params=params, verify=verify_certificate, proxy=proxy)
        if command == "test-module":
            return_results(test_module(client))
        elif demisto.command() == "fetch-incidents":
            last_run = demisto.getLastRun()
            next_run, incidents = fetch_incidents(client, last_run, params)
            demisto.info(f"Fetched {len(incidents)} new incidents")
            demisto.incidents(incidents)
            demisto.setLastRun(next_run)
        elif demisto.command() == "get-remote-data":
            return_results(get_remote_data_command(client, demisto.args()))
        elif demisto.command() == "update-remote-system":
            return_results(update_remote_system_command(client, demisto.args()))
        else:
            CYCOGNITO_COMMANDS = {
                "cycognito-issue-get": cycognito_issue_get_command,
                "cycognito-issues-list": cycognito_issues_list_command,
                "cycognito-issue-investigation-status-change": cycognito_issue_investigation_status_change_command,
                "cycognito-asset-get": cycognito_asset_get_command,
                "cycognito-assets-list": cycognito_assets_list_command,
                "cycognito-asset-investigation-status-change": cycognito_asset_investigation_status_change_command,
            }
            if CYCOGNITO_COMMANDS.get(command):
                args = demisto.args()
                remove_nulls_from_dictionary(trim_spaces_from_args(args))
                return_results(CYCOGNITO_COMMANDS[command](client, args))
            else:
                raise NotImplementedError(f"Command {command} is not implemented")
    except Exception as e:
        return_error(f"Failed to execute {command} command.\nError: \n{e!s}")


if __name__ in ("__main__", "__builtin__", "builtins"):  # pragma: no cover
    main()