Discover AI workloads in Defender for Cloud, assess AI model deployment risks, configure security recommendations for Azure OpenAI and Azure AI services, and build an AI security governance dashboard.
This lab focuses on AI Security Posture Management in Microsoft Defender for Cloud. a set of capabilities purpose-built to discover, assess, and harden AI workloads running in Azure. You will learn how Defender for Cloud automatically discovers Azure OpenAI, Azure AI Services, and Azure Machine Learning deployments, then evaluates them against security benchmarks covering model access controls, network isolation, data encryption, and API security. The lab walks through configuring threat protection for AI services, building attack path analysis for AI compromise scenarios, mapping AI workloads to regulatory compliance frameworks, and integrating AI security findings with Microsoft Sentinel for cross-signal correlation.
A financial services company with 15,000 employees has deployed multiple Azure OpenAI instances for fraud detection, customer service chatbots, and document processing. The security team needs to discover all AI deployments, assess their security posture, monitor for prompt injection attacks, and ensure AI models are not exposing sensitive customer financial data through API endpoints.
In the Azure portal, navigate to Defender for Cloud > Environment settings. Enable the AI threat protection plan for subscriptions containing Azure OpenAI and Azure AI Services resources. This enables discovery, security assessment, and runtime threat detection for AI workloads.
Review the AI workload inventory showing all discovered Azure OpenAI accounts, Azure AI Search instances, Azure Machine Learning workspaces, and Cognitive Services deployments. Examine the security score for each AI resource and identify unprotected AI endpoints.
Defender for Cloud generates specific security recommendations for AI services based on the Microsoft cloud security benchmark. Review and prioritise findings for your Azure OpenAI, Azure AI Services, and Azure Machine Learning deployments.
Microsoft.CognitiveServices/accounts, Microsoft.MachineLearningServices/workspaces// List all unhealthy AI-service security recommendations grouped by
// severity and name.
// WHY: AI services (Azure OpenAI, Cognitive Services, Azure ML) have
// unique security risks - exposed API endpoints can lead to data
// exfiltration, prompt injection, and unauthorized model access.
// This query surfaces which AI-specific controls are failing.
// Properties filter: matches resources under CognitiveServices,
// MachineLearningServices, or OpenAI resource providers.
// OUTPUT: Table of recommendation names with severity and count.
// High-severity findings (e.g., "disable key access", "use private link")
// should be remediated first as they directly expose AI endpoints.
SecurityRecommendation
| where RecommendationState == "Unhealthy"
| where Properties has "CognitiveServices" or Properties has "MachineLearningServices" or Properties has "OpenAI"
| summarize Count=count() by RecommendationSeverity, RecommendationName
| order by RecommendationSeverity asc, Count descRestrict who can access AI model deployments and enforce least-privilege access controls on Azure OpenAI endpoints.
# Disable local (key-based) authentication on Azure OpenAI.
# WHY: API keys are the #1 security risk for AI services - they cannot
# be scoped to specific users, cannot enforce conditional access, and
# provide no per-user audit trail. Disabling keys forces all callers
# to authenticate via Entra ID managed identities or service principals.
# disableLocalAuth: true = reject all API key-based requests.
az cognitiveservices account update \
--name "my-openai-instance" \
--resource-group "rg-ai-workloads" \
--custom-domain "my-openai-instance" \
--api-properties "{\"disableLocalAuth\": true}"
# Grant a managed identity the scoped "Cognitive Services OpenAI User" role.
# This role allows the application to call the model endpoint (chat, completions)
# but NOT deploy new models or modify the resource configuration.
# --assignee-object-id: the Object ID of the app's managed identity
# (find via: az identity show --name --query principalId).
# --scope: restricts the role to this specific OpenAI instance only.
# WHY: Least-privilege access - apps get inference-only permissions,
# not the broad Contributor role that could modify or delete the resource.
az role assignment create \
--assignee-object-id <managed-identity-object-id> \
--role "Cognitive Services OpenAI User" \
--scope "/subscriptions/<sub-id>/resourceGroups/rg-ai-workloads/providers/Microsoft.CognitiveServices/accounts/my-openai-instance"
# Verify that key-based access is now blocked.
# Expected output: an error message confirming that local authentication
# is disabled. If keys are returned, the disableLocalAuth setting did
# not apply - check the resource configuration.
az cognitiveservices account keys list \
--name "my-openai-instance" \
--resource-group "rg-ai-workloads" 2>&1 || echo "Key access disabled - using Entra ID auth only" Configure private endpoints and network restrictions to ensure AI model inference traffic stays on the corporate network and is not exposed to the internet.
pe-openai-prod, subnet snet-ai-servicesprivatelink.openai.azure.com# Disable public access to the Azure OpenAI endpoint.
# After this, the model can only be reached via private endpoints
# within your corporate VNet - no internet-based API calls allowed.
# WHY: Public endpoints expose AI models to brute-force attacks,
# prompt injection from anonymous sources, and data exfiltration risk.
az cognitiveservices account update \
--name "my-openai-instance" \
--resource-group "rg-ai-workloads" \
--public-network-access Disabled
# Create a private endpoint to access Azure OpenAI from your VNet.
# --vnet-name / --subnet: the network where your applications run.
# --group-id "account": the sub-resource type for Cognitive Services.
# --private-connection-resource-id: full ARM ID of the OpenAI instance.
# After creation, traffic to the OpenAI hostname resolves to a private IP
# within your subnet instead of a public Microsoft IP.
az network private-endpoint create \
--name pe-openai-prod \
--resource-group rg-ai-workloads \
--vnet-name vnet-hub \
--subnet snet-ai-services \
--private-connection-resource-id "/subscriptions/<sub-id>/resourceGroups/rg-ai-workloads/providers/Microsoft.CognitiveServices/accounts/my-openai-instance" \
--group-id account \
--connection-name openai-pe-connection
# Create a Private DNS Zone for Azure OpenAI hostname resolution.
# WHY: When applications call my-openai-instance.openai.azure.com,
# DNS must resolve to the private IP. This zone handles that mapping
# so existing code works without endpoint URL changes.
az network private-dns zone create \
--resource-group rg-ai-workloads \
--name "privatelink.openai.azure.com"
# Link the DNS zone to your VNet so VMs and apps in the network
# can resolve the private endpoint hostname automatically.
# --registration-enabled false: prevents auto-registering VM hostnames
# in this zone (not needed for service endpoints).
az network private-dns link vnet create \
--resource-group rg-ai-workloads \
--zone-name "privatelink.openai.azure.com" \
--name link-openai-vnet \
--virtual-network vnet-hub \
--registration-enabled falseDefender for Cloud’s AI threat protection detects anomalous usage patterns including prompt injection attempts, credential exposure in prompts, and unusual API consumption spikes.
// List all AI-specific threat alerts from the last 7 days.
// ProductName "Azure Defender for AI" filters to alerts generated by
// the AI threat protection plan (prompt injection, credential leaks,
// jailbreak attempts, anomalous API volume, suspicious IP access).
// Tactics and Techniques: map to the MITRE ATT&CK framework so SOC
// analysts can correlate with other attack signals.
// OUTPUT: Table of alert details sorted by severity then time.
// High/Critical alerts warrant immediate investigation.
SecurityAlert
| where TimeGenerated > ago(7d)
| where ProductName == "Azure Defender for AI"
| project TimeGenerated, AlertName, AlertSeverity, Description,
ResourceId, Tactics, Techniques
| order by AlertSeverity asc, TimeGenerated desc
// Correlate AI threat alerts with Entra ID sign-in risk signals.
// WHY: An AI jailbreak attempt from an IP that also has a risky sign-in
// is a much stronger indicator of compromise than either signal alone.
// The join matches the caller IP from the AI alert with the sign-in IP.
// OUTPUT: Rows showing the alert, the associated user identity, and
// their risk state (medium/high). Escalate these as priority incidents.
SecurityAlert
| where ProductName == "Azure Defender for AI"
| extend CallerIP = tostring(ExtendedProperties["CallerIpAddress"])
| join kind=inner (
SigninLogs
| where TimeGenerated > ago(7d)
| project IPAddress, UserPrincipalName, RiskState
) on $left.CallerIP == $right.IPAddress
| project TimeGenerated, AlertName, UserPrincipalName, RiskState, CallerIPBuild a custom security initiative that groups all AI-relevant policy definitions into a single compliance standard tailored to your organisation’s AI governance requirements.
AI-Security-Baseline-v1, Category: AI Governance# List all built-in Azure Policy definitions related to AI services.
# Filters by display name containing "AI Services", "Cognitive Services",
# or "Machine Learning" to find applicable security controls.
# OUTPUT: Table of policy definition names and display names.
# Review this list to select which policies to include in your initiative.
az policy definition list \
--query "[?contains(displayName,'AI Services') || contains(displayName,'Cognitive Services') || contains(displayName,'Machine Learning')].{name:name,displayName:displayName}" \
-o table
# Create a custom policy set (initiative) that groups AI-relevant policies
# into a single compliance standard for your organisation.
# --definitions: JSON file listing the policy definition IDs to include
# (e.g., disable key access, enforce private link, enable diagnostics).
# --metadata category "AI Governance": makes the initiative easy to find
# in the portal under the AI Governance category.
# WHY: Custom initiatives let you track AI security posture as a single
# compliance percentage, making it easy to report to leadership.
az policy set-definition create \
--name "ai-security-baseline-v1" \
--display-name "AI Security Baseline v1" \
--description "Custom initiative for AI workload security posture" \
--definitions @ai-initiative-definitions.json \
--metadata '{"category":"AI Governance"}'
# Assign the initiative to the subscription(s) containing AI workloads.
# Replace <sub-id> with your subscription GUID.
# Once assigned, Azure Policy evaluates all AI resources against the
# initiative’s rules and reports compliance in Defender for Cloud.
az policy assignment create \
--name "ai-baseline-assignment" \
--policy-set-definition "ai-security-baseline-v1" \
--scope "/subscriptions/<sub-id>"Define governance rules that automatically assign owners and deadlines when AI security recommendations go unhealthy.
ai-workload-governanceEnable diagnostic logging on AI services and create dashboards to monitor API consumption, error rates, and anomalous usage patterns that may indicate compromised credentials or data exfiltration.
# Enable diagnostic logging for Azure OpenAI to capture all API requests,
# responses, errors, and performance metrics.
# --resource: full ARM ID of the Azure OpenAI instance.
# --workspace: the Log Analytics workspace where logs are sent. Use the
# same workspace connected to Sentinel for cross-signal correlation.
# --logs categoryGroup "allLogs": captures RequestResponse (API calls),
# Audit (config changes), and Metrics (token counts, latency).
# WHY: Without diagnostic logging, you have no visibility into who is
# calling your AI models, what prompts they send, or how many tokens
# they consume. This is essential for detecting anomalous usage,
# credential abuse, and data exfiltration via large API responses.
az monitor diagnostic-settings create \
--name "openai-diagnostics" \
--resource "/subscriptions/<sub-id>/resourceGroups/rg-ai-workloads/providers/Microsoft.CognitiveServices/accounts/my-openai-instance" \
--workspace "/subscriptions/<sub-id>/resourceGroups/rg-security/providers/Microsoft.OperationalInsights/workspaces/law-security" \
--logs '[{"categoryGroup":"allLogs","enabled":true}]' \
--metrics '[{"category":"AllMetrics","enabled":true}]'// Baseline: hourly API call volume per caller over the past 7 days.
// WHY: Establishes a normal usage pattern for each caller IP. Without
// a baseline, you cannot distinguish legitimate spikes (e.g., batch
// processing) from malicious activity (credential theft, enumeration).
// bin(TimeGenerated, 1h): groups calls into hourly windows.
// OUTPUT: Time series per caller showing call count and average tokens.
// Use this to set alerting thresholds based on actual usage patterns.
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.COGNITIVESERVICES"
| where Category == "RequestResponse"
| where TimeGenerated > ago(7d)
| summarize CallCount=count(), AvgTokens=avg(toint(properties_s))
by bin(TimeGenerated, 1h), CallerIPAddress
| order by TimeGenerated desc
// Anomaly detection: flag callers exceeding 3x their 30-day average.
// The baseline calculates each caller’s average daily call volume over
// the previous 30 days. Today’s volume is compared against this baseline.
// Callers with TodayCount > 3x AvgDaily are flagged as anomalous.
// WHY: A sudden 3x+ spike often indicates compromised API credentials
// being used for data extraction, or an automated attack probing the
// model for prompt injection vulnerabilities.
// Ratio column: shows how many times today’s volume exceeds the average.
// OUTPUT: Table of anomalous callers with their IP, today’s count,
// average, and ratio. Investigate callers with the highest ratios first.
let baseline = AzureDiagnostics
| where ResourceProvider == "MICROSOFT.COGNITIVESERVICES"
| where TimeGenerated between(ago(30d) .. ago(1d))
| summarize AvgDaily=count()/30 by CallerIPAddress;
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.COGNITIVESERVICES"
| where TimeGenerated > ago(1d)
| summarize TodayCount=count() by CallerIPAddress
| join kind=inner baseline on CallerIPAddress
| where TodayCount > AvgDaily * 3
| project CallerIPAddress, TodayCount, AvgDaily, Ratio=round(TodayCount*1.0/AvgDaily, 1)Monitor AI model endpoints for potential data exfiltration. attackers may use large prompt–response interactions to extract training data or sensitive information encoded in the model.
// Detect unusually large AI model responses (completions > 4,000 tokens).
// WHY: Attackers may use crafted prompts (e.g., "repeat all data you know")
// to extract training data or sensitive information encoded in the model.
// Large response payloads are a strong indicator of data exfiltration
// attempts, especially from unfamiliar caller IPs.
// CompletionTokens > 4000: threshold for investigation. Normal business
// interactions rarely exceed this. Adjust based on your application’s
// expected response sizes.
// OUTPUT: Table of large-response events sorted by token count.
// Cross-reference CallerIPAddress with known corporate IPs - unknown
// IPs generating large responses are high-priority investigations.
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.COGNITIVESERVICES"
| where Category == "RequestResponse"
| where TimeGenerated > ago(24h)
| extend CompletionTokens = toint(properties_s)
| where CompletionTokens > 4000
| project TimeGenerated, CallerIPAddress, OperationName, CompletionTokens, _ResourceId
| order by CompletionTokens descUse Defender CSPM attack path analysis to identify how an attacker could reach AI resources by chaining vulnerabilities across your environment.
// Find attack paths that target AI resources (Azure OpenAI, Azure ML).
// WHY: Attack paths to AI services are especially dangerous because a
// compromised AI endpoint can be used for prompt injection against
// downstream consumers, mass data exfiltration via API calls, or
// reputational damage through unsafe content generation.
// Target filter: matches paths ending at CognitiveServices (OpenAI/AI
// Services) or MachineLearningServices (Azure ML workspaces).
// OUTPUT: Table of attack paths with risk level, entry point, target
// resource, and suggested remediation steps. Prioritise Critical/High
// paths that start from internet-exposed resources.
SecurityAttackPath
| where Target has "CognitiveServices" or Target has "MachineLearningServices"
| project AttackPathDisplayName, RiskLevel, EntryPoint, Target, RemediationSteps
| order by RiskLevel descStream AI security alerts and recommendations into Microsoft Sentinel for cross-signal correlation with identity, endpoint, and network data.
// Sentinel analytics rule: correlate AI jailbreak/injection alerts
// with risky Entra ID sign-ins within a 2-hour window.
// WHY: A jailbreak attempt alone could be noise, and a risky sign-in
// alone might be a false positive. But BOTH occurring from the same IP
// within 2 hours is a strong indicator of a compromised identity
// actively attacking your AI services.
// ai_alerts: captures jailbreak and injection alerts from Defender for AI,
// extracting the caller IP from extended properties.
// risky_signins: captures medium/high risk sign-ins from Entra ID.
// The join matches on IP address, and the datetime_diff filter limits
// to events within ±2 hours of each other.
// OUTPUT: Correlated rows showing the alert, the user identity, their
// risk level, and the shared IP. These should generate Sentinel incidents
// for immediate SOC investigation.
let ai_alerts = SecurityAlert
| where ProductName == "Azure Defender for AI"
| where AlertName has "jailbreak" or AlertName has "injection"
| extend CallerIP = tostring(ExtendedProperties["CallerIpAddress"])
| project AlertTime=TimeGenerated, AlertName, CallerIP, ResourceId;
let risky_signins = SigninLogs
| where RiskLevelDuringSignIn in ("medium", "high")
| project SigninTime=TimeGenerated, UserPrincipalName, IPAddress, RiskLevelDuringSignIn;
ai_alerts
| join kind=inner risky_signins on $left.CallerIP == $right.IPAddress
| where abs(datetime_diff('hour', AlertTime, SigninTime)) < 2
| project AlertTime, AlertName, UserPrincipalName, CallerIP, RiskLevelDuringSignIn, ResourceIdMap your AI security posture to regulatory compliance frameworks including the EU AI Act, NIST AI RMF, and ISO 42001 to demonstrate governance readiness.
Set up continuous export to stream AI security data to Event Hubs for integration with third-party SIEM, SOAR, or ticketing systems.
# Create an Event Hub namespace for streaming AI security data.
# WHY: Event Hubs enable real-time integration with third-party SIEM,
# SOAR, or ticketing systems (e.g., Splunk, ServiceNow, PagerDuty)
# that cannot natively connect to Log Analytics.
# --sku Standard: supports up to 1 MB/s throughput per partition.
az eventhubs namespace create \
--name evhns-ai-security \
--resource-group rg-security \
--sku Standard \
--location eastus
# Create the Event Hub within the namespace.
# --partition-count 4: allows 4 concurrent consumers to read in parallel.
# Use more partitions for higher throughput requirements.
az eventhubs eventhub create \
--name evh-ai-alerts \
--namespace-name evhns-ai-security \
--resource-group rg-security \
--partition-count 4
# Configure Defender for Cloud continuous export to stream AI alerts
# to the Event Hub in real time.
# --sources filter: only exports alerts from "Azure Defender for AI"
# product, keeping the stream focused and reducing noise.
# --actions actionType "EventHub": sends matched alerts to the Event Hub.
# Replace <sub-id> with your subscription GUID.
# WHY: This enables near-real-time alerting in external tools without
# waiting for Log Analytics query schedules.
az security automation create \
--name auto-ai-export \
--resource-group rg-security \
--scopes "[{\"description\":\"AI subscriptions\",\"scopePath\":\"/subscriptions/<sub-id>\"}]" \
--sources "[{\"eventSource\":\"Alerts\",\"ruleSets\":[{\"rules\":[{\"propertyJPath\":\"ProductName\",\"propertyType\":\"String\",\"expectedValue\":\"Azure Defender for AI\",\"operator\":\"Contains\"}]}]}]" \
--actions "[{\"eventHubResourceId\":\"/subscriptions/<sub-id>/resourceGroups/rg-security/providers/Microsoft.EventHub/namespaces/evhns-ai-security/eventhubs/evh-ai-alerts\",\"actionType\":\"EventHub\"}]"Create Azure Monitor Workbooks that visualise the security posture of all AI workloads in a single dashboard.
// Tile 1: AI resource inventory grouped by type and Azure region.
// WHY: Gives a quick count of all AI assets under management -
// Azure OpenAI, ML workspaces, and AI Search services.
// If resources appear in unexpected regions, investigate whether they
// were provisioned through unauthorised channels (shadow AI).
// OUTPUT: Table of resource types, locations, and counts.
resources
| where type in~ ("microsoft.cognitiveservices/accounts",
"microsoft.machinelearningservices/workspaces",
"microsoft.search/searchservices")
| summarize Count=count() by type, location
| order by Count desc
// Tile 2: AI recommendation compliance status by recommendation name.
// WHY: Shows which security controls are passing vs. failing across
// all AI resources. Recommendations with high Unhealthy counts are
// your top remediation priorities.
// OUTPUT: Table with Healthy and Unhealthy counts per recommendation.
SecurityRecommendation
| where Properties has "CognitiveServices" or Properties has "MachineLearningServices"
| summarize Healthy=countif(RecommendationState=="Healthy"),
Unhealthy=countif(RecommendationState=="Unhealthy")
by RecommendationName
// Tile 3: 30-day trend of AI-specific threat alerts by severity.
// WHY: Reveals whether AI threats are increasing, decreasing, or stable.
// A rising trend in High-severity alerts demands immediate attention
// and may indicate an ongoing targeted campaign against your AI services.
// OUTPUT: Time series grouped by day and severity for line chart display.
SecurityAlert
| where ProductName == "Azure Defender for AI"
| summarize AlertCount=count() by bin(TimeGenerated, 1d), AlertSeverity
| order by TimeGenerated asc
// Tile 4: Top 10 callers by API request volume in the last 7 days.
// WHY: Identifies who is consuming the most AI capacity. Unexpected
// top callers (unknown IPs or service principals) may indicate
// credential theft or unauthorised model access.
// OUTPUT: Table of caller IPs ranked by call count.
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.COGNITIVESERVICES"
| where TimeGenerated > ago(7d)
| summarize Calls=count() by CallerIPAddress
| top 10 by CallsCreate an executive-level dashboard summarising AI security posture, risk trends, compliance status, and remediation progress for board and leadership reporting.
// Executive-level single-row summary of AI security posture.
// Combines recommendation health and threat alert data into one view.
// ai_recs: total AI recommendations, how many pass (Healthy) vs. fail.
// ai_alerts: total alerts in the last 30 days with High and Critical counts.
// The join on dummy=1 merges both single-row summaries into one row.
// HealthyPct: percentage of AI recommendations in a healthy state -
// this is your AI Secure Score. Target >80% for production readiness.
// WHY: Provides the CISO and board with a single snapshot of AI risk
// exposure without requiring them to navigate multiple dashboards.
// OUTPUT: Single row with TotalRecommendations, HealthyPct,
// AlertsLast30d, HighSevAlerts, and CriticalAlerts.
let ai_recs = SecurityRecommendation
| where Properties has "CognitiveServices" or Properties has "MachineLearningServices"
| summarize Total=count(),
Healthy=countif(RecommendationState=="Healthy"),
Unhealthy=countif(RecommendationState=="Unhealthy");
let ai_alerts = SecurityAlert
| where ProductName == "Azure Defender for AI"
| where TimeGenerated > ago(30d)
| summarize AlertsLast30d=count(),
HighSev=countif(AlertSeverity=="High"),
CritSev=countif(AlertSeverity=="Critical");
ai_recs | extend dummy=1
| join kind=inner (ai_alerts | extend dummy=1) on dummy
| project TotalRecommendations=Total, HealthyPct=round(Healthy*100.0/Total,1),
AlertsLast30d, HighSevAlerts=HighSev, CriticalAlerts=CritSev| Resource | Description |
|---|---|
| AI threat protection in Defender for Cloud | Enable and configure threat detection for Azure AI workloads |
| Azure OpenAI managed identity authentication | Replace API keys with Entra ID RBAC for Azure OpenAI |
| Configure virtual networks for Azure AI Services | Network isolation and private endpoint configuration |
| Attack path analysis reference | Identify and remediate critical attack paths to AI resources |
| Custom security initiatives | Create custom policy initiatives for AI governance |
| Governance rules in Defender for Cloud | Automate ownership and SLA assignment for security findings |