Skip to content

Commit 88f06e9

Browse files
authored
Network - 25415 - Enterprise generative AI applications are protected from prompt injection attacks through AI Gateway (#835)
* Feature-25415 * Updated code * Updated code * Fixed URL's
1 parent 89775ac commit 88f06e9

2 files changed

Lines changed: 247 additions & 0 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Prompt Shield in AI Gateway protects enterprise generative AI applications from prompt injection attacks. When organizations allow users to interact with AI applications without this protection, threat actors can exploit prompt injection vulnerabilities through direct attacks (malicious user inputs) or indirect attacks (malicious instructions embedded in external content). Without network-level inspection, these malicious prompts reach the LLM unfiltered, potentially bypassing application-layer safety mechanisms and creating security blind spots.
2+
3+
**Remediation action**
4+
5+
Follow these steps to configure Prompt Shield protection:
6+
7+
1. [Enable the Internet Access traffic forwarding profile](https://learn.microsoft.com/en-us/entra/global-secure-access/how-to-manage-internet-access-profile) to route internet traffic through Global Secure Access
8+
2. [Configure TLS inspection settings](https://learn.microsoft.com/en-us/entra/global-secure-access/how-to-transport-layer-security-settings) and deploy the CA certificate to allow inspection of encrypted AI application traffic
9+
3. [Create prompt policies](https://learn.microsoft.com/en-us/entra/global-secure-access/how-to-ai-prompt-shield#create-a-new-prompt-policy-to-scan-prompts) to scan and block malicious prompts targeting generative AI applications
10+
4. [Link prompt policies to security profiles](https://learn.microsoft.com/en-us/entra/global-secure-access/how-to-ai-prompt-shield#link-the-prompt-policy-to-your-security-profile) to organize them for Conditional Access targeting
11+
5. [Create Conditional Access policies](https://learn.microsoft.com/en-us/entra/global-secure-access/how-to-ai-prompt-shield#create-a-conditional-access-policy) to apply security profiles with prompt policies to users accessing internet resources
12+
6. [Install the Global Secure Access client](https://learn.microsoft.com/en-us/entra/global-secure-access/how-to-install-windows-client) on user devices to enable traffic acquisition
13+
14+
<!--- Results --->
15+
%TestResult%
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
<#
2+
.SYNOPSIS
3+
Enterprise generative AI applications are protected from prompt injection attacks through AI Gateway.
4+
.DESCRIPTION
5+
Verifies that Prompt Shield (AI Gateway) is properly configured to protect against prompt injection attacks.
6+
The test passes if prompt policies exist and are enforced either through the Baseline Profile or through
7+
Security Profiles assigned to Conditional Access policies.
8+
#>
9+
10+
function Test-Assessment-25415 {
11+
[ZtTest(
12+
Category = 'Global Secure Access',
13+
ImplementationCost = 'Medium',
14+
MinimumLicense = ('Entra_Premium_Internet_Access'),
15+
Pillar = 'Network',
16+
RiskLevel = 'High',
17+
SfiPillar = 'Protect networks',
18+
TenantType = ('Workforce'),
19+
TestId = 25415,
20+
Title = 'Enterprise generative AI applications are protected from prompt injection attacks through AI Gateway',
21+
UserImpact = 'Low'
22+
)]
23+
[CmdletBinding()]
24+
param()
25+
26+
# Define constants
27+
[int]$BASELINE_PROFILE_PRIORITY = 65000
28+
29+
#region Data Collection
30+
Write-PSFMessage '🟦 Start Prompt Shield evaluation' -Tag Test -Level VeryVerbose
31+
32+
$activity = 'Checking Prompt Shield configuration for AI Gateway protection'
33+
Write-ZtProgress -Activity $activity -Status 'Querying prompt policies'
34+
35+
# Q1: Get prompt policies
36+
$promptPolicies = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/promptPolicies' -QueryParameters @{
37+
'$expand' = 'policyRules'
38+
} -ApiVersion beta
39+
40+
# Q2: Get filtering profiles with linked policies and Conditional Access policies
41+
Write-ZtProgress -Activity $activity -Status 'Querying security profiles and linked policies'
42+
$filteringProfiles = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/filteringProfiles' -QueryParameters @{
43+
'$expand' = 'policies($expand=policy),conditionalAccessPolicies'
44+
} -ApiVersion beta
45+
46+
# Get all Conditional Access policies
47+
Write-ZtProgress -Activity $activity -Status 'Querying Conditional Access policies'
48+
$allCAPolicies = Get-ZtConditionalAccessPolicy
49+
50+
#endregion Data Collection
51+
52+
#region Assessment Logic
53+
$enabledSecurityProfiles = @()
54+
$enabledBaselineProfiles = @()
55+
$allPromptPolicyIds = @()
56+
57+
# Collect all prompt policy IDs from Q1
58+
if ($promptPolicies) {
59+
$allPromptPolicyIds = $promptPolicies | ForEach-Object { $_.id }
60+
}
61+
62+
# Find profiles linked to each prompt policy
63+
foreach ($promptPolicy in $promptPolicies) {
64+
$findParams = @{
65+
PolicyId = $promptPolicy.id
66+
FilteringProfiles = $filteringProfiles
67+
CAPolicies = $allCAPolicies
68+
BaselinePriority = $BASELINE_PROFILE_PRIORITY
69+
PolicyLinkType = 'promptPolicyLink'
70+
PolicyRules = $promptPolicy.policyRules
71+
}
72+
73+
$linkedProfiles = Find-ZtProfilesLinkedToPolicy @findParams
74+
75+
foreach ($profileLink in $linkedProfiles) {
76+
if ($profileLink.ProfileType -eq 'Baseline Profile' -and $profileLink.PassesCriteria -and $profileLink.ProfileState -eq 'enabled') {
77+
$enabledBaselineProfiles += [PSCustomObject]@{
78+
ProfileId = $profileLink.ProfileId
79+
ProfileName = $profileLink.ProfileName
80+
ProfileState = $profileLink.ProfileState
81+
ProfilePriority = $profileLink.ProfilePriority
82+
PromptPolicyId = $promptPolicy.id
83+
PromptPolicyName = $promptPolicy.name
84+
PromptPolicyAction = $promptPolicy.action
85+
RulesCount = if ($promptPolicy.policyRules) { @($promptPolicy.policyRules).Count } else { 0 }
86+
LastModified = $promptPolicy.lastModifiedDateTime
87+
PromptPolicyLinkState = $profileLink.PolicyLinkState
88+
}
89+
}
90+
elseif ($profileLink.ProfileType -eq 'Security Profile' -and $profileLink.PassesCriteria -and $profileLink.ProfileState -eq 'enabled') {
91+
$matchedCAPolicies = @()
92+
if ($null -ne $profileLink.CAPolicy) {
93+
$matchedCAPolicies = @($profileLink.CAPolicy)
94+
}
95+
96+
$enabledSecurityProfiles += [PSCustomObject]@{
97+
ProfileId = $profileLink.ProfileId
98+
ProfileName = $profileLink.ProfileName
99+
ProfileState = $profileLink.ProfileState
100+
ProfilePriority = $profileLink.ProfilePriority
101+
PromptPolicyId = $promptPolicy.id
102+
PromptPolicyName = $promptPolicy.name
103+
PromptPolicyAction = $promptPolicy.action
104+
RulesCount = if ($promptPolicy.policyRules) { @($promptPolicy.policyRules).Count } else { 0 }
105+
LastModified = $promptPolicy.lastModifiedDateTime
106+
PromptPolicyLinkState = $profileLink.PolicyLinkState
107+
MatchedCAPolicies = $matchedCAPolicies
108+
CAPolicyCount = $matchedCAPolicies.Count
109+
}
110+
}
111+
}
112+
}
113+
$testResultMarkdown = ''
114+
$passed = $false
115+
$mdInfo = ''
116+
117+
# Evaluation logic per spec
118+
if ($null -eq $promptPolicies -or $promptPolicies.Count -eq 0) {
119+
# No prompt policies configured
120+
$testResultMarkdown = "❌ Prompt Shield is not properly configured - no prompt policies exist.`n`n%TestResult%"
121+
$passed = $false
122+
}
123+
elseif ($enabledBaselineProfiles.Count -gt 0) {
124+
# Condition B: Baseline Profile has prompt policies (applies to all traffic)
125+
$testResultMarkdown = "✅ Prompt Shield policies are configured and enforced through the Baseline Profile which applies to all internet traffic.`n`n%TestResult%"
126+
$passed = $true
127+
}
128+
elseif ($enabledSecurityProfiles.Count -gt 0) {
129+
# Condition A: Security profiles with prompt policies AND CA policy assignment
130+
$testResultMarkdown = "✅ Prompt Shield policies are configured and enforced through security profiles assigned to Conditional Access policies.`n`n%TestResult%"
131+
$passed = $true
132+
}
133+
else {
134+
# Prompt policies exist but are not enforced
135+
$testResultMarkdown = "❌ Prompt Shield is not properly configured - policies are not linked to security profiles, or security profiles with prompt policies are not enforced (no CA policy assignment and not using Baseline Profile).`n`n%TestResult%"
136+
$passed = $false
137+
}
138+
139+
#endregion Assessment Logic
140+
141+
#region Report Generation
142+
143+
if ($passed) {
144+
# Build detailed report only when test passes
145+
$formatTemplate = @'
146+
147+
## Prompt Policies (AI Gateway)
148+
149+
| Policy Name | Action | Rules Count | Last Modified |
150+
| :---------- | :----- | :---------- | :------------ |
151+
{0}
152+
{1}
153+
{2}
154+
'@
155+
156+
# Table 1: Prompt Policies
157+
$promptPoliciesRows = ''
158+
if ($promptPolicies -and $promptPolicies.Count -gt 0) {
159+
foreach ($policy in $promptPolicies) {
160+
$policyPortalLink = "https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/EditPromptPolicyMenuBlade.MenuView/~/basics/policyId/$($policy.id)"
161+
$policyName = Get-SafeMarkdown -Text $policy.name
162+
$action = if ($policy.action) { $policy.action } else { 'Not specified' }
163+
$rulesCount = if ($policy.policyRules) { @($policy.policyRules).Count } else { 0 }
164+
$lastModified = if ($policy.lastModifiedDateTime) { $policy.lastModifiedDateTime } else { 'N/A' }
165+
$promptPoliciesRows += "| [$policyName]($policyPortalLink) | $action | $rulesCount | $lastModified |`n"
166+
}
167+
}
168+
169+
# Table 2: Baseline Profiles with Prompt Policies
170+
$baselineProfilesSection = ''
171+
if ($enabledBaselineProfiles.Count -gt 0) {
172+
$baselineProfilesSection += "`n## Prompt Policies Linked to Baseline Profile`n`n"
173+
$baselineProfilesSection += "| Profile Name | Priority | State | Prompt Policy | Policy Link State | Rules Count |`n"
174+
$baselineProfilesSection += "| :----------- | :------- | :---- | :------------ | :---------------- | :---------- |`n"
175+
foreach ($profile in $enabledBaselineProfiles) {
176+
$profilePortalLink = "https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/EditProfileMenuBlade.MenuView/~/basics/profileId/$($profile.ProfileId)"
177+
$policyPortalLink = "https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/EditPromptPolicyMenuBlade.MenuView/~/basics/policyId/$($profile.PromptPolicyId)"
178+
$profileName = Get-SafeMarkdown -Text $profile.ProfileName
179+
$policyName = Get-SafeMarkdown -Text $profile.PromptPolicyName
180+
$baselineProfilesSection += "| [$profileName]($profilePortalLink) | $($profile.ProfilePriority) | $($profile.ProfileState) | [$policyName]($policyPortalLink) | $($profile.PromptPolicyLinkState) | $($profile.RulesCount) |`n"
181+
}
182+
}
183+
184+
# Table 3: Security Profiles with Prompt Policies and CA Assignments
185+
$securityProfilesSection = ''
186+
if ($enabledSecurityProfiles.Count -gt 0) {
187+
$securityProfilesSection += "`n## Security Profiles with Linked Policies`n`n"
188+
$securityProfilesSection += "| Profile Name | State | Priority | Prompt Policy | CA Policies Assigned | Is Baseline |`n"
189+
$securityProfilesSection += "| :----------- | :---- | :------- | :------------ | :------------------- | :---------- |`n"
190+
foreach ($profile in $enabledSecurityProfiles) {
191+
$profilePortalLink = "https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/EditProfileMenuBlade.MenuView/~/basics/profileId/$($profile.ProfileId)"
192+
$policyPortalLink = "https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/EditPromptPolicyMenuBlade.MenuView/~/basics/policyId/$($profile.PromptPolicyId)"
193+
$profileName = Get-SafeMarkdown -Text $profile.ProfileName
194+
$policyName = Get-SafeMarkdown -Text $profile.PromptPolicyName
195+
$isBaseline = if ($profile.ProfilePriority -eq $BASELINE_PROFILE_PRIORITY) { 'Yes' } else { 'No' }
196+
$caCount = $profile.CAPolicyCount
197+
$securityProfilesSection += "| [$profileName]($profilePortalLink) | $($profile.ProfileState) | $($profile.ProfilePriority) | [$policyName]($policyPortalLink) | $caCount | $isBaseline |`n"
198+
}
199+
200+
# Table 4: Conditional Access Policies
201+
$securityProfilesSection += "`n## Conditional Access Policies Assigned to Security Profiles`n`n"
202+
$securityProfilesSection += "| CA Policy Name | Security Profile | CA Policy ID |`n"
203+
$securityProfilesSection += "| :------------- | :--------------- | :----------- |`n"
204+
foreach ($profile in $enabledSecurityProfiles) {
205+
foreach ($caPolicy in $profile.MatchedCAPolicies) {
206+
$caPolicyPortalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/$($caPolicy.Id)"
207+
$profileName = Get-SafeMarkdown -Text $profile.ProfileName
208+
$caPolicyName = Get-SafeMarkdown -Text $caPolicy.DisplayName
209+
$securityProfilesSection += "| [$caPolicyName]($caPolicyPortalLink) | $profileName | $($caPolicy.Id) |`n"
210+
}
211+
}
212+
}
213+
214+
$mdInfo = $formatTemplate -f $promptPoliciesRows, $baselineProfilesSection, $securityProfilesSection
215+
}
216+
else {
217+
# For failed states, just show the portal link
218+
$mdInfo = "[View Prompt Shield Configuration](https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/PromptPolicy.ReactView)`n"
219+
}
220+
221+
$testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
222+
223+
#endregion Report Generation
224+
225+
$params = @{
226+
TestId = '25415'
227+
Status = $passed
228+
Result = $testResultMarkdown
229+
}
230+
231+
Add-ZtTestResultDetail @params
232+
}

0 commit comments

Comments
 (0)