Details
| ID | ThreatZone |
|---|---|
| Provider | Malwation |
| Category | Forensics & Malware Analysis |
| From Version | 6.9.0 |
| Docker Image | demisto/threatzone-sdk:1.1.1.12287331 |
| Supported Modules | Agentix XSIAM |
README
ThreatZone malware analysis sandboxing.
This integration was integrated and tested with ThreatZone.
Configure ThreatZone in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL (e.g. https://app.threat.zone) | The ThreatZone instance root or /public-api URL. The integration appends the suffix exactly once. | True |
| ThreatZone API Key | The API key generated for the ThreatZone workspace. | True |
| Source Reliability | The reliability of the source providing the intelligence data. | False |
| Trust any certificate (not secure) | Whether to trust any certificate (not secure) by disabling TLS certificate validation. | False |
| Use system proxy settings | Whether to use the system proxy settings for SDK requests. | 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.
tz-sandbox-upload-sample
Submits a sample to ThreatZone for sandbox analysis.
Base Command
tz-sandbox-upload-sample
Input
| Argument Name | Description | Required |
|---|---|---|
| entry_id | The entry ID of the file to submit. | Required |
| environment | The environment to run the submission in. Possible values are: w7_x64, w10_x64, w11_x64, linux-ubuntu_2204, macos-ventura, android9. Default is w7_x64. | Optional |
| private | Whether the submission is private. If omitted, the current ThreatZone API-provided default is used. Possible values are: true, false. | Optional |
| extension_check | Whether to enforce MIME-based extension correction before sandbox execution. If omitted, the current ThreatZone API-provided default is used. Possible values are: true, false. | Optional |
| auto | Whether to automatically select a sandbox environment based on the uploaded file type, ignoring the environment argument. Possible values are: true, false. Default is false. | Optional |
| modules | The legacy compatibility argument. ThreatZone v3.2 does not support per-submission module selection, so this value is validated but not forwarded. | Optional |
| analyze_config | The raw analyzeConfig JSON used to override or add sandbox metafields. | Optional |
| timeout | The duration of the submission analysis. If omitted, the current ThreatZone API-provided default is used. Possible values are: 60, 120, 180, 300. | Optional |
| work_path | The working path of the submission. If omitted, the current ThreatZone API-provided default is used. Possible values are: desktop, root, appdata, windows, temp. | Optional |
| mouse_simulation | Whether to enable mouse simulation. If omitted, the current ThreatZone API-provided default is used. Possible values are: true, false. | Optional |
| https_inspection | Whether to enable HTTPS inspection for encrypted traffic. If omitted, the current ThreatZone API-provided default is used. Possible values are: true, false. | Optional |
| internet_connection | Whether to enable internet connection. If omitted, the current ThreatZone API-provided default is used. Possible values are: true, false. | Optional |
| raw_logs | Whether to request raw logs. This legacy compatibility argument is not forwarded because ThreatZone v3.2 does not accept the metafield. Possible values are: true, false. | Optional |
| snapshot | Whether to enable Fast Bootup. If omitted, the current ThreatZone API-provided default is used. Possible values are: true, false. | Optional |
| entrypoint | The filename inside the archive to execute (for archives). | Optional |
| password | The password for password-protected archives. | Optional |
| configurations | The advanced execution configuration as a JSON object (for example preScript, startArguments, or networkConfig). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.Sandbox.UUID | String | The UUID of the sample. |
| ThreatZone.Limits.E_Mail | String | The owner e-mail of current plan. |
| ThreatZone.Limits.API_Limit | String | The remaining/total API request limits of the current plan. |
| ThreatZone.Limits.Concurrent_Limit | String | The remaining/total concurrent analysis limits of the current plan. |
| ThreatZone.Limits.Daily_Submission_Limit | String | The remaining/total daily submission limits of the current plan. |
tz-static-upload-sample
Submits a sample to ThreatZone for static analysis.
Base Command
tz-static-upload-sample
Input
| Argument Name | Description | Required |
|---|---|---|
| entry_id | The entry ID of the file to submit. | Required |
| private | Whether the submission is visible only to members of your workspace. When false, the submission is visible to everyone. Possible values are: true, false. Default is true. | Optional |
| extension_check | Whether to enforce MIME-based extension correction before static scan. Possible values are: true, false. Default is false. | Optional |
| entrypoint | The filename inside the archive to analyze (for archives). | Optional |
| password | The password for password-protected archives. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.Static.UUID | String | The UUID of the sample. |
| ThreatZone.Limits.E_Mail | String | The owner e-mail of current plan. |
| ThreatZone.Limits.API_Limit | String | The remaining/total API request limits of the current plan. |
| ThreatZone.Limits.Concurrent_Limit | String | The remaining/total concurrent analysis limits of the current plan. |
| ThreatZone.Limits.Daily_Submission_Limit | String | The remaining/total daily submission limits of the current plan. |
tz-cdr-upload-sample
Submits a sample to ThreatZone for CDR.
Base Command
tz-cdr-upload-sample
Input
| Argument Name | Description | Required |
|---|---|---|
| entry_id | The entry ID of the file to submit. | Required |
| private | Whether the submission is visible only to members of your workspace. When false, the submission is visible to everyone. Possible values are: true, false. Default is true. | Optional |
| extension_check | Whether to enforce MIME-based extension correction before sanitization. Possible values are: true, false. Default is true. | Optional |
| entrypoint | The filename inside the archive to sanitize (for archives). | Optional |
| password | The password for password-protected archives. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.CDR.UUID | String | The UUID of the sample. |
| ThreatZone.Limits.E_Mail | String | The owner e-mail of current plan. |
| ThreatZone.Limits.API_Limit | String | The remaining/total API request limits of the current plan. |
| ThreatZone.Limits.Concurrent_Limit | String | The remaining/total concurrent analysis limits of the current plan. |
| ThreatZone.Limits.Daily_Submission_Limit | String | The remaining/total daily submission limits of the current plan. |
tz-url-analysis
Submits a URL to ThreatZone for analysis.
Base Command
tz-url-analysis
Input
| Argument Name | Description | Required |
|---|---|---|
| url | The URL to analyze. | Required |
| private | Whether to mark the submission as workspace-private. Possible values are: true, false. Default is true. | Optional |
| safe_browsing | Whether to start an isolated safe-browsing session alongside URL analysis. Possible values are: true, false. Default is false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.URL.UUID | String | The UUID of the URL submission. |
| ThreatZone.Submission.URL.URL | String | The URL submitted for analysis. |
| ThreatZone.Limits.E_Mail | String | The owner e-mail of current plan. |
| ThreatZone.Limits.API_Limit | String | The remaining/total API request limits of the current plan. |
| ThreatZone.Limits.Concurrent_Limit | String | The remaining/total concurrent analysis limits of the current plan. |
| ThreatZone.Limits.Daily_Submission_Limit | String | The remaining/total daily submission limits of the current plan. |
tz-get-result
Retrieve the analysis result from ThreatZone.
Base Command
tz-get-result
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| details | Whether to include inline Indicators, IOCs, YARA matches, artifacts, and configuration data in the readable output. Possible values are: true, false. Default is false. | Optional |
| download_sanitized | Whether to download the sanitized file after a CDR analysis completes. Possible values are: true, false. Default is false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission | Unknown | The raw submission data returned by ThreatZone. |
| ThreatZone.Analysis.STATUS | Number | The status of the submission scanning process as an integer code (0=declined, 1=file received, 2=accepted, 3=running, 4=VM ready, 5=finished). |
| ThreatZone.Analysis.LEVEL | Number | The threat level of the scanned file as an integer code (0=not measured, 1=informative, 2=suspicious, 3=malicious). |
| ThreatZone.Analysis.INFO | String | The submission metadata such as file name/URL and privacy status. |
| ThreatZone.Analysis.REPORT | String | The analysis report of the submission. |
| ThreatZone.Analysis.URL | String | The result page url of the submission. |
| ThreatZone.Analysis.MD5 | String | The md5 hash of the submission. |
| ThreatZone.Analysis.SHA1 | String | The sha1 hash of the submission. |
| ThreatZone.Analysis.SHA256 | String | The sha256 hash of the submission. |
| ThreatZone.Analysis.UUID | String | The UUID of the submission. |
| ThreatZone.Analysis.SANITIZED | String | The url of the sanitized file. |
| ThreatZone.IOC.URL | List | The URL data extracted from IOC. |
| ThreatZone.IOC.IP | List | The IP data extracted from IOC. |
| ThreatZone.IOC.DOMAIN | List | The DOMAIN data extracted from IOC. |
| ThreatZone.IOC.EMAIL | List | The EMAIL data extracted from IOC. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Reliability | String | The reliability of the source providing the intelligence data. |
| DBotScore.Score | Number | The actual score. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | unknown | The vendor used to calculate the score. |
tz-get-indicator-result
Retrieves dynamic behaviour indicators for a submission from ThreatZone.
Base Command
tz-get-indicator-result
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| level | The indicator severity to filter by. Possible values are: benign, suspicious, malicious. | Optional |
| category | The indicator category to filter by. | Optional |
| pid | The process ID to filter by. | Optional |
| attack_code | The MITRE ATT&CK technique code to filter by. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.Indicators.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.Indicators.Data | Unknown | The dynamic behaviour indicators returned by ThreatZone. |
tz-get-ioc-result
Retrieves Indicators of Compromise for a submission from ThreatZone.
Base Command
tz-get-ioc-result
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| type | The IOC type to filter by. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.IOCs.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.IOCs.Data | Unknown | The Indicators of Compromise returned by ThreatZone. |
tz-get-yara-result
Retrieves YARA rules matched during analysis. Use tz-download-yara-rule for the generated rule file.
Base Command
tz-get-yara-result
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| category | The matched YARA rule category to filter by. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.YaraMatches.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.YaraMatches.Data | Unknown | The matched YARA rules returned by ThreatZone. |
tz-get-artifact-result
Retrieves analysis artifacts for a submission from ThreatZone.
Base Command
tz-get-artifact-result
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.Artifacts.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.Artifacts.Data | Unknown | The analysis artifacts returned by ThreatZone. |
tz-get-config-result
Retrieves configuration extractor results for a submission from ThreatZone.
Base Command
tz-get-config-result
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.Config.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.Config.Data | Unknown | The configuration extractor results returned by ThreatZone. |
tz-get-sanitized
Downloads a sanitized file from the ThreatZone API and uploads it to the War Room.
Base Command
tz-get-sanitized
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| InfoFile.Extension | String | The extension of the file sanitized by CDR. |
| InfoFile.Name | String | The name of the file sanitized by CDR. |
| InfoFile.Size | Number | The size of the file sanitized by CDR. |
| InfoFile.EntryID | String | The entry ID of the file sanitized by CDR. |
| InfoFile.Info | String | The info of the file sanitized by CDR. |
| InfoFile.MD5 | String | The MD5 hash of the file sanitized by CDR. |
| InfoFile.SHA1 | String | The SHA1 hash of the file sanitized by CDR. |
| InfoFile.SHA256 | String | The SHA256 hash of the file sanitized by CDR. |
| InfoFile.SHA512 | String | The SHA512 hash of the file sanitized by CDR. |
| InfoFile.SSDeep | String | The SSDeep hash of the file sanitized by CDR. |
tz-download-html-report
Downloads the HTML report for a submission and uploads it to the War Room.
Base Command
tz-download-html-report
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| InfoFile.Extension | String | The extension of the HTML report. |
| InfoFile.Name | String | The name of the downloaded HTML report. |
| InfoFile.Size | Number | The size of the downloaded HTML report. |
| InfoFile.EntryID | String | The entry ID of the downloaded HTML report. |
| InfoFile.Info | String | The info for the downloaded HTML report. |
| InfoFile.MD5 | String | The MD5 hash of the downloaded HTML report. |
| InfoFile.SHA1 | String | The SHA1 hash of the downloaded HTML report. |
| InfoFile.SHA256 | String | The SHA256 hash of the downloaded HTML report. |
| InfoFile.SHA512 | String | The SHA512 hash of the downloaded HTML report. |
| InfoFile.SSDeep | String | The SSDeep hash of the downloaded HTML report. |
tz-get-metafields
Retrieves available ThreatZone metafields, optionally filtered by scan type.
Base Command
tz-get-metafields
Input
| Argument Name | Description | Required |
|---|---|---|
| scan_type | The scan type to filter metafields. Possible values are: sandbox, static, cdr, url, open_in_browser. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Configuration.Metafields.ScanType | String | The scan type used to filter the metafields. |
| ThreatZone.Configuration.Metafields.Data | Unknown | The metafield definitions returned by ThreatZone. |
tz-get-environments
Retrieves available ThreatZone sandbox environments.
Base Command
tz-get-environments
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Configuration.Environments.Data | Unknown | The sandbox environment definitions returned by ThreatZone. |
tz-list-network-configs
Lists network configurations available to the current ThreatZone workspace.
Base Command
tz-list-network-configs
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Configuration.NetworkConfigurations.Data | Unknown | The network configuration summaries returned by ThreatZone. |
tz-open-in-browser
Creates a ThreatZone open-in-browser submission.
Base Command
tz-open-in-browser
Input
| Argument Name | Description | Required |
|---|---|---|
| url | The URL to open in an isolated browser environment. | Required |
| environment | The sandbox environment key for browser execution. | Optional |
| auto | Whether to let ThreatZone select the browser environment. Possible values are: true, false. Default is false. | Optional |
| metafields | The open-in-browser metafields as a JSON object. | Optional |
| private | Whether to mark the submission as workspace-private. Possible values are: true, false. Default is true. | Optional |
| configurations | The advanced execution configuration as a JSON object. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.OpenInBrowser.UUID | String | The UUID of the open-in-browser submission. |
| ThreatZone.Submission.OpenInBrowser.URL | String | The URL submitted to ThreatZone. |
tz-list-submissions
Lists ThreatZone submissions with optional filters.
Base Command
tz-list-submissions
Input
| Argument Name | Description | Required |
|---|---|---|
| page | The page number, starting at 1. Default is 1. | Optional |
| limit | The number of submissions per page (1-100). Default is 20. | Optional |
| level | The comma-separated threat levels. | Optional |
| type | The submission type. Possible values are: file, url. | Optional |
| sha256 | The SHA256 hash filter. | Optional |
| filename | The partial filename filter. | Optional |
| start_date | The ISO 8601 date to include submissions created on or after (for example, 2020-01-01T00:11:22Z). | Optional |
| end_date | The ISO 8601 date to include submissions created on or before (for example, 2020-01-01T00:11:22Z). | Optional |
| private | Whether to filter by privacy status. Possible values are: true, false. | Optional |
| tags | The comma-separated tag filters. | Optional |
| sort | The field used to sort results, such as createdAt. | Optional |
| order | The sort order. Possible values are: asc, desc. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.List | Unknown | The paginated ThreatZone submission results. |
tz-search-submissions-by-sha256
Searches ThreatZone submissions by SHA256 hash.
Base Command
tz-search-submissions-by-sha256
Input
| Argument Name | Description | Required |
|---|---|---|
| sha256 | The SHA256 hash to search for. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.SubmissionSearch.Data | Unknown | The matching submissions. |
tz-get-overview-summary
Retrieves aggregate analysis counts for a submission.
Base Command
tz-get-overview-summary
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.OverviewSummary.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.OverviewSummary.Data | Unknown | The aggregate analysis summary. |
tz-get-eml-analysis
Retrieves parsed EML analysis results.
Base Command
tz-get-eml-analysis
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.EMLAnalysis.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.EMLAnalysis.Data | Unknown | The parsed EML analysis data. |
tz-get-mitre-techniques
Retrieves MITRE ATT&CK techniques matched during analysis.
Base Command
tz-get-mitre-techniques
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.MITRE.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.MITRE.Data | Unknown | The matched MITRE ATT&CK techniques. |
tz-get-static-scan-result
Retrieves the static scan result for a submission.
Base Command
tz-get-static-scan-result
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.StaticScan.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.StaticScan.Data | Unknown | The static scan result. |
tz-get-cdr-result
Retrieves the CDR transformation result for a submission.
Base Command
tz-get-cdr-result
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.CDRResult.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.CDRResult.Data | Unknown | The CDR transformation result. |
tz-get-signature-check-result
Retrieves authenticode and signature-check results.
Base Command
tz-get-signature-check-result
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.SignatureCheck.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.SignatureCheck.Data | Unknown | The signature-check result. |
tz-get-processes
Retrieves processes captured during dynamic analysis.
Base Command
tz-get-processes
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.Processes.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.Processes.Data | Unknown | The captured processes. |
tz-get-process-tree
Retrieves the process tree captured during dynamic analysis.
Base Command
tz-get-process-tree
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.ProcessTree.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.ProcessTree.Data | Unknown | The captured process tree. |
tz-get-url-analysis-result
Retrieves the full URL analysis report.
Base Command
tz-get-url-analysis-result
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the URL or open-in-browser submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.URLAnalysis.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.URLAnalysis.Data | Unknown | The URL analysis report. |
tz-get-behaviours
Retrieves one bounded page of behaviour telemetry.
Base Command
tz-get-behaviours
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| type | The behaviour event type filter. | Optional |
| pid | The process ID filter. | Optional |
| operation | The operation-name filter. | Optional |
| process_name | The exact process-name filter. | Optional |
| page | The page number, starting at 1. Default is 1. | Optional |
| limit | The number of events to return (1-500). Default is 100. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.Behaviours.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.Behaviours.Data | Unknown | The behaviour events and pagination metadata. |
tz-get-syscalls
Retrieves one bounded page of syscall telemetry.
Base Command
tz-get-syscalls
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| page | The page number, starting at 1. Default is 1. | Optional |
| limit | The number of syscall lines to return (1-2000). Default is 500. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.Syscalls.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.Syscalls.Data | Unknown | The syscall lines and pagination metadata. |
tz-get-network-summary
Retrieves aggregate network activity counts.
Base Command
tz-get-network-summary
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.NetworkSummary.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.NetworkSummary.Data | Unknown | The network activity summary. |
tz-get-dns-queries
Retrieves a bounded window of DNS queries.
Base Command
tz-get-dns-queries
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| limit | The maximum items to return (0-1000). | Optional |
| skip | The number of items to skip (0-1000). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.DNSQueries.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.DNSQueries.Data | Unknown | The captured DNS queries. |
tz-get-http-requests
Retrieves a bounded window of HTTP request hosts.
Base Command
tz-get-http-requests
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| limit | The maximum items to return (0-1000). | Optional |
| skip | The number of items to skip (0-1000). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.HTTPRequests.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.HTTPRequests.Data | Unknown | The captured HTTP request hosts. |
tz-get-tcp-connections
Retrieves a bounded window of TCP connections.
Base Command
tz-get-tcp-connections
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| limit | The maximum items to return (0-1000). | Optional |
| skip | The number of items to skip (0-1000). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.TCPConnections.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.TCPConnections.Data | Unknown | The captured TCP connections. |
tz-get-udp-connections
Retrieves a bounded window of UDP connections.
Base Command
tz-get-udp-connections
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| limit | The maximum items to return (0-1000). | Optional |
| skip | The number of items to skip (0-1000). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.UDPConnections.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.UDPConnections.Data | Unknown | The captured UDP connections. |
tz-get-network-threats
Retrieves a bounded window of Suricata network threats.
Base Command
tz-get-network-threats
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| limit | The maximum items to return (0-1000). | Optional |
| skip | The number of items to skip (0-1000). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.NetworkThreats.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.NetworkThreats.Data | Unknown | The detected network threats. |
tz-download-static-scan-strings
Downloads the static scan strings JSON to the War Room.
Base Command
tz-download-static-scan-strings
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| InfoFile.EntryID | String | The entry ID of the downloaded file. |
| InfoFile.Name | String | The name of the downloaded file. |
tz-download-sample
Downloads the original submitted sample to the War Room.
Base Command
tz-download-sample
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| InfoFile.EntryID | String | The entry ID of the downloaded file. |
| InfoFile.Name | String | The name of the downloaded file. |
tz-download-artifact
Downloads an extracted artifact to the War Room.
Base Command
tz-download-artifact
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| artifact_id | The artifact ID returned by tz-get-artifact-result. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| InfoFile.EntryID | String | The entry ID of the downloaded file. |
| InfoFile.Name | String | The name of the downloaded file. |
tz-download-pcap
Downloads the network capture to the War Room.
Base Command
tz-download-pcap
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| InfoFile.EntryID | String | The entry ID of the downloaded file. |
| InfoFile.Name | String | The name of the downloaded file. |
tz-download-yara-rule
Polls for and downloads the generated YARA rule file to the War Room.
Base Command
tz-download-yara-rule
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| timeout | The maximum seconds to poll for generated YARA rule readiness (1-3600). Default is 120. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| InfoFile.EntryID | String | The entry ID of the downloaded file. |
| InfoFile.Name | String | The name of the downloaded file. |
tz-download-url-screenshot
Downloads the URL analysis screenshot to the War Room.
Base Command
tz-download-url-screenshot
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the URL analysis submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| InfoFile.EntryID | String | The entry ID of the downloaded file. |
| InfoFile.Name | String | The name of the downloaded file. |
tz-list-media-files
Lists screenshots and videos available for a submission.
Base Command
tz-list-media-files
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Submission.MediaFiles.UUID | String | The UUID of the submission. |
| ThreatZone.Submission.MediaFiles.Data | Unknown | The available media file metadata. |
tz-download-media-file
Downloads a submission media file to the War Room.
Base Command
tz-download-media-file
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | The UUID of the submission. | Required |
| file_id | The media file ID returned by tz-list-media-files. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| InfoFile.EntryID | String | The entry ID of the downloaded file. |
| InfoFile.Name | String | The name of the downloaded file. |
tz-check-limits
Check the plan limits from ThreatZone API.
Base Command
tz-check-limits
Input
| Argument Name | Description | Required |
|---|---|---|
| detailed | Whether to include plan file limits, enabled modules, and account metadata. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ThreatZone.Limits.E_Mail | String | The owner e-mail of current plan. |
| ThreatZone.Limits.API_Limit | String | The remaining/total API request limits of the current plan. |
| ThreatZone.Limits.Concurrent_Limit | String | The remaining/total concurrent analysis limits of the current plan. |
| ThreatZone.Limits.Daily_Submission_Limit | String | The remaining/total daily submission limits of the current plan. |
| ThreatZone.Plan.File_Size_Limit_MiB | Number | The maximum upload size for the workspace plan (MiB). |
| ThreatZone.Plan.Allowed_Extensions | List | The list of permitted file extensions for uploads. |
| ThreatZone.Plan.Modules | List | The enabled ThreatZone modules for the workspace. |
| ThreatZone.Metadata.Full_Name | String | The full name of the authenticated user. |
| ThreatZone.Metadata.Workspace | String | The workspace identifier or name associated with the account. |
| ThreatZone.Metadata.Plan_Name | String | The name of the active ThreatZone plan. |
| ThreatZone.Metadata.Plan_Status | String | The status of the active ThreatZone plan. |
Configuration parameters
url— Server URL (e.g. https://app.threat.zone) (required)apikey— (required)integrationReliability— Source Reliabilityinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (44)
-
tz-cdr-upload-sampleSubmits a sample to ThreatZone for CDR.
-
tz-check-limitsCheck the plan limits from ThreatZone API.
-
tz-download-artifactDownloads an extracted artifact to the War Room.
-
tz-download-html-reportDownloads the HTML report for a submission and uploads it to the War Room.
-
tz-download-media-fileDownloads a submission media file to the War Room.
-
tz-download-pcapDownloads the network capture to the War Room.
-
tz-download-sampleDownloads the original submitted sample to the War Room.
-
tz-download-static-scan-stringsDownloads the static scan strings JSON to the War Room.
-
tz-download-url-screenshotDownloads the URL analysis screenshot to the War Room.
-
tz-download-yara-rulePolls for and downloads the generated YARA rule file to the War Room.
-
tz-get-artifact-resultRetrieves analysis artifacts for a submission from ThreatZone.
-
tz-get-behavioursRetrieves one bounded page of behaviour telemetry.
-
tz-get-cdr-resultRetrieves the CDR transformation result for a submission.
-
tz-get-config-resultRetrieves configuration extractor results for a submission from ThreatZone.
-
tz-get-dns-queriesRetrieves a bounded window of DNS queries.
-
tz-get-eml-analysisRetrieves parsed EML analysis results.
-
tz-get-environmentsRetrieves available ThreatZone sandbox environments.
-
tz-get-http-requestsRetrieves a bounded window of HTTP request hosts.
-
tz-get-indicator-resultRetrieves dynamic behaviour indicators for a submission from ThreatZone.
-
tz-get-ioc-resultRetrieves Indicators of Compromise for a submission from ThreatZone.
-
tz-get-metafieldsRetrieves available ThreatZone metafields, optionally filtered by scan type.
-
tz-get-mitre-techniquesRetrieves MITRE ATT&CK techniques matched during analysis.
-
tz-get-network-summaryRetrieves aggregate network activity counts.
-
tz-get-network-threatsRetrieves a bounded window of Suricata network threats.
-
tz-get-overview-summaryRetrieves aggregate analysis counts for a submission.
-
tz-get-process-treeRetrieves the process tree captured during dynamic analysis.
-
tz-get-processesRetrieves processes captured during dynamic analysis.
-
tz-get-resultRetrieve the analysis result from ThreatZone.
-
tz-get-sanitizedDownloads a sanitized file from the ThreatZone API and uploads it to the War Room.
-
tz-get-signature-check-resultRetrieves authenticode and signature-check results.
-
tz-get-static-scan-resultRetrieves the static scan result for a submission.
-
tz-get-syscallsRetrieves one bounded page of syscall telemetry.
-
tz-get-tcp-connectionsRetrieves a bounded window of TCP connections.
-
tz-get-udp-connectionsRetrieves a bounded window of UDP connections.
-
tz-get-url-analysis-resultRetrieves the full URL analysis report.
-
tz-get-yara-resultRetrieves YARA rules matched during analysis. Use tz-download-yara-rule for the generated rule file.
-
tz-list-media-filesLists screenshots and videos available for a submission.
-
tz-list-network-configsLists network configurations available to the current ThreatZone workspace.
-
tz-list-submissionsLists ThreatZone submissions with optional filters.
-
tz-open-in-browserCreates a ThreatZone open-in-browser submission.
-
tz-sandbox-upload-sampleSubmits a sample to ThreatZone for sandbox analysis.
-
tz-search-submissions-by-sha256Searches ThreatZone submissions by SHA256 hash.
-
tz-static-upload-sampleSubmits a sample to ThreatZone for static analysis.
-
tz-url-analysisSubmits a URL to ThreatZone for analysis.
"""Unit tests for the SDK-backed ThreatZone integration. The integration delegates all HTTP work to the official `threatzone` Python SDK, so these tests mock the SDK client methods returning pydantic models straight from the SDK's own type system. """ from __future__ import annotations import hashlib import unittest from pathlib import Path from unittest.mock import MagicMock, call, mock_open, patch import pytest import ThreatZone as integration from CommonServerPython import DemistoException from threatzone import ( AnalysisTimeoutError, APIError, AuthenticationError, NotFoundError, ReportUnavailableError, YaraRulePendingError, ) from threatzone import ThreatZone as ThreatZoneSDK from threatzone.testing import FakeThreatZoneAPI, scenarios from threatzone.types import ( Artifact, ArtifactsResponse, ExtractedConfigsResponse, Indicator, IndicatorsResponse, IoC, IoCsResponse, SubmissionCreated, UserInfo, YaraRule, YaraRulesResponse, ) from threatzone.types.indicators import ArtifactHashes, IndicatorLevels from threatzone.types.config import MetafieldOption def _user_info(api_used: int = 5, daily_used: int = 5, concurrent_used: int = 1) -> UserInfo: return UserInfo.model_validate( { "userInfo": { "email": "name@company.com", "fullName": "Test User", "workspace": { "id": "ws-1", "name": "ACME Lab", "alias": "acme", "private": True, "type": "organization", }, "limitsCount": { "apiRequestCount": api_used, "dailySubmissionCount": daily_used, "concurrentSubmissionCount": concurrent_used, }, }, "plan": { "planName": "Enterprise", "startTime": "2025-01-01", "endTime": "2026-01-01", "subsTime": "yearly", "fileLimits": {"extensions": ["exe", "dll"], "fileSize": "256 MiB"}, "submissionLimits": { "apiLimit": 9999, "dailyLimit": 999, "concurrentLimit": 2, }, }, "modules": [ { "moduleId": "m1", "moduleName": "Sandbox", "startTime": "2025-01-01", "endTime": "2026-01-01", }, { "moduleId": "m2", "moduleName": "CDR", "startTime": "2025-01-01", "endTime": "2026-01-01", }, ], } ) def _submission(level: str = "malicious", status: str = "completed", report_type: str = "dynamic"): """Build a minimal Submission via SDK model validation.""" from threatzone.types.submissions import Submission payload = { "uuid": "c89d310b-7862-4534-998a-3eb39d9a9d42", "type": "file", "filename": "sample.exe", "hashes": { "md5": "5d41402abc4b2a76b9719d911017c592", "sha1": "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d", "sha256": "6e899ff7ef160d96787f505b7a9e17b789695bf3206a130c462b3820898257d0", }, "level": level, "private": True, "tags": [], "reports": [{"type": report_type, "status": status, "level": level}], "overview": {"status": "completed"}, "indicators": { "levels": {"malicious": 1, "suspicious": 0, "benign": 0}, "artifactCount": 0, }, "mitreTechniques": [], "createdAt": "2025-01-01T00:00:00Z", "updatedAt": "2025-01-01T00:00:00Z", } return Submission.model_validate(payload) def _make_client() -> integration.Client: """Build a Client without spinning up the real httpx session.""" with ( patch.object(integration, "httpx") as mock_httpx, patch.object(integration, "ThreatZoneSDK") as mock_sdk, ): mock_httpx.Client.return_value = MagicMock() mock_sdk.return_value = MagicMock() return integration.Client(base_url="https://app.threat.zone", api_key="key", verify=True, proxy=False) def _metafield( key: str, default: bool | int | str, *, active: bool = True, accessible: bool = True, ) -> MetafieldOption: return MetafieldOption.model_validate( { "key": key, "label": key, "description": key, "type": "select", "default": default, "active": active, "accessible": accessible, "options": None, } ) # --------------------------------------------------------------------------- # Helper unit tests # --------------------------------------------------------------------------- class TestPureHelpers(unittest.TestCase): def test_normalize_sdk_base_url(self): assert integration.normalize_sdk_base_url("https://app.threat.zone") == "https://app.threat.zone/public-api" assert integration.normalize_sdk_base_url("https://app.threat.zone/public-api/") == "https://app.threat.zone/public-api" def test_extract_api_key_supports_type_9_and_legacy_values(self): assert integration.extract_api_key({"identifier": "", "password": "type-9-key"}) == "type-9-key" assert integration.extract_api_key("legacy-key") == "legacy-key" assert integration.extract_api_key(None) == "" def test_normalize_sdk_base_url_rejects_missing_url(self): with pytest.raises(DemistoException, match="Server URL is required"): integration.normalize_sdk_base_url(" ") def test_parse_json_object_argument(self): assert integration.parse_json_object_argument('{"networkConfig":"id"}', "configurations") == {"networkConfig": "id"} with pytest.raises(DemistoException, match="JSON object"): integration.parse_json_object_argument("[]", "configurations") def test_parse_csv_and_bounded_integer_arguments(self): assert integration.parse_csv_list_argument(" malicious, suspicious, ") == [ "malicious", "suspicious", ] assert integration.parse_bounded_int_argument("500", "limit", minimum=1, maximum=500) == 500 with pytest.raises(DemistoException, match="between 1 and 500"): integration.parse_bounded_int_argument("501", "limit", minimum=1, maximum=500) def test_report_pagination_has_a_hard_page_limit(self): fetch_page = MagicMock() fetch_page.return_value = MagicMock( items=[object()], total=integration.MAX_REPORT_FINDINGS_PAGES * integration.REPORT_FINDINGS_PAGE_SIZE + 1, ) with pytest.raises(DemistoException, match="pagination exceeded"): integration.get_all_report_items(fetch_page, "submission-uuid") assert fetch_page.call_count == integration.MAX_REPORT_FINDINGS_PAGES def test_translate_score_levels(self): assert integration.translate_score(None) == 0 assert integration.translate_score(0) == 0 assert integration.translate_score(1) == 1 assert integration.translate_score(2) == 2 assert integration.translate_score(3) == 3 assert integration.translate_score(99) == 3 def test_get_reputation_reliability_known(self): assert integration.get_reputation_reliability("A - Completely reliable") == "A - Completely reliable" def test_get_reputation_reliability_unknown(self): assert integration.get_reputation_reliability("not-a-real-value") is None def test_parse_modules_argument_csv(self): assert integration.parse_modules_argument("a, b, ,c") == ["a", "b", "c"] def test_parse_modules_argument_json_array(self): assert integration.parse_modules_argument('["a","b"]') == ["a", "b"] def test_parse_modules_argument_empty(self): assert integration.parse_modules_argument(None) is None assert integration.parse_modules_argument("") is None def test_parse_analyze_config_valid(self): result = integration.parse_analyze_config_argument('[{"metafieldId":"x","value":1}]') assert result == [{"metafieldId": "x", "value": 1}] def test_parse_analyze_config_invalid_json(self): with pytest.raises(DemistoException): integration.parse_analyze_config_argument("not-json") def test_metafields_from_legacy_args_merges(self): fields = integration.metafields_from_legacy_args( { "timeout": "120", "work_path": "desktop", "mouse_simulation": "true", "raw_logs": "true", "modules": '["cdr"]', "analyze_config": '[{"metafieldId":"timeout","value":300}]', } ) assert fields["timeout"] == 300 # user override wins assert fields["work_path"] == "desktop" assert fields["mouse_simulation"] is True assert "raw_logs" not in fields assert "modules" not in fields def test_metafields_from_legacy_args_uses_api_defaults(self): assert integration.metafields_from_legacy_args({}, {"snapshot": True}) == {"snapshot": True} def test_metafields_from_legacy_args_forwards_explicit_false(self): assert integration.metafields_from_legacy_args({"snapshot": "false"}, {"snapshot": True}) == {"snapshot": False} def test_sandbox_api_defaults_filters_unavailable_definitions(self): client = _make_client() client.sdk.get_metafields.return_value = [ _metafield("snapshot", True), _metafield("inactive", True, active=False), _metafield("inaccessible", True, accessible=False), ] assert integration.sandbox_api_defaults(client) == {"snapshot": True} client.sdk.get_metafields.assert_called_once_with("sandbox") def test_submission_level_int(self): assert integration.submission_level_int("malicious") == 3 assert integration.submission_level_int("suspicious") == 2 assert integration.submission_level_int("benign") == 1 assert integration.submission_level_int("unknown") == 0 assert integration.submission_level_int(None) is None def test_report_status_int(self): assert integration.report_status_int("completed") == 5 assert integration.report_status_int("in_progress") == 3 assert integration.report_status_int(None) is None def test_parse_file_size_mib(self): assert integration.parse_file_size_mib("256 MiB") == 256 assert integration.parse_file_size_mib(128) == 128 assert integration.parse_file_size_mib("1.5 MiB") == 1.5 assert integration.parse_file_size_mib("unknown") is None class TestClient(unittest.TestCase): @patch.object(integration, "ThreatZoneSDK") @patch.object(integration.httpx, "Client") def test_supplied_http_client_matches_sdk_transport_defaults(self, http_client_mock, sdk_mock): client = integration.Client( base_url="https://app.threat.zone", api_key="key", verify=False, proxy=True, reliability="A - Completely reliable", ) assert isinstance(client, integration.ContentClient) assert client.reliability == "A - Completely reliable" http_client_kwargs = http_client_mock.call_args.kwargs assert http_client_kwargs["verify"] is False assert http_client_kwargs["trust_env"] is True assert http_client_kwargs["follow_redirects"] is True timeout = http_client_kwargs["timeout"] assert timeout.connect == integration.SDK_REQUEST_TIMEOUT_SECONDS assert timeout.read == integration.SDK_REQUEST_TIMEOUT_SECONDS assert timeout.write == integration.SDK_REQUEST_TIMEOUT_SECONDS assert timeout.pool == integration.SDK_REQUEST_TIMEOUT_SECONDS assert sdk_mock.call_args.kwargs["http_client"] is http_client_mock.return_value client.close() # --------------------------------------------------------------------------- # Command-handler tests # --------------------------------------------------------------------------- class TestCheckLimits(unittest.TestCase): def setUp(self): self.client = _make_client() def test_basic(self): self.client.sdk.get_user_info.return_value = _user_info() results = integration.threatzone_check_limits(self.client, {}) assert len(results) == 1 outputs = results[0].outputs assert outputs["E_Mail"] == "name@company.com" assert outputs["API_Limit"] == "5/9999" def test_detailed_adds_plan_and_metadata(self): self.client.sdk.get_user_info.return_value = _user_info() results = integration.threatzone_check_limits(self.client, {"detailed": "true"}) prefixes = [r.outputs_prefix for r in results] assert "ThreatZone.Plan" in prefixes assert "ThreatZone.Metadata" in prefixes plan_result = next(result for result in results if result.outputs_prefix == "ThreatZone.Plan") assert plan_result.outputs["File_Size_Limit_MiB"] == 256 class TestPlanCapacity(unittest.TestCase): def setUp(self): self.client = _make_client() def test_concurrent_limit_blocks_sandbox(self): self.client.sdk.get_user_info.return_value = _user_info(concurrent_used=2) with pytest.raises(DemistoException) as excinfo: integration._verify_plan_capacity(self.client, requires_concurrent=True) assert "Concurrent" in str(excinfo.value) def test_concurrent_limit_does_not_block_url(self): self.client.sdk.get_user_info.return_value = _user_info(concurrent_used=2) integration._verify_plan_capacity(self.client, requires_concurrent=False) def test_api_limit_blocks(self): self.client.sdk.get_user_info.return_value = _user_info(api_used=9999) with pytest.raises(DemistoException) as excinfo: integration._verify_plan_capacity(self.client, requires_concurrent=False) assert "API request limit" in str(excinfo.value) class TestUrlSubmission(unittest.TestCase): def setUp(self): self.client = _make_client() self.client.sdk.get_user_info.return_value = _user_info() def test_url_submission_returns_uuid(self): self.client.sdk.create_url_submission.return_value = SubmissionCreated(uuid="abc", message="ok") results = integration.threatzone_submit_url_analysis(self.client, {"url": "https://example.com", "private": "true"}) primary = results[0] assert primary.outputs["UUID"] == "abc" assert primary.outputs["URL"] == "https://example.com" self.client.sdk.create_url_submission.assert_called_once_with("https://example.com", private=True, safe_browsing=False) def test_url_submission_forwards_safe_browsing(self): self.client.sdk.create_url_submission.return_value = SubmissionCreated(uuid="abc", message="ok") integration.threatzone_submit_url_analysis( self.client, {"url": "https://example.com", "safe_browsing": "true"}, ) assert self.client.sdk.create_url_submission.call_args.kwargs["safe_browsing"] is True def test_url_submission_requires_url(self): with pytest.raises(DemistoException): integration.threatzone_submit_url_analysis(self.client, {}) class TestSandboxUpload(unittest.TestCase): def setUp(self): self.client = _make_client() self.client.sdk.get_user_info.return_value = _user_info() self.client.sdk.get_metafields.return_value = [] self.client.sdk.create_sandbox_submission.return_value = SubmissionCreated(uuid="sb-uuid", message="ok") @patch.object(integration, "demisto") def test_sandbox_upload(self, mock_demisto): mock_demisto.getFilePath.return_value = { "path": "/tmp/generated-entry-id", "name": "original sample.exe", } with patch.object(integration.Path, "open", mock_open(read_data=b"sample")): results = integration.threatzone_sandbox_upload_sample( self.client, { "entry_id": "1", "environment": "w10_x64", "private": "true", "timeout": "120", "configurations": '{"startArguments":"--safe"}', }, ) assert results[0].outputs["UUID"] == "sb-uuid" assert results[0].outputs["FileName"] == "original sample.exe" upload_file = self.client.sdk.create_sandbox_submission.call_args.args[0] assert upload_file.name == "original sample.exe" call_kwargs = self.client.sdk.create_sandbox_submission.call_args.kwargs assert call_kwargs["environment"] == "w10_x64" assert call_kwargs["auto_select_environment"] is False assert call_kwargs["private"] is True assert call_kwargs["metafields"]["timeout"] == 120 assert call_kwargs["configurations"] == {"startArguments": "--safe"} @patch.object(integration, "demisto") def test_sandbox_auto_environment_ignores_explicit_default(self, mock_demisto): mock_demisto.getFilePath.return_value = { "path": "/tmp/sample.exe", "name": "sample.exe", } with patch.object(integration.Path, "open", mock_open(read_data=b"sample")): integration.threatzone_sandbox_upload_sample( self.client, {"entry_id": "1", "environment": "w7_x64", "auto": "true"}, ) call_kwargs = self.client.sdk.create_sandbox_submission.call_args.kwargs assert call_kwargs["environment"] is None assert call_kwargs["auto_select_environment"] is True @patch.object(integration, "demisto") def test_bat_sandbox_uses_sdk_with_api_defaults(self, mock_demisto): mock_demisto.getFilePath.return_value = { "path": "/tmp/sample.bat", "name": "sample.bat", } self.client.sdk.get_metafields.return_value = [ _metafield("private", True), _metafield("snapshot", True), _metafield("timeout", 120), ] with patch.object(integration.Path, "open", mock_open(read_data=b"sample")): results = integration.threatzone_sandbox_upload_sample( self.client, {"entry_id": "1", "environment": "w10_x64", "private": "true"}, ) assert results[0].outputs["UUID"] == "sb-uuid" self.client.sdk.create_sandbox_submission.assert_called_once() call_kwargs = self.client.sdk.create_sandbox_submission.call_args.kwargs assert call_kwargs["environment"] == "w10_x64" assert call_kwargs["private"] is True assert call_kwargs["metafields"] == {"snapshot": True, "timeout": 120} class TestStaticAndCdrUpload(unittest.TestCase): def setUp(self): self.client = _make_client() self.client.sdk.get_user_info.return_value = _user_info() self.client.sdk.create_static_submission.return_value = SubmissionCreated(uuid="static-uuid", message="ok") self.client.sdk.create_cdr_submission.return_value = SubmissionCreated(uuid="cdr-uuid", message="ok") @patch.object(integration, "demisto") def test_static_upload(self, mock_demisto): mock_demisto.getFilePath.return_value = { "path": "/tmp/generated-entry-id", "name": "original-static.exe", } with patch.object(integration.Path, "open", mock_open(read_data=b"sample")): results = integration.threatzone_static_or_cdr_upload( self.client, {"entry_id": "1", "private": "false", "extension_check": "false"}, "static", ) assert results[0].outputs["UUID"] == "static-uuid" upload_file = self.client.sdk.create_static_submission.call_args.args[0] assert upload_file.name == "original-static.exe" call_kwargs = self.client.sdk.create_static_submission.call_args.kwargs assert call_kwargs["private"] is False assert call_kwargs["dynamic_mimetype_check"] is False @patch.object(integration, "demisto") def test_cdr_upload(self, mock_demisto): mock_demisto.getFilePath.return_value = { "path": "/tmp/generated-entry-id", "name": "original-document.docx", } with patch.object(integration.Path, "open", mock_open(read_data=b"sample")): results = integration.threatzone_static_or_cdr_upload( self.client, {"entry_id": "1", "private": "true", "extension_check": "true"}, "cdr", ) assert results[0].outputs["UUID"] == "cdr-uuid" upload_file = self.client.sdk.create_cdr_submission.call_args.args[0] assert upload_file.name == "original-document.docx" call_kwargs = self.client.sdk.create_cdr_submission.call_args.kwargs assert call_kwargs["private"] is True assert call_kwargs["dynamic_mimetype_check"] is True class TestSectionHandlers(unittest.TestCase): def setUp(self): self.client = _make_client() def test_get_indicator_result(self): self.client.sdk.get_indicators.return_value = IndicatorsResponse( items=[ Indicator( id="ind-1", name="Suspicious behavior", description="desc", category=["cat"], level="suspicious", score=50, pids=[], attackCodes=[], eventIds=[], syscallLineNumbers=[], author="system", ) ], total=1, levels=IndicatorLevels(malicious=0, suspicious=1, benign=0), ) result = integration.threatzone_get_indicator_result( self.client, { "uuid": "u", "level": "suspicious", "category": "cat", "pid": "7", "attack_code": "T1055", }, )[0] assert result.outputs["UUID"] == "u" assert result.outputs["Data"][0]["name"] == "Suspicious behavior" self.client.sdk.get_indicators.assert_called_once_with( "u", page=1, limit=integration.REPORT_FINDINGS_PAGE_SIZE, level="suspicious", category="cat", pid=7, attack_code="T1055", ) def test_get_ioc_result_fetches_every_page(self): self.client.sdk.get_iocs.side_effect = [ IoCsResponse( items=[IoC(type="domain", value="evil.example", artifacts=[])], total=2, ), IoCsResponse( items=[IoC(type="ip", value="192.0.2.1", artifacts=[])], total=2, ), ] result = integration.threatzone_get_ioc_result(self.client, {"uuid": "u", "type": "domain"})[0] assert result.outputs["Data"][0]["value"] == "evil.example" assert result.outputs["Data"][1]["value"] == "192.0.2.1" assert self.client.sdk.get_iocs.call_args_list == [ call("u", page=1, limit=integration.REPORT_FINDINGS_PAGE_SIZE, type="domain"), call("u", page=2, limit=integration.REPORT_FINDINGS_PAGE_SIZE, type="domain"), ] def test_get_yara_result(self): self.client.sdk.get_yara_rules.return_value = YaraRulesResponse( items=[YaraRule(rule="EvilRule", category="malicious", artifacts=[])], total=1, ) result = integration.threatzone_get_yara_result(self.client, {"uuid": "u", "category": "malicious"})[0] assert result.outputs["Data"][0]["rule"] == "EvilRule" self.client.sdk.get_yara_rules.assert_called_once_with( "u", page=1, limit=integration.REPORT_FINDINGS_PAGE_SIZE, category="malicious", ) def test_get_artifact_result(self): self.client.sdk.get_artifacts.return_value = ArtifactsResponse( items=[ Artifact( id="art-1", filename="dropped.bin", size=10, type="dropped_file", source="dropped", hashes=ArtifactHashes(md5="m", sha1="s", sha256="x"), tags=[], ) ], total=1, ) result = integration.threatzone_get_artifact_result(self.client, {"uuid": "u"})[0] assert result.outputs["Data"][0]["filename"] == "dropped.bin" def test_get_config_empty(self): self.client.sdk.get_extracted_configs.return_value = ExtractedConfigsResponse(items=[], total=0) result = integration.threatzone_get_config_result(self.client, {"uuid": "u"})[0] assert result.outputs is None def test_requires_uuid(self): for handler in ( integration.threatzone_get_indicator_result, integration.threatzone_get_ioc_result, integration.threatzone_get_yara_result, integration.threatzone_get_artifact_result, integration.threatzone_get_config_result, ): with pytest.raises(DemistoException): handler(self.client, {}) class TestConfigurationAndSubmissionCommands(unittest.TestCase): def setUp(self): self.client = _make_client() def test_configuration_commands(self): metafield = _metafield("timeout", 120) self.client.sdk.get_metafields.return_value = [metafield] self.client.sdk.get_environments.return_value = [] self.client.sdk.list_network_configs.return_value = [] metafields = integration.threatzone_get_metafields(self.client, {"scan_type": "sandbox"})[0] environments = integration.threatzone_get_environments(self.client, {})[0] network_configs = integration.threatzone_list_network_configs(self.client, {})[0] assert metafields.outputs == { "Data": [metafield.model_dump(by_alias=True, exclude_none=True, mode="json")], "ScanType": "sandbox", } assert environments.outputs == {"Data": []} assert network_configs.outputs == {"Data": []} self.client.sdk.get_metafields.assert_called_once_with("sandbox") self.client.sdk.get_environments.assert_called_once_with() self.client.sdk.list_network_configs.assert_called_once_with() def test_get_all_metafields_omits_filter(self): self.client.sdk.get_metafields.return_value = MagicMock() integration.threatzone_get_metafields(self.client, {}) self.client.sdk.get_metafields.assert_called_once_with() def test_get_metafields_rejects_unknown_scan_type(self): with pytest.raises(DemistoException, match="scan_type"): integration.threatzone_get_metafields(self.client, {"scan_type": "unknown"}) def test_open_in_browser_maps_sdk_arguments(self): self.client.sdk.get_user_info.return_value = _user_info() self.client.sdk.create_open_in_browser_submission.return_value = SubmissionCreated(uuid="browser-u", message="ok") result = integration.threatzone_open_in_browser( self.client, { "url": "https://example.com", "environment": "w11_x64", "auto": "false", "metafields": '{"timeout":120}', "private": "false", "configurations": '{"networkConfig":"config-id"}', }, )[0] assert result.outputs["UUID"] == "browser-u" self.client.sdk.create_open_in_browser_submission.assert_called_once_with( "https://example.com", environment="w11_x64", auto_select_environment=False, metafields={"timeout": 120}, private=False, configurations={"networkConfig": "config-id"}, ) def test_list_submissions_maps_filters(self): response = MagicMock() response.model_dump.return_value = { "items": [], "total": 0, "page": 2, "limit": 50, "totalPages": 0, } self.client.sdk.list_submissions.return_value = response result = integration.threatzone_list_submissions( self.client, { "page": "2", "limit": "50", "level": "malicious,suspicious", "tags": "tag-1,tag-2", "private": "true", "type": "file", }, )[0] assert result.outputs_prefix == "ThreatZone.Submission.List" assert result.outputs["page"] == 2 call_kwargs = self.client.sdk.list_submissions.call_args.kwargs assert call_kwargs["level"] == ["malicious", "suspicious"] assert call_kwargs["tags"] == ["tag-1", "tag-2"] assert call_kwargs["private"] is True assert call_kwargs["type"] == "file" def test_search_submissions_serializes_empty_result(self): self.client.sdk.search_by_sha256.return_value = [] result = integration.threatzone_search_submissions(self.client, {"sha256": "a" * 64})[0] assert result.outputs == {"Data": []} self.client.sdk.search_by_sha256.assert_called_once_with("a" * 64) @pytest.mark.parametrize( ("sdk_method", "section"), [ ("get_overview_summary", "OverviewSummary"), ("get_eml_analysis", "EMLAnalysis"), ("get_mitre_techniques", "MITRE"), ("get_static_scan_results", "StaticScan"), ("get_cdr_results", "CDRResult"), ("get_signature_check_results", "SignatureCheck"), ("get_processes", "Processes"), ("get_process_tree", "ProcessTree"), ("get_url_analysis", "URLAnalysis"), ("get_network_summary", "NetworkSummary"), ], ) def test_uuid_section_sdk_mappings(sdk_method, section): client = _make_client() getattr(client.sdk, sdk_method).return_value = {"value": sdk_method} result = integration.threatzone_get_uuid_section(client, {"uuid": "u"}, sdk_method, section, "Title")[0] assert result.outputs == {"UUID": "u", "Data": {"value": sdk_method}} getattr(client.sdk, sdk_method).assert_called_once_with("u") class TestTelemetryCommands(unittest.TestCase): def setUp(self): self.client = _make_client() def test_behaviours_defaults_and_filters(self): self.client.sdk.get_behaviours.return_value = {"items": [], "total": 0} result = integration.threatzone_get_behaviours( self.client, {"uuid": "u", "pid": "42", "process_name": "sample.exe"}, )[0] assert result.outputs["UUID"] == "u" self.client.sdk.get_behaviours.assert_called_once_with( "u", type=None, pid=42, operation=None, process_name="sample.exe", page=1, limit=100, ) def test_behaviours_bounds_limit(self): with pytest.raises(DemistoException, match="between 1 and 500"): integration.threatzone_get_behaviours(self.client, {"uuid": "u", "limit": "501"}) def test_syscalls_defaults_and_limit_bound(self): self.client.sdk.get_syscalls.return_value = {"items": [], "total": 0} integration.threatzone_get_syscalls(self.client, {"uuid": "u"}) self.client.sdk.get_syscalls.assert_called_once_with("u", page=1, limit=500) with pytest.raises(DemistoException, match="between 1 and 2000"): integration.threatzone_get_syscalls(self.client, {"uuid": "u", "limit": "2001"}) def test_network_window_mappings(self): for sdk_method in ( "get_dns_queries", "get_http_requests", "get_tcp_connections", "get_udp_connections", "get_network_threats", ): sdk_mock = getattr(self.client.sdk, sdk_method) sdk_mock.return_value = [] result = integration.threatzone_get_network_data( self.client, {"uuid": "u", "limit": "1000", "skip": "0"}, sdk_method, "Section", "Title", )[0] assert result.outputs == {"UUID": "u", "Data": []} sdk_mock.assert_called_once_with("u", limit=1000, skip=0) def test_network_window_rejects_out_of_range(self): with pytest.raises(DemistoException, match="between 0 and 1000"): integration.threatzone_get_network_data( self.client, {"uuid": "u", "skip": "1001"}, "get_dns_queries", "DNSQueries", "DNS Queries", ) class TestGetResult(unittest.TestCase): def setUp(self): self.client = _make_client() self.client.sdk.get_submission.return_value = _submission() # By default, the legacy IOC query returns no items. self.client.sdk.get_iocs.return_value = IoCsResponse(items=[], total=0) def test_basic_result(self): results = integration.threatzone_get_result(self.client, {"uuid": "u"}) prefixes = [r.outputs_prefix for r in results if hasattr(r, "outputs_prefix")] assert "ThreatZone.Submission" in prefixes assert "ThreatZone.Analysis" in prefixes assert "ThreatZone.IOC" in prefixes analysis = next(r for r in results if r.outputs_prefix == "ThreatZone.Analysis") submission = next(r for r in results if r.outputs_prefix == "ThreatZone.Submission") assert analysis.outputs["LEVEL"] == 3 assert analysis.outputs["STATUS"] == 5 assert analysis.outputs["REPORT"]["status"] == 5 assert submission.outputs["Summary"]["REPORT"]["status"] == 5 assert submission.outputs["reports"][0]["status"] == "completed" assert analysis.outputs["SHA256"].startswith("6e899ff7") def test_legacy_iocs_include_every_page(self): self.client.sdk.get_iocs.side_effect = [ IoCsResponse(items=[IoC(type="domain", value="one.example", artifacts=[])], total=2), IoCsResponse(items=[IoC(type="domain", value="two.example", artifacts=[])], total=2), ] results = integration.threatzone_get_result(self.client, {"uuid": "u"}) legacy_iocs = next(result for result in results if result.outputs_prefix == "ThreatZone.IOC") assert legacy_iocs.outputs["DOMAIN"] == ["one.example", "two.example"] def test_details_use_paginated_finding_endpoints(self): self.client.sdk.get_indicators.return_value = IndicatorsResponse( items=[], total=0, levels=IndicatorLevels(malicious=0, suspicious=0, benign=0), ) self.client.sdk.get_yara_rules.return_value = YaraRulesResponse(items=[], total=0) self.client.sdk.get_artifacts.return_value = ArtifactsResponse(items=[], total=0) self.client.sdk.get_extracted_configs.return_value = ExtractedConfigsResponse(items=[], total=0) integration.threatzone_get_result(self.client, {"uuid": "u", "details": "true"}) expected_page_call = call("c89d310b-7862-4534-998a-3eb39d9a9d42", page=1, limit=100) assert self.client.sdk.get_indicators.call_args == expected_page_call assert self.client.sdk.get_yara_rules.call_args == expected_page_call assert self.client.sdk.get_iocs.call_args_list == [expected_page_call] def test_url_analysis_preserves_legacy_type_label(self): self.client.sdk.get_submission.return_value = _submission(report_type="url_analysis") results = integration.threatzone_get_result(self.client, {"uuid": "u"}) analysis = next(result for result in results if result.outputs_prefix == "ThreatZone.Analysis") assert analysis.outputs["TYPE"] == "urlAnalysis" def test_declined_status_raises(self): self.client.sdk.get_submission.return_value = _submission(status="error") with pytest.raises(DemistoException) as excinfo: integration.threatzone_get_result(self.client, {"uuid": "u"}) assert "declined" in str(excinfo.value).lower() class TestDownloads(unittest.TestCase): def setUp(self): self.client = _make_client() @patch.object(integration, "_save_download", return_value={"EntryID": "entry-1"}) def test_download_html_report(self, save_download_mock): download = MagicMock() self.client.sdk.download_html_report.return_value = download result = integration.threatzone_get_html_report_file(self.client, {"uuid": "u"}) assert result["EntryID"] == "entry-1" save_download_mock.assert_called_once_with(download, "threatzone-report-u.html") @patch.object(integration, "_save_download", return_value={"EntryID": "entry-2"}) def test_download_cdr_result(self, save_download_mock): download = MagicMock() self.client.sdk.download_cdr_result.return_value = download result = integration.threatzone_get_sanitized_file(self.client, {"uuid": "u"}) assert result["EntryID"] == "entry-2" save_download_mock.assert_called_once_with(download, "sanitized-u.zip") @patch.object(integration, "file_result_existing_file", return_value={"EntryID": "entry-3"}) def test_save_download_streams_to_existing_file(self, existing_file_result_mock): download = MagicMock() download.filename = "report.html" download.save.return_value = Path("report.html") result = integration._save_download(download, "fallback.html") assert result["EntryID"] == "entry-3" download.save.assert_called_once_with("report.html") download.read.assert_not_called() download.close.assert_called_once_with() existing_file_result_mock.assert_called_once_with("report.html", "report.html") @patch.object(integration, "file_result_existing_file", return_value={"EntryID": "entry-4"}) def test_save_download_uses_fallback_for_unnamed_response(self, existing_file_result_mock): download = MagicMock() download.filename = "download" download.save.return_value = Path("fallback.html") result = integration._save_download(download, "fallback.html") assert result["EntryID"] == "entry-4" download.save.assert_called_once_with("fallback.html") download.close.assert_called_once_with() existing_file_result_mock.assert_called_once_with("fallback.html", "fallback.html") @patch.object(integration, "_save_download", return_value={"EntryID": "entry"}) def test_sdk_stream_download_mappings(self, save_download_mock): mappings = ( ("get_static_scan_strings", "{uuid}_strings.json", None), ("download_sample", "sample-{uuid}", None), ("download_artifact", "artifact-{uuid}", "artifact_id"), ("download_pcap", "threatzone-{uuid}.pcap", None), ) for sdk_method, fallback, id_argument in mappings: save_download_mock.reset_mock() download = MagicMock() getattr(self.client.sdk, sdk_method).return_value = download args = {"uuid": "u", "artifact_id": "artifact-1"} integration.threatzone_download_sdk_file( self.client, args, sdk_method, fallback, id_argument=id_argument, ) expected_args = ("u", "artifact-1") if id_argument else ("u",) getattr(self.client.sdk, sdk_method).assert_called_once_with(*expected_args) save_download_mock.assert_called_once_with(download, fallback.format(uuid="u")) @patch.object(integration, "_save_download", return_value={"EntryID": "yara-entry"}) def test_generated_yara_immediate_success(self, save_download_mock): download = MagicMock() self.client.sdk.download_yara_rule.return_value = download result = integration.threatzone_download_yara_rule(self.client, {"uuid": "u"}) assert result == {"EntryID": "yara-entry"} save_download_mock.assert_called_once_with(download, "u.yar") @patch.object(integration, "_save_download", return_value={"EntryID": "yara-entry"}) @patch.object(integration.demisto, "executeCommand") @patch.object(integration.time, "monotonic", side_effect=[0.0, 1.0, 4.0]) def test_generated_yara_polls_with_server_retry(self, monotonic_mock, execute_command_mock, save_download_mock): download = MagicMock() self.client.sdk.download_yara_rule.side_effect = [ YaraRulePendingError("pending", retry_after=2.5), YaraRulePendingError("pending"), download, ] integration.threatzone_download_yara_rule(self.client, {"uuid": "u", "timeout": "120"}) assert execute_command_mock.call_args_list == [ call("Sleep", {"seconds": "2.5"}), call("Sleep", {"seconds": str(integration.YARA_POLL_INTERVAL_SECONDS)}), ] save_download_mock.assert_called_once_with(download, "u.yar") @patch.object(integration.demisto, "executeCommand") @patch.object(integration.time, "monotonic", side_effect=[0.0, 119.0]) def test_generated_yara_timeout(self, monotonic_mock, execute_command_mock): self.client.sdk.download_yara_rule.side_effect = YaraRulePendingError("pending", retry_after=2.0) with pytest.raises(DemistoException, match="Timed out after 120 seconds"): integration.threatzone_download_yara_rule(self.client, {"uuid": "u"}) execute_command_mock.assert_not_called() @patch.object(integration.demisto, "executeCommand") @patch.object(integration.time, "monotonic", side_effect=[0.0, 0.0, 0.0]) def test_generated_yara_polling_has_an_attempt_limit(self, monotonic_mock, execute_command_mock): self.client.sdk.download_yara_rule.side_effect = YaraRulePendingError("pending", retry_after=0.0) with pytest.raises(DemistoException, match="Timed out after 2 seconds"): integration.threatzone_download_yara_rule(self.client, {"uuid": "u", "timeout": "2"}) assert self.client.sdk.download_yara_rule.call_count == 2 assert execute_command_mock.call_args_list == [ call("Sleep", {"seconds": str(integration.MIN_YARA_POLL_INTERVAL_SECONDS)}), call("Sleep", {"seconds": str(integration.MIN_YARA_POLL_INTERVAL_SECONDS)}), ] @patch.object(integration, "fileResult", return_value={"EntryID": "screenshot"}) def test_screenshot_bytes_become_war_room_file(self, file_result_mock): self.client.sdk.get_screenshot.return_value = b"png" result = integration.threatzone_download_url_screenshot(self.client, {"uuid": "u"}) assert result == {"EntryID": "screenshot"} file_result_mock.assert_called_once_with("threatzone-url-screenshot-u.png", b"png") @patch.object(integration, "fileResult", return_value={"EntryID": "media"}) def test_media_file_uses_validated_server_filename(self, file_result_mock): media = MagicMock() media.id = "media-1" media.name = "screen.png" self.client.sdk.list_media_files.return_value = [media] self.client.sdk.get_media_file.return_value = b"png" result = integration.threatzone_download_media_file( self.client, {"uuid": "u", "file_id": "media-1"}, ) assert result == {"EntryID": "media"} file_result_mock.assert_called_once_with("screen.png", b"png") def test_media_file_rejects_unsafe_server_filename(self): media = MagicMock() media.id = "media-1" media.name = "../screen.png" self.client.sdk.list_media_files.return_value = [media] with pytest.raises(DemistoException, match="unsafe media filename"): integration.threatzone_download_media_file( self.client, {"uuid": "u", "file_id": "media-1"}, ) class TestSdkExceptionFormatting(unittest.TestCase): def test_known_exceptions(self): assert "Authorization" in integration._format_sdk_exception(AuthenticationError("nope", status_code=401)) assert "not found" in integration._format_sdk_exception(NotFoundError("nope", status_code=404)).lower() assert "API error" in integration._format_sdk_exception(APIError("boom", status_code=500)) assert "timed out" in integration._format_sdk_exception(AnalysisTimeoutError("late", uuid="u", elapsed=1.0)) assert "not yet available" in integration._format_sdk_exception(ReportUnavailableError("wait")) class TestTestModule(unittest.TestCase): def test_ok(self): client = _make_client() client.sdk.get_user_info.return_value = _user_info() assert integration.test_module(client) == "ok" def test_auth_failure(self): client = _make_client() client.sdk.get_user_info.side_effect = AuthenticationError("bad", status_code=401) result = integration.test_module(client) assert "Authorization" in result class TestSdkConsumerContract(unittest.TestCase): def test_completed_submission_through_fake_api(self): sample_bytes = b"threatzone-sdk-contract-sample" sample_sha256 = hashlib.sha256(sample_bytes).hexdigest() fake_api = FakeThreatZoneAPI() scenarios.seed_malicious_pe(fake_api, sha256=sample_sha256) sdk = ThreatZoneSDK( api_key="test-key", base_url="https://fake.threat.zone/public-api", http_client=fake_api.as_httpx_client(), ) created = sdk.create_sandbox_submission(sample_bytes, private=True) sdk.get_submission(created.uuid) sdk.get_submission(created.uuid) client = _make_client() client.sdk = sdk results = integration.threatzone_get_result(client, {"uuid": created.uuid}) analysis = next(result for result in results if result.outputs_prefix == "ThreatZone.Analysis") assert analysis.outputs["STATUS"] == 5 assert analysis.outputs["LEVEL"] == 3 assert analysis.outputs["REPORT"]["status"] == 5 @patch.object(integration, "_save_download", return_value={"EntryID": "fake-download"}) def test_extended_commands_through_fake_api(self, save_download_mock): fake_api = FakeThreatZoneAPI() scenarios.seed_malicious_pe(fake_api) sdk = ThreatZoneSDK( api_key="test-key", base_url="https://fake.threat.zone/public-api", http_client=fake_api.as_httpx_client(), ) created = sdk.create_sandbox_submission(b"sample", private=True) sdk.get_submission(created.uuid) sdk.get_submission(created.uuid) client = _make_client() client.sdk = sdk assert integration.threatzone_get_metafields(client, {"scan_type": "sandbox"})[0].outputs["Data"] assert integration.threatzone_list_submissions(client, {})[0].outputs["items"] assert integration.threatzone_get_uuid_section( client, {"uuid": created.uuid}, "get_overview_summary", "OverviewSummary", "Overview", )[0].outputs["Data"] assert integration.threatzone_get_network_data( client, {"uuid": created.uuid}, "get_network_threats", "NetworkThreats", "Network Threats", )[0].outputs["Data"] integration.threatzone_download_sdk_file( client, {"uuid": created.uuid}, "download_sample", "sample-{uuid}", ) integration.threatzone_download_yara_rule(client, {"uuid": created.uuid}) assert save_download_mock.call_count == 2 for download_call in save_download_mock.call_args_list: download_call.args[0].close() if __name__ == "__main__": unittest.main()