> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cognisafe.uk/llms.txt
> Use this file to discover all available pages before exploring further.

# Azure Sentinel Integration

> Ingest Cognisafe AI threat events into Microsoft Sentinel for SOC workflows

## Architecture

```
Cognisafe (threat_detected webhook)
        │
        ▼  HTTPS POST  X-Cognisafe-Signature
Azure Logic App (HTTP trigger)
        │  verify HMAC, parse payload
        ▼
Log Analytics Data Collector API
        │  POST /api/logs  CognisafeThreatEvents_CL
        ▼
Log Analytics Workspace
        │
        ├── Sentinel Analytics Rules (KQL)
        ├── Sentinel Workbooks
        └── Sentinel Incidents → SOC queue
```

The Logic App acts as a thin translation layer: it validates the HMAC signature, maps the Cognisafe JSON payload to the `CognisafeThreatEvents_CL` schema, and forwards to the Log Analytics Data Collector API. No custom code deployment is required — all logic lives in the Logic App definition.

<Note>
  This integration uses the **classic Log Analytics Data Collector API** (HTTP Data Collector) which is GA and broadly available. Microsoft's newer DCE/DCR pipeline is also supported; see the Tip at the end of Step 3 if you prefer it.
</Note>

***

## Step 1: Create a Log Analytics Workspace

```bash theme={null}
# Variables — adjust to your environment
RESOURCE_GROUP="rg-cognisafe-sentinel"
LOCATION="uksouth"
WORKSPACE_NAME="law-cognisafe-prod"
SENTINEL_SOLUTION="SecurityInsights"

az group create \
  --name "$RESOURCE_GROUP" \
  --location "$LOCATION"

az monitor log-analytics workspace create \
  --resource-group "$RESOURCE_GROUP" \
  --workspace-name "$WORKSPACE_NAME" \
  --location "$LOCATION" \
  --sku PerGB2018 \
  --retention-time 90

# Retrieve workspace ID and primary key (needed in Step 3)
WORKSPACE_ID=$(az monitor log-analytics workspace show \
  --resource-group "$RESOURCE_GROUP" \
  --workspace-name "$WORKSPACE_NAME" \
  --query customerId -o tsv)

WORKSPACE_KEY=$(az monitor log-analytics workspace get-shared-keys \
  --resource-group "$RESOURCE_GROUP" \
  --workspace-name "$WORKSPACE_NAME" \
  --query primarySharedKey -o tsv)

echo "Workspace ID: $WORKSPACE_ID"
echo "Workspace Key: $WORKSPACE_KEY"   # store in Key Vault

# Enable Sentinel on the workspace
az security insights solution create \
  --resource-group "$RESOURCE_GROUP" \
  --workspace-name "$WORKSPACE_NAME" \
  --sentinel-onboarding-state "Enabled" 2>/dev/null || \
az monitor log-analytics solution create \
  --resource-group "$RESOURCE_GROUP" \
  --workspace "$WORKSPACE_NAME" \
  --solution-type "$SENTINEL_SOLUTION"
```

***

## Step 2: Create the Custom Table

Custom log tables in Log Analytics end with `_CL`. The schema below matches the Cognisafe `threat_detected` webhook payload.

```bash theme={null}
# Define the custom table schema via the REST API
# (Portal: Log Analytics workspace → Tables → Create → New custom log table)

TABLE_SCHEMA='{
  "properties": {
    "schema": {
      "name": "CognisafeThreatEvents_CL",
      "columns": [
        { "name": "TimeGenerated",    "type": "datetime" },
        { "name": "RequestId",        "type": "string" },
        { "name": "ProjectId",        "type": "string" },
        { "name": "AgentName",        "type": "string" },
        { "name": "AgentTag",         "type": "string" },
        { "name": "ScorerName",       "type": "string" },
        { "name": "ScoreLabel",       "type": "string" },
        { "name": "ScoreValue",       "type": "real" },
        { "name": "Severity",         "type": "string" },
        { "name": "OwaspCategory",    "type": "string" },
        { "name": "OwaspDescription", "type": "string" },
        { "name": "Model",            "type": "string" },
        { "name": "PromptSnippet",    "type": "string" },
        { "name": "LatencyMs",        "type": "int" },
        { "name": "RunId",            "type": "string" },
        { "name": "ReportUrl",        "type": "string" },
        { "name": "EventId",          "type": "string" }
      ]
    }
  }
}'
```

<Tip>
  Once data flows in, you can add columns via **Log Analytics → Tables → CognisafeThreatEvents\_CL → Edit schema** without re-creating the table.
</Tip>

***

## Step 3: Logic App Webhook Receiver

### Create the Logic App

```bash theme={null}
LOGIC_APP_NAME="la-cognisafe-webhook"

az logic workflow create \
  --resource-group "$RESOURCE_GROUP" \
  --name "$LOGIC_APP_NAME" \
  --location "$LOCATION" \
  --definition '{"definition": {"$schema": "...", "actions": {}, "triggers": {}}}'
```

Then replace the workflow definition with the ARM template below (deploy via portal **Logic App Designer → Code view**, or `az deployment group create`):

```json theme={null}
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspaceId":  { "type": "string" },
    "workspaceKey": { "type": "securestring" },
    "webhookSecret": { "type": "securestring" }
  },
  "resources": [
    {
      "type": "Microsoft.Logic/workflows",
      "apiVersion": "2019-05-01",
      "name": "la-cognisafe-webhook",
      "location": "[resourceGroup().location]",
      "identity": { "type": "SystemAssigned" },
      "properties": {
        "definition": {
          "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
          "triggers": {
            "manual": {
              "type": "Request",
              "kind": "Http",
              "inputs": {
                "schema": {},
                "method": "POST"
              }
            }
          },
          "actions": {
            "Parse_Payload": {
              "type": "ParseJson",
              "inputs": {
                "content": "@triggerBody()",
                "schema": {
                  "type": "object",
                  "properties": {
                    "event":      { "type": "string" },
                    "id":         { "type": "string" },
                    "created_at": { "type": "string" },
                    "project_id": { "type": "string" },
                    "data": {
                      "type": "object",
                      "properties": {
                        "request_id":         { "type": "string" },
                        "agent_name":         { "type": "string" },
                        "agent_tag":          { "type": "string" },
                        "scorer_name":        { "type": "string" },
                        "score_label":        { "type": "string" },
                        "score_value":        { "type": "number" },
                        "severity":           { "type": "string" },
                        "owasp_category":     { "type": "string" },
                        "owasp_description":  { "type": "string" },
                        "model":              { "type": "string" },
                        "prompt_snippet":     { "type": "string" },
                        "latency_ms":         { "type": "integer" },
                        "run_id":             { "type": "string" },
                        "report_url":         { "type": "string" }
                      }
                    }
                  }
                }
              },
              "runAfter": {}
            },
            "Verify_HMAC": {
              "type": "If",
              "expression": {
                "and": [
                  {
                    "equals": [
                      "@concat('sha256=', encodeBase64(hmacSha256(base64(parameters('webhookSecret')), triggerBody())))",
                      "@triggerOutputs()['headers']['X-Cognisafe-Signature']"
                    ]
                  }
                ]
              },
              "actions": {
                "Send_to_Log_Analytics": {
                  "type": "Http",
                  "inputs": {
                    "method": "POST",
                    "uri": "@concat('https://', parameters('workspaceId'), '.ods.opinsights.azure.com/api/logs?api-version=2016-04-01')",
                    "headers": {
                      "Content-Type": "application/json",
                      "Log-Type": "CognisafeThreatEvents",
                      "x-ms-date": "@utcNow('R')",
                      "Authorization": "@concat('SharedKey ', parameters('workspaceId'), ':', listKeys(resourceId('Microsoft.OperationalInsights/workspaces', 'law-cognisafe-prod'), '2020-08-01').primarySharedKey)"
                    },
                    "body": [
                      {
                        "TimeGenerated":    "@body('Parse_Payload')?['created_at']",
                        "RequestId":        "@body('Parse_Payload')?['data']?['request_id']",
                        "ProjectId":        "@body('Parse_Payload')?['project_id']",
                        "AgentName":        "@body('Parse_Payload')?['data']?['agent_name']",
                        "AgentTag":         "@body('Parse_Payload')?['data']?['agent_tag']",
                        "ScorerName":       "@body('Parse_Payload')?['data']?['scorer_name']",
                        "ScoreLabel":       "@body('Parse_Payload')?['data']?['score_label']",
                        "ScoreValue":       "@body('Parse_Payload')?['data']?['score_value']",
                        "Severity":         "@body('Parse_Payload')?['data']?['severity']",
                        "OwaspCategory":    "@body('Parse_Payload')?['data']?['owasp_category']",
                        "OwaspDescription": "@body('Parse_Payload')?['data']?['owasp_description']",
                        "Model":            "@body('Parse_Payload')?['data']?['model']",
                        "PromptSnippet":    "@body('Parse_Payload')?['data']?['prompt_snippet']",
                        "LatencyMs":        "@body('Parse_Payload')?['data']?['latency_ms']",
                        "RunId":            "@body('Parse_Payload')?['data']?['run_id']",
                        "ReportUrl":        "@body('Parse_Payload')?['data']?['report_url']",
                        "EventId":          "@body('Parse_Payload')?['id']"
                      }
                    ]
                  }
                }
              },
              "else": {
                "actions": {
                  "Return_401": {
                    "type": "Response",
                    "inputs": {
                      "statusCode": 401,
                      "body": "Invalid signature"
                    }
                  }
                }
              },
              "runAfter": { "Parse_Payload": ["Succeeded"] }
            },
            "Return_200": {
              "type": "Response",
              "inputs": { "statusCode": 200 },
              "runAfter": { "Verify_HMAC": ["Succeeded"] }
            }
          }
        },
        "parameters": {
          "workspaceId":  { "value": "[parameters('workspaceId')]" },
          "workspaceKey": { "value": "[parameters('workspaceKey')]" },
          "webhookSecret": { "value": "[parameters('webhookSecret')]" }
        }
      }
    }
  ]
}
```

After deployment, copy the Logic App's HTTP trigger URL from the **Overview** blade and paste it into Cognisafe **Settings → Webhooks → Endpoint URL**.

<Tip>
  For the DCE/DCR pipeline (recommended for new workspaces in 2026+): replace the `Send_to_Log_Analytics` action with a call to your Data Collection Endpoint URL using a managed identity bearer token obtained from `https://management.azure.com/` audience. The payload schema remains identical.
</Tip>

***

## Step 4: Sentinel Analytics Rules

Create these rules in **Microsoft Sentinel → Analytics → Create → Scheduled query rule**.

### Rule 1 — Critical Severity Spike

Fires when more than 5 critical events occur within any 10-minute window across a single project.

```kql theme={null}
CognisafeThreatEvents_CL
| where TimeGenerated > ago(10m)
| where Severity == "critical"
| summarize EventCount = count() by ProjectId, bin(TimeGenerated, 10m)
| where EventCount > 5
| project TimeGenerated, ProjectId, EventCount
```

**Rule settings:**

* Query frequency: every 5 minutes
* Lookup period: 10 minutes
* Alert threshold: results > 0
* Incident grouping: group by `ProjectId`

### Rule 2 — LLM01 Prompt Injection Detected

Fires on any single detection of prompt injection regardless of severity — zero tolerance.

```kql theme={null}
CognisafeThreatEvents_CL
| where TimeGenerated > ago(5m)
| where OwaspCategory == "LLM01"
| project
    TimeGenerated,
    ProjectId,
    AgentName,
    AgentTag,
    Model,
    ScoreValue,
    Severity,
    PromptSnippet,
    ReportUrl
```

**Rule settings:**

* Query frequency: every 5 minutes
* Lookup period: 5 minutes
* Alert threshold: results > 0
* Severity: High
* Tactics: InitialAccess, Execution

### Rule 3 — New Agent Name Appearing (Potential Rogue Agent)

Detects an `AgentName` that has not been seen in the previous 7 days — a signal that an unregistered or rogue agent is active.

```kql theme={null}
let known_agents = (
    CognisafeThreatEvents_CL
    | where TimeGenerated between (ago(8d) .. ago(1d))
    | summarize by AgentName
);
CognisafeThreatEvents_CL
| where TimeGenerated > ago(1d)
| where AgentName !in (known_agents)
| summarize
    FirstSeen = min(TimeGenerated),
    EventCount = count(),
    Scorers = make_set(ScorerName),
    Projects = make_set(ProjectId)
  by AgentName
| project FirstSeen, AgentName, EventCount, Scorers, Projects
```

**Rule settings:**

* Query frequency: every 1 hour
* Lookup period: 1 day
* Alert threshold: results > 0
* Severity: Medium
* Tactics: Discovery, Persistence

***

## Step 5: Sentinel Workbook

Create a new Workbook in **Microsoft Sentinel → Workbooks → Add workbook** and paste these query tiles.

### Threats by OWASP Category (Pie Chart)

```kql theme={null}
CognisafeThreatEvents_CL
| where TimeGenerated > ago(24h)
| where ScoreLabel == "fail"
| summarize Count = count() by OwaspCategory
| order by Count desc
```

Visualisation: **Pie chart**, Label = `OwaspCategory`, Value = `Count`.

### Threat Timeline (Line Chart)

```kql theme={null}
CognisafeThreatEvents_CL
| where TimeGenerated > ago(7d)
| where ScoreLabel == "fail"
| summarize Threats = count() by bin(TimeGenerated, 1h), Severity
| order by TimeGenerated asc
```

Visualisation: **Time chart**, X = `TimeGenerated`, Y = `Threats`, Split = `Severity`.

### Top Flagged Agents (Bar Chart)

```kql theme={null}
CognisafeThreatEvents_CL
| where TimeGenerated > ago(24h)
| where ScoreLabel == "fail"
| summarize
    TotalThreats = count(),
    CriticalCount = countif(Severity == "critical"),
    LastSeen = max(TimeGenerated)
  by AgentName
| order by TotalThreats desc
| take 10
```

Visualisation: **Bar chart**, X = `AgentName`, Y = `TotalThreats`.

***

## Incident Enrichment

Add the Cognisafe `RunId` and `ReportUrl` to Sentinel incidents via a Playbook (Logic App triggered on incident creation):

```json theme={null}
{
  "actions": {
    "Get_Incident_Entities": {
      "type": "ApiConnection",
      "inputs": {
        "host": { "connection": { "name": "@parameters('$connections')['azuresentinel']['connectionId']" } },
        "method": "post",
        "path": "/Incidents/subscriptions/@{encodeURIComponent(triggerBody()?['WorkspaceSubscriptionId'])}/resourceGroups/@{encodeURIComponent(triggerBody()?['WorkspaceResourceGroup'])}/workspaces/@{encodeURIComponent(triggerBody()?['WorkspaceId'])}/Incidents/@{encodeURIComponent(triggerBody()?['object']?['name'])}/entities"
      }
    },
    "Add_Comment": {
      "type": "ApiConnection",
      "inputs": {
        "host": { "connection": { "name": "@parameters('$connections')['azuresentinel']['connectionId']" } },
        "method": "post",
        "path": "/Incidents/subscriptions/.../Comment",
        "body": {
          "message": "@concat('Cognisafe Report: ', triggerBody()?['object']?['properties']?['additionalData']?['ReportUrl'])"
        }
      },
      "runAfter": { "Get_Incident_Entities": ["Succeeded"] }
    }
  }
}
```

***

## RBAC

The Logic App's **system-assigned managed identity** needs:

```bash theme={null}
# Get the Logic App principal ID
PRINCIPAL_ID=$(az logic workflow show \
  --resource-group "$RESOURCE_GROUP" \
  --name "$LOGIC_APP_NAME" \
  --query "identity.principalId" -o tsv)

# Grant Log Analytics Contributor (for Data Collector API write access)
az role assignment create \
  --assignee "$PRINCIPAL_ID" \
  --role "Log Analytics Contributor" \
  --scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.OperationalInsights/workspaces/$WORKSPACE_NAME"

# Grant Sentinel Reader for SOC analysts who only need to view
SOC_GROUP_ID="<aad-group-object-id>"
az role assignment create \
  --assignee "$SOC_GROUP_ID" \
  --role "Microsoft Sentinel Reader" \
  --scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP"
```

<Warning>
  Do not assign **Microsoft Sentinel Contributor** to the Logic App identity. It only needs to write to Log Analytics, not to modify Sentinel configuration.
</Warning>
