Tessian
Tessian is an email security platform that allows organizations to protect their users from inbound phishing threats, outbound data loss (both malicious and accidental) and account takeovers.
Data Enrichment & Threat Intelligence · Tessian
Details
| ID | Tessian |
|---|---|
| Provider | Proofpoint |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Tessian is an email security platform that allows organizations to protect their users from inbound phishing threats, outbound data loss (both malicious and accidental) and account takeovers.
Configure Tessian in Cortex
| Parameter | Description | Required |
|---|---|---|
| Portal URL | The URL that you use to access the Tessian Portal. Please include the extension, e.g. “example.tessian-platform.com” or “example.tessian-app.com” | True |
| API Key | The API Key to use to connect to the Tessian API. This can be found under “Security Integrations” in your Tessian Portal (/0/admin/integrations/api/tokens) | True |
| Trust any certificate (not secure) | False | |
| 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.
tessian-list-events
This command allows you to pull Tessian event data into your XSOAR instance.
Base Command
tessian-list-events
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of events you would like Tessian to return per call. The maximum value is 100. The minimum value is 2. | Optional |
| after_checkpoint | If provided, this parameter must be set to the checkpoint returned by a previous request to this endpoint. When provided, events from the previous request will not be included in the response from this request. If the new checkpoint returned by this request is used in yet another call to this endpoint events from both previous requests will not be included in the response (and so on). By making a number of consecutive requests to this endpoint where the checkpoint from the previous request is provided, clients can get all events from the Tessian platform, even when there are many more than can be returned in a single request. This process is often referred to as pagination. If an event is updated, it will no longer be excluded from subsequent requests. | Optional |
| created_after | Only include events that were created after this time. For example, 2020-02-02T19:00:00Z. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Tessian.EventsOutput.checkpoint | String | This value can be provided to a subsequent request via the after_checkpoint query parameter to ensure that events from this request are not returned in future responses. This allows clients to paginate through results. |
| Tessian.EventsOutput.additional_results | Boolean | True if there may be more events that can be immediately retrieved. |
| Tessian.EventsOutput.results | Unknown | The events returned by this request. |
Command example
!tessian-list-events limit=2
Context Example
{
"Tessian": {
"EventsOutput": {
{
"checkpoint": "string",
"additional_results": true,
"results": [
{
"id": "string",
"type": "string",
"created_at": "2019-08-24T14:15:22Z",
"updated_at": "2019-08-24T14:15:22Z",
"portal_link": "string",
"outbound_email_details": {
"send_time": "2019-08-24T14:15:22Z",
"tessian_action": "WARN",
"message_id": "string",
"tessian_id": "string",
"from": null,
"transmitter": null,
"reply_to": [
null
],
"recipients": {
"to": [
null
],
"cc": [
null
],
"bcc": [
null
],
"all": [
null
],
"count": 0
},
"subject": "string",
"attachments": {
"names": [
"string"
],
"count": 0,
"bytes": 0
}
},
"guardian_details": {
"triggered_filter_ids": [
"string"
],
"type": "MISDIRECTED_EMAIL",
"triggered_filter_names": [
"string"
],
"breach_prevented": true,
"anomalous_recipients": [
null
],
"suggested_recipients": [
null
],
"anomalous_attachments": [
"string"
],
"final_outcome": null,
"user_responses": [
"SEND"
],
"admin_action": "SAFE",
"justifications": [
"string"
],
"user_shown_message": true
}
},
{
"id": "string",
"type": "string",
"created_at": "2019-08-24T14:15:22Z",
"updated_at": "2019-08-24T14:15:22Z",
"portal_link": "string",
"inbound_email_details": {
"received_time": "2019-08-24T14:15:22Z",
"urls": ["strings"],
"attachment_urls": ["strings"],
"message_id": "string",
"tessian_id": "string",
"from": null,
"transmitter": null,
"reply_to": [null],
"recipients": {
"to": [
null
],
"cc": [
null
],
"bcc": [
null
],
"all": [
null
],
"count": 0
},
"subject": "string",
"attachments": {
"names": [
"string"
],
"count": 0,
"bytes": 0
}
},
"defender_details": {
"burst_attack_id": "string",
"intent_types": ["INVOICE"],
"threat_signal_types": ["string"],
"threat_types": ["MATCHED_DENYLIST"],
"spf_result": null,
"dkim_result": null,
"dmarc_result": null,
"sender_location": null,
"users_responded": {
"malicious": 1,
"safe" 0,
"unsure": 0,
"deleted": 0,
},
"admin_label": null,
"quarantine_status_count": {
"admin_quarantine_status_count": {
"not_quarantined": 0,
"quarantined": 1,
"released": 0,
"deleted": 0,
"pending_release": 0,
},
"user_quarantine_status_count": {
"not_quarantined": 0,
"quarantined": 1,
"released": 0,
"deleted": 0,
"pending_release": 0,
},
},
"deletion_status_count": {
"deleted": 1,
"deletion_pending": 0,
"not_deleted": 0,
},
"number_protected_users": 1,
"confidence": HIGH,
"impersonation_type": null,
"impersonated_domain": null,
"impersonated_address": null,
}
}
]
}
}
}
}
Human Readable Output
Tessian Events
Checkpoint: eyJzb3J0X3ZhbHVlcyI6IFsxNjkxNTkyNTc4Mjg4LCAiaW5ib3VuZC1lNWI1MmQyYWQ3ZGQ4MTdhMGRhYmVhZjgzMDhhYWMwMDhkZDY3ZDg1ZTQ3MTk1NDE4NTZmMzRkN2JlY2Y4ZTNlIl0sICJyZXZlcnNlIjogZmFsc2V9
Additional Results: True
Number of events returned: 2
Results
Event ID Event Type Event Created At Event Updated At Portal Link string string 2019-08-24T14:15:22Z 2019-08-24T14:15:22Z string string string 2019-08-24T14:15:22Z 2019-08-24T14:15:22Z string
tessian-release-from-quarantine
This command allows you to release a quarantined emails associated with an event from Tessian.
Base Command
tessian-release-from-quarantine
Input
| Argument Name | Description | Required |
|---|---|---|
| event_id | The ID of the event you would like to release from quarantine. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Tessian.ReleaseFromQuarantineOutput.number_of_actions_attempted | String | The number of users that release from quarantine actions were attempted for. |
| Tessian.ReleaseFromQuarantineOutput.number_of_actions_succeeded | String | The number of users that the release from quarantine action was successful for. |
| Tessian.ReleaseFromQuarantineOutput.results | Unknown | The results of the release action. This is an array of objects mapping the email address of users to the result of the release action. |
| Tessian.ReleaseFromQuarantineOutput.event_id | String | The event ID that was submitted for release. |
Command example
!tessian-release-from-quarantine event_id="string"
Context Example
{
"Tessian": {
"EventsOutput": {
"number_of_actions_attempted": 1,
"number_of_actions_succeeded": 1,
"results": [
{
"user_address": "test_user@example.com",
"error": null,
},
{
"user_address": "test_user2@example.com",
"error": "EMAIL_ALREADY_REMEDIATED",
},
]
}
}
}
Human Readable Output
Release from Quarantine Action
Event ID: string
Number of Release Actions Successfully Initiated: 1
Number of Release Actions Failed: 1
Errors
Recipient Error test_user2@example.com EMAIL_ALREADY_REMEDIATED
tessian-delete-from-quarantine
This command allows you to delete quarantined emails associated with an event from Tessian.
Base Command
tessian-delete-from-quarantine
Input
| Argument Name | Description | Required |
|---|---|---|
| event_id | The ID of the event you would like to delete from quarantine. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Tessian.DeleteFromQuarantineOutput.number_of_actions_attempted | String | The number of users that delete from quarantine actions were attempted for. |
| Tessian.DeleteFromQuarantineOutput.number_of_actions_succeeded | String | The number of users that the delete from quarantine action was successful for. |
| Tessian.DeleteFromQuarantineOutput.results | Unknown | The results of the delete action. This is an array of objects mapping the email address of users to the result of the delete action. |
| Tessian.DeleteFromQuarantineOutput.event_id | String | The event ID that was submitted for deletion. |
Command example
!tessian-delete-from-quarantine event_id="string"
Context Example
{
"Tessian": {
"EventsOutput": {
"number_of_actions_attempted": 1,
"number_of_actions_succeeded": 1,
"results": [
{
"user_address": "test_user@example.com",
"error": null,
},
{
"user_address": "test_user2@example.com",
"error": "EMAIL_ALREADY_REMEDIATED",
},
]
}
}
}
Human Readable Output
Delete from Quarantine Action
Event ID: string
Number of Delete Actions Successfully Initiated: 1
Number of Delete Actions Failed: 1
Errors
Recipient Error test_user2@example.com EMAIL_ALREADY_REMEDIATED
tessian-delete-from-inbox
This command allows you to delete emails associated with a Tessian event from your inbox.
Base Command
tessian-delete-from-inbox
Input
| Argument Name | Description | Required |
|---|---|---|
| event_id | The ID of the event you would like to delete from inbox. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Tessian.DeleteFromQuarantineOutput.number_of_actions_attempted | String | The number of users that delete from inbox actions were attempted for. |
| Tessian.DeleteFromQuarantineOutput.number_of_actions_succeeded | String | The number of users that the delete from inbox action was successful for. |
| Tessian.DeleteFromQuarantineOutput.results | Unknown | The results of the delete action. This is an array of objects mapping the email address of users to the result of the delete action. |
| Tessian.DeleteFromQuarantineOutput.event_id | String | The event ID that was submitted for deletion. |
Command example
!tessian-delete-from-inbox event_id="string"
Context Example
{
"Tessian": {
"EventsOutput": {
"number_of_actions_attempted": 1,
"number_of_actions_succeeded": 1,
"results": [
{
"user_address": "test_user@example.com",
"error": null,
},
{
"user_address": "test_user2@example.com",
"error": "ALREADY_DELETED",
},
]
}
}
}
Human Readable Output
Delete from Inbox Action
Event ID: string
Number of Delete Actions Successfully Initiated: 1
Number of Delete Actions Failed: 1
Errors
Recipient Error test_user2@example.com ALREADY_DELETED
Configuration parameters
url— Portal URL (required)api_key— (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (4)
-
tessian-delete-from-inboxThis command allows you to delete emails associated with a Tessian event from your inbox.
-
tessian-delete-from-quarantineThis command allows you to delete quarantined emails associated with an event from Tessian.
-
tessian-list-eventsThis command allows you to pull Tessian event data into your XSOAR instance.
-
tessian-release-from-quarantineThis command allows you to release a quarantined emails associated with an event from Tessian.
from typing import Any import demistomock as demisto # noqa: F401 import urllib3 from CommonServerPython import * # noqa: F401 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 & Tessian """ CLIENT CLASS """ class Client(BaseClient): """Client class to interact with the service API This Client implements API calls, and does not contain any XSOAR 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. For this implementation, no special attributes defined """ def list_events(self, limit: int | None, after_checkpoint: str | None, created_after: str | None) -> dict[str, Any]: params = assign_params(limit=limit, after_checkpoint=after_checkpoint, created_after=created_after) return self._http_request(method="GET", url_suffix="/api/v1/events", params=params, resp_type="json", ok_codes=(200,)) def release_from_quarantine(self, event_id: str) -> dict[str, Any]: return self._http_request( method="POST", url_suffix="/api/v1/remediation/release_from_quarantine", json_data={"event_id": event_id}, resp_type="json", ok_codes=(200,), ) def delete_from_quarantine(self, event_id: str) -> dict[str, Any]: return self._http_request( method="POST", url_suffix="/api/v1/remediation/delete_from_quarantine", json_data={"event_id": event_id}, resp_type="json", ok_codes=(200,), ) def delete_from_inbox(self, event_id: str) -> dict[str, Any]: return self._http_request( method="POST", url_suffix="/api/v1/remediation/delete_from_inbox", json_data={"event_id": event_id}, resp_type="json", ok_codes=(200,), ) """ HELPER FUNCTIONS """ def format_url(url: str) -> str: """ This function strips the url to make sure it's in the expected format. We want to be working with a url that looks like this: https://domain.tessian.com """ # Remove leading http/https, we do this so that we can add the https:// prefix in the return if url.startswith("http://"): # We should never have insecure portals, but just in case the customer enters their url # with http:// for whatever reason, we'll strip it to add a secure prefix url = url[7:] elif url.startswith("https://"): # Just strip this so that we can ensure it's not there for the rest of the logic. url = url[8:] # Remove trailing slashes if "/" in url: # We will disregard everything after the trailing slash to obtain the portal URL. # This should cover customers who enter their api url by mistake. url = url.split("/")[0] # Add the https:// prefix in the return return f"https://{url}" """ COMMAND FUNCTIONS """ def list_events_command(client: Client, args: dict[str, Any]) -> CommandResults: limit = int(args.get("limit", 100)) after_checkpoint = args.get("after_checkpoint", None) created_after = args.get("created_after", None) results = client.list_events(limit, after_checkpoint, created_after) summary_results = [ { "Event ID": event.get("id"), "Event Type": event.get("type"), "Event Created At": event.get("created_at"), "Event Updated At": event.get("updated_at"), "Portal Link": event.get("portal_link"), } for event in results.get("results", []) ] markdown = "# Tessian Events\n" markdown += f'## Checkpoint: {results.get("checkpoint")}\n' markdown += f'## Additional Results: {results.get("additional_results")}\n' markdown += f'### Number of events returned: {len(results.get("results", []))}\n' markdown += tableToMarkdown( name="Results", t=summary_results, headers=["Event ID", "Event Type", "Event Created At", "Event Update At", "Portal Link"], ) return CommandResults( outputs_prefix="Tessian.EventsOutput", outputs_key_field="checkpoint", outputs=results, raw_response=results, readable_output=markdown, ) def release_from_quarantine_command(client: Client, args: dict[str, Any]) -> CommandResults: event_id = args.get("event_id", None) if event_id is None: raise ValueError("Event ID is required") results = client.release_from_quarantine(event_id) results["event_id"] = event_id markdown = "# Release from Quarantine Action\n" markdown += f"## Event ID: {event_id}\n" markdown += f'## Number of Release Actions Successfully Initiated: {results.get("number_of_actions_succeeded")}\n' failure_count = results.get("number_of_actions_attempted", 0) - results.get("number_of_actions_succeeded", 0) markdown += f"## Number of Release Actions Failed: {failure_count}\n" if results.get("number_of_actions_attempted") != results.get("number_of_actions_succeeded"): failures = [ {"Recipient": failure.get("user_address"), "Error": failure.get("error")} for failure in results.get("results", []) if failure.get("error") is not None ] markdown += tableToMarkdown( name="Errors", t=failures, headers=["Recipient", "Error"], ) return CommandResults( outputs_prefix="Tessian.ReleaseFromQuarantineOutput", outputs_key_field="event_id", outputs=results, raw_response=results, readable_output=markdown, ) def delete_from_quarantine_command(client: Client, args: dict[str, Any]) -> CommandResults: event_id = args.get("event_id", None) if event_id is None: raise ValueError("Event ID is required") results = client.delete_from_quarantine(event_id) results["event_id"] = event_id markdown = "# Delete from Quarantine Action\n" markdown += f"## Event ID: {event_id}\n" markdown += f'## Number of Delete Actions Successfully Initiated: {results.get("number_of_actions_succeeded")}\n' failure_count = results.get("number_of_actions_attempted", 0) - results.get("number_of_actions_succeeded", 0) markdown += f"## Number of Delete Actions Failed: {failure_count}\n" if results.get("number_of_actions_attempted") != results.get("number_of_actions_succeeded"): failures = [ {"Recipient": failure.get("user_address"), "Error": failure.get("error")} for failure in results.get("results", []) if failure.get("error") is not None ] markdown += tableToMarkdown( name="Errors", t=failures, headers=["Recipient", "Error"], ) return CommandResults( outputs_prefix="Tessian.DeleteFromQuarantineOutput", outputs_key_field="event_id", outputs=results, raw_response=results, readable_output=markdown, ) def delete_from_inbox_command(client: Client, args: dict[str, Any]) -> CommandResults: event_id = args.get("event_id", None) if event_id is None: raise ValueError("Event ID is required") results = client.delete_from_inbox(event_id) results["event_id"] = event_id markdown = "# Delete from Inbox Action\n" markdown += f"## Event ID: {event_id}\n" markdown += f'## Number of Delete Actions Successfully Initiated: {results.get("number_of_actions_succeeded")}\n' failure_count = results.get("number_of_actions_attempted", 0) - results.get("number_of_actions_succeeded", 0) markdown += f"## Number of Delete Actions Failed: {failure_count}\n" if results.get("number_of_actions_attempted") != results.get("number_of_actions_succeeded"): failures = [ {"Recipient": failure.get("user_address"), "Error": failure.get("error")} for failure in results.get("results", []) if failure.get("error") is not None ] markdown += tableToMarkdown( name="Errors", t=failures, headers=["Recipient", "Error"], ) return CommandResults( outputs_prefix="Tessian.DeleteFromInboxOutput", outputs_key_field="event_id", outputs=results, raw_response=results, readable_output=markdown, ) def test_module(client: Client) -> str: # pragma: no cover """ Tests API connectivity and authentication' Returning 'ok' indicates that connection to the service is successful. Raises exceptions if something goes wrong. """ try: response = client.list_events(2, None, None) success = demisto.get(response, "checkpoint") if success is None: return f"Unexpected result from the service: expected checkpoint to be a string, response={response!s}" return "ok" except Exception as e: exception_text = str(e).lower() if "forbidden" in exception_text or "authorization" in exception_text: return "Authorization Error: make sure API Key is correctly set" else: raise e """ MAIN FUNCTION """ def main() -> None: # pragma: no cover """main function, parses params and runs command functions :return: :rtype: """ # get the service API url params = demisto.params() args = demisto.args() base_url = format_url(params.get("url")) api_key = params.get("api_key", {}).get("password") # 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: dict = {} headers["Authorization"] = f"API-Token {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() == "tessian-list-events": return_results(list_events_command(client, args)) elif demisto.command() == "tessian-release-from-quarantine": return_results(release_from_quarantine_command(client, args)) elif demisto.command() == "tessian-delete-from-quarantine": return_results(delete_from_quarantine_command(client, args)) elif demisto.command() == "tessian-delete-from-inbox": return_results(delete_from_inbox_command(client, args)) else: raise NotImplementedError(f"Either the command, {demisto.command}, is not supported yet or it does not exist.") # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()