MetaDefender Aether

Next-generation unified Zero-Day detection solution, combining Threat Reputation, Dynamic Analysis, Threat Scoring, and Threat Hunting into a single adaptive detection pipeline (previously known as MetaDefender Sandbox).

Forensics & Malware Analysis · MetaDefender Aether

Details

IDMetaDefender Aether
CategoryForensics & Malware Analysis
From Version6.10.0
Docker Imagedemisto/fastapi:0.125.0.10158186

README

Next-generation unified Zero-Day detection solution, combining the proven capabilities of MetaDefender Sandbox with built-in Threat Intelligence, Threat Scoring and Threat Hunting - all delivered as a single adaptive detection pipeline. (previously known as MetaDefender Sandbox)

Configure MetaDefender Aether on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for MetaDefender Aether.
  3. Click Add instance to create and configure a new integration instance.

    Parameter Required
    Server URL (e.g. https://www.filescan.io/api) True
    API Key True
    Trust any certificate (not secure) False
    Use system proxy settings False
  4. Click Test to validate the URLs, the API Key and connection.

Commands

You can execute these commands from the Cortex XSOAR 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.

metadefender-aether-scan-url


Scan URL with MetaDefender Aether

Note: MetaDefender Aether handles URL scanning as a file scan.

Notice: Submitting indicators using this command might make the indicator data publicly available. See the vendor’s documentation for more details.

Base Command

metadefender-aether-scan-url

Input

Argument Name Description Required
url The URL to submit Required
timeout The timeout for the polling in seconds Optional
hide_polling_output Hide polling output Optional
description Uploaded file/url description Optional
tags Tags array to propagate Optional
password Custom password, in case uploaded archive is protected Optional
is_private If file should not be available for download by other users Optional

Context Output

Path Type Description
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.
File.Name String The full file name.
File.SHA256 String The SHA256 hash of the file.
File.Malicious.Vendor String The vendor that reported the file as malicious.
MetaDefender.Aether.Analysis.finalVerdict.verdict String The final verdict.
MetaDefender.Aether.Analysis.allTags Unknown All tags.
MetaDefender.Aether.Analysis.overallState String Overall state of the scan.
MetaDefender.Aether.Analysis.taskReference.name String Name of the main scan task.
MetaDefender.Aether.Analysis.taskReference.additionalInfo Unknown Additional information about the main scan task.
MetaDefender.Aether.Analysis.taskReference.ID String ID of the main scan task.
MetaDefender.Aether.Analysis.taskReference.state String State of the main scan task.
MetaDefender.Aether.Analysis.taskReference.resourceReference Unknown Resource reference of the main scan task.
MetaDefender.Aether.Analysis.taskReference.opcount Number Counter.
MetaDefender.Aether.Analysis.taskReference.processTime Number processTime.
MetaDefender.Aether.Analysis.subtaskReferences Unknown Status of scan subtasks.
MetaDefender.Aether.Analysis.allSignalGroups Unknown All signal groups.
MetaDefender.Aether.Analysis.resources Unknown Resources.
MetaDefender.Aether.Analysis.file.name String The name of the file.
MetaDefender.Aether.Analysis.file.hash String The SHA256 of the file.
MetaDefender.Aether.Analysis.file.type String The type of the submission.

Command example

!metadefender-aether-scan-url url=https://www.test.com

Context Example

{
    "DBotScore":
    [
        {
            "Indicator": "1111111111111111111111111111111111111111111111111111111111111111",
            "Score": 1,
            "Type": "file",
            "Vendor": "MetaDefender Aether"
        }
    ],
    "File":
    [
        {
            "Name": "https://www.test.com",
            "SHA256": "1111111111111111111111111111111111111111111111111111111111111111"
        }
    ],
    "MetaDefender":
    {
        "Aether":
        {
            "Analysis":
            [
                {
                    "finalVerdict":
                    {
                        "verdict": "BENIGN"
                    },
                    "allTags":
                    [
                        {
                            "source": "MEDIA_TYPE",
                            "sourceIdentifier": "12345678",
                            "isRootTag": true,
                            "tag":
                            {
                                "name": "html",
                                "synonyms":
                                [],
                                "descriptions":
                                [],
                                "verdict":
                                {
                                    "verdict": "INFORMATIONAL",
                                    "threatLevel": 0.1,
                                    "confidence": 1
                                }
                            }
                        }
                    ],
                    "overallState": "success_partial",
                    "taskReference":
                    {
                        "name": "transform-file",
                        "additionalInfo":
                        {
                            "submitName": "https://www.test.com",
                            "submitTime": 1679014774270,
                            "digests":
                            {
                                "SHA-256": "1111111111111111111111111111111111111111111111111111111111111111"
                            }
                        },
                        "ID": "abcd-1234",
                        "state": "SUCCESS",
                        "resourceReference":
                        {
                            "type": "TRANSFORM_FILE",
                            "name": "file",
                            "ID": "abcd-5678"
                        },
                        "opcount": 1,
                        "processTime": 20350
                    },
                    "subtaskReferences":
                    [
                        {
                            "name": "domain-resolve",
                            "additionalInfo": 72,
                            "ID": "12345678",
                            "state": "SUCCESS",
                            "resourceReference":
                            {
                                "type": "DOMAIN_RESOLVE",
                                "name": "domain-resolve",
                                "ID": "123456789"
                            },
                            "opcount": 20,
                            "processTime": 11309
                        }
                    ],
                    "allSignalGroups":
                    [
                        {
                            "identifier": "I000",
                            "description": "OSINT source detected malicious resource",
                            "averageSignalStrength": 0.75,
                            "peakSignalStrength": 0.75,
                            "finalSignalStrength": 0.75,
                            "verdict":
                            {
                                "verdict": "LIKELY_MALICIOUS",
                                "threatLevel": 0.75,
                                "confidence": 1
                            },
                            "allTags":
                            [],
                            "signals":
                            [
                                {
                                    "strength": 0.75,
                                    "isStrictlyBasedOnInputData": false,
                                    "signalReadable": "OSINT provider TEST provider (2/93)",
                                    "additionalInfo": "https://www.google.com",
                                    "originPath": "osint.results.verdict",
                                    "originType": "INPUT_FILE",
                                    "originIdentifier": "1234"
                                }
                            ]
                        }
                    ],
                    "resources":
                    {
                        "00f1e4d6-27fb-45e8-8a02-dc53818044ec":
                        {
                            "resourceReference":
                            {
                                "name": "osint"
                            },
                            "results":
                            []
                        }
                    },
                    "file":
                    {
                        "name": "https://www.test.com",
                        "hash": "1111111111111111111111111111111111111111111111111111111111111111",
                        "type": "other"
                    }
                }
            ]
        }
    }
}

Human Readable Output

Scan Result (digest)

FileHash FileName FileType FinalVerdict SubtaskReferences Tags
1111111111111111111111111111111111111111111111111111111111111111 https://www.test.com other BENIGN osint, url-render, domain-resolve html, png

metadefender-aether-scan-file


Scan File with MetaDefender Aether

Base Command

metadefender-aether-scan-file

Input

Argument Name Description Required
entry_id The War Room entry ID of the file to submit Required
timeout The timeout for the polling in seconds Optional
hide_polling_output Hide polling output Optional
description Uploaded file/url description Optional
tags Tags array to propagate Optional
password Custom password, in case uploaded archive is protected Optional
is_private If file should not be available for download by other users Optional

Context Output

Path Type Description
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.
File.Name String The full file name.
File.SHA256 String The SHA256 hash of the file.
File.Malicious.Vendor String The vendor that reported the file as malicious.
MetaDefender.Aether.Analysis.finalVerdict.verdict String The final verdict.
MetaDefender.Aether.Analysis.allTags Unknown All tags.
MetaDefender.Aether.Analysis.overallState String Overall state of the scan.
MetaDefender.Aether.Analysis.taskReference.name String Name of the main scan task.
MetaDefender.Aether.Analysis.taskReference.additionalInfo Unknown Additional information about the main scan task.
MetaDefender.Aether.Analysis.taskReference.ID String ID of the main scan task.
MetaDefender.Aether.Analysis.taskReference.state String State of the main scan task.
MetaDefender.Aether.Analysis.taskReference.resourceReference Unknown Resource reference of the main scan task.
MetaDefender.Aether.Analysis.taskReference.opcount Number Counter.
MetaDefender.Aether.Analysis.taskReference.processTime Number processTime.
MetaDefender.Aether.Analysis.subtaskReferences Unknown Status of scan subtasks.
MetaDefender.Aether.Analysis.allSignalGroups Unknown All signal groups.
MetaDefender.Aether.Analysis.resources Unknown Resources.
MetaDefender.Aether.Analysis.file.name String The name of the file.
MetaDefender.Aether.Analysis.file.hash String The SHA256 of the file.
MetaDefender.Aether.Analysis.file.type String The type of the submission.

Command example

!metadefender-aether-scan-file entry_id=1234@abcd-efgh-ijkl-mnop-xyz

Context Example

{
    "DBotScore":
    [
        {
            "Indicator": "1111111111111111111111111111111111111111111111111111111111111111",
            "Score": 1,
            "Type": "file",
            "Vendor": "MetaDefender Aether"
        }
    ],
    "File":
    [
        {
            "Name": "1234@abcd-efgh-ijkl-mnop-xyz",
            "SHA256": "1111111111111111111111111111111111111111111111111111111111111111"
        }
    ],
    "MetaDefender":
    {
        "Aether":
        {
            "Analysis":
            [
                {
                    "finalVerdict":
                    {
                        "verdict": "MALICIOUS"
                    },
                    "allTags":
                    [
                        {
                            "source": "SIGNAL",
                            "sourceIdentifier": "1234",
                            "isRootTag": false,
                            "tag": 
                            {
                                "name": "packed",
                                "synonyms": [],
                                "descriptions": [],
                                "verdict": {
                                    "verdict": "SUSPICIOUS",
                                    "threatLevel": 0.5,
                                    "confidence": 1
                                }
                            }
                        }
                    ],
                    "overallState": "success_partial",
                    "taskReference":
                    {
                        "name": "transform-file",
                        "additionalInfo": {
                            "submitName": "bad_file.exe",
                            "submitTime": 1679011634945,
                            "digests": {
                                "SHA-256": "1111111111111111111111111111111111111111111111111111111111111111"
                            }
                        },
                        "ID": "1234",
                        "state": "SUCCESS",
                        "resourceReference": {
                            "type": "TRANSFORM_FILE",
                            "name": "file",
                            "ID": "0101010101"
                        },
                        "opcount": 1,
                        "processTime": 7180
                    },
                    "subtaskReferences":
                    [
                        {
                            "name": "domain-resolve",
                            "additionalInfo": 72,
                            "ID": "12345678",
                            "state": "SUCCESS",
                            "resourceReference":
                            {
                                "type": "DOMAIN_RESOLVE",
                                "name": "domain-resolve",
                                "ID": "123456789"
                            },
                            "opcount": 20,
                            "processTime": 11309
                        }
                    ],
                    "allSignalGroups":
                    [
                        {
                            "identifier": "Y002",
                            "description": "Matched a malicious YARA rule",
                            "averageSignalStrength": 1,
                            "peakSignalStrength": 1,
                            "finalSignalStrength": 1,
                            "verdict": {
                                "verdict": "MALICIOUS",
                                "threatLevel": 1,
                                "confidence": 1
                            },
                            "allTags": [],
                            "signals": [
                                {
                                    "strength": 1,
                                    "isStrictlyBasedOnInputData": true,
                                    "signalReadable": "Matched YARA with strength \"0.75\"",
                                    "additionalInfo": "PUP_InstallRex_AntiFWb",
                                    "originPath": "file.yaraMatches",
                                    "originType": "INPUT_FILE",
                                    "originIdentifier": "111111111111111111111111111"
                                }
                            ]
                        }
                    ],
                    "resources":
                    {
                        "00f1e4d6-27fb-45e8-8a02-dc53818044ec":
                        {
                            "resourceReference":
                            {
                                "name": "osint"
                            },
                            "results":
                            []
                        }
                    },
                    "file":
                    {
                        "name": "1234@abcd-efgh-ijkl-mnop-xyz",
                        "hash": "1111111111111111111111111111111111111111111111111111111111111111",
                        "type": "other"
                    }
                }
            ]
        }
    }
}

Human Readable Output

Scan Result (digest)

FileHash FileName FileType FinalVerdict SubtaskReferences Tags
1111111111111111111111111111111111111111111111111111111111111111 1234@abcd-efgh-ijkl-mnop-xyz pe MALICIOUS visualization, osint, domain-resolve html, peexe

metadefender-aether-search-query


Search for reports. Finds reports and uploaded files by various tokens.

Base Command

metadefender-aether-search-query

Input

Argument Name Description Required
query The query string Required
limit Number of total results. Maximum 50 Optional
page Page number, starting from 1 Optional
page_size The page size. Can be 5, 10 or 20 Optional

Context Output

Path Type Description
MetaDefender.Aether.Analysis.id String The analysis id.
MetaDefender.Aether.Analysis.file.name String The name of the file.
MetaDefender.Aether.Analysis.file.sha256 String The SHA256 of the file.
MetaDefender.Aether.Analysis.verdict String The final verdict.
MetaDefender.Aether.Analysis.state String Overall state of the scan.
MetaDefender.Aether.Analysis.date Date The scan date.
MetaDefender.Aether.Analysis.file.mime_type String The file MimeType.
MetaDefender.Aether.Analysis.file.short_type String The type of the submission.
MetaDefender.Aether.Analysis.tags Unknown All tags.

Command example

!metadefender-aether-search-query query="834d1dbfab8330ea5f1844f6e905ed0ac19d1033ee9a9f1122ad2051c56783dc"

Context Example

{
    "MetaDefender":
    {
        "Aether":
        {
            "Analysis":
            [
                {
                    "id": "b4f92c03-0fc2-4a40-9d34-8f2b05dd240c",
                    "file": {
                        "name": "bad_file.exe",
                        "mime_type": "application/x-msdownload",
                        "short_type": "peexe",
                        "sha256": "834d1dbfab8330ea5f1844f6e905ed0ac19d1033ee9a9f1122ad2051c56783dc"
                    },
                    "state": "success",
                    "verdict": "malicious",
                    "tags": [
                        {
                            "source": "MEDIA_TYPE",
                            "sourceIdentifier": "834d1dbfab8330ea5f1844f6e905ed0ac19d1033ee9a9f1122ad2051c56783dc",
                            "isRootTag": true,
                            "tag": {
                                "name": "peexe",
                                "synonyms": [],
                                "descriptions": [],
                                "verdict": {
                                    "verdict": "INFORMATIONAL",
                                    "threatLevel": 0.1,
                                    "confidence": 1
                                }
                            }
                        }
                    ],
                    "date": "03/20/2023, 14:28:09"
                }
            ]
        }
    }
}

Human Readable Output

Analysis Result

Id SampleName SHA256 Verdict State Date
8c38be8c-7cfd-4d64-be41-c98a795c9ce0 bad_file.exe 834d1dbfab8330ea5f1844f6e905ed0ac19d1033ee9a9f1122ad2051c56783dc MALICIOUS success_partial 03/14/2023, 15:07:07
e334d27f-e2b1-46c9-9936-7d3155eb3706 bad_file.exe 834d1dbfab8330ea5f1844f6e905ed0ac19d1033ee9a9f1122ad2051c56783dc UNKNOWN success 03/14/2020, 15:03:48

Configuration parameters

  • url — Server URL (e.g. https://www.filescan.io/api) (required)
  • api_key — (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (3)

  • metadefender-aether-scan-file

    Scan file resource.

  • metadefender-aether-scan-url

    Scan URL resource.

  • metadefender-aether-search-query

    Search for reports. Finds reports and uploaded files by various tokens.

from CommonServerPython import *
from CommonServerUserPython import *
from ContentClientApiModule import *

""" IMPORTS """
import traceback
from typing import Any
import urllib3

# Disable insecure warnings
urllib3.disable_warnings()

"""GLOBALS/PARAMS """
INTEGRATION_NAME = "MetaDefender Aether Integration"
INTEGRATION_CONTEXT_NAME = "MetaDefender.Aether"
TIMEOUT = 600


class Client(ContentClient):
    def __init__(self, api_key: str, base_url: str, proxy: bool, verify: bool):
        super().__init__(base_url=base_url, proxy=proxy, verify=verify)
        self.api_key = api_key

        if self.api_key:
            self._headers = {"X-Api-Key": self.api_key}

    def test_module(self) -> dict:
        """
        Return information about the user. (Need API key)
        """
        request_result = self._http_request(
            method="GET",
            ok_codes=(200,),
            url_suffix="/users/me",
        )
        return request_result

    def post_sample(self, args: dict[str, Any]) -> dict[str, Any]:
        data = {}

        if description := args.get("description"):
            data["description"] = description
        if tags := args.get("tags"):
            data["tags"] = tags
        if password := args.get("password"):
            data["password"] = password
        if is_private := args.get("is_private"):
            data["is_private"] = is_private

        if url := args.get("url"):
            data["url"] = url

            return self._http_request(method="POST", url_suffix="/scan/url", ok_codes=(200,), data=data)

        elif entry_id := args.get("entry_id"):
            try:
                file_entry = demisto.getFilePath(entry_id)
            except Exception as e:
                demisto.debug(f"Failed to find file entry with id: {entry_id}. Traceback: {traceback.format_exc()}")
                raise DemistoException(f'Failed to find file entry with id:"{entry_id}". got error: {e}')

            with open(file_entry["path"], "rb") as file:
                return self._http_request(
                    method="POST",
                    url_suffix="/scan/file",
                    ok_codes=(200,),
                    data=data,
                    files={"file": (file_entry["name"], file)},
                )
        else:
            raise DemistoException("No file or URL was provided.")

    def get_scan_result(self, flow_id: str) -> dict[str, Any]:
        filters = [
            "filter=general",
            "filter=finalVerdict",
            "filter=allTags",
            "filter=overallState",
            "filter=taskReference",
            "filter=subtaskReferences",
            "filter=allSignalGroups",
        ]

        postfix = "&".join(filters)

        url_suffix = f"/scan/{flow_id}/report?{postfix}"

        response = self._http_request(
            method="GET",
            ok_codes=(200,),
            url_suffix=url_suffix,
        )

        return response

    def get_search_query(self, query_string: str, page: int, page_size: int) -> dict[str, Any]:
        return self._http_request(
            method="GET",
            ok_codes=(200,),
            params={"query": query_string, "page_size": page_size, "page": page},
            url_suffix="/reports/search",
        )


""" HELPER FUNCTIONS """


def build_one_reputation_result(report: dict[str, Any]):
    score = Common.DBotScore.NONE

    final_verdict = report.get("finalVerdict", {})
    threat_level = final_verdict.get("threatLevel", 0)
    # Threatlevel based on https://www.opswat.com/docs/filescan/datasheet/verdict
    if threat_level == 0 or not isinstance(threat_level, int | float):
        score = Common.DBotScore.NONE
    elif threat_level <= 0.25:
        score = Common.DBotScore.GOOD
    elif 0.25 < threat_level < 0.75:
        score = Common.DBotScore.SUSPICIOUS
    elif threat_level >= 0.75:
        score = Common.DBotScore.BAD

    report_file = report.get("file", {})
    report_hash = report_file.get("hash", None)

    dbot_score = Common.DBotScore(
        indicator=report_hash,
        indicator_type=DBotScoreType.FILE,
        integration_name="MetaDefender Aether",
        score=score,
    )

    file = Common.File(name=report_file.get("name"), sha256=report_hash, dbot_score=dbot_score)

    tags = [tag.get("tag", {}).get("name") for tag in report.get("allTags", [])]
    subtasks = [subtask.get("name") for subtask in report.get("subtaskReferences", [])]
    human_readable = {
        "FileName": report_file.get("name"),
        "FileHash": report_hash,
        "FileType": report_file.get("type"),
        "FinalVerdict": report.get("finalVerdict", {}).get("verdict"),
        "Tags": tags,
        "SubtaskReferences": subtasks,
    }
    readable_output = tableToMarkdown("Scan Result (digest):", human_readable)

    results = CommandResults(
        outputs_prefix=f"{INTEGRATION_CONTEXT_NAME}.Analysis",
        outputs_key_field="sha256",
        outputs=report,
        raw_response=report,
        readable_output=readable_output,
        indicator=file,
    )
    return results


def build_search_query_result(analyses: list[dict]) -> list[CommandResults]:
    command_results = []
    for analysis in analyses:
        file_result = analysis.get("file", {})
        hr_analysis = {
            "Id": analysis.get("id"),
            "SampleName": file_result.get("name"),
            "SHA256": file_result.get("sha256"),
            "Verdict": analysis.get("verdict"),
            "State": analysis.get("state"),
            "Date": analysis.get("date"),
            "MIMEType": file_result.get("mime_type"),
            "Type": file_result.get("short_type"),
            "Tags": analysis.get("tags"),
        }

        score = Common.DBotScore.NONE
        verdict = analysis.get("verdict", "UNKNOWN")
        verdict_upper = verdict.upper()
        if verdict_upper in ("BENIGN", "INFORMATIONAL", "TRUSTED", "NO_THREAT_DETECTED", "NO_THREAT"):
            score = Common.DBotScore.GOOD
        elif verdict_upper in ("MALICIOUS", "LIKELY_MALICIOUS", "HIGH_RISK", "CONFIRMED_THREAT"):
            score = Common.DBotScore.BAD
        elif verdict_upper in ("SUSPICIOUS", "LOW_RISK"):
            score = Common.DBotScore.SUSPICIOUS

        dbot_score = Common.DBotScore(
            indicator=file_result.get("sha256"),
            indicator_type=DBotScoreType.FILE,
            integration_name="MetaDefender Aether",
            score=score,
        )

        file_indicator = Common.File(
            name=file_result.get("name"),
            sha256=file_result.get("sha256"),
            dbot_score=dbot_score,
        )

        command_result = CommandResults(
            outputs=analysis,
            readable_output=tableToMarkdown("Analysis Result:", hr_analysis),
            outputs_prefix=f"{INTEGRATION_CONTEXT_NAME}.Analysis",
            outputs_key_field="id",
            indicator=file_indicator,
        )
        command_results.append(command_result)

    return command_results


def sample_submission(client: Client, args: dict[str, Any]) -> PollResult:
    res = client.post_sample(args)
    partial_res = CommandResults(readable_output=f'Waiting for submission "{res.get("flow_id")}" to finish...')
    return PollResult(
        response=CommandResults(
            outputs=res,
            outputs_prefix=f"{INTEGRATION_CONTEXT_NAME}.Submission",
            outputs_key_field="flow_id",
        ),
        args_for_next_run={"flow_id": res.get("flow_id"), **args},
        continue_to_poll=True,
        partial_result=partial_res,
    )


def build_reputation_result(api_reponse: dict[str, Any]):
    reports = api_reponse.get("reports", {})
    command_res_ls = []
    for report in reports.values():
        command_res_ls.append(build_one_reputation_result(report))
    return command_res_ls


def is_valid_pass(api_response: dict[str, Any]):
    if "rejected_files" not in api_response:
        return True
    return all(reject.get("rejected_reason") != "INVALID_PASSWORD" for reject in api_response["rejected_files"])


@polling_function(
    name=demisto.command(),
    timeout=arg_to_number(demisto.args().get("timeout")) or TIMEOUT,
    interval=5,
    poll_message="Polling result",
    requires_polling_arg=False,
)
def polling_submit_command(args: dict[str, Any], client: Client):
    if flow_id := args.get("flow_id"):
        api_response = client.get_scan_result(flow_id)
        successful_response = False

        if api_response.get("allFinished", False):
            successful_response = True

        if successful_response:
            if not is_valid_pass(api_response):
                raise DemistoException("Invalid password!")

            return PollResult(response=build_reputation_result(api_response), continue_to_poll=False)

        return PollResult(
            response=[
                CommandResults(
                    outputs=api_response,
                    outputs_prefix=f"{INTEGRATION_CONTEXT_NAME}.Submission",
                    outputs_key_field="flow_id",
                    readable_output=f'Waiting for submission "{api_response.get("flow_id")}" to finish...',
                ),
            ],
            continue_to_poll=True,
            args_for_next_run={"flow_id": args.get("flow_id"), **args},
        )
    else:
        return sample_submission(client, args)


""" COMMANDS """


def test_module_command(client: Client, *_) -> str:
    """Performs a basic GET request to check if the API is reachable and authentication is successful."""
    results = client.test_module()
    if "accountId" in results:
        return "ok"
    raise DemistoException(f"\nTest module failed, {results}")


def scan_command(client: Client, args: dict[str, Any]):
    return polling_submit_command(args=args, client=client)


def search_query_command(client: Client, args: dict[str, Any]):
    def validate_args():
        if page_size and page_size not in [5, 10, 20]:
            raise DemistoException("Page size value must be 5, 10 or 20")
        if page and page <= 0:
            raise DemistoException("Page must be an integer and greater than 0")
        if limit and (limit <= 0 or limit > 50):
            raise DemistoException("Limit must be an integer and between 1 and 50")

    items = []
    query_string = args.get("query", "")
    page_size = arg_to_number(args.get("page_size"))
    page = arg_to_number(args.get("page"))
    limit = arg_to_number(args.get("limit")) or 10

    validate_args()

    if page_size and not page:
        page = 1
    elif not page_size and page:
        page_size = 10

    if page_size and page:
        items = client.get_search_query(query_string, page, page_size).get("items", [])
    else:
        page_size = 20
        page = 1
        continue_query = True
        while continue_query:
            response = client.get_search_query(query_string, page, page_size)
            actual_items = response.get("items", [])
            items += actual_items

            # Stop if no items returned or we've reached the limit
            if not actual_items or len(items) >= limit:
                continue_query = False
            else:
                page += 1
        items = items[0:limit]

    if items:
        return build_search_query_result(items)
    return CommandResults(readable_output="No Results were found.")


""" COMMANDS MANAGER / SWITCH PANEL """


def main():
    params = demisto.params()
    base_url = params.get("url", "")
    api_key = params.get("api_key", {}).get("password")
    verify = not params.get("insecure", False)
    proxy = params.get("proxy", False)

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

    try:
        client = Client(api_key=api_key, base_url=base_url, verify=verify, proxy=proxy)
        handle_proxy()

        if command == "test-module":
            return_results(test_module_command(client))
        elif command == "metadefender-aether-scan-url":
            return_results(scan_command(client, args))
        elif command == "metadefender-aether-scan-file":
            return_results(scan_command(client, args))
        elif command == "metadefender-aether-search-query":
            return_results(search_query_command(client, args))
        else:
            raise NotImplementedError(f"{command} command is not implemented.")
    except Exception as e:
        err_msg = f"Exception in {INTEGRATION_NAME} : Failed to execute {command} command: [{e!r}]\n{traceback.format_exc()}"
        return_error(err_msg, error=e)


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