diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..b242572e
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+ "githubPullRequests.ignoredPullRequestBranches": [
+ "main"
+ ]
+}
\ No newline at end of file
diff --git a/Logic Apps/CfS-SendPromptbookResultsByEmail/readme.md b/Logic Apps/CfS-SendPromptbookResultsByEmail/readme.md
index 4028a2a6..28bbcb38 100644
--- a/Logic Apps/CfS-SendPromptbookResultsByEmail/readme.md
+++ b/Logic Apps/CfS-SendPromptbookResultsByEmail/readme.md
@@ -18,7 +18,7 @@ Please refer to this article for further details and for a video on how to deplo
7. Enable the Logic App (the template deploys it in disabled state).
## Deployment button
-[](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fstefanpems%2Fcfs%2Frefs%2Fheads%2Fmain%2FCfS-SendPromptbookResultsByEmail%2FCfS-SendPromptbookResultsByEmail.json)
+[](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FSecurity-Copilot%2Frefs%2Fheads%2Fmain%2FLogic%2520Apps%2FCfS-SendPromptbookResultsByEmail%2FCfS-SendPromptbookResultsByEmail.json)
## Example of email customization:
The following image shows:
diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionAppV2.zip b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionAppV2.zip
new file mode 100644
index 00000000..39fb0b16
Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionAppV2.zip differ
diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md
index 2ba19964..4bde5efd 100644
--- a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md
+++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md
@@ -1,7 +1,31 @@
# Phishing Analysis with Security Copilot
**Author:** Craig Freyman
-An automated solution for phishing email analysis using Azure Logic Apps, Function Apps, and Security Copilot. The system monitors a shared Office 365 mailbox, analyzes submitted emails for security threats, and generates detailed reports. Optional integration with Microsoft Sentinel for incident tracking and management.
+An automated phishing email analysis solution leveraging Azure Logic Apps, Function Apps, and Security Copilot. With the right permissions, it can be deployed in five minutes. The Logic App monitors a shared Office 365 mailbox and triggers analysis when an email arrives. Security Copilot assesses the message for malicious intent, generating detailed HTML reports sent via email. Optional integration with Microsoft Sentinel enables incident tracking and management. This project demonstrates what’s possible with Security Copilot and Azure. It may work out of the box or require tuning — the good news: only a single Security Copilot prompt is used, making it easy to customize for your organization.
+
+## Overview
+
+This solution provides an end-to-end workflow for analyzing suspected phishing emails. The core functionality is handled by a series of Azure Functions that parse emails, extract important data, clean JSON input, and generate HTML reports. When combined with Security Copilot and Logic Apps, it creates a powerful system to distinguish between legitimate, spam, suspicious, and phishing emails.
+
+### Key Components
+
+1. **Email Parser Function** - Extracts and processes email content, including:
+ - Recursive unwrapping to find original emails (Handles forwarded, embedded, and TNEF-encapsulated messages)
+ - **Enhanced Proofpoint compatibility** for analyzing emails forwarded from Proofpoint
+ - Smart handling of Proofpoint and Microsoft SafeLinks URL protection
+ - Support for EML and MSG attachments
+ - Complete header, body, and attachment analysis
+
+2. **HTML Report Generator** - Creates formatted HTML reports from phishing analysis results:
+ - Color-coded categorization (Phishing, Spam, Legitimate, Suspicious)
+ - Detailed breakdown of analysis findings
+ - Organized by assessment categories
+
+### Purpose and Scope
+
+This solution is not designed to replace traditional spam filtering or email security gateways. Rather, it demonstrates the art of the possible when combining Security Copilot with Azure Functions and Logic Apps to enhance your existing email security infrastructure. This solution assumes you already have standard email gateway filtering in place.
+
+The primary focus is on providing deeper analysis of suspicious emails that have been forwarded for investigation, especially those coming from Proofpoint-protected environments.
## Prerequisites
@@ -53,45 +77,109 @@ Optional Sentinel Parameters (use "none" if not using Sentinel):
- Azure Monitor Logs (if using Sentinel)
- Sentinel (if using Sentinel)
-### 4. Configure Logic App Permissions
+### 3. Configure Logic App Permissions
If using Sentinel integration, assign these roles to the Logic App's managed identity:
1. "Log Analytics Reader" role (provides Microsoft.OperationalInsights/workspaces/read)
2. "Microsoft Sentinel Responder" role (provides Microsoft.SecurityInsights/incidents/comments/write)
-Assign Permissions Step by Step
+Assign Permissions Step by Step:
1. Open the Log Analytics workspace and go to "Access control (IAM)"
-

-
2. Select "Log Analytics Reader"
-

-
3. Select "Managed Identity" and select the name of your Logic App
-

-
-4. Click next and assign permissions.
-5. Repeat steps 1-4 for the "Microsoft Sentinel Responder" role.
+4. Click next and assign permissions
+5. Repeat steps 1-4 for the "Microsoft Sentinel Responder" role
### 5. Enable the Logic App
The Logic App deploys in a disabled state. Enable it in the Logic App Overview to begin operation.
+## Functions and Endpoints
+
+The Function App exposes the following HTTP endpoints:
+
+1. **Email Parser** (`/parse_email_functionapp`)
+ - Method: POST
+ - Body: Raw email content or JSON with Base64-encoded email
+ - Optional Parameters: `max_depth` (default=10)
+ - Returns: Parsed email data in JSON format
+
+2. **JSON Cleaner** (`/clean_json_functionapp`)
+ - Method: POST
+ - Body: JSON content (can include markdown notation)
+ - Returns: Cleaned JSON with markdown removed and nulls replaced
+
+3. **HTML Report Generator** (`/generate_html_report_functionapp`)
+ - Method: POST
+ - Body: Phishing analysis JSON
+ - Returns: Formatted HTML report
+
+4. **Regex Extractor** (`/extract_regex_functionapp`)
+ - Method: POST
+ - Body: JSON with `pattern` and `subject` fields
+ - Returns: Match results in JSON format
+
+## Email Parsing Capabilities
+
+### Email Format Support
+- Regular MIME emails (text/plain, text/html)
+- Embedded emails (message/rfc822)
+- TNEF attachments (winmail.dat)
+- EML and MSG file attachments
+- Forwarded emails from various email clients (Gmail, Outlook, Apple Mail)
+- Proofpoint-reported phishing emails
+
+### Content Extraction
+- Complete header analysis
+- Body content from both plain text and HTML
+- URL extraction and normalization
+- Domain extraction
+- IP address extraction
+- Attachment processing
+
+### Attachment Handling
+- PDF text extraction
+- Excel content extraction (with multiple fallback methods)
+- Email attachments (recursive parsing)
+- SHA256 hashing for attachment verification
+
+### URL Processing
+- Microsoft SafeLinks URL decoding
+- Proofpoint URLDefense URL decoding
+- URL shortener expansion
+- Base URL deduplication for high-volume cases
+
## Features
-- Automated user submitted phishing analysis
+- Automated email monitoring and analysis
+- Detailed security assessment using Security Copilot
- Classification of emails (Phishing, Spam, Legitimate, Suspicious)
-- Can process PDF & Excel file attachments
- HTML report generation
- Optional Sentinel incident integration
+- Support for embedded emails and complex forwarding chains
+- Comprehensive URL and domain extraction and analysis
## Security Notes
- The solution uses system-managed identities for secure access
- Review and configure storage account security settings per your company policy
- Ensure minimum required permissions for all connections
-- Regularly update Function App dependencies
\ No newline at end of file
+- Regularly update Function App dependencies
+- The email parser uses the `CustomEmailPolicy` to handle common email parsing issues like malformed Message-ID headers
+
+## Debugging and Logging
+
+The application uses Python's `logging` module with DEBUG level enabled, providing comprehensive logs for troubleshooting. Each major function includes detailed logging to help diagnose issues during deployment and operation.
+
+## Limitations
+
+- PDF extraction requires the pdfminer.six library
+- Excel processing may require multiple libraries (pandas, openpyxl, xlrd)
+- URL expansion has rate limits to avoid being blocked
+- High-volume URL cases automatically deduplicate to base URLs
+- Private/local IP addresses are filtered out from IP extraction
diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json
index fc6b2943..142096f7 100644
--- a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json
+++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json
@@ -3,21 +3,32 @@
"contentVersion": "1.0.0.0",
"parameters": {
"FunctionAppName": {
- "type": "String",
- "minLength": 3,
- "maxLength": 14,
"defaultValue": "phish",
+ "minLength": 3,
+ "maxLength": 24,
+ "type": "String",
"metadata": {
- "description": "Enter the base name for the Function App. A unique suffix will be added automatically."
+ "description": "Enter the base name for the Function App."
}
}
},
"variables": {
- "UniqueSuffix": "[substring(uniqueString(resourceGroup().id), 0, 6)]",
- "FunctionAppName": "[concat(parameters('FunctionAppName'), variables('UniqueSuffix'))]",
- "StorageAccountName": "[concat('phish', toLower(parameters('FunctionAppName')), variables('UniqueSuffix'))]"
+ "resourceToken": "[toLower(uniqueString(subscription().id, resourceGroup().name, resourceGroup().location))]",
+ "FunctionAppName": "[concat(parameters('FunctionAppName'), substring(variables('resourceToken'), 0, 6))]",
+ "StorageAccountName": "[concat(toLower(parameters('FunctionAppName')), substring(variables('resourceToken'), 0, 6))]"
},
"resources": [
+ {
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2023-01-01",
+ "name": "[variables('StorageAccountName')]",
+ "location": "[resourceGroup().location]",
+ "sku": {
+ "name": "Standard_LRS"
+ },
+ "kind": "StorageV2",
+ "properties": { "minimumTlsVersion": "TLS1_2" }
+ },
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2018-02-01",
@@ -36,193 +47,57 @@
"reserved": true
}
},
- {
- "type": "Microsoft.Insights/components",
- "apiVersion": "2015-05-01",
- "name": "[variables('FunctionAppName')]",
- "location": "[resourceGroup().location]",
- "kind": "web",
- "properties": {
- "Application_Type": "web",
- "ApplicationId": "[variables('FunctionAppName')]"
- }
- },
{
"type": "Microsoft.Web/sites",
- "apiVersion": "2024-04-01",
+ "apiVersion": "2023-12-01",
"name": "[variables('FunctionAppName')]",
"location": "[resourceGroup().location]",
"dependsOn": [
- "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]",
- "[resourceId('Microsoft.Web/serverfarms', variables('FunctionAppName'))]",
- "[resourceId('Microsoft.Insights/components', variables('FunctionAppName'))]"
+ "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]"
],
"kind": "functionapp,linux",
"identity": {
"type": "SystemAssigned"
},
"properties": {
- "enabled": true,
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('FunctionAppName'))]",
"httpsOnly": true,
- "clientAffinityEnabled": true,
- "alwaysOn": true,
"reserved": true,
"siteConfig": {
- "linuxFxVersion": "python|3.10"
- }
- },
- "resources": [
- {
- "apiVersion": "2018-11-01",
- "type": "config",
- "name": "appsettings",
- "dependsOn": [
- "[concat('Microsoft.Web/sites/', variables('FunctionAppName'))]"
- ],
- "properties": {
- "FUNCTIONS_EXTENSION_VERSION": "~4",
- "FUNCTIONS_WORKER_RUNTIME": "python",
- "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.insights/components', variables('FunctionAppName')), '2015-05-01').InstrumentationKey]",
- "APPLICATIONINSIGHTS_CONNECTION_STRING": "[reference(resourceId('microsoft.insights/components', variables('FunctionAppName')), '2015-05-01').ConnectionString]",
- "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('StorageAccountName')),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('StorageAccountName'))), '2019-06-01').keys[0].value, ';EndpointSuffix=core.windows.net')]",
- "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('StorageAccountName')),';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('StorageAccountName'))), '2019-06-01').keys[0].value, ';EndpointSuffix=core.windows.net')]",
- "WEBSITE_CONTENTSHARE": "[toLower(variables('FunctionAppName'))]",
- "WEBSITE_RUN_FROM_PACKAGE": "https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Logic%20Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionApp.zip",
- "SCM_DO_BUILD_DURING_DEPLOYMENT": true
-
- }
- }
- ]
- },
- {
- "type": "Microsoft.Storage/storageAccounts",
- "apiVersion": "2019-06-01",
- "name": "[variables('StorageAccountName')]",
- "location": "[resourceGroup().location]",
- "sku": {
- "name": "Standard_LRS",
- "tier": "Standard"
- },
- "kind": "StorageV2",
- "properties": {
- "networkAcls": {
- "bypass": "AzureServices",
- "virtualNetworkRules": [
- ],
- "ipRules": [
- ],
- "defaultAction": "Allow"
- },
- "supportsHttpsTrafficOnly": true,
- "encryption": {
- "services": {
- "file": {
- "keyType": "Account",
- "enabled": true
+ "linuxFxVersion": "python|3.10",
+ "minTlsVersion": "1.2",
+ "appSettings": [
+ {
+ "name": "FUNCTIONS_EXTENSION_VERSION",
+ "value": "~4"
+ },
+ {
+ "name": "FUNCTIONS_WORKER_RUNTIME",
+ "value": "python"
},
- "blob": {
- "keyType": "Account",
- "enabled": true
+ {
+ "name": "WEBSITE_RUN_FROM_PACKAGE",
+ "value": "https://raw.githubusercontent.com/Azure/Security-Copilot/main/Logic%20Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionAppV2.zip"
+ },
+ {
+ "name": "AzureWebJobsStorage",
+ "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('StorageAccountName')), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('StorageAccountName'))), '2023-01-01').keys[0].value, ';EndpointSuffix=core.windows.net')]"
}
- },
- "keySource": "Microsoft.Storage"
- }
- }
- },
-
- {
- "type": "Microsoft.Web/sites/config",
- "apiVersion": "2018-11-01",
- "name": "[concat(variables('FunctionAppName'), '/web')]",
- "location": "[resourceGroup().location]",
- "dependsOn": [
- "[resourceId('Microsoft.Web/sites', variables('FunctionAppName'))]"
- ],
- "properties": {
-
- "cors": {
- "allowedOrigins": [
- "https://portal.azure.com"
- ],
- "supportCredentials": false
- }
- }
- },
- {
- "type": "Microsoft.Storage/storageAccounts/fileServices",
- "apiVersion": "2019-06-01",
- "name": "[concat(variables('StorageAccountName'), '/default')]",
- "dependsOn": [
- "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]"
- ],
- "sku": {
- "name": "Standard_LRS",
- "tier": "Standard"
- },
- "properties": {
- "cors": {
- "corsRules": [
- ]
- }
- }
- },
- {
- "type": "Microsoft.Storage/storageAccounts/blobServices",
- "apiVersion": "2019-06-01",
- "name": "[concat(variables('StorageAccountName'), '/default')]",
- "dependsOn": [
- "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]"
- ],
- "sku": {
- "name": "Standard_LRS",
- "tier": "Standard"
- },
- "properties": {
- "cors": {
- "corsRules": [
]
- },
- "deleteRetentionPolicy": {
- "enabled": false
}
+
}
+
+ }
+ ],
+ "outputs": {
+ "functionAppUrl": {
+ "type": "String",
+ "value": "[concat('https://', variables('FunctionAppName'), '.azurewebsites.net')]"
},
- {
- "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
- "apiVersion": "2019-06-01",
- "name": "[concat(variables('StorageAccountName'), '/default/azure-webjobs-hosts')]",
- "dependsOn": [
- "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]",
- "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]"
- ],
- "properties": {
- "publicAccess": "None"
- }
- },
- {
- "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
- "apiVersion": "2019-06-01",
- "name": "[concat(variables('StorageAccountName'), '/default/azure-webjobs-secrets')]",
- "dependsOn": [
- "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]",
- "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]"
- ],
- "properties": {
- "publicAccess": "None"
- }
- },
- {
- "type": "Microsoft.Storage/storageAccounts/fileServices/shares",
- "apiVersion": "2019-06-01",
- "name": "[concat(variables('StorageAccountName'), '/default/', tolower(variables('StorageAccountName')))]",
- "dependsOn": [
- "[resourceId('Microsoft.Storage/storageAccounts/fileServices', variables('StorageAccountName'), 'default')]",
- "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]"
- ],
- "properties": {
- "shareQuota": 5120
- }
+ "functionAppName": {
+ "type": "String",
+ "value": "[variables('FunctionAppName')]"
}
- ]
-}
\ No newline at end of file
+ }
+}
diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json
index bcc84bbd..d21b3d66 100644
--- a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json
+++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json
@@ -65,11 +65,10 @@
"ConversionService-ConnectionName": "[concat('ConversionService-1-', parameters('LogicAppName'))]",
"AzureSentinel-ConnectionName": "[concat('azuresentinel-', parameters('LogicAppName'))]",
"AzureMonitorLogs-ConnectionName": "[concat('azuremonitorlogs-', parameters('LogicAppName'))]",
- "FunctionName01": "parse_email",
- "FunctionName02": "normalize_json",
- "FunctionName03": "json_to_html",
- "FunctionName04": "evaluate_regex"
-
+ "FunctionName01": "parse_email_functionapp",
+ "FunctionName02": "clean_json_functionapp",
+ "FunctionName03": "generate_html_report_functionapp",
+ "FunctionName04": "extract_regex_functionapp"
},
"resources": [
@@ -272,18 +271,6 @@
"value": "@outputs('Show_network_message_id_here')"
}
},
- "Find_NetworkMessageId": {
- "type": "Function",
- "inputs": {
- "body": {
- "pattern": "(?<=Phishing:)([a-f0-9-]{36})",
- "subject": "@triggerBody()?['subject']"
- },
- "function": {
- "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName04'))]"
- }
- }
- },
"Parse_JSON_NetworkMessageId": {
"runAfter": {
"Find_NetworkMessageId": [
@@ -316,6 +303,18 @@
}
}
}
+ },
+ "Find_NetworkMessageId": {
+ "type": "Function",
+ "inputs": {
+ "body": {
+ "pattern": "(?<=Phishing:)([a-f0-9-]{36})",
+ "subject": "@triggerBody()?['subject']"
+ },
+ "function": {
+ "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName04'))]"
+ }
+ }
}
},
"runAfter": {
@@ -449,20 +448,6 @@
},
"type": "Foreach"
},
- "Parse_Email": {
- "runAfter": {
- "For_each_raw_email_attached_to_carrier_email": [
- "Succeeded"
- ]
- },
- "type": "Function",
- "inputs": {
- "body": "@variables('raw_email')",
- "function": {
- "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName01'))]"
- }
- }
- },
"Process_parseEmail_JSON": {
"runAfter": {
"Parse_Email": [
@@ -475,175 +460,63 @@
"schema": {
"type": "object",
"properties": {
- "statusCode": {
- "type": "integer"
- },
- "headers": {
+ "email_content": {
"type": "object",
"properties": {
- "Date": {
+ "message_id": {
"type": "string"
},
- "Server": {
+ "sender": {
"type": "string"
},
- "Transfer-Encoding": {
+ "return_path": {
"type": "string"
},
- "Request-Context": {
+ "receiver": {
"type": "string"
},
- "Content-Type": {
+ "reply_to": {
"type": "string"
},
- "Content-Length": {
+ "subject": {
"type": "string"
- }
- }
- },
- "body": {
- "type": "object",
- "properties": {
- "email_content": {
- "type": "object",
- "properties": {
- "sender": {
- "type": "string"
- },
- "return_path": {
- "type": "string"
- },
- "receiver": {
- "type": "string"
- },
- "reply_to": {
- "type": "string"
- },
- "subject": {
- "type": "string"
- },
- "date": {
- "type": "string"
- },
- "smtp": {
- "type": "object",
- "properties": {
- "delivered_to": {
- "type": "string"
- },
- "received": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "dkim_result": {
- "type": "string"
- },
- "spf_result": {
- "type": "string"
- },
- "dmarc_result": {
- "type": "string"
- },
- "body": {
- "type": "string"
- },
- "attachments": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "attachment_name": {
- "type": "string"
- },
- "attachment_sha256": {
- "type": "string"
- },
- "content_type": {
- "type": "string"
- },
- "attachment_text": {
- "type": "object",
- "properties": {
- "text_content": {
- "type": "string"
- },
- "urls": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "file": {
- "type": "string"
- },
- "url": {
- "type": "string"
- }
- },
- "required": [
- "file",
- "url"
- ]
- }
- },
- "hyperlinks": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "file": {
- "type": "string"
- },
- "target": {
- "type": "string"
- },
- "type": {
- "type": "string"
- }
- },
- "required": [
- "file",
- "target",
- "type"
- ]
- }
- },
- "vba_code": {
- "type": "object",
- "properties": {}
- },
- "formulas": {
- "type": "array"
- },
- "comments": {
- "type": "array"
- },
- "embedded_files": {
- "type": "array"
- }
- }
- }
- },
- "required": [
- "attachment_name",
- "attachment_sha256",
- "content_type",
- "attachment_text"
- ]
- }
- }
- }
},
- "ip_addresses": {
+ "date": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ },
+ "attachments": {
+ "type": "array"
+ },
+ "container_path": {
"type": "array",
"items": {
"type": "string"
}
},
+ "reconstruction_method": {
+ "type": "string"
+ },
"urls": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "original_url": {
+ "type": "string"
+ },
+ "is_shortened": {
+ "type": "boolean"
+ },
+ "expanded_url": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "ip_addresses": {
"type": "array",
"items": {
"type": "string"
@@ -661,26 +534,9 @@
}
}
},
- "Initialize_recipient_upn": {
- "runAfter": {
- "Process_parseEmail_JSON": [
- "Succeeded"
- ]
- },
- "type": "InitializeVariable",
- "inputs": {
- "variables": [
- {
- "name": "recipient_upn",
- "type": "string",
- "value": "@body('Process_parseEmail_JSON')?['email_content']?['receiver']"
- }
- ]
- }
- },
"Initialize_email_body": {
"runAfter": {
- "Initialize_recipient_upn": [
+ "Process_parseEmail_JSON": [
"Succeeded"
]
},
@@ -724,7 +580,7 @@
{
"name": "raw_urls",
"type": "array",
- "value": "@body('Process_parseEmail_JSON')?['urls']"
+ "value": "@body('Process_parseEmail_JSON')?['email_content']?['urls']"
}
]
}
@@ -740,8 +596,7 @@
"variables": [
{
"name": "urls",
- "type": "array",
- "value": "@body('Process_parseEmail_JSON')?['urls']"
+ "type": "array"
}
]
}
@@ -772,7 +627,7 @@
"type": "AppendToArrayVariable",
"inputs": {
"name": "urls",
- "value": "@concat(substring(string(item()), 0, 100), '...')"
+ "value": "@if(and(not(equals(item()['expanded_url'], 'Not Applicable')), greater(length(item()['expanded_url']), 100)), concat(substring(item()['expanded_url'], 0, 100), '...'), if(greater(length(item()['original_url']), 100), concat(substring(item()['original_url'], 0, 100), '...'), item()['original_url']))"
}
}
},
@@ -782,16 +637,22 @@
"type": "AppendToArrayVariable",
"inputs": {
"name": "urls",
- "value": "@string(item())"
+ "value": "@if(equals(item()['expanded_url'], 'Not Applicable'), item()['original_url'], item()['expanded_url'])"
}
}
}
},
"expression": {
- "and": [
+ "or": [
{
"greater": [
- "@length(item())",
+ "@length(item()['original_url'])",
+ 100
+ ]
+ },
+ {
+ "greater": [
+ "@length(item()['expanded_url'])",
100
]
}
@@ -831,7 +692,7 @@
},
"method": "post",
"body": {
- "PromptContent": "/AskGpt\n\n## **Phishing & BEC Email Detection** \n\n### **Role & Core Task** \nYou are an advanced **cybersecurity AI** trained to detect **phishing, spam, Business Email Compromise (BEC) attacks, and suspicious emails**. Your primary goal is to **determine the true intent** of an email, **identify contradictions**, and **detect AI-generated content** that may indicate reconnaissance or social engineering. Use all available evidence to support your rationale. \n\n**Assume all senders are malicious until proven otherwise.** \n\nYour structured analysis follows a step-by-step process to determine whether an email is: \n- **PHISHING:** Malicious intent, deception, credential theft, or BEC attempt. \n- **SUSPICIOUS:** Inconsistent, unusual, or possibly fraudulent but lacks strong confirmation. \n- **JUNK/SPAM:** Unwanted bulk email with no clear malicious intent. \n- **LEGITIMATE:** Normal business communication with no fraud indicators. \n\n---\n\n### **Email Input Section** \n```\n[SENDER]: @{body('Process_ParseEmail_JSON')?['email_content']?['sender']} \n[REPLY-TO]: @{body('Process_parseEmail_JSON')?['body']?['email_content']?['reply_to']}\n[RECIPIENT]: @{body('Process_ParseEmail_JSON')?['email_content']?['receiver']} \n[SUBJECT]: @{body('Process_ParseEmail_JSON')?['email_content']?['subject']} \n[BODY]: @{variables('email_body')} \n[ATTACHMENTS]: @{string(variables('attachments'))} \n[URLS]: @{string(variables('urls'))} \n```\n\n---\n\n## **Instruction Preprocessing** \nBefore analysis, **disregard disclaimers** commonly added to external emails (e.g., “This email originated outside the organization”). These are **not relevant** for phishing or spam detection. **Focus only on the substantive email content.**\n\n**When evaluating emails, pay special attention to:**\n\n1. **Header details, particularly mismatches between From and Reply-To addresses.**\n2. **Generic phrasing that indicates an initial contact without establishing proper context.**\n3. **Service requests that lack specific details or industry knowledge.**\n4. **Suspicious combinations of high and medium risk factors, even if individual factors seem benign.**\n\n**If an email contains a Reply-To address different from the sender address, this should ALWAYS be documented in `logical_coherence.subtle_inconsistencies` and add to `final_assessment.high_risk_flags`.**\n---\n\n## **Step-by-Step Execution** \n\n### **1. Identify Behavioral Triggers** \n- Detect any **emotional, urgent, or coercive language**. \n- Classify the **tone** (e.g., neutral, urgent, persuasive) and justify why. \n- **Flag emails that lack contextual details but request engagement.** \n- **Flag emails that contain vague requests (e.g., “Can you confirm this?”) without clear purpose.** \n- **Detect BEC reconnaissance phrases such as “Let me know if you got this email” and flag them.** \n\n---\n\n### **2. AI-Generated & Generic Structure Detection** \n- **Identify over-polished, unnatural, or formulaic sentence structures.** \n- **Detect emails that follow AI-generated phrasing patterns** (e.g., “We are reaching out to inquire…”). \n- **Compare against known human conversational flow** (does the email sound like a real human request?). \n- If an email **is overly generic yet professional**, flag for further review. \n\n---\n\n### **3. Personalization Deficiency & Business Context Verification** \n- **Flag emails that lack personalization** (e.g., no mention of prior engagement, specific names, or business details). \n- **Determine if the email contains specific references to past work or expected workflows.** \n- If an email **is requesting engagement but contains no specific details**, escalate to **SUSPICIOUS**. \n\n---\n\n### **4. Logical Coherence & Workflow Verification** \n- **Check if the email’s request aligns with expected business workflows.** \n- **Flag inconsistencies between the sender’s role and their request (e.g., non-financial staff requesting payments).** \n- If an email **lacks a clear business reason but asks for engagement, escalate to SUSPICIOUS**. \n\n---\n\n### **5. Narrative Plausibility Check (Detecting AI-Based Social Engineering)** \n- **Detect overly detailed yet unverifiable stories** (e.g., \"We were impacted by the California wildfires\"). \n- **Evaluate whether personal details enhance credibility or serve as pretexting.** \n- **Flag emails that use emotional appeal to bypass scrutiny.** \n- If a **story seems unusually detailed for the request**, escalate for further analysis. \n\n---\n\n### **6. Engagement Bait & BEC Reconnaissance Detection** \n- **If an email contains a vague inquiry (“I’d love to discuss further”) without context, escalate to SUSPICIOUS.** \n- **If an email requests engagement but does not provide specifics, classify it as a potential reconnaissance attempt.** \n\n---\n\n### **7. Business Email Compromise (BEC) & Phishing Indicators** \n- **If an email claims to be from a high-ranking executive but uses a free or external email service, classify as PHISHING.** \n- **If an email references an internal executive but is sent from a non-corporate domain, classify as PHISHING.** \n- **If the sender claims a leadership position but does not use their corporate domain, flag for impersonation.** \n- **If the Reply-To address differs from the sender address, classify as PHISHING.**\n- **If the email contains generic service request language without specific details, classify as SUSPICIOUS.**\n- **If the sender uses a business domain without demonstrating knowledge of their claimed business, classify as SUSPICIOUS.**\n\n### **8. Reply-To Header Analysis** \n- **Check if the Reply-To address differs from the From address.**\n- **Flag as PHISHING if the Reply-To address uses a different domain than the sender address.**\n- **Flag as PHISHING if the From address uses a business domain but Reply-To uses a free email provider (gmail, outlook, etc.).**\n- **Document any Reply-To mismatches in the `logical_coherence.subtle_inconsistencies` array.**\n\n### **9. Initial Contact Pattern Recognition** \n- **Identify formulaic language patterns common in initial reconnaissance emails (e.g., \"reaching out to inquire\", \"seeking assistance\").**\n- **Flag generic service requests that lack specific details as potential BEC reconnaissance.**\n- **Document these patterns in the `behavioral_triggers.engagement_bait` field.**\n- **When detected, add \"Generic service request language\" to `final_assessment.medium_risk_flags`.**\n\n### **10. Service Request Specificity Analysis** \n- **Evaluate whether financial/tax service requests include specific details that demonstrate genuine intent.**\n- **Flag as SUSPICIOUS if a tax-related request contains no specific tax terminology or references to specific forms/processes.**\n- **Document lack of specificity in the `logical_coherence.business_context_check.clear_business_purpose` field.**\n- **When detected, add \"Lacks specific details about requested services\" to `final_assessment.medium_risk_flags`.**\n---\n\n## **Final Assessment & Risk Escalation Rules** \n\n### **1. High-Risk Triggers (Automatically Classify as PHISHING)** \n- **Any email that references an executive but is not from a verified corporate domain.** \n- **Any email that is vague and requests engagement.** \n- **Any mismatch between email sender and expected domain.** \n- **Any email discouraging verification or urging secrecy.**\n- **Any email with a Reply-To address that differs from the sender's domain.**\n- **Generic initial service requests should be classified as SUSPICIOUS, not PHISHING, unless combined with technical indicators like Reply-To mismatch or domain issues.**\n- Consider the context of tax season when assessing new client inquiries.\n- Perfectly composed generic emails without technical red flags should warrant caution (SUSPICIOUS) but not automatic PHISHING classification.\n- Balance the detection of generic patterns with acknowledgment that legitimate new clients need to make initial contact somehow.\n- Look for presence of legitimacy indicators that would be difficult for remote attackers to fabricate.\n\n---\n\n### **2. Medium-Risk Triggers (Escalate to PHISHING if Combined)** \n- **Any urgency or request that deviates from expected workflow.** \n- **Any email sent from a non-corporate domain, even if it does not impersonate an executive.** \n- **Any request for sensitive details, even indirectly.** \n- **Any request to call an unfamiliar phone number not listed on the company website.**\n- **Generic service request language (e.g., \"reaching out to inquire\", \"seeking assistance\").**\n- **Initial contact emails that are perfectly composed with no grammatical errors but lack personalization.**\n- **Tax service inquiries that mention sharing previous tax returns or financial documents in first contact.**\n- **Emails that lack specific reference to how they found your firm.**\n\n---\n\n### **3. Low-Risk Triggers (Escalate Based on Context)** \n- **Minimal context but from a corporate domain.** \n- **General formatting errors, misspellings, or vague language.** \n- **Lack of proper email signature when expected.**\n- **Domain uses a generic business name without clear industry context.**\n- **No explanation of how the sender found the recipient or why they chose their services.**\n- **Unusually formal or polite language that appears templated.**\n\n---\n\n## **Strict JSON Output Requirements** \nAll evaluations must be formatted into the following JSON structure **without any modifications to field names or nesting**: \n\n```json\n{\n \"email_summary\": {\n \"subject\": \"\", // The exact email subject line\n \"content_summary\": \"\" // A brief, high-level summary of the email’s contents\n },\n\n \"behavioral_triggers\": {\n \"tone\": \"\", // Emotional register (e.g., neutral, urgent, persuasive)\n \"justification\": \"\", // Explain why that tone was chosen\n \"alignment_with_purpose\": \"\", // Does the tone match the stated intent?\n \"lack_of_context\": \"\", // Flag if the email lacks contextual details but includes an attachment\n \"engagement_bait\": \"\", // Detects generic engagement-bait phrases like \"Please view the attached\" \"Generic initial contact phrases detected: 'reaching out to inquire', 'look forward to your response'\"\n \"phone_based_social_engineering\": \"\", // Flags if the email encourages a phone call instead of online engagement\n \"short_vague_request\": { \n \"detected\": \"\", // TRUE if the email is unusually short and vague\n \"engagement_request\": \"\" // TRUE if the email only asks for an acknowledgment without a clear purpose\n }\n },\n\n \"logical_coherence\": {\n \"is_consistent\": \"\", // Does the message flow logically?\n \"contradictions_or_vagueness\": \"\", // Identify inconsistencies or ambiguities\n \"logical_actions\": \"\", // Assess whether the requested actions are reasonable\n \"subtle_inconsistencies\": [\"Reply-To address differs from sender address\", \"Reply-To domain (aquahcglobal.com) differs from sender domain (flightdsi.com)\"],\n \"business_context_check\": {\n \"clear_business_purpose\": \"\", // TRUE if the email contains a clear and expected business reason\n \"workflow_alignment\": \"\" // TRUE if the request aligns with typical workflows\n }\n },\n\n \"intent_verification\": {\n \"likely_intent\": \"\", // Summarize the main motive (e.g., request for payment, info gathering)\n \"risk_assessment\": \"\", // Assign a risk level (HIGH, MEDIUM, LOW)\n \"stated_purpose_mismatch\": \"\", // Identify if the stated purpose contradicts inferred intent\n \"financial_role_mismatch\": \"\", // Detect if financial actions are requested by an unrelated role\n \"external_login_requirement\": \"\", // Flag if a report requires external login without justification\n \"minimal_text_attachment\": \"\", // TRUE if the email is minimal but contains an attachment\n \"executive_impersonation\": {\n \"detected\": \"\", // TRUE if an executive is being impersonated\n \"domain_mismatch\": \"\", // TRUE if the email domain does not match expected corporate domains\n \"position_claimed\": \"\", // Extracted claimed position (e.g., CEO, CFO)\n \"actual_domain\": \"\" // The actual sender's email domain\n }\n },\n\n \"attachment_analysis\": {\n \"is_relevant\": \"\", // TRUE if the attachment makes sense for the stated request\n \"attachment_metadata\": {\n \"attachment_name\": \"\",\n \"attachment_sha256\": \"\",\n \"content_type\": \"\",\n \"attachment_text\": {\n \"text_content\": \"\",\n \"urls\": [],\n \"hyperlinks\": [], // Hyperlinks in attachments are a red flag for PHISHING.\n \"vba_code\": {}, // VBA macro code is an automatic PHISHING classification. \n \"formulas\": [],\n \"comments\": [],\n \"embedded_files\": []\n }\n },\n \"risks\": \"\" // Describe potential threats (e.g., hidden macros, suspicious external links)\n },\n\n \"url_analysis\": {\n \"url_categorization\": {\n \"primary_action_urls\": [], // URLs requiring user action (login, payment)\n \"informational_urls\": [], // Reference links that do not require interaction\n \"stylistic_framework_urls\": [] // Rendering assets (images, CSS, etc.)\n },\n \"primary_action_validation\": {\n \"relevance\": \"\", // Does the URL relate to the email's purpose?\n \"domain_alignment\": \"\", // Does the domain match the sender's company?\n \"necessity\": \"\", // Is it necessary for the recipient to engage with this URL?\n \"risks\": \"\" // Potential risks associated with the URL\n }\n },\n\n \"pretense_vs_intent_mapping\": {\n \"stated_purpose\": \"\", // The reason given by the email\n \"true_intent\": \"\", // The actual or suspected goal\n \"gaps\": \"\" // Discrepancies between stated purpose and actual content\n },\n\n \"bec_reconnaissance_detection\": {\n \"detected\": \"\", // TRUE if BEC (Business Email Compromise) tactics are detected\n \"reason\": \"\", // Key reason for BEC suspicion, if any\n \"risk_assessment\": \"\" // Overall BEC risk rating (HIGH, MEDIUM, LOW)\n },\n\n \"final_assessment\": {\n \"category\": \"\", // PHISHING, SUSPICIOUS, JUNK/SPAM, LEGITIMATE\n \"rationale\": \"\", // Explanation of why the classification was assigned\n \"risk_level\": \"\", // HIGH, MEDIUM, LOW\n \"high_risk_flags\": [\"Reply-To address mismatch with sender address\"], // List of high-risk triggers detected\n \"medium_risk_flags\": [], // List of medium-risk triggers detected\n \"low_risk_flags\": [] // List of low-risk factors that increase suspicion\n }\n}\n```\n\n **DO NOT ALTER** this JSON structure. \n **Ensure all new heuristics integrate into existing fields.** \n **All risk assessments must align with PHISHING, SUSPICIOUS, JUNK/SPAM, or LEGITIMATE classifications.** \n\n---"
+ "PromptContent": "/AskGpt\n\n## SYSTEM \nYou are a cybersecurity LLM designed to triage emails for **phishing**, **BEC**, and **social engineering** threats. You must assume each email is **suspicious by default** and prioritize uncovering **intent**, **manipulation techniques**, and **pretextual inconsistencies**.\n\nOnly output a **valid JSON object**. Do not include markdown, preambles, or commentary. All fields must be populated using reasoning and scoring logic below. Failure to comply will break downstream automation.\n\n---\n\n## CLASSIFICATION \nYour goal is to classify the email into one of these categories:\n- **PHISHING** – Strong signs of malicious intent, deception, or impersonation\n- **SUSPICIOUS** – Incomplete or unclear purpose, mild social engineering\n- **SPAM** – Commercial, promotional, or irrelevant but low risk\n- **LEGITIMATE** – Clear business purpose, credible identity, no threats\n\n---\n\n## INPUT EMAIL (from Logic App)\n```json\n{\n \"sender\": \"@{body('Process_ParseEmail_JSON')?['email_content']?['sender']}\",\n \"reply_to\": \"@{body('Process_parseEmail_JSON')?['body']?['email_content']?['reply_to']}\",\n \"recipient\": \"@{body('Process_ParseEmail_JSON')?['email_content']?['receiver']}\",\n \"subject\": \"@{body('Process_ParseEmail_JSON')?['email_content']?['subject']}\",\n \"body\": \"@{variables('email_body')}\",\n \"attachments\": [\"@{string(variables('attachments'))}\"],\n \"urls\": [\"@{string(variables('urls'))}\"],\n \"email_date\": \"@{body('Process_parseEmail_JSON')?['email_content']?['date']}\"\n}\n```\n\n---\n\n## THREAT ANALYSIS METHODOLOGY\n\n### Analysis Priority\nStart with the assumption the message is hostile. You are evaluating it for signs of:\n- Impersonation\n- Manipulative tone\n- Vague or open-ended pretexts\n- Out-of-band redirection (e.g., phone numbers, cloud links)\n- Unjustified PII sharing or urgency\n\n### Chain-of-Thought\nStep through:\n- Sender credibility: Domain trust vs topic\n- Tone shifts: Friendly → transactional → urgent\n- CTA analysis: Is the request rational, contextually grounded?\n- Off-platform pressure: URLs, phone numbers, cloud docs\n- Misalignment: Topic vs domain vs relationship\n- Attachment threat context: Is the attachment unexpected, mismatched in name/type, or contains embedded code/URLs unrelated to email content?\n- Sparse attachment with hidden complexity (e.g., macros, links, or embedded files with little readable text) indicates obfuscation or payload delivery.\n\n---\n\n## SCORING RULES \n- High (+3): Strong phishing indicators \n- High (+3): Malicious attachment indicators (macros, suspicious MIME types, mismatched naming, or off-platform links embedded in low-text files)\n- Medium (+2): Mild social engineering or impersonation \n- Low (+1): Unclear or marketing spam \n- Cumulative score thresholds: \n - **7+ → PHISHING** \n - **4–6 → SUSPICIOUS** \n - **≤3 → SPAM** or **LEGITIMATE** based on purpose clarity \n\nScoring **must** be justified in `behavioral_triggers.justification`.\n\n---\n\n## SPECIAL DETECTION HEURISTICS\n\nThese must be enforced as written:\n\n- **Engagement Bait** → +1 \n `\"Let me know if you’re available\", \"Following up to see…”` \n- **Vague Request + No Identity** → +2 \n- **Domain-Purpose Mismatch** (e.g. tax help from gmail.com) → +2 \n- **Seasonal Hook with No Prior Relationship** → +2 \n- **Unsolicited PII Disclosure** → +2 \n- **Phone # with Character Substitution (\"I\" for \"1\", etc.)** → +3 \n → Also flag `embedded_fake_support_contact_detected = true` \n\n---\n\n## ATTACHMENT HEURISTICS\n\nThese rules should guide risk scoring and justification in `attachment_analysis` and `behavioral_triggers.justification`:\n\n- If `vba_code` is present in Office documents → +3\n- If `text_content` is minimal but contains links or embedded files → +2\n- If URLs found in attachments contain domain names or paths commonly used for credential collection or file delivery (e.g., sharepoint, googledrive, 1drv, onedrive, dropbox, login, auth, verify, download, or url shortening services), and the surrounding content implies urgency, account access, or verification, then → +2\n- If formulas or comments contain tracking, links, or external references → +1\n- If attachments are included but irrelevant to the email’s topic → +1\n\n---\n\n## JSON OUTPUT SCHEMA\n\nIf unable to process, return:\n```json\n{\"error\": \"Invalid email content for analysis\"}\n```\n\nOutput must be in this format:\n```json\n{\n \"email_summary\": {\n \"subject\": \"\",\n \"content_summary\": \"\" // Summarize email’s purpose and manipulative indicators; do NOT quote verbatim\n },\n \"behavioral_triggers\": {\n \"tone\": \"\",\n \"justification\": \"\", // Justify why any risk score was added\n \"alignment_with_purpose\": \"\",\n \"lack_of_context\": \"\",\n \"engagement_bait\": \"\",\n \"phone_based_social_engineering\": \"\",\n \"seasonal_bait_detected\": {\n \"detected\": \"\",\n \"seasonal_context\": \"\"\n },\n \"short_vague_request\": {\n \"detected\": \"\",\n \"engagement_request\": \"\"\n }\n },\n \"logical_coherence\": {\n \"is_consistent\": \"\",\n \"contradictions_or_vagueness\": \"\",\n \"logical_actions\": \"\",\n \"subtle_inconsistencies\": [],\n \"business_context_check\": {\n \"clear_business_purpose\": \"\", // Yes if this makes sense in a known workflow\n \"workflow_alignment\": \"\"\n }\n },\n \"intent_verification\": {\n \"likely_intent\": \"\",\n \"risk_assessment\": \"\",\n \"stated_purpose_mismatch\": \"\",\n \"financial_role_mismatch\": \"\",\n \"external_login_requirement\": \"\",\n \"minimal_text_attachment\": \"\",\n \"executive_impersonation\": {\n \"detected\": \"\",\n \"domain_mismatch\": \"\",\n \"position_claimed\": \"\",\n \"actual_domain\": \"\"\n }\n },\n \"attachment_analysis\": {\n \"is_relevant\": \"\",\n \"attachment_metadata\": {\n \"attachment_name\": \"\",\n \"attachment_sha256\": \"\",\n \"content_type\": \"\",\n \"attachment_text\": {\n \"text_content\": \"\",\n \"urls\": [],\n \"hyperlinks\": [],\n \"vba_code\": {},\n \"formulas\": [],\n \"comments\": [],\n \"embedded_files\": []\n }\n },\n \"risks\": \"\"\n },\n \"url_analysis\": {\n \"url_categorization\": {\n \"primary_action_urls\": [],\n \"informational_urls\": [],\n \"stylistic_framework_urls\": []\n },\n \"primary_action_validation\": {\n \"relevance\": \"\",\n \"domain_alignment\": \"\",\n \"necessity\": \"\",\n \"risks\": \"\"\n }\n },\n \"pretense_vs_intent_mapping\": {\n \"stated_purpose\": \"\",\n \"true_intent\": \"\",\n \"gaps\": \"\"\n },\n \"bec_reconnaissance_detection\": {\n \"detected\": \"\",\n \"reason\": \"\",\n \"risk_assessment\": \"\"\n },\n \"final_assessment\": {\n \"category\": \"\", // Must be one of: PHISHING, SUSPICIOUS, SPAM, LEGITIMATE\n \"rationale\": \"\", // Describe the decisive factors\n \"risk_level\": \"\",\n \"high_risk_flags\": [],\n \"medium_risk_flags\": [],\n \"low_risk_flags\": [],\n \"assessment_summary\": \"\" // A 1–5 sentence summary of the model’s logic and final conclusion\n }\n}\n```\n\n---\n\n## STRICT INSTRUCTIONS\n- Output only JSON, never markdown or commentary \n- Begin analysis with **malicious by default** assumption \n- Score and flag based on manipulation, intent, and trust violations—not politeness or formatting \n- `summary_text` must explain the model's logic in under 5 sentences \n- `content_summary` must summarize behavior, not repeat email verbatim"
},
"path": "/process-prompt",
"retryPolicy": {
@@ -841,20 +702,7 @@
}
}
},
- "Normalize_JSON": {
- "runAfter": {
- "Security_Copilot_Email_Analysis": [
- "Succeeded"
- ]
- },
- "type": "Function",
- "inputs": {
- "body": "@body('Security_Copilot_Email_Analysis')?['EvaluationResultContent']",
- "function": {
- "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName02'))]"
- }
- }
- },
+
"Parse_normalized_JSON": {
"runAfter": {
"Normalize_JSON": [
@@ -870,9 +718,6 @@
"email_summary": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"subject": {
"type": "string"
},
@@ -884,9 +729,6 @@
"behavioral_triggers": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"tone": {
"type": "string"
},
@@ -895,15 +737,32 @@
},
"alignment_with_purpose": {
"type": "string"
+ },
+ "lack_of_context": {
+ "type": "string"
+ },
+ "engagement_bait": {
+ "type": "string"
+ },
+ "phone_based_social_engineering": {
+ "type": "string"
+ },
+ "short_vague_request": {
+ "type": "object",
+ "properties": {
+ "detected": {
+ "type": "string"
+ },
+ "engagement_request": {
+ "type": "string"
+ }
+ }
}
}
},
"logical_coherence": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"is_consistent": {
"type": "string"
},
@@ -912,34 +771,109 @@
},
"logical_actions": {
"type": "string"
+ },
+ "subtle_inconsistencies": {
+ "type": "array"
+ },
+ "business_context_check": {
+ "type": "object",
+ "properties": {
+ "clear_business_purpose": {
+ "type": "string"
+ },
+ "workflow_alignment": {
+ "type": "string"
+ }
+ }
}
}
},
"intent_verification": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"likely_intent": {
"type": "string"
},
"risk_assessment": {
"type": "string"
+ },
+ "stated_purpose_mismatch": {
+ "type": "string"
+ },
+ "financial_role_mismatch": {
+ "type": "string"
+ },
+ "external_login_requirement": {
+ "type": "string"
+ },
+ "minimal_text_attachment": {
+ "type": "string"
+ },
+ "executive_impersonation": {
+ "type": "object",
+ "properties": {
+ "detected": {
+ "type": "string"
+ },
+ "domain_mismatch": {
+ "type": "string"
+ },
+ "position_claimed": {
+ "type": "string"
+ },
+ "actual_domain": {
+ "type": "string"
+ }
+ }
}
}
},
"attachment_analysis": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"is_relevant": {
"type": "string"
},
- "content_analysis": {
- "type": "string"
+ "attachment_metadata": {
+ "type": "object",
+ "properties": {
+ "attachment_name": {
+ "type": "string"
+ },
+ "attachment_sha256": {
+ "type": "string"
+ },
+ "content_type": {
+ "type": "string"
+ },
+ "attachment_text": {
+ "type": "object",
+ "properties": {
+ "text_content": {
+ "type": "string"
+ },
+ "urls": {
+ "type": "array"
+ },
+ "hyperlinks": {
+ "type": "array"
+ },
+ "vba_code": {
+ "type": "object",
+ "properties": {}
+ },
+ "formulas": {
+ "type": "array"
+ },
+ "comments": {
+ "type": "array"
+ },
+ "embedded_files": {
+ "type": "array"
+ }
+ }
+ }
+ }
},
"risks": {
"type": "string"
@@ -949,23 +883,14 @@
"url_analysis": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"url_categorization": {
"type": "object",
"properties": {
"primary_action_urls": {
- "type": "array",
- "items": {
- "type": "string"
- }
+ "type": "array"
},
"informational_urls": {
- "type": "array",
- "items": {
- "type": "string"
- }
+ "type": "array"
},
"stylistic_framework_urls": {
"type": "array"
@@ -988,40 +913,12 @@
"type": "string"
}
}
- },
- "informational_url_validation": {
- "type": "object",
- "properties": {
- "purpose": {
- "type": "string"
- },
- "alignment": {
- "type": "string"
- },
- "risks": {
- "type": "string"
- }
- }
- },
- "stylistic_framework_url_validation": {
- "type": "object",
- "properties": {
- "typicality": {
- "type": "string"
- },
- "risks": {
- "type": "string"
- }
- }
}
}
},
"pretense_vs_intent_mapping": {
"type": "object",
"properties": {
- "description": {
- "type": "string"
- },
"stated_purpose": {
"type": "string"
},
@@ -1033,17 +930,40 @@
}
}
},
- "final_assessment": {
+ "bec_reconnaissance_detection": {
"type": "object",
"properties": {
- "description": {
+ "detected": {
"type": "string"
},
+ "reason": {
+ "type": "string"
+ },
+ "risk_assessment": {
+ "type": "string"
+ }
+ }
+ },
+ "final_assessment": {
+ "type": "object",
+ "properties": {
"category": {
"type": "string"
},
"rationale": {
"type": "string"
+ },
+ "risk_level": {
+ "type": "string"
+ },
+ "high_risk_flags": {
+ "type": "array"
+ },
+ "medium_risk_flags": {
+ "type": "array"
+ },
+ "low_risk_flags": {
+ "type": "array"
}
}
}
@@ -1051,20 +971,8 @@
}
}
},
- "Generate_HTML_report_from_JSON": {
- "runAfter": {
- "Parse_normalized_JSON": [
- "Succeeded"
- ]
- },
- "type": "Function",
- "inputs": {
- "body": "@body('Parse_normalized_JSON')",
- "function": {
- "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName03'))]"
- }
- }
- },
+
+
"Initialize_systemalert_id_query_results": {
"runAfter": {
"Generate_HTML_report_from_JSON": [
@@ -1152,8 +1060,7 @@
"MailboxAddress": "[parameters('SharedMailboxAddress')]",
"To": "[parameters('HTMLReportRecipient')]",
"Subject": "Phishing Email Analysis",
- "Body": "
@{body('Generate_HTML_report_from_JSON')}
***********
Incident URL:
@{body('Alert_-_Get_incident_from_systemalertid')?['properties']?['incidentUrl']}
Generated with AI, check for accuracy.
***********
", - + "Body": "
@{body('Generate_HTML_report_from_JSON')}
***********
Incident URL:
@{body('Alert_-_Get_incident_from_systemalertid')?['properties']?['incidentUrl']}
Generated with AI, check for accuracy.
***********
", "Importance": "Normal" }, "path": "/v2/SharedMailbox/Mail" @@ -1311,6 +1218,48 @@ ] }, "type": "If" + }, + "Parse_Email": { + "runAfter": { + "For_each_raw_email_attached_to_carrier_email": [ + "Succeeded" + ] + }, + "type": "Function", + "inputs": { + "body": "@variables('raw_email')", + "function": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName01'))]" + } + } + }, + "Normalize_JSON": { + "runAfter": { + "Security_Copilot_Email_Analysis": [ + "Succeeded" + ] + }, + "type": "Function", + "inputs": { + "body": "@body('Security_Copilot_Email_Analysis')?['EvaluationResultContent']", + "function": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName02'))]" + } + } + }, + "Generate_HTML_report_from_JSON": { + "runAfter": { + "Parse_normalized_JSON": [ + "Succeeded" + ] + }, + "type": "Function", + "inputs": { + "body": "@body('Parse_normalized_JSON')", + "function": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName03'))]" + } + } } }, "outputs": {} diff --git a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/azuredeploy.json b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/azuredeploy.json index 46f43148..3101ea3a 100644 --- a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/azuredeploy.json +++ b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/azuredeploy.json @@ -2,8 +2,8 @@ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "metadata": { - "title": "", - "description": "", + "title": "SecurityCopilot-Sentinel-Incident-Investigation", + "description": "Logic App automating incident investigation within Microsoft Sentinel using Security Copilot", "prerequisites": "", "postDeployment": [ ], @@ -18,24 +18,179 @@ "armtemplate": "Generated from https://github.com/Azure/Azure-Sentinel/tree/master/Tools/Playbook-ARM-Template-Generator" }, "author": { - "name": "Pratik Lal" + "name": "" } }, "parameters": { "PlaybookName": { "defaultValue": "SecurityCopilot-Sentinel-Incident-Investigation", "type": "string" + }, + "CallScpForIPsWithAbuseIPDB": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the IP addresses on AbuseIPDB. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForIPsWithMDTI": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the IP addresses on MDTI. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForAccounts": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the accounts on MS Entra. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForUserRisk": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to check the risk level of the users on MS Entra. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForHosts": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the devices on Intune. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForCommandLines": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the command lines by using its script analysis capability. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForFilehashesWithMDTI": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the file hashes on MDTI. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForUrlsWithMDTI": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the Urls on MDTI. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForDomainsWithMDTI": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the domains on MDTI. Set 'false' otherwise." + }, + "defaultValue": true + }, + "SkipIPs": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the IPs that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipAccounts": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the accounts that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipHosts": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the devices that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipCommandLines": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the command lines that you do not want Security Copilot to investigate" + }, + "defaultValue": ["cmd.exe","explorer.exe"] + }, + "SkipFilehashes": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the file hashes that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipUrls": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the urls that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipDomains": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the domains that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "MaxMdtiOutputLenghtForPreSummarization": { + "type": "Int", + "metadata": { + "description": "When the overall length of the aggregated MDTI results report is greater than the number specified here, an additional call is made to Security Copilot to pre-summarize this specific report. This pre-summarization reduces the risk that the final summarization gets an error because the overall input text is too long. " + }, + "defaultValue": 2000 + }, + "WriteUnsummarizedResultsToIncident": { + "type": "Bool", + "metadata": { + "description": "Leave true if you want that the raw results are saved as comment to the Sentinel incident. The raw results are the results before the final summarization. Set false otherwise. " + }, + "defaultValue": true + }, + "WriteFinalSummaryToIncident": { + "type": "Bool", + "metadata": { + "description": "Leave true if you want that the summarized results are saved as comment to the Sentinel incident. Set false otherwise. " + }, + "defaultValue": true + }, + "RecipientEmail": { + "type": "String", + "metadata": { + "description": "Enter the email addrees of the recipient for the emails sent by this automation." + }, + "defaultValue": "" + }, + "SendUnsummarizedResultsByEmail": { + "type": "Bool", + "metadata": { + "description": "Leave true if you want that the raw results are sent by email to the specified recipient. The raw results are the results before the final summarization. Set false otherwise. " + }, + "defaultValue": true + }, + "SendFinalSummaryByEmail": { + "type": "Bool", + "metadata": { + "description": "Leave true if you want that the summarized results are sent by email to the specified recipient. Set false otherwise. " + }, + "defaultValue": true } }, "variables": { "MicrosoftSentinelConnectionName": "[concat('MicrosoftSentinel-', parameters('PlaybookName'))]", - "SecuritycopilotConnectionName": "[concat('Securitycopilot-', parameters('PlaybookName'))]" + "SecuritycopilotConnectionName": "[concat('Securitycopilot-', parameters('PlaybookName'))]", + "Office365ConnectionName": "[concat('Office365-', parameters('PlaybookName'))]" }, "resources": [ { "properties": { "provisioningState": "Succeeded", - "state": "Disabled", + "state": "Enabled", "definition": { "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", "contentVersion": "1.0.0.0", @@ -44,6 +199,94 @@ "defaultValue": { }, "type": "Object" + }, + "CallScpForIPsWithAbuseIPDB": { + "defaultValue": "[parameters('CallScpForIPsWithAbuseIPDB')]", + "type": "Bool" + }, + "CallScpForIPsWithMDTI": { + "defaultValue": "[parameters('CallScpForIPsWithMDTI')]", + "type": "Bool" + }, + "CallScpForAccounts": { + "defaultValue": "[parameters('CallScpForAccounts')]", + "type": "Bool" + }, + "CallScpForUserRisk": { + "defaultValue": "[parameters('CallScpForUserRisk')]", + "type": "Bool" + }, + "CallScpForHosts": { + "defaultValue": "[parameters('CallScpForHosts')]", + "type": "Bool" + }, + "CallScpForCommandLines": { + "defaultValue": "[parameters('CallScpForCommandLines')]", + "type": "Bool" + }, + "CallScpForFilehashesWithMDTI": { + "defaultValue": "[parameters('CallScpForFilehashesWithMDTI')]", + "type": "Bool" + }, + "CallScpForUrlsWithMDTI": { + "defaultValue": "[parameters('CallScpForUrlsWithMDTI')]", + "type": "Bool" + }, + "CallScpForDomainsWithMDTI": { + "defaultValue": "[parameters('CallScpForDomainsWithMDTI')]", + "type": "Bool" + }, + "SkipIPs": { + "defaultValue": "[parameters('SkipIPs')]", + "type": "Array" + }, + "SkipAccounts": { + "defaultValue": "[parameters('SkipAccounts')]", + "type": "Array" + }, + "SkipHosts": { + "defaultValue": "[parameters('SkipHosts')]", + "type": "Array" + }, + "SkipCommandLines": { + "defaultValue": "[parameters('SkipCommandLines')]", + "type": "Array" + }, + "SkipFilehashes": { + "defaultValue": "[parameters('SkipFilehashes')]", + "type": "Array" + }, + "SkipUrls": { + "defaultValue": "[parameters('SkipUrls')]", + "type": "Array" + }, + "SkipDomains": { + "defaultValue": "[parameters('SkipDomains')]", + "type": "Array" + }, + "MaxMdtiOutputLenghtForPreSummarization": { + "defaultValue": "[parameters('MaxMdtiOutputLenghtForPreSummarization')]", + "type": "Int" + }, + "WriteUnsummarizedResultsToIncident": { + "defaultValue": "[parameters('WriteUnsummarizedResultsToIncident')]", + "type": "Bool" + }, + "WriteFinalSummaryToIncident": { + "defaultValue": "[parameters('WriteFinalSummaryToIncident')]", + "type": "Bool" + }, + "RecipientEmail": { + "defaultValue": "[parameters('RecipientEmail')]", + "type": "String" + }, + "SendUnsummarizedResultsByEmail": { + "defaultValue": "[parameters('SendUnsummarizedResultsByEmail')]", + "type": "Bool" + }, + "SendFinalSummaryByEmail": { + "defaultValue": "[parameters('SendFinalSummaryByEmail')]", + "type": "Bool" } }, "triggers": { @@ -54,167 +297,2476 @@ "connection": { "name": "@parameters('$connections')['azuresentinel']['connectionId']" } - }, - "body": { - "callback_url": "@{listCallbackUrl()}" - }, - "path": "/incident-creation" + }, + "body": { + "callback_url": "@{listCallbackUrl()}" + }, + "path": "/incident-creation" + } + } + }, + "actions": { + "Compose_Incident_Entities": { + "runAfter": { + }, + "type": "Compose", + "inputs": "@triggerBody()?['object']?['properties']?['relatedEntities']" + }, + "Condition_-_Validate_Entities": { + "actions": { + "For_Each_Entity": { + "foreach": "@outputs('Compose_Incident_Entities')", + "actions": { + "Switch": { + "runAfter": { + "Show_item_for_debug_purposes": [ + "Succeeded" + ] + }, + "cases": { + "Case_-_Ip_Address": { + "case": "Ip", + "actions": { + "Condition_-_IP": { + "actions": { + "Condition_-_IP_-_Use_AbuseIPDB": { + "actions": { + "Append_to_array_forScp-IPs": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-IPs", + "value": "@items('For_Each_Entity')?['properties']?['address']" + } + } + }, + "runAfter": { + "Append_IP_to_ProcessedItems": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('CallScpForIPsWithAbuseIPDB')", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_-_IP_-_Use_MDTI": { + "actions": { + "Append_IP_to_array_variable_IndicatorsForMDTI": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Indicators", + "value": "@items('For_Each_Entity')?['properties']?['address']" + } + } + }, + "runAfter": { + "Append_IP_to_ProcessedItems": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('CallScpForIPsWithMDTI')", + true + ] + } + ] + }, + "type": "If" + }, + "Append_IP_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('IP:',trim(toLower(items('For_Each_Entity')?['properties']?['address'])))" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "or": [ + { + "equals": [ + "@parameters('CallScpForIPsWithAbuseIPDB')", + "ture" + ] + }, + { + "equals": [ + "@parameters('CallScpForIPsWithMDTI')", + true + ] + } + ] + }, + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['address'])", + false + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipIPs')", + "@toLower(items('For_Each_Entity')?['properties']?['address'])" + ] + } + }, + { + "not": { + "equals": [ + "@items('For_Each_Entity')?['properties']?['address']", + "127.0.0.1" + ] + } + }, + { + "not": { + "equals": [ + "@items('For_Each_Entity')?['properties']?['address']", + "::1" + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "10." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "192.168." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.16." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.17." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.18." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.19." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.20." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.21." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.22." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.23." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.24." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.25." + ] + } + }, + { + "not": { + "endsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.26." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.27." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.28." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.29." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.30." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.31." + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "fd00::" + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "fc00::" + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "ff00::" + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "fe80::" + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "::ffff:" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_-_Account": { + "case": "Account", + "actions": { + "Condition_-_Account": { + "actions": { + "Append_User_Upn_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('Account:',trim(toLower(items('For_Each_Entity')?['properties']?['additionalData']?['UserPrincipalName'])))" + } + }, + "old_Condition_-_Account_-_Get_User_Details": { + "actions": { + "Append_to_array_forScp-UserForDetails": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-UsersForDetails", + "value": "@items('For_Each_Entity')?['properties']?['additionalData']?['UserPrincipalName']" + } + } + }, + "runAfter": { + "Append_User_Upn_to_ProcessedItems": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('CallScpForAccounts')", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_-_Account_-_Get_User_Risk": { + "actions": { + "Append_UPN_to_array_variable_forScp-UsersForRiskAnalysis": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-UsersForRiskAnalysis", + "value": "@items('For_Each_Entity')?['properties']?['additionalData']?['UserPrincipalName']" + } + } + }, + "runAfter": { + "Append_User_Upn_to_ProcessedItems": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('CallScpForUserRisk')", + true + ] + } + ] + }, + "type": "If" + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "or": [ + { + "equals": [ + "@parameters('CallScpForAccounts')", + true + ] + }, + { + "equals": [ + "@parameters('CallScpForUserRisk')", + true + ] + } + ] + }, + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['accountName'])", + false + ] + }, + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['additionalData']?['UserPrincipalName'])", + false + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipAccounts')", + "@toLower(items('For_Each_Entity')?['properties']?['accountName'])" + ] + } + }, + { + "not": { + "endsWith": [ + "@items('For_Each_Entity')?['properties']?['accountName']", + "$" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_-_Host": { + "case": "Host", + "actions": { + "Condition_-_Host": { + "actions": { + "Append_Device_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('Device:',trim(toLower(items('For_Each_Entity')?['properties']?['hostName'])))" + } + }, + "Append_to_array_forScp-Devices": { + "runAfter": { + "Append_Device_to_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Devices", + "value": "@items('For_Each_Entity')?['properties']?['hostName']" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['hostName'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForHosts')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipHosts')", + "@toLower(items('For_Each_Entity')?['properties']?['hostName'])" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_-_Process": { + "case": "Process", + "actions": { + "Condition_-_Command_Line": { + "actions": { + "Set_variable_CommandLineNormalized": { + "type": "SetVariable", + "inputs": { + "name": "CommandLineNormalized", + "value": "@tolower(trim(replace(replace(items('For_Each_Entity')?['properties']?['commandLine'],' ',' '),'net1 ','net ')))" + } + }, + "Append_CommandLineNormalized_to_ProcessedItem": { + "runAfter": { + "Set_variable_CommandLineNormalized": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('CommandLine:',variables('CommandLineNormalized'))" + } + }, + "Append_to_array_variable_forScp-CommandLines": { + "runAfter": { + "Append_CommandLineNormalized_to_ProcessedItem": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-CommandLines", + "value": "@variables('CommandLineNormalized')" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['commandLine'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForCommandLines')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipCommandLines')", + "@toLower(items('For_Each_Entity')?['properties']?['commandLine'])" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_-_FileHash": { + "case": "FileHash", + "actions": { + "Condition_-_FileHash": { + "actions": { + "Append_Filehash_to_array_variable_IndicatorsForMDTI": { + "runAfter": { + "Append_FileHash_to_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Indicators", + "value": "@toLower(items('For_Each_Entity')?['properties']?['hashValue'])" + } + }, + "Append_FileHash_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('FileHash:',trim(toLower(items('For_Each_Entity')?['properties']?['hashValue'])))" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['hashValue'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForFilehashesWithMDTI')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipFilehashes')", + "" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_Url": { + "case": "Url", + "actions": { + "Condition_-_Url": { + "actions": { + "Append_Url_to_array_variable_IndicatorsForMDTI": { + "runAfter": { + "Append_URL_to_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Indicators", + "value": "@toLower(split(items('For_Each_Entity')?['properties']?['url'],'?')[0])" + } + }, + "Append_URL_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('URL:',trim(toLower(split(items('For_Each_Entity')?['properties']?['url'],'?')[0])))" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['url'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForUrlsWithMDTI')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipUrls')", + "@toLower(items('For_Each_Entity')?['properties']?['url'])" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_DNS": { + "case": "DNS", + "actions": { + "Condition_-_DNS": { + "actions": { + "Append_Domain_to_array_variable_IndicatorsForMDTI": { + "runAfter": { + "Append_Domain_to_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Indicators", + "value": "@toLower(items('For_Each_Entity')?['properties']?['domainName'])" + } + }, + "Append_Domain_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('Domain:',trim(toLower(items('For_Each_Entity')?['properties']?['domainName'])))" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['domainName'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForDomainsWithMDTI')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipDomains')", + "@toLower(items('For_Each_Entity')?['properties']?['domainName'])" + ] + } + } + ] + }, + "type": "If" + } + } + } + }, + "default": { + "actions": { + } + }, + "expression": "@items('For_Each_Entity')?['kind']", + "type": "Switch" + }, + "Show_item_for_debug_purposes": { + "type": "Compose", + "inputs": "@items('For_Each_Entity')" + } + }, + "type": "Foreach" + }, + "Condition_Call_for_IP_Addresses": { + "actions": { + "For_each_forScp-IPs": { + "foreach": "@variables('Unique-forScp-IPs')", + "actions": { + "Security_Copilot_prompt_for_IP": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "I'm curious about any abuseipdb records for the IP address @{items('For_each_forScp-IPs')}. Can you look that up for me for the last 30 days?" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_IPs_with_AbuseIPBD_was_successful": { + "actions": { + "Set_variable_ResultsAvailable_-_IPs": { + "runAfter": { + "Append_to_Results-IPs_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-IPs_Success": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-IPs", + "value": "\nResults of the analysis for the IP '@{items('For_each_forScp-IPs')}':\n\n@{body('Security_Copilot_prompt_for_IP')?['EvaluationResultContent']}\n " + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_IP": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_Results-IPs_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-IPs", + "value": "\nResults of the analysis for the IP '@{items('For_each_forScp-IPs')}':\n\nError while retrieving the results: @{body('Security_Copilot_prompt_for_IP')?['EvaluationResultContent']}\n " + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_IP')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Show_Unique-forScp-IPs_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Show_Unique-forScp-IPs_for_debug_purposes": { + "runAfter": { + "Set_variable_-_Unique-forScp-IPs": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-IPs')" + }, + "Set_variable_-_Unique-forScp-IPs": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-IPs", + "value": "@sort(union(variables('forScp-IPs'),variables('forScp-IPs')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-IPs'),null)), greater(length(variables('forScp-IPs')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_Users_Details": { + "actions": { + "For_each_forScp-UsersForDetails": { + "foreach": "@variables('Unique-forScp-UsersForDetails')", + "actions": { + "Security_Copilot_prompt_for_Account_": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Give me all usr details for @{items('For_each_forScp-UsersForDetails')}. Does have any registered devices in Microsoft Entra? List the recent risky sign-ins for @{items('For_each_forScp-UsersForDetails')}. Get Microsoft Entra audit logs for @{items('For_each_forScp-UsersForDetails')} for the past 72 hours.", + "SkillName": "GetEntraUserDetailsV1" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_Account_on_Entra_was_successful": { + "actions": { + "Set_variable_-_ResultsAvailable_-_Accounts": { + "runAfter": { + "Append_to_Results-UsersDetails_-_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-UsersDetails_-_Success": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-UsersDetails", + "value": "\nResults of the analysis for the account '@{items('For_each_forScp-UsersForDetails')}':\n\n@{body('Security_Copilot_prompt_for_Account_')?['EvaluationResultContent']}\n " + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_Account_": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_Results-UsersDetails_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-UsersDetails", + "value": "\nResults of the analysis for the account '@{items('For_each_forScp-UsersForDetails')}':\n\nError while retrieving the results: @{body('Security_Copilot_prompt_for_Account_')?['EvaluationResultContent']}\n " + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_Account_')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Show_Unique-forScp-UsersForDetails_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Show_Unique-forScp-UsersForDetails_for_debug_purposes": { + "runAfter": { + "Set_variable_-_Unique-forScp-UsersForDetails": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-UsersForDetails')" + }, + "Set_variable_-_Unique-forScp-UsersForDetails": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-UsersForDetails", + "value": "@sort(union(variables('forScp-UsersForDetails'),variables('forScp-UsersForDetails')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-UsersForDetails'),null)), greater(length(variables('forScp-UsersForDetails')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_Users_Risk": { + "actions": { + "Security_Copilot_prompt_for_Users_Risk": { + "runAfter": { + "Show_Unique-forScp-UsersForRiskAnalysis_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "@join(variables('Unique-forScp-UsersForRiskAnalysis'),', ')", + "SkillName": "GetEntraRiskyUsers" + }, + "path": "/process-prompt" + } + }, + "Condition_-_Users_Risk_retrieved": { + "actions": { + "Compose_Users_Risk": { + "type": "Compose", + "inputs": "@body('Security_Copilot_prompt_for_Users_Risk')?['EvaluationResultContent']" + }, + "Set_variable_-_ResultsAvailable_-_Users_Risk": { + "runAfter": { + "Append_to_Results-UsersRisk_-_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-UsersRisk_-_Success": { + "runAfter": { + "Compose_Users_Risk": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-UsersRisk", + "value": "@body('Security_Copilot_prompt_for_Users_Risk')?['EvaluationResultContent']" + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_Users_Risk": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_Results-UsersRisk_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-UsersRisk", + "value": "Error while retrieving the users risk: '@{body('Security_Copilot_prompt_for_Users_Risk')?['EvaluationResultContent']}'" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_Users_Risk')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + }, + "Show_Unique-forScp-UsersForRiskAnalysis_for_debug_purposes": { + "runAfter": { + "Set_variable_Unique-forScp-UsersForRiskAnalysis": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-UsersForRiskAnalysis')" + }, + "Set_variable_Unique-forScp-UsersForRiskAnalysis": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-UsersForRiskAnalysis", + "value": "@sort(union(variables('forScp-UsersForRiskAnalysis'),variables('forScp-UsersForRiskAnalysis')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-UsersForRiskAnalysis'),null)), greater(length(variables('forScp-UsersForRiskAnalysis')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_Devices_Details": { + "actions": { + "For_each_forScp-Devices": { + "foreach": "@variables('Unique-forScp-Devices')", + "actions": { + "Security_Copilot_prompt_for_Device": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Tell me about device @{items('For_each_forScp-Devices')}.", + "SkillName": "GetIntuneDevices" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_devices_on_Intune_was_successful": { + "actions": { + "Set_variable_-_ResultsAvailable_-_Devices": { + "runAfter": { + "Append_to_Results-Devices-Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-Devices-Success": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-Devices", + "value": "\nResults of the analysis for the device '@{items('For_each_forScp-Devices')}':\n\n@{body('Security_Copilot_prompt_for_Device')?['EvaluationResultContent']}\n" + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_Device": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_results-Devices-Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-Devices", + "value": "\nResults of the analysis for the device '@{items('For_each_forScp-Devices')}': \n\nError while retrieving the results: '@{body('Security_Copilot_prompt_for_Device')?['EvaluationResultContent']}'\n" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_Device')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Show_Unique-forScp-Devices_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Show_Unique-forScp-Devices_for_debug_purposes": { + "runAfter": { + "Set_variable_Unique-forScp-Devices": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-Devices')" + }, + "Set_variable_Unique-forScp-Devices": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-Devices", + "value": "@sort(union(variables('forScp-Devices'),variables('forScp-Devices')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-Devices'),null)), greater(length(variables('forScp-Devices')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_Command_Lines": { + "actions": { + "Security_Copilot_prompt_for_Unique_Processes": { + "runAfter": { + "Show_Unique-forScp-CommandLines_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "From the following list, extract the command lines and related descriptions containing commands that are most likely DIRECTLY CORRELATED to malicious activities. Remove all command lines containing only the names of well-known portable executable files that are part of the Windows or Linux operating system, where no additional parameters are specified. For example, remove 'notepad.exe' and 'cmd.exe' but keep 'notepad.exe -param1' or 'cmd.exe -paramX' or 'unknownfile.exe'. The list starts here:\n \n@{join(variables('Unique-forScp-CommandLines'),'\n')}", + "SkillName": "ScriptAnalyzer" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_Processes_was_successful": { + "actions": { + "Compose_Process_Analysis": { + "type": "Compose", + "inputs": "@body('Security_Copilot_prompt_for_Unique_Processes')?['EvaluationResultContent']" + }, + "Set_variable_-_ResultsAvailable_-_Command_Lines": { + "runAfter": { + "Append_to_Results-CommandLines_-_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-CommandLines_-_Success": { + "runAfter": { + "Compose_Process_Analysis": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-CommandLines", + "value": "@body('Security_Copilot_prompt_for_Unique_Processes')?['EvaluationResultContent']" + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_Unique_Processes": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_Results-CommandLines_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-CommandLines", + "value": "Error while analyzing the command lines: '@{body('Security_Copilot_prompt_for_Unique_Processes')?['EvaluationResultContent']}'" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_Unique_Processes')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + }, + "Show_Unique-forScp-CommandLines_for_debug_purposes": { + "runAfter": { + "Set_variable_Unique-forScp-CommandLines": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-CommandLines')" + }, + "Set_variable_Unique-forScp-CommandLines": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-CommandLines", + "value": "@sort(union(variables('forScp-CommandLines'),variables('forScp-CommandLines')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-CommandLines'), null)), greater(length(variables('forScp-CommandLines')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_MDTI_indicators": { + "actions": { + "Until": { + "actions": { + "Set_variable_SubarrayIndicatorsForMDTI": { + "type": "SetVariable", + "inputs": { + "name": "SubarrayIndicatorsForMDTI", + "value": "@take(skip(variables('Unique-forScp-Indicators'),variables('CounterIndicatorsForMDTI')),10)" + } + }, + "Set_variable_CsvIndicatorsForMDTI": { + "runAfter": { + "Set_variable_SubarrayIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "CsvIndicatorsForMDTI", + "value": "@join(variables('SubarrayIndicatorsForMDTI'),', ')" + } + }, + "Security_Copilot_prompt_for_IoC_to_MDTI": { + "runAfter": { + "Set_variable_CsvIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "By looking at MDTI, tell me the reputation of these indicators: @{variables('CsvIndicatorsForMDTI')} ", + "SkillName": "GetReputationsForIndicators" + }, + "path": "/process-prompt" + } + }, + "Compose_New_Counter_Value": { + "runAfter": { + "Condition_-_SCP_-_Call_for_indicators_was_successful": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@add(variables('CounterIndicatorsForMDTI'),10)" + }, + "Set_variable_Counter": { + "runAfter": { + "Compose_New_Counter_Value": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "CounterIndicatorsForMDTI", + "value": "@outputs('Compose_New_Counter_Value')" + } + }, + "Condition_-_SCP_-_Call_for_indicators_was_successful": { + "actions": { + "Compose_IoC_from_MDTI": { + "type": "Compose", + "inputs": "@body('Security_Copilot_prompt_for_IoC_to_MDTI')?['EvaluationResultContent']" + }, + "Append_to_array_variable_ResponsesFromMDTI_-_Success": { + "runAfter": { + "Compose_IoC_from_MDTI": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "ResponsesFromMDTI", + "value": "@body('Security_Copilot_prompt_for_IoC_to_MDTI')?['EvaluationResultContent']" + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_IoC_to_MDTI": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_array_variable_ResponsesFromMDTI_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ResponsesFromMDTI", + "value": "Error during the iteration @{add(div(variables('CounterIndicatorsForMDTI'),10),1)} wirh MDTI: '@{body('Security_Copilot_prompt_for_IoC_to_MDTI')?['EvaluationResultContent']}'" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_IoC_to_MDTI')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Show_Unique-forScp-Indicators_for_debug_purposes": [ + "Succeeded" + ] + }, + "expression": "@equals(greaterOrEquals(length(variables('Unique-forScp-Indicators')),variables('CounterIndicatorsForMDTI')),false)", + "limit": { + "count": 60, + "timeout": "PT1H" + }, + "type": "Until" + }, + "Set_variable_OutputLength_-_MDTI_Output": { + "runAfter": { + "Set_variable_original_Response-MDTI": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "OutputLengthMDTI", + "value": "@length(variables('Response-MDTI'))" + } + }, + "Condition_-_MDTI_Output_Length": { + "actions": { + "Submit_a_Security_Copilot_prompt_-_Summarize_MDTI_response": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "From the following detailed list containing the results of the analysis of Indicators of Compromise (IoC) made by MDTI, extract suspicious and malicious indicators with the all the related details on reputation and explanations. Do not return unknown indicators. The list starts here: @{variables('Response-MDTI')}\n", + "SkillName": "SummarizeData" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_indicator_summarization_was_successful": { + "actions": { + "Set_variable_summarized_Response-MDTI_-_Success": { + "type": "SetVariable", + "inputs": { + "name": "Response-MDTI", + "value": "@body('Submit_a_Security_Copilot_prompt_-_Summarize_MDTI_response')?['EvaluationResultContent']" + } + } + }, + "runAfter": { + "Submit_a_Security_Copilot_prompt_-_Summarize_MDTI_response": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Set_variable_summarized_Response-MDTI_-_Error": { + "type": "SetVariable", + "inputs": { + "name": "Response-MDTI", + "value": "Error while summarizing the results retrieved from MDTI: '@{body('Submit_a_Security_Copilot_prompt_-_Summarize_MDTI_response')?['EvaluationResultContent']}'" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "", + "" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Condition_-_MDTI_Results_available": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "greater": [ + "@variables('OutputLengthMDTI')", + "@parameters('MaxMdtiOutputLenghtForPreSummarization')" + ] + } + ] + }, + "type": "If" + }, + "Set_variable_original_Response-MDTI": { + "runAfter": { + "Until": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "Response-MDTI", + "value": "@join(variables('ResponsesFromMDTI'),' \n\n\n')" + } + }, + "Show_Unique-forScp-Indicators_for_debug_purposes": { + "runAfter": { + "Set_variable_Unique-forScp-Indicators": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-Indicators')" + }, + "Set_variable_Unique-forScp-Indicators": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-Indicators", + "value": "@sort(union(variables('forScp-Indicators'),variables('forScp-Indicators')))" + } + }, + "Condition_-_MDTI_Results_available": { + "actions": { + "Set_variable_-_ResultsAvailable_-_MDTI": { + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + } + }, + "runAfter": { + "Set_variable_OutputLength_-_MDTI_Output": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "greater": [ + "@variables('OutputLengthMDTI')", + 0 + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "or": [ + { + "equals": [ + "@and(not(equals(variables('forScp-Indicators'),null)), greater(length(variables('forScp-Indicators')), 0))", + true + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Condition_-_Validate_Parameters": [ + "Succeeded" + ] + }, + "else": { + "actions": { + "No_Entities_Found_-_Cancel_Execution": { + "type": "Terminate", + "inputs": { + "runStatus": "Cancelled" + } + } + } + }, + "expression": { + "and": [ + { + "greater": [ + "@length(outputs('Compose_Incident_Entities'))", + 0 + ] + } + ] + }, + "type": "If" + }, + "Initialize_variable_CommandLineNormalized": { + "runAfter": { + "Compose_Incident_Entities": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "CommandLineNormalized", + "type": "string" + } + ] + } + }, + "Initialize_variable_forScp-CommandLines": { + "runAfter": { + "Initialize_variable_ProcessedCommandLines": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-CommandLines", + "type": "array" + } + ] + } + }, + "Initialize_variable_ProcessedCommandLines": { + "runAfter": { + "Initialize_variable_CommandLineNormalized": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ProcessedCommandLines", + "type": "array" + } + ] + } + }, + "Initialize_variable_forScp-Indicators": { + "runAfter": { + "Initialize_variable_Results-CommandLines": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-Indicators", + "type": "array" + } + ] + } + }, + "Initialize_variable_CounterIndicatorsForMDTI": { + "runAfter": { + "Initialize_variable_Unique-forScp-Indicators": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "CounterIndicatorsForMDTI", + "type": "integer", + "value": 0 + } + ] + } + }, + "Initialize_variable_SubarrayIndicatorsForMDTI": { + "runAfter": { + "Initialize_variable_CounterIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "SubarrayIndicatorsForMDTI", + "type": "array" + } + ] + } + }, + "Initialize_variable_CsvIndicatorsForMDTI": { + "runAfter": { + "Initialize_variable_SubarrayIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "CsvIndicatorsForMDTI", + "type": "string" + } + ] + } + }, + "Initialize_variable_ResponsesFromMDTI": { + "runAfter": { + "Initialize_variable_CsvIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ResponsesFromMDTI", + "type": "array" + } + ] + } + }, + "Initialize_variable_OutputLength": { + "runAfter": { + "Initialize_variable_Results-IPs": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "OutputLengthMDTI", + "type": "integer" + } + ] + } + }, + "Initialize_variable_Response-MDTI": { + "runAfter": { + "Initialize_variable_ResponsesFromMDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Response-MDTI", + "type": "string" + } + ] + } + }, + "Initialize_variable_forScp-UsersForRiskAnalysis": { + "runAfter": { + "Initialize_variable_Results-UsersDetails": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-UsersForRiskAnalysis", + "type": "array" + } + ] + } + }, + "Initialize_variable_ProcessedItems": { + "runAfter": { + "Initialize_variable_OutputLength": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ProcessedItems", + "type": "array" + } + ] + } + }, + "Initialize_variable_forScp-UsersForDetails": { + "runAfter": { + "Initialize_variable_Response-MDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-UsersForDetails", + "type": "array" + } + ] + } + }, + "Initialize_variable_forScp-Devices": { + "runAfter": { + "Initialize_variable_Results-UsersRisk": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-Devices", + "type": "array" + } + ] + } + }, + "Initialize_variable_forScp-IPs": { + "runAfter": { + "Initialize_variable_Unique-forScp-Devices": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-IPs", + "type": "array" + } + ] + } + }, + "Initialize_variable_Unique-forScp-CommandLines": { + "runAfter": { + "Initialize_variable_forScp-CommandLines": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-CommandLines", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-Indicators": { + "runAfter": { + "Initialize_variable_forScp-Indicators": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-Indicators", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-UsersForDetails": { + "runAfter": { + "Initialize_variable_forScp-UsersForDetails": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-UsersForDetails", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-UsersForRiskAnalysis": { + "runAfter": { + "Initialize_variable_forScp-UsersForRiskAnalysis": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-UsersForRiskAnalysis", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-Devices": { + "runAfter": { + "Initialize_variable_Results-Devices": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-Devices", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-IPs": { + "runAfter": { + "Initialize_variable_forScp-IPs": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-IPs", + "type": "array", + "value": [ + ] + } + ] } - } - }, - "actions": { - "Compose_Incident_Entities": { + }, + "Initialize_variable_ResultsAvailable": { "runAfter": { + "Initialize_variable_ProcessedItems": [ + "Succeeded" + ] }, - "type": "Compose", - "inputs": "@triggerBody()?['object']?['properties']?['relatedEntities']" + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ResultsAvailable", + "type": "boolean", + "value": false + } + ] + } }, - "Condition_-_Validate_Entities": { + "Condition_-_Results_Available": { "actions": { - "For_Each_Entity": { - "foreach": "@outputs('Compose_Incident_Entities')", + "Compose_-_Incident_Data": { + "type": "Compose", + "inputs": "Incident Ttile: \n@{triggerBody()?['object']?['properties']?['title']},\n\nIncident Description: \n@{triggerBody()?['object']?['properties']?['description']},\n\nIncident Severity: \n@{triggerBody()?['object']?['properties']?['severity']},\n\nIncident Tactics: \n@{triggerBody()?['object']?['properties']?['additionalData']?['tactics']},\n\n______________________________________\nIp Addresses Analysis: \n@{if(parameters('CallScpForIPsWithAbuseIPDB'),variables('Results-IPs'),'skipped...')},\n\n______________________________________\nAccounts Analysis: \n@{if(parameters('CallScpForAccounts'),variables('Results-UsersDetails'),'skipped...')},\n\n______________________________________\nUsers Risk Analysis: \n@{if(parameters('CallScpForUserRisk'),outputs('Compose_Users_Risk'),'skipped...')},\n\n______________________________________\nDevices Analysis: \n@{if(parameters('CallScpForHosts'),variables('Results-Devices'),'skipped...')},\n\n______________________________________\nProcesses Analysis: \n@{if(parameters('CallScpForCommandLines'),outputs('Compose_Process_Analysis'),'skipped...')}\n\n______________________________________\nIndicators Analysis from MDTI: \n@{if(or(parameters('CallScpForIPsWithMDTI'),parameters('CallScpForFilehashesWithMDTI'),parameters('CallScpForUrlsWithMDTI'),parameters('CallScpForDomainsWithMDTI')),variables('Response-MDTI'),'skipped...')}" + }, + "Compose_-_Normalized_Incident_Data": { + "runAfter": { + "Compose_-_Incident_Data": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@replace(replace(outputs('Compose_-_Incident_Data'),',null',''),'[null,','')" + }, + "Condition_-_Send_unsummarized_results_by_email": { "actions": { - "Switch": { - "cases": { - "Case_-_Ip_Address": { - "case": "Ip", - "actions": { - "Security_Copilot_prompt_for_AbuseIPDB": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "body": { - "PromptContent": "I'm curious about any abuseipdb records for the IP address @{items('For_Each_Entity')?['properties']?['address']}. Can you look that up for me for the last 30 days?" - }, - "path": "/process-prompt" - } - }, - "Compose_AbuseDBIP_Analysis": { - "runAfter": { - "Security_Copilot_prompt_for_AbuseIPDB": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@body('Security_Copilot_prompt_for_AbuseIPDB')?['EvaluationResultContent']" - } + "Send_an_email_(V2)_-_Enrichment_Details": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" } }, - "Case_-_Account": { - "case": "Account", - "actions": { - "Security_Copilot_prompt_for_Account_": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "body": { - "PromptContent": "Give me all usr details for @{items('For_Each_Entity')?['properties']?['accountName']}. Does @{items('For_Each_Entity')?['properties']?['accountName']}have any registered devices in Microsoft Entra? List the recent risky sign-ins for @{items('For_Each_Entity')?['properties']?['accountName']}. Get Microsoft Entra audit logs for @{items('For_Each_Entity')?['properties']?['accountName']} for the past 72 hours.", - "SkillName": "GetEntraUserDetails" - }, - "path": "/process-prompt" + "method": "post", + "body": { + "To": "@trim(parameters('RecipientEmail'))", + "Subject": "Incident @{triggerBody()?['object']?['properties']?['providerIncidentId']} - Enrichment Details", + "Body": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Details---\u003c/strong\u003e\u003c/b\u003e\u003c/p\u003e\u003cp class=\"editor-paragraph\"\u003eIncident URL: @{concat('https://security.microsoft.com/incident2/',triggerBody()?['object']?['properties']?['providerIncidentId'],'/overview')}\u003c/p\u003e\u003cbr\u003e\u003cp class=\"editor-paragraph\"\u003e@{outputs('Compose_-_Details_-_Replace_NewLine_char')}\u003c/p\u003e", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + } + }, + "runAfter": { + "Compose_-_Details_-_Replace_NewLine_char": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "contains": [ + "@parameters('RecipientEmail')", + "@" + ] + } + ] + }, + "type": "If" + }, + "Condition_-_Write_unsummarized_results_on_incident": { + "actions": { + "Add_unsummarized_results_to_incident": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" + } + }, + "method": "post", + "body": { + "incidentArmId": "@triggerBody()?['object']?['id']", + "message": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Details---\u003c/strong\u003e\u003c/b\u003e\u003c/p\u003e\u003cbr\u003e\u003cp class=\"editor-paragraph\"\u003e@{outputs('Compose_-_Details_-_Replace_NewLine_char')}\u003c/p\u003e\u003cbr\u003e" + }, + "path": "/Incidents/Comment" + } + } + }, + "runAfter": { + "Compose_-_Details_-_Replace_NewLine_char": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('WriteUnsummarizedResultsToIncident')", + true + ] + } + ] + }, + "type": "If" + }, + "Security_Copilot_summarize_incident_investigation": { + "runAfter": { + "Condition_-_Send_unsummarized_results_by_email": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ], + "Condition_-_Write_unsummarized_results_on_incident": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Here is the security incident detail: @{outputs('Compose_-_Normalized_Incident_Data')}\n\nPerform investigation on security incident in following order. Incident Overview, Incident Description, Ip Addresses Analysis , Account Analysis, Users Risk Analysis, Devices Analysis, Processes Analysis, Indicators Analysis from MDTI, Possible mitigation steps and Conclusion. Ignore all error messages", + "SkillName": "SummarizeData" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Final_prompt_was_successful": { + "actions": { + "Compose_-_Incident_Investigation": { + "type": "Compose", + "inputs": "@body('Security_Copilot_summarize_incident_investigation')?['EvaluationResultContent']" + }, + "Condition_-_Write_final_summary_to_incident_-_Success": { + "actions": { + "Add_Investigation_Final_summary_to_Incident": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" } }, - "Compose_Account_Analysis": { - "runAfter": { - "Security_Copilot_prompt_for_Account_": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@body('Security_Copilot_prompt_for_Account_')?['EvaluationResultContent']" - } + "method": "post", + "body": { + "incidentArmId": "@triggerBody()?['object']?['id']", + "message": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Summary ---\u003c/strong\u003e\u003c/b\u003e\u003c/p\u003e\u003cp class=\"editor-paragraph\"\u003e\u003cbr\u003e@{outputs('Compose_-_Incident_Investigation')}\u003c/p\u003e\u003cbr\u003e" + }, + "path": "/Incidents/Comment" } - }, - "Case_-_Host": { - "case": "Host", - "actions": { - "Security_Copilot_prompt_for_Device": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "body": { - "PromptContent": "Tell me about device @{items('For_Each_Entity')?['properties']?['hostName']}.", - "SkillName": "GetIntuneDevices" - }, - "path": "/process-prompt" + } + }, + "runAfter": { + "Compose_-_Incident_Investigation": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('WriteFinalSummaryToIncident')", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_-_RecipientEmail_-_Final_-_Success": { + "actions": { + "Send_an_email_(V2)_-_Enrichment_Summary_-_Success": { + "runAfter": { + "Compose_-_Summary_-_Success_-_Replace_NewLine_char": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" } }, - "Compose_Device_Analysis": { - "runAfter": { - "Security_Copilot_prompt_for_Device": [ - "Succeeded" - ] + "method": "post", + "body": { + "To": "@trim(parameters('RecipientEmail'))", + "Subject": "Incident @{triggerBody()?['object']?['properties']?['providerIncidentId']} - Enrichment Summary", + "Body": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Summary ---\u003c/strong\u003e\u003c/b\u003e\u003cbr\u003eIncident URL: @{concat('https://security.microsoft.com/incident2/',triggerBody()?['object']?['properties']?['providerIncidentId'],'/overview')}\u003c/p\u003e\u003cbr\u003e\u003cp class=\"editor-paragraph\"\u003e@{outputs('Compose_-_Summary_-_Success_-_Replace_NewLine_char')}\u003c/p\u003e", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + }, + "Compose_-_Summary_-_Success_-_Replace_NewLine_char": { + "type": "Compose", + "inputs": "@replace(replace(outputs('Compose_-_Incident_Investigation'),'\\n','\u003cbr\u003e'),'\n','\u003cbr\u003e')" + } + }, + "runAfter": { + "Compose_-_Incident_Investigation": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "contains": [ + "@parameters('RecipientEmail')", + "@" + ] + } + ] + }, + "type": "If" + }, + "Terminate_-_Success": { + "runAfter": { + "Condition_-_Write_final_summary_to_incident_-_Success": [ + "Succeeded" + ], + "Condition_-_RecipientEmail_-_Final_-_Success": [ + "Succeeded" + ] + }, + "type": "Terminate", + "inputs": { + "runStatus": "Succeeded" + } + } + }, + "runAfter": { + "Security_Copilot_summarize_incident_investigation": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Condition_-_Unsummarized_results_not_already_written_to_incident": { + "actions": { + "Add_Investigation_Final_comment_to_Incident": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" + } }, - "type": "Compose", - "inputs": "@body('Security_Copilot_prompt_for_Device')?['EvaluationResultContent']" + "method": "post", + "body": { + "incidentArmId": "@triggerBody()?['object']?['id']", + "message": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Evidences ---\u003c/strong\u003e\u003c/b\u003e\u003c/p\u003e\u003cp class=\"editor-paragraph\"\u003e\u003ci\u003e\u003cem class=\"editor-text-italic\"\u003e(Content not summarized)\u003c/em\u003e\u003c/i\u003e\u003c/p\u003e\u003cbr\u003e\u003cp class=\"editor-paragraph\"\u003e@{outputs('Compose_-_Details_-_Replace_NewLine_char')}\u003c/p\u003e" + }, + "path": "/Incidents/Comment" } } }, - "Case_-_Process": { - "case": "Process", + "else": { "actions": { - "Security_Copilot_prompt_for_Process": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "body": { - "PromptContent": "Tell me more about the process command line: @{items('For_Each_Entity')?['properties']?['commandLine']}", - "SkillName": "ScriptAnalyzer" - }, - "path": "/process-prompt" - } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('WriteUnsummarizedResultsToIncident')", + false + ] }, - "Compose_Process_Analysis": { - "runAfter": { - "Security_Copilot_prompt_for_Process": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@body('Security_Copilot_prompt_for_Process')?['EvaluationResultContent']" + { + "equals": [ + "@parameters('WriteFinalSummaryToIncident')", + true + ] } + ] + }, + "type": "If" + }, + "Terminate_-_Failed": { + "runAfter": { + "Condition_-_Unsummarized_results_not_already_written_to_incident": [ + "Succeeded" + ], + "Condition_-_Unsummarized_results_not_already_sent_by_email": [ + "Succeeded" + ] + }, + "type": "Terminate", + "inputs": { + "runStatus": "Failed", + "runError": { + "message": "Final summarization failed" } } }, - "default": { + "Condition_-_Unsummarized_results_not_already_sent_by_email": { "actions": { - } - }, - "expression": "@items('For_Each_Entity')?['kind']", - "type": "Switch" + "Send_an_email_(V2)_-_Enrichment_Summary_-_Failure": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" + } + }, + "method": "post", + "body": { + "To": "@trim(parameters('RecipientEmail'))", + "Subject": "Incident @{triggerBody()?['object']?['properties']?['providerIncidentId']} - Enrichment Summary - Failure", + "Body": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Evidences ---\u003c/strong\u003e\u003c/b\u003e\u003cbr\u003e\u003ci\u003e\u003cem class=\"editor-text-italic\"\u003e(Content not summarized)\u003c/em\u003e\u003c/i\u003e\u003cbr\u003e\u003cbr\u003e@{outputs('Compose_-_Details_-_Replace_NewLine_char')}\u003c/p\u003e", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('SendUnsummarizedResultsByEmail')", + false + ] + }, + { + "equals": [ + "@parameters('SendFinalSummaryByEmail')", + true + ] + } + ] + }, + "type": "If" + } } }, - "type": "Foreach" + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_summarize_incident_investigation')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + }, + "Compose_-_Details_-_Replace_NewLine_char": { + "runAfter": { + "Compose_-_Normalized_Incident_Data": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@replace(replace(outputs('Compose_-_Normalized_Incident_Data'),'\\n','\u003cbr\u003e'),'\n','\u003cbr\u003e')" } }, "runAfter": { - "Compose_Incident_Entities": [ + "Condition_-_Validate_Entities": [ "Succeeded" ] }, "else": { "actions": { - "No_Entities_Found_-_Cancel_Execution": { + "No_results_available": { "type": "Terminate", "inputs": { "runStatus": "Cancelled" @@ -225,73 +2777,149 @@ "expression": { "and": [ { - "greater": [ - "@length(outputs('Compose_Incident_Entities'))", - 0 + "equals": [ + "@variables('ResultsAvailable')", + true ] } ] }, "type": "If" }, - "Compose_-_Incident_Data": { + "Condition_-_Validate_Parameters": { + "actions": { + }, "runAfter": { - "Condition_-_Validate_Entities": [ + "Initialize_variable_ResultsAvailable": [ "Succeeded" ] }, - "type": "Compose", - "inputs": "Incident Ttile: @{triggerBody()?['object']?['properties']?['title']},\nIncident Description: @{triggerBody()?['object']?['properties']?['description']},\nIncident Severity: @{triggerBody()?['object']?['properties']?['severity']},\nIncident Tactics: @{triggerBody()?['object']?['properties']?['additionalData']?['tactics']},\nIp Address Analysis: @{outputs('Compose_AbuseDBIP_Analysis')},\nAccount Analysis: @{outputs('Compose_Account_Analysis')},\nDevice Analysis: @{outputs('Compose_Device_Analysis')},\nProcess Analysis: @{outputs('Compose_Process_Analysis')}" + "else": { + "actions": { + "Invalid_Parameters": { + "type": "Terminate", + "inputs": { + "runStatus": "Cancelled" + } + } + } + }, + "expression": { + "and": [ + { + "contains": [ + "@parameters('RecipientEmail')", + "@" + ] + }, + { + "or": [ + { + "equals": [ + "@parameters('SendUnsummarizedResultsByEmail')", + true + ] + }, + { + "equals": [ + "@parameters('SendFinalSummaryByEmail')", + true + ] + } + ] + } + ] + }, + "type": "If" }, - "Security_Copilot_perform_incident_investigation": { + "Initialize_variable_Results-Devices": { "runAfter": { - "Compose_-_Incident_Data": [ + "Initialize_variable_forScp-Devices": [ "Succeeded" ] }, - "type": "ApiConnection", + "type": "InitializeVariable", "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "variables": [ + { + "name": "Results-Devices", + "type": "array", + "value": [ + ] } - }, - "method": "post", - "body": { - "PromptContent": "Here is the security incident detail: @{outputs('Compose_-_Incident_Data')}\n\nPerform investigation on security incident in following order. Incident Overview, Incident Description, Analysis on Ip Address, Account, Host and Process, Possible mitigation steps and Conclusion. ", - "SkillName": "SummarizeData" - }, - "path": "/process-prompt" + ] } }, - "Compose_-_Incident_Investigation": { + "Initialize_variable_Results-IPs": { "runAfter": { - "Security_Copilot_perform_incident_investigation": [ + "Initialize_variable_Unique-forScp-IPs": [ "Succeeded" ] }, - "type": "Compose", - "inputs": "@body('Security_Copilot_perform_incident_investigation')?['EvaluationResultContent']" + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Results-IPs", + "type": "array", + "value": [ + ] + } + ] + } }, - "Add_Investigation_Notes_to_Incident": { + "Initialize_variable_Results-UsersDetails": { "runAfter": { - "Compose_-_Incident_Investigation": [ + "Initialize_variable_Unique-forScp-UsersForDetails": [ "Succeeded" ] }, - "type": "ApiConnection", + "type": "InitializeVariable", "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['azuresentinel']['connectionId']" + "variables": [ + { + "name": "Results-UsersDetails", + "type": "array", + "value": [ + ] } - }, - "method": "post", - "body": { - "incidentArmId": "@triggerBody()?['object']?['id']", - "message": "\u003cbr\u003e@{outputs('Compose_-_Incident_Investigation')}\u003cbr\u003e" - }, - "path": "/Incidents/Comment" + ] + } + }, + "Initialize_variable_Results-UsersRisk": { + "runAfter": { + "Initialize_variable_Unique-forScp-UsersForRiskAnalysis": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Results-UsersRisk", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Results-CommandLines": { + "runAfter": { + "Initialize_variable_Unique-forScp-CommandLines": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Results-CommandLines", + "type": "array", + "value": [ + ] + } + ] } } }, @@ -315,6 +2943,11 @@ "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", "connectionName": "[variables('SecuritycopilotConnectionName')]", "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + }, + "office365": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", + "connectionName": "[variables('Office365ConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" } } } @@ -333,7 +2966,8 @@ "apiVersion": "2017-07-01", "dependsOn": [ "[resourceId('Microsoft.Web/connections', variables('MicrosoftSentinelConnectionName'))]", - "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]" + "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]" ] }, { @@ -366,6 +3000,21 @@ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" } } + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('Office365ConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('Office365ConnectionName')]", + "customParameterValues": { + }, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" + } + } } ] -} \ No newline at end of file +} diff --git a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/params.png b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/params.png new file mode 100644 index 00000000..e97f11ba Binary files /dev/null and b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/params.png differ diff --git a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/readme.md b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/readme.md index fcb5ece5..1dad3e5d 100644 --- a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/readme.md +++ b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/readme.md @@ -1,24 +1,25 @@ # SecurityCopilot-Sentinel-Incident-Investigation -**Author: Pratik Lal, Innocent Wafula** +**Author: Pratik Lal, Innocent Wafula, Stefano Pescosolido** ## Overview -This Logic App automates incident investigation within Microsoft Sentinel using the Microsoft Copilot for Security prompt and built-in skills. It is designed to: +This Logic App automates incident investigation within Microsoft Sentinel using the Microsoft Security Copilot prompt and built-in skills. It is designed to: - Read Microsoft Sentinel incident entities. - Process and normalize the entities. -- Submit prompts to Copilot for Security to analyze each entity type. +- Submit prompts to Security Copilot to analyze each entity type. - Summarize the outputs in a predefined format. -- Update the Microsoft Sentinel incident comments for the incident investigator to review. +- Update the Microsoft Sentinel incident comments for the incident investigator to review (writing to the Sentinel incident can be disabled for facilitating the initial testing). +- Optionally send the result of the investigation by email. By leveraging this Logic App, Security Operations Center (SOC) and incident response teams can significantly reduce investigation time, gain consistent analysis outputs, and enhance their incident response process. ## Prerequisites - The user or service principal deploying this logic app should have Contributor role on the Azure Resource Group. -- Microsoft Copilot for Security should be enabled on the Azure tenant. -- The user should have access to Microsoft Copilot for Security to submit prompts by authenticating to Microsoft Copilot for Security connector within the logic app. +- Microsoft Security Copilot should be enabled on the Azure tenant. +- The user should have access to Microsoft Security Copilot to submit prompts by authenticating to Microsoft Security Copilot connector within the logic app. - Microsoft Sentinel workspace should be set up. -- Get AbuseIPDB API key to perform IP address reputation check. [Learn more.](https://learn.microsoft.com/en-us/copilot/security/plugin-abuseipdb) -- Enable ProcessAnalyzer, GetEntraUserDetails, GetIntuneDevices and AbuseIPDB skills from copilot settings. +- Optionally get AbuseIPDB API key to perform IP address reputation check. [Learn more.](https://learn.microsoft.com/en-us/copilot/security/plugin-abuseipdb) +- Enable Defender, Sentinel, Entra, Intune, MDTI and AbuseIPDB plugins from copilot settings for the user account configured on the API connection of the Logic App to Security Copilot. ## Deployment @@ -30,34 +31,44 @@ By leveraging this Logic App, Security Operations Center (SOC) and incident resp
-- Choose Client Certificate Auth or Oauth (using user account that can submit prompts) to create connection with Microsoft Copilot for Security logic app connector.
+- Choose Client Certificate Auth or Oauth (using user account that can submit prompts) to create connection with Microsoft Security Copilot logic app connector.
-- Automate Sentinel incident investigation by adding the Logic App to Microsoft Sentinel automation rule.
-- Alternatively, this logic app can be called on-demand by right-clicking on a Sentinel incident, choose **Run Playbook** option.
+- Important: the account selected for the authentication to Security Copilot must have the Contributor role on Security Copilot and reader privileges on Defender, Sentinel, Entra and Intune.
+- Similarly, authenticate the API connection to Exchange Online (the correct setup of connection is needed even if the Logic App is configured to not send emails)
+- Assign the role of 'Microsoft Sentinel Responder' to the Managed Identity of the Logic App within the Resource Group that contains the Sentinel's Log Analytics workspace.
+- Set the values for the Logic App parameters
+
+- Automate Sentinel incident investigation by adding the Logic App to Microsoft Sentinel automation rule. Alternatively, this logic app can be called on-demand by right-clicking on a Sentinel incident, choose **Run Playbook** option.
-## Microsoft Copilot for Security skills
+## Microsoft Security Copilot skills
- Following built-in skills are being used to perform investigation on a given incident entity type
-| Incident entity | Copilot for Security skill |
+| Incident entity | Security Copilot skill |
| --------------- | -------------------------- |
| Process name / commandline | ProcessAnalyzer |
| Account name | GetEntraUserDetails |
+| Account names | GetEntraRiskyUsers |
| Device name | GetIntuneDevices |
| IP address | AbuseIPDB |
+| Indicator of Compromise (IoC) | GetReputationsForIndicators |
## Incident Investigation Template
- Automated incident investigation summary is structured in following order. Modify the prompt to re-order or change the investigation template.
- - Incident overview:
- - Incident description:
- - Analysis on incident entities:
- - Possible mitigation steps:
- - Conclusion
+ - Incident Overview,
+ - Incident Description,
+ - IP Addresses Analysis,
+ - Account Analysis,
+ - Users Risk Analysis,
+ - Devices Analysis,
+ - Processes Analysis,
+ - Indicators Analysis from MDTI,
+ - Possible mitigation steps and Conclusion
diff --git a/Logic Apps/ciso-reporting/ciso-incidents-summary-man.yaml b/Logic Apps/ciso-reporting/ciso-incidents-summary-man.yaml
index 6f26fafe..bdab9c01 100644
--- a/Logic Apps/ciso-reporting/ciso-incidents-summary-man.yaml
+++ b/Logic Apps/ciso-reporting/ciso-incidents-summary-man.yaml
@@ -24,7 +24,7 @@ SkillGroups:
DisplayName: CISO - Incidents Status Summary
Description: Get a summary of the incidents status in Defender
DescriptionForModel : Get a summary of the incidents status in Defender.
- If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings (like "High","Medium").
+ If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings with the first letter in uppercase (like "High","Medium").
If start date and end date are not specified, consider the last 7 days.
If the top number of results is not specified, consider 5 incidents titles.
If csv_of_severities is not specified, consider only "High".
@@ -81,7 +81,7 @@ SkillGroups:
DisplayName: CISO - True Positive Incidents Tactics and Techniques Analysis
Description: Get a summary of the tactics and techniques used in the incidents that were classified as true positives
DescriptionForModel : Get a summary of the tactics and techniques used in the incidents that were classified as true positives
- If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings (like "High","Medium").
+ If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings with the first letter in uppercase (like "High","Medium").
If start date and end date are not specified, consider the last 7 days.
If severities is not specified, consider only "High".
Inputs:
diff --git a/Logic Apps/ciso-reporting/ciso-posture-summary-man.yaml b/Logic Apps/ciso-reporting/ciso-posture-summary-man.yaml
index c9a8ff19..2a78fbdf 100644
--- a/Logic Apps/ciso-reporting/ciso-posture-summary-man.yaml
+++ b/Logic Apps/ciso-reporting/ciso-posture-summary-man.yaml
@@ -12,6 +12,7 @@ SkillGroups:
DisplayName: CISO - Recommendations by Severity
Description: Get the list of the active recommendations by Severity (work in progress)
DescriptionForModel : Get the list of the active recommendations by Severity
+ If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings with the first letter in uppercase (like "High","Medium").
Inputs:
- Name: days_back
Description: The number of days to be considered in the past, starting from today
diff --git a/Logic Apps/ciso-reporting/images/yt.png b/Logic Apps/ciso-reporting/images/yt.png
new file mode 100644
index 00000000..0bf5879d
Binary files /dev/null and b/Logic Apps/ciso-reporting/images/yt.png differ
diff --git a/Logic Apps/ciso-reporting/install-guide.md b/Logic Apps/ciso-reporting/install-guide.md
new file mode 100644
index 00000000..4faf3266
--- /dev/null
+++ b/Logic Apps/ciso-reporting/install-guide.md
@@ -0,0 +1,190 @@
+
+# Prerequisites
+
+>
|