PanoraysFindingsAPI

Retrieve and monitor internal security findings for your organization from the Panorays platform to streamline self-assessment posture and automate internal incident response within Cortex XSOAR.

Vulnerability Management · Panorays

Details

IDPanoraysFindingsAPI
ProviderPanorays
CategoryVulnerability Management
From Version6.0.0
Docker Imagedemisto/python3:3.12.13.10116658

README

Retrieve and monitor internal security findings for your organization from the Panorays platform to streamline self-assessment posture and automate internal incident response within Cortex XSOAR.
This integration was integrated and tested with version v2 of PanoraysFindingsAPI.

Configure Panorays Findings API in Cortex

Parameter Required
Panorays PAPI base URL True
apikey True
API Key True
Trust any certificate (not secure) False
Use system proxy settings False
Maximum number of incidents to fetch per run False
First fetch timestamp (e.g., 7 days) False
Incident type False
Fetch incidents False

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.

panorays-finding-list


Lists the company findings as detected by Panorays.

Base Command

panorays-finding-list

Input

Argument Name Description Required
limit The maximum number of findings to return. Default is 50. Optional
page The page number of results to retrieve. Default is 1. Optional

Context Output

There is no context output for this command.

Configuration parameters

  • url — Panorays PAPI base URL (required)
  • apikey — (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • max_fetch — Maximum number of incidents to fetch per run
  • first_fetch — First fetch timestamp (e.g., 7 days)
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • isFetch — Fetch incidents

Commands (1)

  • panorays-finding-list

    Lists the company findings as detected by Panorays.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401
import urllib3
import json
from typing import Any
from datetime import UTC

urllib3.disable_warnings()


class Client(BaseClient):
    def get_company_findings(self, limit: int, page: int) -> dict[str, Any]:
        params = {"limit": limit, "page": page}
        return self._http_request(method="GET", url_suffix="/v2/findings", params=params)


def verify_module(client: Client) -> str:
    try:
        client._http_request("GET", "/v2/findings", params={"limit": 1})
        return "ok"
    except Exception as e:
        if "Unauthorized" in str(e) or "Forbidden" in str(e):
            raise Exception("Authorization Error: check your API Key.") from e
        raise e


def finding_list_command(client: Client, args: dict[str, Any]) -> CommandResults:
    limit = int(args.get("limit") or 50)
    page = int(args.get("page") or 1)
    raw_response = client.get_company_findings(limit=limit, page=page)
    findings = raw_response.get("data", [])

    markdown_data = []
    for finding in findings:
        markdown_data.append(
            {
                "Finding ID": finding.get("id"),
                "Category": finding.get("category"),
                "Affected Asset": finding.get("asset_name"),
                "Risk Level": finding.get("severity"),
                "State": finding.get("status"),
            }
        )

    markdown = tableToMarkdown(f"Panorays Findings (Page {page})", markdown_data, removeNull=True)
    return CommandResults(
        readable_output=markdown,
        outputs_prefix="Panorays.Finding",
        outputs_key_field="id",
        outputs=findings,
        raw_response=raw_response,
    )


def _make_aware(dt):
    """Ensure a datetime is timezone-aware (UTC). Returns None if dt is None."""
    if dt is None:
        return None
    if dt.tzinfo is None:
        return dt.replace(tzinfo=UTC)
    return dt


def fetch_incidents_command(client: Client, last_run: dict, first_fetch_time: str, max_fetch: int) -> tuple[dict, list[dict]]:
    last_fetch = last_run.get("last_fetch")
    if last_fetch:
        last_fetch_time = _make_aware(arg_to_datetime(last_fetch))
    else:
        last_fetch_time = _make_aware(arg_to_datetime(first_fetch_time))

    raw_response = client.get_company_findings(limit=max_fetch, page=1)
    findings = raw_response.get("data", [])

    incidents = []
    latest_created_time = last_fetch_time

    for finding in findings:
        finding_time = _make_aware(arg_to_datetime(finding.get("insert_ts")))

        if last_fetch_time and finding_time and finding_time <= last_fetch_time:
            continue

        incidents.append(
            {
                "name": f"Panorays Finding: {finding.get('asset_name', 'Unknown')}",
                "details": finding.get("finding_text", ""),
                "occurred": finding.get("insert_ts"),
                "rawJSON": json.dumps(finding),
            }
        )

        if finding_time and (not latest_created_time or finding_time > latest_created_time):
            latest_created_time = finding_time

    next_run = {"last_fetch": latest_created_time.strftime("%Y-%m-%dT%H:%M:%SZ") if latest_created_time else last_fetch}
    return next_run, incidents


def main() -> None:
    try:
        params = demisto.params()
        command = demisto.command()

        api_key = params.get("apikey")
        base_url = params.get("url", "https://api.panoraysapp.com")

        headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
        verify_certificate = not bool(params.get("insecure", False))
        proxy = bool(params.get("proxy", False))

        client = Client(base_url=base_url, verify=verify_certificate, proxy=proxy, headers=headers)

        if command == "test-module":
            return_results(verify_module(client))
        elif command == "panorays-finding-list":
            return_results(finding_list_command(client, demisto.args()))
        elif command == "fetch-incidents":
            last_run = demisto.getLastRun()
            first_fetch_time = params.get("first_fetch", "3 days")
            max_fetch = int(params.get("max_fetch") or 50)

            next_run, incidents = fetch_incidents_command(client, last_run, first_fetch_time, max_fetch)

            demisto.setLastRun(next_run)
            demisto.incidents(incidents)

    except Exception as e:
        return_error(f"Error: {str(e)}")


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()