RedCanary

Red Canary collects endpoint data using Carbon Black Response and CrowdStrike Falcon. The collected data is standardized into a common schema which allows teams to detect, analyze and respond to security incidents.

Deception & Breach Simulation · Red Canary

Details

IDRedCanary
ProviderZscaler
CategoryDeception & Breach Simulation
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Overview


Red Canary collects endpoint data using Carbon Black Response and CrowdStrike Falcon. The collected data is standardized into a common schema, which allows teams to detect, analyze and respond to security incidents.

 

Configure RedCanary on Cortex XSOAR


  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for RedCanary.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • Domain (e.g., https://xsoar.my.redcanary.co)
    • API Key
    • Fetch incidents
    • Incident type
    • Use system proxy settings
    • Trust any certificate (not secure)
  4. Click Test to validate the URLs, token, and connection.

Fetched Incidents Data


  • Type: RedCanaryDetection
  • ID
  • Headline
  • Severity
  • Summary
  • Classification
  • Subclassification
  • Time
  • Acknowledged
  • RemediationStatus

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.

  1. Acknowledge a detection: redcanary-acknowledge-detection
  2. Update the remediation state of a detection: redcanary-update-remediation-state
  3. Get a list of detections: redcanary-list-detections
  4. Get a list of endpoints: redcanary-list-endpoints
  5. Execute a playbook on a detection: redcanary-execute-playbook
  6. Get an endpoint by endpoint ID: redcanary-get-endpoint
  7. Get a list of detections: redcanary-get-endpoint-detections
  8. Get a detection by the detection ID: redcanary-get-detection

1. Acknowledge a detection


Mark a detection as acknowledged to inform that it is being handled.

Base Command
redcanary-acknowledge-detection
Input
Argument Name Description Required
id Detection ID. Can be obtained from the context. Required
 
Context Output

There is no context output for this command.

Command Example
!redcanary-acknowledge-detection id=4
Human Readable Output

image

2. Update the remediation state of a detection


Updates the remediation state of a detection.

Base Command
redcanary-update-remediation-state
Input
Argument Name Description Required
id Detection ID. Can be obtained from the context. Required
remediation-state How the detection was remediated. Required
comment Description of why and how the detection was remediated. This is ignored for remediated state. Optional
 
Context Output

There is no context output for this command.

Command Example
!redcanary-update-remediation-state id=4 remediation-state=not_remediated_false_positive comment="updated by DBot"
Human Readable Output

image

3. Get a list of detections


Get a list of confirmed detections.

Base Command
redcanary-list-detections
Input
Argument Name Description Required
page The page results to fetch. Default is first page. Optional
per-page Number of results to return per page Optional
 
Context Output
Path Type Description
RedCanary.Detection.ID number Detection ID
RedCanary.Detection.Headline string Human-readable text about the detection
RedCanary.Detection.Severity string The severity level of the detection (High, medium, or Low)
RedCanary.Detection.Summary string A summary of information about the detection
RedCanary.Detection.Classification string Detection classification
RedCanary.Detection.Subclassification string Detection sub-classification
RedCanary.Detection.Time date The time the detection was confirmed as a threat by Red Canary
RedCanary.Detection.Acknowledged boolean Whether or not the detection is acknowledged
RedCanary.Detection.RemediationStatus string The state the detection is currently in
RedCanary.Detection.Reason string The reason for the detection
RedCanary.Detection.EndpointUserID number The ID of the user in the infected endpoint
RedCanary.Detection.EndpointID number The ID of the endpoint infected in the detection
Endpoint.Hostname string Fully-qualified endpoint hostname
Endpoint.ID string Endpoint ID
Endpoint.IPAddress unknown IP address of the endpoint
Endpoint.MACAddress unknown MAC address of the endpoint
Endpoint.OS string The operating system used by the endpoint
Endpoint.OSVersion string The complete operating system version identifier used by the endpoint
Endpoint.Memory number Physical memory of the endpoint in bytes
Endpoint.IsIsolated boolean Whether or not the endpoint is currently network isolated
Endpoint.IsDecommissioned boolean Whether or not the endpoint is decommissioned
Account.Username string Endpoint user name
Account.Hostname string Endpoint host name
 
Command Example
!redcanary-list-detections page=1 per-page=1
Context Example
{
    "Account": {
        "Username": "administrator",
        "Hostname": "xsoar-host"
    },
    "Endpoint": {
        "MACAddress": [
            "08:29:42:f0:b3:7c"
        ],
        "OS": "Windows",
        "Hostname": "xsoar-host",
        "IsDecommissioned": true,
        "IsIsolated": true,
        "OSVersion": "Windows Server 2016 Server Datacenter, 64-bit",
        "IPAddress": [
            "181.61.87.106"
        ],
        "ID": 2
    },
    "RedCanary": {
        "Detection": [
            {
                "Severity": "medium",
                "Classification": "Suspicious Activity",
                "Acknowledged": true,
                "Subclassification": [
                    "Process"
                ],
                "Summary": "The Windows Task Scheduler Configuration Tool was launched via command prompt and leveraged to create multiple suspicious scheduled tasks.",
                "Headline": "[DEM-6] Suspicious Activity (Process)",
                "Time": "2018-09-12T15:38:17Z",
                "Type": "RedCanaryDetection",
                "ID": 6
            },
        ]
    },
}
Human Readable Output

image

4. Get a list of endpoints


Returns a list of endpoints.

Base Command
redcanary-list-endpoints
Input
Argument Name Description Required
page Page number in the query response. Default is 1. Optional
per-page Number of returned results per page. Default is 50 Optional
 
Context Output
Path Type Description
Endpoint.Hostname string Fully-qualified endpoint hostname
Endpoint.ID string Endpoint ID
Endpoint.IPAddress unknown IP address of the endpoint
Endpoint.MACAddress unknown MAC address of the endpoint
Endpoint.OS string The operating system used by the endpoint
Endpoint.OSVersion string The complete operating system version identifier used by the endpoint
Endpoint.Memory number Physical memory of the endpoint in bytes
Endpoint.IsIsolated boolean Whether or not the endpoint is currently network isolated
Endpoint.IsDecommissioned boolean Whether or not the endpoint is decommissioned
 
Command Example
!redcanary-list-endpoints
Context Example
{
    "EndPoint": [
        {
            "MACAddress": [
                "06:79:02:e0:b0:7c"
            ],
            "OS": "Windows",
            "Hostname": "ec2amaz-tmlcd45",
            "IsDecommissioned": true,
            "IsIsolated": true,
            "OSVersion": "Windows Server 2016 Server Datacenter, 64-bit",
            "IPAddress": [
                "172.31.37.106"
            ],
            "ID": 2
        },
}
Human Readable Output

image

5. Execute a playbook on a detection


Execute a predefined playbook on a detection.

Base Command
redcanary-execute-playbook
Input
Argument Name Description Required
playbook-id Playbook ID of the playbook to execute Optional
detection-id Detection ID of the detection to execute the playbook on. Can be retrieved from the context. Required
playbook-name Playbook name of the playbook to execute. If the playbook ID is specified, this is argument is ignored. Optional
 
Context Output

There is no context output for this command.

Command Example
!redcanary-execute-playbook detection-id=4 playbook-name=Isolate
Human Readable Output

image

6. Get an endpoint by the endpoint ID


Get an endpoint by the endpoint ID.

Base Command
redcanary-get-endpoint
Input
Argument Name Description Required
id Endpoint ID Required
 
Context Output
Path Type Description
Endpoint.Hostname string Fully-qualified endpoint hostname
Endpoint.ID string Endpoint ID
Endpoint.IPAddress unknown IP address of the endpoint
Endpoint.MACAddress unknown MAC address of the endpoint
Endpoint.OS string The operating system used by the endpoint
Endpoint.OSVersion string The complete operating system version identifier used by the endpoint
Endpoint.Memory number Physical memory of the endpoint in bytes
Endpoint.IsIsolated boolean Whether or not the endpoint is currently network isolated
Endpoint.IsDecommissioned boolean Whether or not the endpoint is decommissioned
 
Command Example
!redcanary-get-endpoint id=2
Context Example
{
    "EndPoint": [
        {
            "MACAddress": [
                "06:79:02:e0:b0:7c"
            ],
            "OS": "Windows",
            "Hostname": "ec2amaz-tmlcd45",
            "IsDecommissioned": true,
            "IsIsolated": true,
            "OSVersion": "Windows Server 2016 Server Datacenter, 64-bit",
            "IPAddress": [
                "172.31.37.106"
            ],
            "ID": 2
        },
}
Human Readable Output

image

7. Get a list of detections


Get a list of detections associated with the endpoint.

Base Command
redcanary-get-endpoint-detections
Input
Argument Name Description Required
id Endpoint ID Required
 
Context Output
Path Type Description
RedCanary.Detection.ID number Detection ID
RedCanary.Detection.Headline string Human-readable text about the detection
RedCanary.Detection.Severity string The severity of the detection (High, medium, or Low)
RedCanary.Detection.Summary string A summary of information about the detection
RedCanary.Detection.Classification string Detection classification
RedCanary.Detection.Subclassification string Detection subclassification
RedCanary.Detection.Time date The time the detection was confirmed as a threat by Red Canary
RedCanary.Detection.Acknowledged boolean Whether or not the detection is acknowledged
RedCanary.Detection.RemediationStatus string The state the detection is currently in
Endpoint.Hostname string Fully-qualified endpoint hostname
Endpoint.ID string Endpoint ID
Endpoint.IPAddress unknown IP address of the endpoint
Endpoint.MACAddress unknown MAC address of the endpoint
Endpoint.OS string The operating system used by the endpoint
Endpoint.OSVersion string The complete operating system version identifier used by the endpoint
Endpoint.Memory number Physical memory of the endpoint in bytes
Endpoint.IsIsolated boolean Whether or not the endpoint is currently network isolated
Endpoint.IsDecommissioned boolean Whether or not the endpoint is decommissioned
Account.Username string Endpoint user name
Account.Hostname string Endpoint host name
 
Command Example
!redcanary-get-endpoint-detections id=4
Context Example
{
    "Account": {
        "Username": "administrator",
        "Hostname": "xsoar-host"
    },
    "Endpoint": {
        "MACAddress": [
            "08:29:42:f0:b3:7c"
        ],
        "OS": "Windows",
        "Hostname": "xsoar-host",
        "IsDecommissioned": true,
        "IsIsolated": true,
        "OSVersion": "Windows Server 2016 Server Datacenter, 64-bit",
        "IPAddress": [
            "181.61.87.106"
        ],
        "ID": 2
    },
    "RedCanary": {
        "Detection": [
            {
                "Severity": "medium",
                "Classification": "Suspicious Activity",
                "Acknowledged": true,
                "Subclassification": [
                    "Process"
                ],
                "Summary": "The Windows Task Scheduler Configuration Tool was launched via command prompt and leveraged to create multiple suspicious scheduled tasks.",
                "Headline": "[DEM-6] Suspicious Activity (Process)",
                "Time": "2018-09-12T15:38:17Z",
                "Type": "RedCanaryDetection",
                "ID": 6
            },
        ]
    },
}
Human Readable Output

image

8. Get a detection by the detection ID


Returns a detection by the detection ID.

Base Command
redcanary-get-detection
Input
Argument Name Description Required
id Detection ID Required
 
Context Output
Path Type Description
RedCanary.Detection.ID number Detection ID
RedCanary.Detection.Headline string Human-readable text about the detection
RedCanary.Detection.Severity string The severity of the detection. High, medium, or low
RedCanary.Detection.Summary string A summary of information about the detection
RedCanary.Detection.Classification string Detection classification
RedCanary.Detection.Subclassification string Detection subclassification
RedCanary.Detection.Time date The time the detection was confirmed as a threat by Red Canary
RedCanary.Detection.Acknowledged boolean Whether or not the detection is acknowledged
RedCanary.Detection.RemediationStatus string The state the detection is currently in
RedCanary.Detection.Reason string The reason for the detection
RedCanary.Detection.EndpointUserID number The ID of the user in the infected endpoint
RedCanary.Detection.EndpointID number The ID of the endpoint infected in the detection
Endpoint.Hostname string Fully-qualified endpoint hostname
Endpoint.ID string Endpoint ID
Endpoint.IPAddress unknown IP address of the endpoint
Endpoint.MACAddress unknown MAC address of the endpoint
Endpoint.OS string The operating system used by the endpoint
Endpoint.OSVersion string The complete operating system version identifier used by the endpoint
Endpoint.Memory number Physical memory of the endpoint in bytes
Endpoint.IsIsolated boolean Whether or not the endpoint is currently network isolated
Endpoint.IsDecommissioned boolean Whether or not the endpoint is decommissioned
Account.Username string Endpoint user name
Account.Hostname string Endpoint host name
Domain.Name string Domain name
File.Name string File name
File.MD5 string File MD5
File.SHA256 string File SHA-256
File.Path string File path in the endpoint
File.Extension string File extension
IP.Address string IP Address
IP.Port string Port
Process.Name string Process name
Process.Path string Process binary path
Process.MD5 string Binary MD5
Process.SHA256 string Binary SHA-256
Process.StartTime date Process execution time
Process.CommandLine string Process command line
 
Command Example
!redcanary-get-detection id=4
Context Example
{
    "Account": {
        "Username": "administrator",
        "Hostname": "ec2amaz-tmlcd45"
    },
    "Endpoint": {
        "MACAddress": [
            "06:79:02:e0:b0:7c"
        ],
        "OS": "Windows",
        "Hostname": "ec2amaz-tmlcd45",
        "IsDecommissioned": false,
        "IsIsolated": true,
        "OSVersion": "Windows Server 2016 Server Datacenter, 64-bit",
        "IPAddress": [
            "172.31.37.106"
        ],
        "ID": 4
    },
    "Process": {
        "Path": "c:\\windows\\system32\\regsvr32.exe",
        "CommandLine": "regsvr32.exe  /s /u /i:https://raw.githubusercontent.com/redcanaryco/atomic-red-team/master/atomics/T1117/RegSvr32.sct scrobj.dll",
        "Name": "c:\\windows\\system32\\regsvr32.exe",
        "StartTime": "2018-09-12T11:30:23Z",
        "MD5": "8cf9086be38a15e905924b4a45d814d9"
    },
    "IP": {
        "Port": 443,
        "Address": "151.101.12.133"
    },
    "Domain": {
        "Name": "raw.githubusercontent.com"
    },
    "RedCanary": {
        "Detection": {
            "Severity": "medium",
            "Classification": "Malicious Software",
            "Headline": "[DEM-4] Malicious Software",
            "Acknowledged": false,
            "Summary": "An instance of `Regsvr32` was leveraged to download Atomic Red Team code and execute it on this system.",
            "Time": "2018-09-12T11:15:55Z",
            "Type": "RedCanaryDetection",
            "ID": 4
        }
    },
    "File": [
        {
            "Path": "c:\\windows\\explorer.exe",
            "Name": "c:\\windows\\explorer.exe",
            "Extension": ".exe",
            "MD5": "a7190e945f2d00fe4af31289f4e64b8c"
        },
    ]
}
Human Readable Output

image

Configuration parameters

  • domain — Domain (for example, https://demisto.my.redcanary.co) (required)
  • api_key — API Key
  • api_key_creds
  • isFetch — Fetch incidents
  • isFetchAcknowledged — Fetch acknowledged incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • fetch_time — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)
  • fetch_limit — Maximum number of incidents to pull per fetch

Commands (8)

  • redcanary-acknowledge-detection

    Mark a detection as acknowledged to inform that it's being handled.

  • redcanary-execute-playbook

    Execute a predefined playbook on a detection.

  • redcanary-get-detection

    Get a detection by unique identifier.

  • redcanary-get-endpoint

    Get an endpoint by unique identifier.

  • redcanary-get-endpoint-detections

    Get a list of detections associated with the endpoint.

  • redcanary-list-detections

    Get a list of confirmed detections.

  • redcanary-list-endpoints

    Get a list of endpoints.

  • redcanary-update-remediation-state

    Update the remediation state of a detection.

import demistomock as demisto
import json
import RedCanary
from freezegun import freeze_time
from datetime import datetime

last_run_dict = {"time": "2019-12-13T17:23:22Z", "last_event_ids": []}
latest_time_of_occurrence_of_incidents1 = "2019-12-30T22:00:50Z"
latest_time_of_occurrence_of_incidents2 = "2020-12-25T02:07:37Z"
number_of_incidents = 3


class Mocker:
    # this mocker will return a different response in every following call
    def __init__(self):
        self.counter = 0
        self.res1 = res1
        self.res2 = res2

    def execute(self):
        self.counter = self.counter + 1
        if self.counter % 2 == 0:
            return self.res1
        return self.res2


with open("./TestData/incidents.json") as f:
    data = json.load(f)

with open("TestData/incidents2.json") as f2:
    data2 = json.load(f2)

with open("./TestData/get_full_timeline_raw1.json") as f3:
    res1 = json.load(f3)

with open("./TestData/get_full_timeline_raw2.json") as f4:
    res2 = json.load(f4)


def test_fetch_when_last_run_is_time(mocker):
    """Unit test
    Given
    - raw response of the http request
    When
    - fetching incidents
    Then
    - check the number of incidents that are being created
    check that the time in last_run is the on of the latest incident
    """
    mocker.patch.object(demisto, "incidents")
    mocker.patch.object(demisto, "setLastRun")
    mocker.patch.object(demisto, "getLastRun")
    mocker.patch.object(RedCanary, "get_unacknowledged_detections", return_value=data["data"])
    mocker.patch.object(RedCanary, "get_full_timeline", return_value=None)
    last_run, incidents = RedCanary.fetch_incidents(last_run_dict, 2)

    assert len(incidents) == number_of_incidents
    assert last_run["time"] == latest_time_of_occurrence_of_incidents1


def test_get_endpoint_context():
    """
    Given:
     - Endpoint data with missing MAC address details (None)

    When:
     - Listing endpoints and generating endpoint standard context

    Then:
     - Ensure get_endpoint_context runs successfully
     - Verify expected endpoint standard context is returned
    """
    endpoint = [
        {
            "id": "1234",
            "attributes": {
                "hostname": "hostname1",
                "platform": "OS X",
                "operating_system": "Mac OSX 10.14.6",
                "is_isolated": False,
                "is_decommissioned": False,
                "endpoint_network_addresses": [
                    {
                        "attributes": {
                            "ip_address": {
                                "attributes": {
                                    "ip_address_matches_rfc_1918?": True,
                                    "ip_address_reverse_dns": None,
                                    "ip_address_defanged": "192.169.1[.]16",
                                    "ip_address_is_link_local?": False,
                                    "ip_address_matches_rfc_4193?": False,
                                    "ip_address": "192.169.1.16",
                                },
                                "type": "primitives.IpAddress",
                            },
                            "mac_address": {"attributes": {"address": "g9:gg:c2:0f:3d:5f"}, "type": "primitives.MacAddress"},
                        }
                    },
                    {
                        "attributes": {
                            "ip_address": {
                                "attributes": {
                                    "ip_address_matches_rfc_1918?": False,
                                    "ip_address_reverse_dns": None,
                                    "ip_address_defanged": "100.144.153[.]501",
                                    "ip_address_is_link_local?": False,
                                    "ip_address_matches_rfc_4193?": False,
                                    "ip_address": "100.144.153.501",
                                },
                                "type": "primitives.IpAddress",
                            },
                            "mac_address": None,
                        }
                    },
                ],
            },
        }
    ]

    endpoint_context = RedCanary.get_endpoint_context(endpoint)
    assert endpoint_context == [
        {
            "Hostname": "hostname1",
            "ID": "1234",
            "IPAddress": ["192.169.1.16", "100.144.153.501"],
            "IsDecommissioned": False,
            "IsIsolated": False,
            "MACAddress": ["g9:gg:c2:0f:3d:5f"],
            "OS": "OS X",
            "OSVersion": "Mac OSX 10.14.6",
        }
    ]


def test_detections_to_entry_without_endpoint(mocker):
    """
    Given:
     - detection data with missing 'affected_endpoint' details

    Then:
     - Ensure detections_to_entry runs successfully
     - Verify expected result is returned
    """
    detection_data = [
        {
            "type": "Detection",
            "id": 1,
            "attributes": {
                "headline": "Suspicious Activity",
                "confirmed_at": "2023-09-18T21:32:52.039Z",
                "summary": "A user made a series of API calls to expose instance passwords.",
                "severity": "high",
                "last_activity_seen_at": "2023-09-18T20:47:23.609Z",
                "classification": {"superclassification": "Suspicious Activity", "subclassification": ["Reconnaissance"]},
                "time_of_occurrence": "2023-09-18T20:47:23.609Z",
                "last_acknowledged_at": None,
                "last_acknowledged_by": None,
                "associated_releasable_intelligence_profiles": [],
            },
            "hostname": None,
            "username": "username",
            "relationships": {
                "related_endpoint_user": {
                    "links": {"related": "https://example.com/openapi/v3/endpoint_users/11111111"},
                    "data": {"type": "endpoint_user", "id": 11111111},
                }
            },
            "links": {
                "self": {"href": "https://example.com/openapi/v3/detections/1"},
                "activity_timeline": {"href": "https://example.com/openapi/v3/detections/1/timeline"},
                "detectors": {"href": "https://example.com/openapi/v3/detections/1/detectors"},
            },
        }
    ]

    expected_result = {
        "Type": "RedCanaryDetection",
        "ID": 1,
        "Headline": "Suspicious Activity",
        "Severity": "high",
        "Summary": "A user made a series of API calls to expose instance passwords.",
        "Classification": "Suspicious Activity",
        "Subclassification": ["Reconnaissance"],
        "Time": "2023-09-18T20:47:23Z",
        "Acknowledged": True,
        "RemediationStatus": "",
        "Reason": "",
        "EndpointID": "",
        "EndpointUserID": 11111111,
    }
    # Call the function with the sample data
    endpoint_users = [{"Username": "username"}]
    mocker.patch.object(RedCanary, "get_endpoint_user_context", return_value=endpoint_users)
    result = RedCanary.detections_to_entry(detection_data)
    # Assert that the result is as expected
    assert result["Contents"][0] == expected_result


def test_detections_to_entry_without_relationships(mocker):
    """
    Given:
     - detection data with missing 'relationship' details

    Then:
     - Ensure detections_to_entry runs successfully
    """
    detection_data = [
        {
            "type": "Detection",
            "id": 1,
            "attributes": {
                "headline": "Suspicious Activity",
                "confirmed_at": "2023-09-18T21:32:52.039Z",
                "summary": "A user made a series of API calls to expose instance passwords.",
                "severity": "high",
                "last_activity_seen_at": "2023-09-18T20:47:23.609Z",
                "classification": {"superclassification": "Suspicious Activity", "subclassification": ["Reconnaissance"]},
                "time_of_occurrence": "2023-09-18T20:47:23.609Z",
                "last_acknowledged_at": None,
                "last_acknowledged_by": None,
                "associated_releasable_intelligence_profiles": [],
            },
        }
    ]

    expected_result = {
        "Type": "RedCanaryDetection",
        "ID": 1,
        "Headline": "Suspicious Activity",
        "Severity": "high",
        "Summary": "A user made a series of API calls to expose instance passwords.",
        "Classification": "Suspicious Activity",
        "Subclassification": ["Reconnaissance"],
        "Time": "2023-09-18T20:47:23Z",
        "Acknowledged": True,
        "RemediationStatus": "",
        "Reason": "",
        "EndpointID": "",
        "EndpointUserID": "",
    }
    # Call the function with the sample data
    endpoint_users = [{"Username": "username"}]
    mocker.patch.object(RedCanary, "get_endpoint_user_context", return_value=endpoint_users)
    result = RedCanary.detections_to_entry(detection_data)
    # Assert that the result is as expected
    assert result["Contents"][0] == expected_result


def test_fetch_multiple_times_when_already_fetched_incident_keep(mocker):
    """Unit test
    Given
    - raw response of the http request
    When
    - fetching incidents couple of times
    Then
    - fetch for 3 times
    in the first time makes sure 3 incidents were created
    in the others there the same incidents are being fetched as data but no new incidents are being created
    """
    mocker.patch.object(demisto, "incidents")
    mocker.patch.object(demisto, "setLastRun")

    mocker.patch.object(demisto, "getLastRun")
    mocker.patch.object(RedCanary, "get_unacknowledged_detections", return_value=data["data"])
    mocker.patch.object(RedCanary, "get_full_timeline", return_value=None)

    # fetching for the first time
    last_run, incidents = RedCanary.fetch_incidents(last_run_dict, 2)
    assert len(incidents) == 3
    assert last_run["time"] == "2019-12-30T22:00:50Z"

    # fetching for the second time
    last_run, incidents = RedCanary.fetch_incidents(last_run, 2)
    assert len(incidents) == 0
    assert last_run["time"] == "2019-12-30T22:00:50Z"

    # fetching for the third time
    last_run, incidents = RedCanary.fetch_incidents(last_run, 2)
    assert len(incidents) == 0
    assert last_run["time"] == "2019-12-30T22:00:50Z"


def test_fetch_multiple_times_with_new_incidents(mocker):
    """Unit test
    Given
    - raw response of the http request
    When
    - fetching incidents couple of times
    fetch incidents for the first time - as in previous tests
    fetch again with new incidents
    Then
    one of the incidents in the new fetch was shown before
    makes sure it is not created again
    the last_run in getting updated
    """
    mocker.patch.object(demisto, "incidents")
    mocker.patch.object(demisto, "setLastRun")

    mocker.patch.object(demisto, "getLastRun")
    mocker.patch.object(RedCanary, "get_unacknowledged_detections", return_value=data["data"])
    mocker.patch.object(RedCanary, "get_full_timeline", return_value=None)

    # fetching for the first time
    last_run, incidents = RedCanary.fetch_incidents(last_run_dict, 2)
    assert len(incidents) == 3
    assert last_run["time"] == "2019-12-30T22:00:50Z"

    # fetching for the second time
    mocker.patch.object(RedCanary, "get_unacknowledged_detections", return_value=data2["data"])
    last_run, incidents = RedCanary.fetch_incidents(last_run, 2)
    # only one incidents is being created out of the 2 that were fetched
    assert len(incidents) == 1
    assert last_run["time"] == latest_time_of_occurrence_of_incidents2


def test_def_get_full_timeline(mocker):
    """Unit test
    Given
    - raw response of the http request from 2 different requests
    - the data is the same but the page number is different
    When
    - keep getting the same data in different pages
    Then
    make sure the loop stops and doesn't cause a timeout
    """
    response = Mocker()
    mocker.patch.object(RedCanary, "http_get", return_value=response.execute())
    activities = RedCanary.get_full_timeline(1)
    result1 = response.execute()
    result2 = response.execute()
    # make sure the results are not the same, they are from different pages, but the data is
    assert result1 != result2
    assert result1["data"] == result2["data"]
    # make sure the loop ends
    assert activities


def util_load_json(path):
    with open(path, encoding="utf-8") as f:
        return json.load(f)


def test_get_detection_command_includes_domain(mocker):
    """
    Given
    - Raw response of get_full_timeline function
    - Response from get_detection command.
    When
    - Running the get_detection command
    Then
    Make sure that the Domain info exists in the context data under the Domain key.
    """
    detection_data = util_load_json("TestData/detection.json")
    timeline_data = util_load_json("TestData/detection_timeline.json")
    mocker.patch("RedCanary.demisto.args", return_value={"id": "87"})

    def http_get_side_effect(url, params=None):
        if url == "/detections/87":
            return {"data": [detection_data]}
        elif url.startswith(f"/detections/{detection_data['id']}/timeline"):
            return timeline_data
        return {}

    mocker.patch("RedCanary.http_get", side_effect=http_get_side_effect)

    # Call the command
    result = RedCanary.get_detection_command()

    # Validate the Domain context was returned
    entry_context = result.get("EntryContext", {})
    assert "Domain(val.Username == obj.Username)" in entry_context
    assert isinstance(entry_context["Domain(val.Username == obj.Username)"], list)
    assert any("example.domain" in json.dumps(d) for d in entry_context["Domain(val.Username == obj.Username)"])


def test_get_detection_command_includes_process_and_files(mocker):
    """
    Given
    - Raw response of get_full_timeline function
    - Response from get_detection command.
    When
    - Running the get_detection command
    Then
    Make sure that the Process and File info exists in the context data under the Domain key.
    """
    detection_data = util_load_json("TestData/detection2.json")
    timeline_data = util_load_json("TestData/detection_timeline2.json")
    mocker.patch("RedCanary.demisto.args", return_value={"id": "106"})

    def http_get_side_effect(url, params=None):
        if url == "/detections/106":
            return {"data": [detection_data]}
        elif url.startswith(f"/detections/{detection_data['id']}/timeline"):
            return timeline_data
        return {}

    mocker.patch("RedCanary.http_get", side_effect=http_get_side_effect)

    # Call the command
    result = RedCanary.get_detection_command()

    # Validate the Domain context was returned
    entry_context = result.get("EntryContext", {})
    # print(entry_context)
    assert "Process(val.Username == obj.Username)" in entry_context
    assert "File(val.Name == obj.Name)" in entry_context
    # Ensure they are lists
    processes = entry_context["Process(val.Username == obj.Username)"]
    files = entry_context["File(val.Name == obj.Name)"]
    assert isinstance(processes, list)
    assert isinstance(files, list)

    # Ensure they are not empty
    assert len(processes) > 0, "Expected at least one process in context, got empty list"
    assert len(files) > 0, "Expected at least one file in context, got empty list"


with open("./TestData/detections.json") as f:
    detections_data = json.load(f)


@freeze_time("2023-10-31 10:00:00")
def test_get_unacknowledged_detections(mocker):
    """Unit test
    Given
    - a list of detections from the list_detections command
    When
    - getting unacknowledged detections
    Then
    - make sure only the unacknowledged detections are returned
    """
    # Test with only unacknowledged detections
    mocker.patch.object(RedCanary, "list_detections", side_effect=[detections_data["unacknowledged_detection"]["data"], []])
    results = list(RedCanary.get_unacknowledged_detections(t=datetime.now()))
    assert len(results) == 1
    assert results[0]["id"] == "1"

    # Test with only acknowledged detections
    mocker.patch.object(RedCanary, "list_detections", side_effect=[detections_data["acknowledged_detection"]["data"], []])
    results = list(RedCanary.get_unacknowledged_detections(t=datetime.now()))
    assert len(results) == 0

    # Test with a mix of acknowledged and unacknowledged detections
    mocker.patch.object(RedCanary, "list_detections", side_effect=[detections_data["mixed_detections"]["data"], []])
    results = list(RedCanary.get_unacknowledged_detections(t=datetime.now()))
    assert len(results) == 1
    assert results[0]["id"] == "1"

    # Test with a mix of detections and is_fetch_acknowledged=True
    mocker.patch.object(RedCanary, "list_detections", side_effect=[detections_data["mixed_detections"]["data"], []])
    results = list(RedCanary.get_unacknowledged_detections(t=datetime.now(), is_fetch_acknowledged=True))
    assert len(results) == 2

    # Test with no detections
    mocker.patch.object(RedCanary, "list_detections", return_value=[])
    results = list(RedCanary.get_unacknowledged_detections(t=datetime.now()))
    assert len(results) == 0


def test_process_timeline(mocker):
    """Unit test
    Given
    - a detection ID and mock timeline data containing process execution events
    When
    - processing the timeline to extract activities, files, and processes
    Then
    - verify that the timeline is correctly parsed and structured data is returned
    - ensure activities contain proper time, type, and notes information
    - confirm files and processes are extracted with expected attributes
    """
    detection_id = "12345"

    mock_timeline_data = [
        {
            "type": "activity_timelines.EventActivityOccurred",
            "attributes": {
                "occurred_at": "2023-10-31T10:00:00Z",
                "analyst_notes": "Process execution detected",
                "type": "process_activity_occurred",
                "process_execution": {
                    "attributes": {
                        "operating_system_process": {
                            "attributes": {
                                "image": {
                                    "type": "primitives.File",
                                    "attributes": {"md5": None, "sha256": None, "path": None, "file_type": None},
                                },
                                "command_line": {
                                    "type": "primitives.ProcessCommandLine",
                                    "attributes": {"command_line": "", "command_line_decoded": "", "identified_encodings": []},
                                },
                                "started_at": "2023-10-31T09:59:00Z",
                            }
                        }
                    }
                },
            },
        }
    ]

    # Mock the get_full_timeline function
    mocker.patch.object(RedCanary, "get_full_timeline", return_value=mock_timeline_data)

    # Call the function
    activities, _, files, _, processes = RedCanary.process_timeline(detection_id)

    # Verify that get_full_timeline was called with correct detection_id
    RedCanary.get_full_timeline.assert_called_once_with(detection_id)

    # Verify activities
    assert len(activities) == 1

    # Check first activity (process)
    assert activities[0]["Time"] == "2023-10-31T10:00:00Z"
    assert activities[0]["Type"] == "process activity occurred"
    assert activities[0]["Notes"] == "Process execution detected"
    assert activities[0]["Activity Details"] == {}

    # Verify files
    assert len(files) == 1
    expected_file = {"Name": "", "MD5": None, "SHA256": None, "Path": None, "Extension": ""}
    assert files[0] == expected_file

    # Verify processes
    assert len(processes) == 1
    expected_process = {
        "Name": "",
        "Path": None,
        "MD5": None,
        "SHA256": None,
        "StartTime": "2023-10-31T09:59:00Z",
        "CommandLine": "",
    }
    assert processes[0] == expected_process