GreatHorn

The only cloud-native security platform that stops targeted social engineering and phishing attacks on cloud email platforms like Office 365 and G Suite.

Network Security · GreatHorn

Details

IDGreatHorn
ProviderSF2 GH Holdings
CategoryNetwork Security
From Version6.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

The only cloud-native security platform that stops targeted social engineering and phishing attacks on cloud email platforms like Office 365 and G Suite.
This integration was integrated and tested with version 2.0 of GreatHorn

Configure GreatHorn in Cortex

Parameter Description Required
url Base URL True
api_version API Version True
apikey API Key True
insecure Trust any certificate (not secure) False
proxy Use system proxy settings 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.

gh-get-message


Return message details for the specified event

Base Command

gh-get-message

Input

Argument Name Description Required
id GreatHorn eventId, multiple values supported via CSV. Required
includeheaders Whether or not to include full message headers in the War Room output. Possible values are: true, false. Default is false. Optional
showalllinks Whether or not to show all links in the War Room output. When false only suspicious and malicious links will be returned to the War Room. Possible values are: true, false. Default is false. Optional

Context Output

Path Type Description
GreatHorn.Message.eventId Number The GreatHorn event id
GreatHorn.Message.origin String Mailbox email was discovered
GreatHorn.Message.status String Has the system taken action on the event
GreatHorn.Message.xMailer Unknown X-Mailer header entry
GreatHorn.Message.sourcePath String GreatHorn discovered domain of sender
GreatHorn.Message.ip String GreatHorn discovered originating ip of sender
GreatHorn.Message.bodyOnlyWhitespace Number Body of email content is only whitespace
GreatHorn.Message.collector Unknown Email provider email discovered
GreatHorn.Message.dkim String dmarc authentication result
GreatHorn.Message.spf String spf authentication result
GreatHorn.Message.contentHash String Hash of email body conten
GreatHorn.Message.violations Number All body of email policy matches
GreatHorn.Message.workflow String Current action of event
GreatHorn.Message.targets String All recepients of the email
GreatHorn.Message.source String Email sender address
GreatHorn.Message.location String Location of sender ip origin
GreatHorn.Message.quarReleasedBy Unknown Who released the quarantined email
GreatHorn.Message.quarDeleted Unknown Has the event been deleted from quarantined
GreatHorn.Message.quarDeletedBy Unknown Who deleted the quarantined email
GreatHorn.Message.quarDenied Unknown Has the event been denied released from quarantined
GreatHorn.Message.subject String Email subject
GreatHorn.Message.xAuthResults Unknown X-Original-Authentication-Results header entry
GreatHorn.Message.dmarc String dmarc authentication result
GreatHorn.Message.returnPath String Return-Path header entry’
GreatHorn.Message.received String Received header entry
GreatHorn.Message.replyTo String Reply-To header entry
GreatHorn.Message.timestamp Date timestamp of the event, usually receivedTime
GreatHorn.Message.flag Number All policies the event matched
GreatHorn.Message.homographScore Number GreatHorn homograph score
GreatHorn.Message.owlScore Number GreatHorn threat score
GreatHorn.Message.anomalyScore Number GreatHorn anomaly score
GreatHorn.Message.authScore Number GreatHorn illegitmacy score
GreatHorn.Message.remediation Unknown Remediation action taken
GreatHorn.Message.quarantined Unknown Has the event been quarantined
GreatHorn.Message.quarExpired Unknown Has the event been expired from quarantined
GreatHorn.Message.quarReleaseRequested Unknown Has the event been requested to be relased from quarantined
GreatHorn.Message.quarReleased Unknown Has the event been released from quarantined
GreatHorn.Message.displayName String Display name of sender
GreatHorn.Message.country String Country of sender ip country
GreatHorn.Message.region String Region of sender ip origin
GreatHorn.Message.authenticationResults String Authentication-Results header entry
GreatHorn.Message.messageId String Message-Id header entry
GreatHorn.Message.headers Object Full set of headers for the email
GreatHorn.Message.links.resolvedUrl Unknown The URL of the resolved link if it points elsewhere
GreatHorn.Message.links.text String The text showing for the link discovered in the body of the email
GreatHorn.Message.links.url String URL of link discovered in body of email
GreatHorn.Message.links.tags String List of tags describing the analysis of the event

Command Example

!gh-get-message id="12345" includeheaders="true"

Context Example

{}

Human Readable Output

GreatHorn event not found

gh-search-message


Search for message based on filtering input

Base Command

gh-search-message

Input

Argument Name Description Required
fields The fields to include in the response. By default, all fields are returned. Optional
filters The criteria to use in filtering search results. This should be input as a dictionary. Optional
limit The maximum number of entries to return per page of results. Default is 10; max is 200. Default is 10. Optional
offset The zero-based offset of the first item in the collection. Default is 0; max is 10000. Optional
sort The field to use in sorting results. Default is eventId. Default is eventId. Optional
sortDir Indicates if the sort direction is ascending or descending. Default is descending. Possible values are: desc, asc. Default is desc. Optional

Context Output

Path Type Description
GreatHorn.Message.eventId Number The GreatHorn event id
GreatHorn.Message.origin String Mailbox email was discovered
GreatHorn.Message.status String Has the system taken action on the event
GreatHorn.Message.xMailer Unknown X-Mailer header entry
GreatHorn.Message.sourcePath String GreatHorn discovered domain of sender
GreatHorn.Message.ip String GreatHorn discovered originating ip of sender
GreatHorn.Message.bodyOnlyWhitespace Number Body of email content is only whitespace
GreatHorn.Message.collector Unknown Email provider email discovered
GreatHorn.Message.dkim String dmarc authentication result
GreatHorn.Message.spf String spf authentication result
GreatHorn.Message.contentHash String Hash of email body conten
GreatHorn.Message.violations Number All body of email policy matches
GreatHorn.Message.workflow String Current action of event
GreatHorn.Message.targets String All recepients of the email
GreatHorn.Message.source String Email sender address
GreatHorn.Message.location String Location of sender ip origin
GreatHorn.Message.quarReleasedBy Unknown Who released the quarantined email
GreatHorn.Message.quarDeleted Unknown Has the event been deleted from quarantined
GreatHorn.Message.quarDeletedBy Unknown Who deleted the quarantined email
GreatHorn.Message.quarDenied Unknown Has the event been denied released from quarantined
GreatHorn.Message.subject String Email subject
GreatHorn.Message.xAuthResults Unknown X-Original-Authentication-Results header entry
GreatHorn.Message.dmarc String dmarc authentication result
GreatHorn.Message.returnPath String Return-Path header entry’
GreatHorn.Message.received String Received header entry
GreatHorn.Message.replyTo String Reply-To header entry
GreatHorn.Message.timestamp Date timestamp of the event, usually receivedTime
GreatHorn.Message.flag Number All policies the event matched
GreatHorn.Message.homographScore Number GreatHorn homograph score
GreatHorn.Message.owlScore Number GreatHorn threat score
GreatHorn.Message.anomalyScore Number GreatHorn anomaly score
GreatHorn.Message.authScore Number GreatHorn illegitmacy score
GreatHorn.Message.remediation Unknown Remediation action taken
GreatHorn.Message.quarantined Unknown Has the event been quarantined
GreatHorn.Message.quarExpired Unknown Has the event been expired from quarantined
GreatHorn.Message.quarReleaseRequested Unknown Has the event been requested to be relased from quarantined
GreatHorn.Message.quarReleased Unknown Has the event been released from quarantined
GreatHorn.Message.displayName String Display name of sender
GreatHorn.Message.country String Country of sender ip country
GreatHorn.Message.region String Region of sender ip origin
GreatHorn.Message.authenticationResults String Authentication-Results header entry
GreatHorn.Message.messageId String Message-Id header entry
GreatHorn.Message.headers Object Full set of headers for the email
GreatHorn.Message.links.resolvedUrl Unknown The URL of the resolved link if it points elsewhere
GreatHorn.Message.links.text String The text showing for the link discovered in the body of the email
GreatHorn.Message.links.url String URL of link discovered in body of email
GreatHorn.Message.links.tags String List of tags describing the analysis of the event

Command Example

!gh-search-message filters="[{\"targets\": [\"penguin@scuftysails.com\"], \"origin\": [\"action@ifttt.com\"]}]"

Context Example

{
    "GreatHorn": {
        "Message": {
            "Message": [],
            "SearchCount": 0
        }
    }
}

Human Readable Output

Events

No entries.

gh-remediate-message


Perform the specified remediation action on message

Base Command

gh-remediate-message

Input

Argument Name Description Required
action The action to take on the given message. Possible values are: archive, banner, delete, label, move, quarantine, delete, removeattachments, review, trash. Required
eventId The GreatHorn event ID. Required
hasButton If true, the banner will include a button enabling the end-user to remove the banner. Default is True. Possible values are: True, False. Default is True. Optional
message The text to display in the email’s banner. Optional
label The name of the label to add. If the label name does not exist, it will be created. Optional
location The target location in the user’s mailbox. If the location does not exist, it will be created. Optional

Context Output

Path Type Description
GreatHorn.Remediation.action String Remediation action requested to be taken on the event
GreatHorn.Remediation.eventId String The Greathorn event ID
GreatHorn.Remediation.reason String Details of error seen if any
GreatHorn.Remediation.success Number Indication if the request was successful

Command Example

!gh-remediate-message action="banner" message="This email may be a phishing attempt" eventId="20128"

Context Example

{
    "GreatHorn": {
        "Remediation": {
            "action": "banner",
            "eventId": "20128",
            "reason": "completed",
            "success": true
        }
    }
}

Human Readable Output

Remediate action banner applied successfully to message 20128

gh-revert-remediate-message


Revert the specified remediation action on the given message

Base Command

gh-revert-remediate-message

Input

Argument Name Description Required
action Remediation action to revert. Possible values are: banner, quarantinerequest, quarantinerelease, quarantinedeny, removeattachments, review. Required
eventId The GreatHorn event ID. Required

Context Output

Path Type Description
GreatHorn.Remediation.action String Remediation action that was reverted
GreatHorn.Remediation.eventId String The GreatHorn event ID
GreatHorn.Remediation.reason String Details of error seen if any
GreatHorn.Remediation.success Number Indication if the request was successful

Command Example

!gh-revert-remediate-message action="banner" eventId="20128"

Context Example

{
    "GreatHorn": {
        "Remediation": {
            "action": "banner",
            "eventId": "20128",
            "reason": "completed",
            "success": true
        }
    }
}

Human Readable Output

Revert action banner applied successfully to message 20128

gh-get-policy


Retrieve details about the policy specified

Base Command

gh-get-policy

Input

Argument Name Description Required
policyid The ID of the policy. Optional

Context Output

Path Type Description
GreatHorn.Policy.name String The user-defined name of the policy
GreatHorn.Policy.enabled Number Whether the policy is enabled
GreatHorn.Policy.config String The match configuration of the policy
GreatHorn.Policy.id Number The ID of the policy
GreatHorn.Policy.description String The user-defined description of the policy

Command Example

!gh-get-policy policyid="16567"

Context Example

{
    "GreatHorn": {
        "Policy": {
            "actions": [
                {
                    "addresses": [
                        ""
                    ],
                    "quarantineNotification": false,
                    "releaseNotification": false,
                    "type": "quarantine"
                }
            ],
            "config": [
                "or",
                [
                    "and",
                    {
                        "opt": "from",
                        "type": "regex",
                        "values": [
                            "asdf2@asdf2.com",
                            "asdf@asdf.com"
                        ]
                    }
                ]
            ],
            "description": "",
            "enabled": true,
            "id": 16567,
            "name": "Penalty box policy"
        }
    }
}

Human Readable Output

Policy

ID Name Enabled Description Actions
16567 Penalty box policy true   quarantine

gh-set-policy


Retrieve details about the policy specified.

Base Command

gh-set-policy

Input

Argument Name Description Required
updatemethod Update method for the given policy. Possible values are: patch, put. Required
policyid The ID of the policy. Required
policyjson Policy defintion or policy change defintion. Input as a dictionary. Required

Context Output

Path Type Description
GreatHorn.Policy.id Number The ID of the policy.

Command Example

!gh-set-policy policyid="16567" updatemethod="patch" policyjson="{\"config\": [\"or\", [\"and\", {\"opt\": \"from\", \"values\": [\"asdf@asdf.com\",\"asdf2@asdf2.com\"], \"type\": \"regex\"}]]}"

Context Example

{
    "GreatHorn": {
        "Policy": {
            "id": "16567",
            "success": true
        }
    }
}

Human Readable Output

Update applied successfully to policy 16567

Configuration parameters

  • url — Base URL (required)
  • api_version — API Version (required)
  • apikey — API Key (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (6)

  • gh-get-message

    Return message details for the specified event

  • gh-get-policy

    Retrieve details about the policy specified

  • gh-remediate-message

    Perform the specified remediation action on message

  • gh-revert-remediate-message

    Revert the specified remediation action on the given message

  • gh-search-message

    Search for message based on filtering input

  • gh-set-policy

    Retrieve details about the policy specified.

import json
import traceback
from typing import Any

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

# Disable insecure warnings
urllib3.disable_warnings()

""" CLIENT CLASS """


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

    This Client implements API calls, and does not contain any Demisto logic.
    Should only do requests and return data.
    It inherits from BaseClient defined in CommonServer Python.
    Most calls use _http_request() that handles proxy, SSL verification, etc.
    """

    def get_policy(self, policy_id: str | None = None) -> dict[str, Any]:
        if policy_id:
            return self._http_request(method="GET", url_suffix=f"/policy/{policy_id}")
        else:
            return self._http_request(method="GET", url_suffix="/policy")

    def set_policy(self, policy_id: str, update_method: str, policy_json: dict[str, Any]) -> dict[str, Any]:
        update_method = update_method.upper()
        return self._http_request(method=update_method, url_suffix=f"/policy/{policy_id}", json_data=policy_json)

    def search_events(self, args: dict | None) -> dict[str, Any]:
        return self._http_request(method="POST", url_suffix="/search/events", json_data=args)

    def remediate_message(self, action: str, action_args: dict | None) -> dict[str, Any]:
        return self._http_request(method="POST", url_suffix=f"/remediation/{action}", json_data=action_args)

    def revert_remediate_message(self, action: str, action_args: dict | None) -> dict[str, Any]:
        return self._http_request(method="POST", url_suffix=f"/remediation/revert/{action}", json_data=action_args)


""" COMMAND FUNCTIONS """


def test_module(client: Client) -> 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: ``Client``
    :param Client: GreatHorn client to use

    :return: 'ok' if test passed, anything else will fail the test.
    :rtype: ``str``
    """

    try:
        client.get_policy()
    except DemistoException as e:
        if "Forbidden" in str(e):
            raise ValueError("Authorization Error: make sure API Key is correctly set")
        else:
            raise e
    return "ok"


def gh_search_message_command(client: Client, args: dict[str, Any]) -> CommandResults:
    fields = argToList(args.get("fields"))
    limit = args.get("limit")
    sort = args.get("sort")
    sortDir = args.get("sortDir")
    offset = args.get("offset")
    filters = json.loads(args.get("filters", "[]"))

    args = {"filters": filters}
    if fields is not None and len(fields) > 0:
        if "eventId" not in fields:
            fields.append("eventId")
        args["fields"] = fields
    if limit is not None:
        args["limit"] = limit
    if offset is not None:
        args["offset"] = offset
    if sort is not None:
        args["sort"] = sort
    if sortDir is not None:
        args["sortDir"] = sortDir

    results = client.search_events(args)
    events = []

    if fields is None or len(fields) == 0:
        for event in results.get("results", []):
            e = {
                "ID": event.get("eventId"),
                "From Address": event.get("source"),
                "Mailbox": event.get("origin"),
                "Return Path": event.get("sourcePath"),
                "Subject": event.get("subject"),
                "Occurred": event.get("timestamp"),
            }

            policy_names = []
            policy_actions = []
            if event.get("flag") is not None:
                for policy_id in event.get("flag", []):
                    policy = client.get_policy(policy_id).get("policy", {})
                    actions = []
                    for action in policy.get("actions"):
                        actions.append(action.get("type"))
                    policy_names.append(policy.get("name"))
                    policy_actions.extend(actions)

                e["Policy Hits"] = policy_names
                e["Policy Actions"] = policy_actions
            if len(event.get("files", [])) > 0:
                e["Has Attachments"] = True
            else:
                e["Has Attachments"] = False
            if len(event.get("links", [])) > 0:
                e["Has Links"] = True
            else:
                e["Has Links"] = False

            events.append(e)

        events_md = tableToMarkdown(
            "Events",
            events,
            [
                "ID",
                "From Address",
                "Mailbox",
                "Return Path",
                "Subject",
                "Policy Hits",
                "Policy Actions",
                "Occurred",
                "Has Attachments",
                "Has Links",
            ],
        )
    else:
        events_md = tableToMarkdown("Events", results.get("results", []), fields)

    result = {"Message": results.get("results"), "SearchCount": results.get("total")}

    return CommandResults(
        readable_output=events_md, outputs_prefix="GreatHorn.Message", outputs_key_field="eventId", outputs=result
    )


def gh_revert_remediate_message_command(client: Client, args: dict[str, Any]) -> CommandResults:
    valid_actions: dict[str, list] = {
        "banner": [],
        "quarantinerequest": [],
        "quarantinerelease": [],
        "quarantinedeny": [],
        "removeattachments": [],
        "review": [],
    }
    if args.get("action", "").lower() not in valid_actions:
        raise ValueError('Invalid action "{}" specified'.format(args.get("action")))
    action = args.get("action", "").lower()
    for arg in valid_actions.get(action, []):
        if args.get(arg) is None:
            raise ValueError(f'Revert action "{action}" requires argument "{arg}" to be specified')

    del args["action"]
    if action == "quarantinerequest":
        action = "quarantine/request"
    if action == "quarantinerelease":
        action = "quarantine"
    if action == "quarantinedeny":
        action = "quarantine/deny"

    results = client.revert_remediate_message(action, args)

    human_readable = ""
    if results.get("success") is True:
        human_readable = "Revert action {} applied successfully to message {}".format(action, args.get("eventId"))
    else:
        if results.get("reason") == "alreadyDone":
            human_readable = "Revert action {} has already been performed on message {}".format(action, args.get("eventId"))
    results["action"] = action
    results["eventId"] = args.get("eventId")

    return CommandResults(
        readable_output=human_readable, outputs_prefix="GreatHorn.Remediation", outputs_key_field="eventId", outputs=results
    )


def gh_remediate_message_command(client: Client, args: dict[str, Any]) -> CommandResults:
    valid_actions: dict[str, list] = {
        "archive": [],
        "banner": ["hasButton", "message"],
        "delete": [],
        "label": ["label"],
        "move": ["location"],
        "quarantine": [],
        "removeattachments": [],
        "review": [],
        "trash": [],
    }
    if args.get("action", "").lower() not in valid_actions:
        raise ValueError('Invalid action "{}" specified'.format(args.get("action")))
    action = args.get("action", "").lower()
    for arg in valid_actions.get(action, []):
        if args.get(arg) is None:
            raise ValueError(f'Remediate action "{action}" requires argument "{arg}" to be specified')

    del args["action"]
    if action != "banner":
        args.pop("hasButton", None)

    results = client.remediate_message(action, args)

    human_readable = ""
    if results.get("success") is True:
        human_readable = "Remediate action {} applied successfully to message {}".format(action, args.get("eventId"))
    else:
        if results.get("reason") == "alreadyDone":
            human_readable = "Remediate action {} has already been performed on message {}".format(action, args.get("eventId"))
    results["action"] = action
    results["eventId"] = args.get("eventId")

    return CommandResults(
        readable_output=human_readable, outputs_prefix="GreatHorn.Remediation", outputs_key_field="eventId", outputs=results
    )


def gh_set_policy_command(client: Client, args: dict[str, Any]) -> CommandResults:
    update_method = args.get("updatemethod", "").lower()
    policy_id = args.get("policyid", "")
    policy_json = json.loads(args.get("policyjson", {}))
    if update_method not in ["patch", "put"]:
        raise ValueError("Invalid updatemethod specified, please use either put or patch.")
    results = client.set_policy(policy_id, update_method, policy_json)
    human_readable = ""
    if results.get("success") is True:
        human_readable = "Update applied successfully to policy {}".format(args.get("policyid"))
    results["id"] = args.get("policyid")
    return CommandResults(
        readable_output=human_readable, outputs_prefix="GreatHorn.Policy", outputs_key_field="id", outputs=results
    )


def gh_get_policy_command(client: Client, args: dict[str, Any]) -> CommandResults:
    policy_ids = argToList(args.get("policyid"))
    results = []
    if policy_ids:
        for policy_id in policy_ids:
            result = client.get_policy(policy_id)
            results.append(result.get("policy"))
    else:
        results = client.get_policy().get("policies", [])

    policies = []
    for r in results:
        if not isinstance(r, dict):  # Make mypy calm down about the policy dict
            continue
        actions = []
        for action in r.get("actions", []):
            actions.append(action.get("type"))
        policy = {
            "ID": r.get("id", ""),
            "Name": r.get("name", ""),
            "Enabled": r.get("enabled", ""),
            "Description": r.get("description", ""),
            "Actions": ",".join(actions),
        }
        policies.append(policy)
    policies_md = tableToMarkdown("Policy", policies, ["ID", "Name", "Enabled", "Description", "Actions"])

    return CommandResults(readable_output=policies_md, outputs_prefix="GreatHorn.Policy", outputs_key_field="id", outputs=results)


def gh_get_message_command(client: Client, args: dict[str, Any]) -> CommandResults:
    ghid = argToList(args.get("id"))
    includeheaders = args.get("includeheaders", "false").lower() == "true"
    showalllinks = args.get("showalllinks", "false").lower() == "true"
    result = client.search_events({"filters": [{"eventId": ghid}]})

    if len(result.get("results", [])) > 0:
        message = result.get("results", [None])[0]

        envelope = {
            "ID": message.get("eventId"),
            "Received": message.get("timestamp"),
            "Mailbox": message.get("origin"),
            "Recipients": message.get("targets"),
            "Subject": message.get("subject"),
            "Display Name": message.get("displayName"),
            "From Address": message.get("source"),
            "From Domain": message.get("source").split("@")[-1],
            "Reply-To": message.get("replyTo"),
            "Return-Path": message.get("sourcePath"),
            "IP Address": message.get("ip"),
        }

        envelope_md = tableToMarkdown("Message Details", envelope, envelope.keys())

        authentication = {
            "SPF": message.get("spf"),
            "DKIM": message.get("dkim"),
            "DMARC": message.get("dmarc"),
            "Authentication Results": message.get("authenticationResults"),
        }

        authentication_md = tableToMarkdown("Message Authentication", authentication, authentication.keys())

        scores = {
            "OWL": message.get("owlScore"),
            "Sender Anomaly": message.get("anomalyScore"),
            "Authenication Risk": message.get("authScore"),
            "Name Spoofing": message.get("homographScore"),
        }

        scores_md = tableToMarkdown("Risk Analysis Factors", scores, scores.keys())

        links = []
        for link in message.get("links"):
            link_dict = {"Text": link.get("text"), "Url": link.get("url"), "Tags": ", ".join(link.get("tags", []))}
            if showalllinks:
                links.append(link_dict)
            else:
                if "suspicious" in link_dict["Tags"] or "malicious" in link_dict["Tags"]:
                    links.append(link_dict)
            # break
        if showalllinks:
            links_md = tableToMarkdown("Links", links, ["Text", "Url", "Tags"])
        else:
            links_md = tableToMarkdown("Suspicious/Malicious Links", links, ["Text", "Url", "Tags"])

        files = []

        for file in message.get("files"):
            f = {"Name": file.get("fileName"), "Type": file.get("fileType"), "SHA256": file.get("fileHash")}
            files.append(f)

        files_md = tableToMarkdown("Files", files, ["Name", "Type", "SHA256"])

        policies = []

        if message.get("flag") is not None:
            for policy_id in message.get("flag"):
                policy = client.get_policy(policy_id).get("policy", {})
                actions = []
                for action in policy.get("actions"):
                    actions.append(action.get("type"))
                p = {"ID": policy.get("id"), "Name": policy.get("name"), "Actions": ",".join(actions)}
                policies.append(p)

        policies_md = tableToMarkdown("Policies", policies, ["ID", "Name", "Actions"])

        headers = []
        msgheaders = message.get("headers")
        for header in message.get("headers"):
            h = {"Name": header, "Value": msgheaders[header]}
            headers.append(h)

        if includeheaders:
            headers_md = tableToMarkdown("Headers", headers, ["Name", "Value"])
        else:
            headers_md = ""

        message_md = envelope_md + authentication_md + scores_md + links_md + files_md + policies_md + headers_md

        return CommandResults(
            readable_output=message_md,
            outputs_prefix="GreatHorn.Message",
            outputs_key_field="eventId",
            outputs=result.get("results", [None])[0],
        )
    else:
        return CommandResults(readable_output="GreatHorn event not found", outputs={})


""" MAIN FUNCTION """


def main():
    """main function, parses params and runs command functions

    :return:
    :rtype:
    """

    api_key = demisto.params().get("apikey")

    # get the service API url
    base_url = urljoin(demisto.params()["url"], demisto.params()["api_version"])

    # if your Client class inherits from BaseClient, SSL verification is
    # handled out of the box by it, just pass ``verify_certificate`` to
    # the Client constructor
    verify_certificate = not demisto.params().get("insecure", False)

    # if your Client class inherits from BaseClient, system proxy is handled
    # out of the box by it, just pass ``proxy`` to the Client constructor
    proxy = demisto.params().get("proxy", False)

    demisto.debug(f"Command being called is {demisto.command()}")
    try:
        headers = {"Authorization": f"Bearer {api_key}"}
        client = Client(base_url=base_url, verify=verify_certificate, headers=headers, proxy=proxy)

        if demisto.command() == "test-module":
            # This is the call made when pressing the integration Test button.
            result = test_module(client)
            return_results(result)

        elif demisto.command() == "gh-get-message":
            return_results(gh_get_message_command(client, demisto.args()))

        elif demisto.command() == "gh-get-policy":
            return_results(gh_get_policy_command(client, demisto.args()))

        elif demisto.command() == "gh-set-policy":
            return_results(gh_set_policy_command(client, demisto.args()))

        elif demisto.command() == "gh-remediate-message":
            return_results(gh_remediate_message_command(client, demisto.args()))

        elif demisto.command() == "gh-revert-remediate-message":
            return_results(gh_revert_remediate_message_command(client, demisto.args()))

        elif demisto.command() == "gh-search-message":
            return_results(gh_search_message_command(client, demisto.args()))

    # Log exceptions and return errors
    except Exception as e:
        demisto.error(traceback.format_exc())  # print the traceback
        return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}")


""" ENTRY POINT """

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