AnomaliSecurityAnalyticsAlerts
The Anomali Security Analytics pack allows users to manage security alerts by interacting directly with the Anomali Security Analytics platform. It supports creating search jobs, monitoring their status, retrieving results, and updating alert statuses or comments, streamlining integration with Palo Alto XSOAR.
Analytics & SIEM · Anomali Security Analytics
Details
| ID | AnomaliSecurityAnalyticsAlerts |
|---|---|
| Provider | Anomali |
| Category | Analytics & SIEM |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
README
The Anomali Security Analytics pack allows users to manage security alerts by interacting directly with the Anomali Security Analytics platform. It supports creating search jobs, monitoring their status, retrieving results, and updating alert statuses or comments, streamlining integration with Palo Alto XSOAR.
This integration was integrated and tested with version 1.0 of AnomaliSecurityAnalyticsAlerts.
Configure Anomali Security Analytics Alerts in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | True | |
| Username | True | |
| API Key | True | |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Fetch incidents | False | |
| Incident type | False | |
| Fetch query | Search query for fetching alerts. The default fetch query is “alert”. | False |
| Maximum incidents to fetch. | Maximum number of incidents per fetch. The default value is 200. | False |
| First fetch time | First alert created date to fetch. e.g., “1 min ago”,”2 weeks ago”,”3 months ago” | False |
| Incidents Fetch Interval | 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.
anomali-security-analytics-search-job-create
Create a new search job.
Base Command
anomali-security-analytics-search-job-create
Input
| Argument Name | Description | Required |
|---|---|---|
| query | Search expression or keyword you’re looking for in logs, e.g. alerts. | Required |
| source | Filters results by the log source or origin system, e.g. third_party_xsoar_integration. Default value is third_party. Default is third_party. | Optional |
| from | Timerange - start time, e.g., 1 hour, 30 minutes. Default value is 1 day. Default is 1 day. | Optional |
| to | Timerange - end time, e.g., 1 hour, 30 minutes. Default value is present. Default is 0 minutes. | Optional |
| timezone | The desired timezone for the log source. Pass the official IANA name for the time zone you are interested in, e.g. Europe/London, America/New_York. Default value is UTC. Default is UTC. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AnomaliSecurityAnalytics.SearchJob.job_id | String | Job ID of the search job. |
Human Readable Output
Search Job Created
| job_id | status |
|---|---|
| 7af7bc62c807446fa4bf7ad12dfbe64b | in progress |
anomali-security-analytics-search-job-status
Get the status of one or more search jobs.
Base Command
anomali-security-analytics-search-job-status
Input
| Argument Name | Description | Required |
|---|---|---|
| job_id | Unique identifier assigned to a background process or job. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AnomaliSecurityAnalytics.SearchJobStatus.job_id | String | Job ID of the search job. |
| AnomaliSecurityAnalytics.SearchJobStatus.status | String | Current status of the search job. e.g. RUNNING, DONE. |
| AnomaliSecurityAnalytics.SearchJobStatus.progress | Number | Indicates the search progress. Numeric float value between 0 and 1. |
| AnomaliSecurityAnalytics.SearchJobStatus.scanned | Number | Number of records scanned by the search query. |
| AnomaliSecurityAnalytics.SearchJobStatus.total | Number | Total number of records matched by the search query. |
| AnomaliSecurityAnalytics.SearchJobStatus.start_time | Number | Start timestamp of the time interval of the search query, in UNIX timestamp milliseconds. |
| AnomaliSecurityAnalytics.SearchJobStatus.end_time | Number | End timestamp of the time interval of the search query, in UNIX timestamp milliseconds. |
| AnomaliSecurityAnalytics.SearchJobStatus.bucket_length | Number | Width of each histogram bin depending on the search duration. |
| AnomaliSecurityAnalytics.SearchJobStatus.num_of_bucket | Number | Number of buckets in which you receive all the search results. |
| AnomaliSecurityAnalytics.SearchJobStatus.is_aggregated | Boolean | Whether the search results are aggregated. |
| AnomaliSecurityAnalytics.SearchJobStatus.histogram_buckets | Array | Number of histogram buckets used to bucket the search result. |
Human Readable Output
Search Job Status
| job_id | status | progress | scanned | total | start_time | end_time |
|---|---|---|---|---|---|---|
| 7af7bc62c807446fa4bf7ad12dfbe64b | in progress | 0.5 | 100 | 100 | 1741867250299 | 1741867250299 |
anomali-security-analytics-search-job-results
Get search job results.
Base Command
anomali-security-analytics-search-job-results
Input
| Argument Name | Description | Required |
|---|---|---|
| job_id | Unique identifier assigned to a background process or job. | Required |
| offset | Offset of records returned from the search result job. For example, if offset=10 and fetch_size=30, then this API will return results indexed 10 to 40. Default value is 0. | Optional |
| fetch_size | Number of records returned from the search result job. Maximum rows is 1000. Default value is 25. Default is 25. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AnomaliSecurityAnalytics.SearchJobResults.job_id | String | Job ID of the search job. |
| AnomaliSecurityAnalytics.SearchJobResults.status | String | Status of the search. |
| AnomaliSecurityAnalytics.SearchJobResults.count | Number | Number of records returned. |
| AnomaliSecurityAnalytics.SearchJobResults.has_next | Boolean | Indicates if more pages are available. |
| AnomaliSecurityAnalytics.SearchJobResults.is_aggregated | Boolean | Indicates if the search is aggregated. |
| AnomaliSecurityAnalytics.SearchJobResults.records | Array | List of records containing the fields included in the fields response attribute. |
| AnomaliSecurityAnalytics.SearchJobResults.result_row_count | Number | Total number of records retrieved by the search. |
| AnomaliSecurityAnalytics.SearchJobResults.search_end_time | Number | End timestamp of the search (UNIX timestamp in milliseconds). |
| AnomaliSecurityAnalytics.SearchJobResults.search_start_time | Number | Start timestamp of the search (UNIX timestamp in milliseconds). |
| AnomaliSecurityAnalytics.SearchJobResults.status | String | Status of the search job. |
| AnomaliSecurityAnalytics.SearchJobResults.types | Array | Data types of the search record attributes. |
Human Readable Output
Search Job Results
| id | name | owner | status | severity | alert_time | search_job_id |
|---|---|---|---|---|---|---|
| 905 | AlertTriageDemo | test@anomali.com | new | high | 1741867250299 | 7af7bc62c807446fa4bf7ad12dfbe64b |
anomali-security-analytics-alert-update
Update various fields of an alert including status, comment, assignee, severity, and owner.
Base Command
anomali-security-analytics-alert-update
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | Universally unique identifier assigned to uniquely identify objects such as Jobs, Alerts, Observables, Threat model entities. You can find it in search job results command. | Required |
| comment | Field for adding analyst notes or remarks to Match events, IOC submissions and Alert triage decisions. Please provide at least one field. | Optional |
| status | Current state of the observable in ThreatStream, e.g., active, inactive, falsepos. Please provide at least one field. | Optional |
| assignee | Assignee of the alert. Please provide at least one field. | Optional |
| owner | Owner of the alert. Please provide at least one field. | Optional |
| severity | Severity of the alert. Please provide at least one field. | Optional |
Context Output
| Path | Type | Description | ||||
|---|---|---|---|---|---|---|
| AnomaliSecurityAnalytics.UpdateAlert.message | String | Confirmation message returned after updating the alert status. | AnomaliSecurityAnalytics.UpdateAlert.uuid | String | The UUID of the updated alert. | |
| AnomaliSecurityAnalytics.UpdateAlert.updated_fields | Dictionary | The fields that were updated in the alert. |
Human Readable Output
Update Alert
| message | uuid | updated_fields |
|---|---|---|
| Updated alert successfully | 19e19eabd55e4b05a505fb64a803501d | {“comment”: “test_update”, “status”: “test_update”} |
Configuration parameters
url— Server URL (required)credentials— Username (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidentsincidentType— Incident typefetch_query— Fetch querymax_fetch— Maximum incidents to fetch.first_fetch— First fetch timeincidentFetchInterval— Incidents Fetch Interval
Commands (4)
-
anomali-security-analytics-alert-updateUpdate various fields of an alert including status, comment, assignee, severity, and owner.
-
anomali-security-analytics-search-job-createCreate a new search job.
-
anomali-security-analytics-search-job-resultsGet search job results.
-
anomali-security-analytics-search-job-statusGet the status of one or more search jobs.
from AnomaliSecurityAnalyticsAlerts import ( Client, command_create_search_job, command_get_search_job_status, command_get_search_job_results, command_update_alert, fetch_incidents, ) from CommonServerPython import * from CommonServerUserPython import * from freezegun import freeze_time import pytest @freeze_time("2025-03-01") def test_command_create_search_job(mocker): """ Given: - Valid query, source, from, to and timezone parameters When: - client.create_search_job returns a job id Then: - Validate that command_create_search_job returns a CommandResults object with outputs containing the correct job_id and a status of "in progress" """ client = Client(server_url="https://test.com", username="test_user", api_key="test_api_key", verify=True, proxy=False) return_data = {"job_id": "1234"} mocker.patch.object(client, "_http_request", return_value=return_data) args = {"query": "alert", "source": "source", "from": "1 day", "to": "1 hour"} result = command_create_search_job(client, args) assert isinstance(result, CommandResults) outputs = result.outputs assert outputs.get("job_id") == "1234" assert "Search Job Created" in result.readable_output def test_command_get_search_job_status_running(mocker): """ Given: - A job_id whose search job is still RUNNING. When: - client.get_search_job_status returns a non-DONE status. Then: - Validate that CommandResults is returned with correct status and job_id. """ client = Client(server_url="https://test.com", username="test_user", api_key="test_api_key", verify=True, proxy=False) status_response = {"status": "RUNNING", "progress": 0.5} mocker.patch.object(client, "_http_request", return_value=status_response) args = {"job_id": "job_running"} results = command_get_search_job_status(client, args) assert isinstance(results, list) assert len(results) == 1 assert results[0].outputs["status"] == "RUNNING" assert results[0].outputs["job_id"] == "job_running" assert "Search Job Status" in results[0].readable_output def test_command_get_search_job_results_completed_with_fields(mocker): """ Given: - A valid job_id with a status of DONE. When: - client.get_search_job_status returns DONE and client.get_search_job_results returns a response with fields and records. Then: - Validate that command_get_search_job_results returns a CommandResults object with a markdown table. """ client = Client(server_url="https://test.com", username="test_user", api_key="test_api_key", verify=True, proxy=False) results_response = { "fields": ["event_time", "sourcetype", "dcid", "src"], "records": [ ["1727647847687", "myexamplesourcetype", "78", "1.2.3.4"], ["1727647468096", "aws_cloudtrail", "1", "1.2.3.5"], ], "types": ["timestamp", "string", "string", "string"], "result_row_count": 2, "status": "DONE", } mocker.patch.object(client, "_http_request", return_value=results_response) args = {"job_id": "job_done", "offset": 0, "fetch_size": 2} results = command_get_search_job_results(client, args) assert isinstance(results, list) assert len(results) == 1 outputs = results[0].outputs assert outputs.get("job_id") == "job_done" expected_records = [ {"event_time": "1727647847687", "sourcetype": "myexamplesourcetype", "dcid": "78", "src": "1.2.3.4"}, {"event_time": "1727647468096", "sourcetype": "aws_cloudtrail", "dcid": "1", "src": "1.2.3.5"}, ] assert outputs.get("records") == expected_records readable_output = results[0].readable_output assert "Search Job Results" in readable_output for header in ["event_time", "sourcetype", "dcid", "src"]: assert header in readable_output def test_command_get_search_job_status_invalid(mocker): """ Given: - An invalid job_id When: - client.get_search_job_status returns an error message Then: - Validate that CommandResults is returned with error in readable_output. """ client = Client(server_url="https://test.com", username="test_user", api_key="test_api_key", verify=True, proxy=False) error_response = {"error": "Invalid Job ID"} mocker.patch.object(client, "_http_request", return_value=error_response) args = {"job_id": "invalid_job"} results = command_get_search_job_status(client, args) assert isinstance(results, list) assert len(results) == 1 assert "Failed to retrieve status" in results[0].readable_output assert "Invalid Job ID" in results[0].readable_output def test_command_get_search_job_results_no_fields_records(mocker): """ Given: - A valid job_id When: - client.get_search_job_results returns a response without 'fields' and 'records' Then: - Validate that command_get_search_job_results returns a list of CommandResults with the expected output from the fallback branch """ client = Client(server_url="https://test.com", username="test_user", api_key="test_api_key", verify=True, proxy=False) results_response = {"result": "raw data", "complete": True} mocker.patch.object(client, "_http_request", return_value=results_response) args = {"job_id": "job_no_fields", "offset": 0, "fetch_size": 2} results = command_get_search_job_results(client, args) assert isinstance(results, list) assert len(results) == 1 outputs = results[0].outputs assert outputs == results_response human_readable = results[0].readable_output assert "Search Job Results" in human_readable assert "raw data" in human_readable def test_command_update_alert_status_and_comment(mocker): """ Given: - 'status', 'comment' and 'uuid' parameters When: - client.update_alert returns a response Then: - Validate that command_update_alert returns a CommandResults object with outputs equal to the mocked response """ client = Client(server_url="https://test.com", username="test_user", api_key="test_api_key", verify=True, proxy=False) return_data = {"updated": True} mocker.patch.object(client, "_http_request", return_value=return_data) args = {"status": "IN_PROGRESS", "comment": "Test comment", "uuid": "alert-uuid-123"} result = command_update_alert(client, args) assert isinstance(result, CommandResults) assert "Alert Updated Successfully" in result.readable_output assert result.outputs["updated_fields"] == {"status": "IN_PROGRESS", "comment": "Test comment"} def test_command_update_alert_with_unsupported_fields(mocker): """ Given: - 'status', 'uuid' and an unsupported field 'foo', 'foo' will be ingored When: - client.update_alert returns a response Then: - Validate that unsupported field is ignored and appears in ignored_fields. """ client = Client(server_url="https://test.com", username="test_user", api_key="test_api_key", verify=True, proxy=False) mocker.patch.object(client, "_http_request", return_value={"updated": True}) args = {"uuid": "alert-uuid-456", "status": "CLOSED", "foo": "bar"} result = command_update_alert(client, args) assert isinstance(result, CommandResults) assert "Alert Updated Successfully" in result.readable_output assert result.outputs["updated_fields"] == {"status": "CLOSED"} def test_command_update_alert_no_supported_fields(mocker): """ Given: - Only 'uuid' and unsupported field When: - command_update_alert is invoked Then: - Validate that DemistoException is raised for having no valid fields to update. """ client = Client(server_url="https://test.com", username="test_user", api_key="test_api_key", verify=True, proxy=False) args = {"uuid": "1234", "foo": "bar"} with pytest.raises(DemistoException, match="No valid fields provided to update"): command_update_alert(client, args) def test_command_update_alert_no_uuid(mocker): """ Given: - No 'uuid' parameter provided When: - command_update_alert is invoked Then: - Validate that DemistoException is raised for missing UUID parameter """ client = Client(server_url="https://test.com", username="test_user", api_key="test_api_key", verify=True, proxy=False) args = {"status": "closed"} with pytest.raises(DemistoException) as e: command_update_alert(client, args) assert "Please provide 'uuid' parameter" in str(e.value) @freeze_time("2025-03-01T12:00:00Z") def test_fetch_incidents(mocker): """ Given: - Valid parameters When: - fetch_incidents is invoked Then: - Validate that fetch_incidents returns a list of incidents """ client = Client(server_url="https://test.com", username="test_user", api_key="test_api_key", verify=True, proxy=False) mocker.patch.object(client, "create_search_job", return_value={"job_id": "12345"}) mocker.patch.object(client, "get_search_job_status", return_value={"status": "DONE"}) mocker.patch.object( client, "get_search_job_results", return_value={"fields": ["uuid_", "event_time", "severity"], "records": [["abc-123", "1727613600000", "high"]]}, ) mocker.patch("CommonServerPython.demisto.params", return_value={"first_fetch": "3 days"}) mocker.patch("CommonServerPython.demisto.getLastRun", return_value={}) mocker.patch("CommonServerPython.demisto.setLastRun") incidents = fetch_incidents(client) assert isinstance(incidents, list) assert len(incidents) == 1 incident = incidents[0] assert "rawJSON" in incident assert "occurred" in incident assert "name" in incident assert incident["name"].startswith("Anomali Alert") @freeze_time("2025-03-01T12:00:00Z") def test_fetch_incidents_with_offset(mocker): """ Given: - A last run state with a non-zero offset - A full page of results equal to fetch_limit When: - fetch_incidents is invoked Then: - Validate that offset is incremented and last_fetch is unchanged """ client = Client(server_url="https://test.com", username="test_user", api_key="test_api_key", verify=True, proxy=False) mocker.patch.object(client, "create_search_job", return_value={"job_id": "job-id-456"}) mocker.patch.object(client, "get_search_job_status", return_value={"status": "DONE"}) fetch_limit = 3 sample_records = [ ["abc-001", "1727613600000", "low"], ["abc-002", "1727613610000", "medium"], ["abc-003", "1727613620000", "high"], ] mocker.patch.object( client, "get_search_job_results", return_value={"fields": ["uuid_", "event_time", "severity"], "records": sample_records}, ) mocker.patch("CommonServerPython.demisto.params", return_value={"first_fetch": "3 days", "max_fetch": fetch_limit}) mocker.patch("CommonServerPython.demisto.getLastRun", return_value={"last_fetch": "2024-01-01T00:00:00Z", "offset": 3}) set_last_run_mock = mocker.patch("CommonServerPython.demisto.setLastRun") incidents = fetch_incidents(client) assert isinstance(incidents, list) assert len(incidents) == 3 for incident in incidents: assert "name" in incident assert "occurred" in incident assert "rawJSON" in incident args, _ = set_last_run_mock.call_args new_last_run = args[0] assert new_last_run["offset"] == 6 assert new_last_run["last_fetch"] == "2024-01-01T00:00:00Z" @freeze_time("2025-03-01T12:00:00Z") def test_fetch_incidents_job_failure(mocker): """ Given: - create_search_job returns empty job_id When: - fetch_incidents is invoked Then: - DemistoException is raised """ client = Client(server_url="https://test.com", username="test_user", api_key="test_api_key", verify=True, proxy=False) mocker.patch.object(client, "create_search_job", return_value={}) mocker.patch("CommonServerPython.demisto.params", return_value={"first_fetch": "3 days", "fetch_limit": 200}) mocker.patch("CommonServerPython.demisto.getLastRun", return_value={}) with pytest.raises(DemistoException, match="Failed to create search job."): fetch_incidents(client) def test_fetch_incidents_missing_event_time(mocker): """ Given: - A record that does not contain event_time field. When: - fetch_incidents is called. Then: - The incident should be skipped (not raise an error). """ client = Client(server_url="https://test.com", username="test", api_key="key", verify=False, proxy=False) mocker.patch.object(client, "create_search_job", return_value={"job_id": "job123"}) mocker.patch.object(client, "get_search_job_status", return_value={"status": "DONE"}) mocker.patch.object(client, "get_search_job_results", return_value={"fields": ["uuid_"], "records": [["abc-001"]]}) mocker.patch("CommonServerPython.demisto.params", return_value={"first_fetch": "1 day"}) mocker.patch("CommonServerPython.demisto.getLastRun", return_value={}) mocker.patch("CommonServerPython.demisto.setLastRun") incidents = fetch_incidents(client) assert len(incidents) == 0 @freeze_time("2025-03-01T12:00:00Z") def test_fetch_incidents_no_records(mocker): """ Given: - A valid job_id - The search job completes but returns no records When: - fetch_incidents is called Then: - No incidents should be returned - Last run should be updated with offset = 0 """ client = Client(server_url="https://test.com", username="test", api_key="key", verify=False, proxy=False) mocker.patch.object(client, "create_search_job", return_value={"job_id": "job-empty"}) mocker.patch.object(client, "get_search_job_status", return_value={"status": "DONE"}) mocker.patch.object(client, "get_search_job_results", return_value={"fields": ["uuid_", "event_time"], "records": []}) mocker.patch("CommonServerPython.demisto.params", return_value={"first_fetch": "1 day"}) mocker.patch("CommonServerPython.demisto.getLastRun", return_value={"last_fetch": "2025-02-01T00:00:00Z", "offset": 20}) set_last_run_mock = mocker.patch("CommonServerPython.demisto.setLastRun") incidents = fetch_incidents(client) assert isinstance(incidents, list) assert len(incidents) == 0 args, _ = set_last_run_mock.call_args last_run = args[0] assert last_run["offset"] == 0 assert last_run["last_fetch"] == "2025-03-01T12:00:00Z" def test_fetch_incidents_job_timeout(mocker): """ Given: - A valid job_id - The search job is still RUNNING. When: - fetch_incidents is called. Then: - DemistoException is raised. """ client = Client(server_url="https://test.com", username="test", api_key="key", verify=False, proxy=False) mocker.patch.object(client, "create_search_job", return_value={"job_id": "timeout-job"}) mocker.patch.object(client, "get_search_job_status", return_value={"status": "RUNNING"}) mocker.patch("CommonServerPython.demisto.params", return_value={"first_fetch": "1 day"}) mocker.patch("CommonServerPython.demisto.getLastRun", return_value={}) with pytest.raises(DemistoException, match="did not complete in time"): fetch_incidents(client)