Skip to content

Commit 3397a31

Browse files
JosephRWaltergithub-actions[bot]DickTracyII
authored
Create new Entra baseline policy to block high risk Entra AI agents (#2154)
* Initial changes to support policy 9.1v1 Block high risk AI agents * Updated 9.1v1 policy name and CreateReportStubs file * Updated baseline and rego to handle Environment and License issues. Added some functional and unit tests * Added whitespace and fixed unit test * Removed trailing spaces * Enabled App Exclusions and added more unit tests * Updated CreateReportStubs file * Updated TestResults.json * Removed unnecessary import in unit tests * Auto-generate ScubaBaselines.json from markdown baselines This file was automatically generated by the GitHub Actions workflow. Workflow: generate_baseline_json.yaml Trigger: Baseline markdown file changes in PR #2154 Generated: 2026-05-14 18:46:38 UTC * Temporary functional test changes to test handling different tenant environments * Fixed linter issues * Fixes for functional test updates * Updated Rego logic and associated tests * Fixed functional test for g5 * Fixed identation and converted object from list to string * Updated the rego logic, corresponding tests, and comments to prioritize license issues * Report details adjustment and updates to support app exclusions * Updated rego to match util function name change * Updates to fix Regal Linter errors * Fix typo in rationale for AI agent policy Corrected the spelling of 'permissiona' to 'permissions' in the rationale section. --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Dick Tracy II <54296228+DickTracyII@users.noreply.github.com>
1 parent c881298 commit 3397a31

10 files changed

Lines changed: 451 additions & 1 deletion

File tree

PowerShell/ScubaGear/Rego/AADConfig.rego

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import data.utils.aad.CAPLINK
2222
import data.utils.aad.DomainReportDetails
2323
import data.utils.aad.INT_MAX
2424
import data.utils.key.Count
25+
import data.utils.aad.EnsureTrimmedArray
2526

2627

2728
#############
@@ -1605,3 +1606,84 @@ tests contains {
16051606
"RequirementMet": false
16061607
}
16071608
#--
1609+
1610+
############
1611+
# MS.AAD.9 #
1612+
############
1613+
1614+
#
1615+
# MS.AAD.9.1v1
1616+
#--
1617+
1618+
# If policy matches basic conditions, special conditions,
1619+
# & all exclusions are intentional, save the policy name
1620+
AIAgents contains CAPolicy.DisplayName if {
1621+
some CAPolicy in input.conditional_access_policies
1622+
1623+
### Common checks for conditional access policies
1624+
ContainsValue(CAPolicy.Conditions.Applications.IncludeApplications, "All") == true
1625+
CAPolicy.State == "enabled"
1626+
###
1627+
1628+
### Conditional access checks specific to this policy
1629+
"all" in CAPolicy.Conditions.ClientAppTypes
1630+
# CAPolicy.Conditions.AgentIdRiskLevels is a string, which can contain multiple values
1631+
# The helper function EnsureTrimmedArray turns the string into a comma delimited list
1632+
# with leading and trailing spaces removed
1633+
"high" in EnsureTrimmedArray(CAPolicy.Conditions.AgentIdRiskLevels)
1634+
"block" in CAPolicy.GrantControls.BuiltInControls
1635+
"All" in CAPolicy.Conditions.ClientApplications.IncludeAgentIdServicePrincipals
1636+
###
1637+
1638+
# Only match policies with user and group exclusions per the confile file
1639+
AppExclusionsFullyExempt(CAPolicy, "MS.AAD.9.1v1") == true
1640+
}
1641+
1642+
default AAD_9_1_Not_Applicable_Due_To_Environment := false
1643+
1644+
# Returns true if the M365 Environment used by the tenant does not support AI Agents
1645+
AAD_9_1_Not_Applicable_Due_To_Environment := true if {
1646+
# Check for an environment the feature is not available in
1647+
input.scuba_config.M365Environment in {"gcchigh", "dod"}
1648+
# Only N/A if no valid policies are found, future proofing in case the feature becomes available
1649+
Count(AIAgents) == 0
1650+
# Only N/A if the tenant does have the required license, otherwise results in a license error
1651+
Count(Aad2P2Licenses) > 0
1652+
}
1653+
1654+
# First test is for N/A case
1655+
tests contains {
1656+
"PolicyId": PolicyId,
1657+
"Criticality": "Shall/Not-Implemented",
1658+
"Commandlet": ["Get-MgBetaIdentityConditionalAccessPolicy"],
1659+
"ActualValue": [],
1660+
"ReportDetails": CheckedSkippedDetails(PolicyId, Reason),
1661+
"RequirementMet": false
1662+
} if {
1663+
PolicyId := "MS.AAD.9.1v1"
1664+
Reason := concat(" ", [
1665+
"This policy is not applicable to GCC High or DOD environments because the feature",
1666+
"is not yet available. See %v for more info"
1667+
])
1668+
AAD_9_1_Not_Applicable_Due_To_Environment == true
1669+
}
1670+
1671+
# Pass if at least 1 policy meets all conditions & has correct
1672+
# license.
1673+
tests contains {
1674+
"PolicyId": "MS.AAD.9.1v1",
1675+
"Criticality": "Shall",
1676+
"Commandlet": ["Get-MgBetaIdentityConditionalAccessPolicy"],
1677+
"ActualValue": AIAgents,
1678+
"ReportDetails": ReportDetailsArrayLicenseWarningCap(AIAgents, DescriptionString),
1679+
"RequirementMet": Status
1680+
} if {
1681+
DescriptionString := "conditional access policy(s) found that meet(s) all requirements"
1682+
AAD_9_1_Not_Applicable_Due_To_Environment == false
1683+
Conditions := [
1684+
Count(Aad2P2Licenses) > 0,
1685+
Count(AIAgents) > 0
1686+
]
1687+
Status := Count(FilterArray(Conditions, false)) == 0
1688+
}
1689+
#--

PowerShell/ScubaGear/Rego/Utils/AAD.rego

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,22 @@ IsGeneralMFA(Policy) := true if {
233233
Count(Strengths - PRMFA) > 0
234234
} else := false
235235

236+
# Returns a json object as a list. This is used to future proof against changes microsoft
237+
# may make to the input where a field may change from a string to an array of strings.
238+
# The function ensures that the output is always an array of strings, whether the input
239+
# is null, a string, or an array of strings.
240+
# Case 1: Input is null -> output is empty array
241+
# Case 2: Input is an array -> output is the same array
242+
# Case 3: Input is a string -> output is an array of trimmed items split by commas
243+
EnsureTrimmedArray(JsonObject) := [] if {
244+
JsonObject == null
245+
} else := JsonObject if {
246+
is_array(JsonObject)
247+
} else := arr if {
248+
is_string(JsonObject)
249+
parts := split(JsonObject, ",")
250+
arr = [trim(y, " ") | some y in parts]
251+
}
236252

237253
############################################################################
238254
# Report formatting functions for MS.AAD.6.1v1 #

PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/CreateReportStubs/TestResults.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,18 @@
464464
"ReportDetails": "1 conditional access policy(s) found that meet(s) all requirements:\u003cbr/\u003eLive - MFA SHALL be required for Highly Privileged Roles. \u003ca href=\u0027#caps\u0027\u003eView all CA policies\u003c/a\u003e.",
465465
"RequirementMet": true
466466
},
467+
{
468+
"ActualValue": [
469+
470+
],
471+
"Commandlet": [
472+
"Get-MgBetaIdentityConditionalAccessPolicy"
473+
],
474+
"Criticality": "Shall",
475+
"PolicyId": "MS.AAD.9.1v1",
476+
"ReportDetails": "0 conditional access policy(s) found that meet(s) all requirements. \u003ca href=\u0027#caps\u0027\u003eView all CA policies\u003c/a\u003e.",
477+
"RequirementMet": false
478+
},
467479
{
468480
"ActualValue": true,
469481
"Commandlet": [

PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/CreateReportStubs/aad.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,47 @@ Guest invites SHOULD only be allowed to specific external domains that have been
799799
800800
4. Click **Save**.
801801
802+
## 9. AI Security
803+
804+
This section provides policies that help reduce security risks related to the usage of AI Agents and automated services that interact with tenant resources.
805+
806+
### Policies
807+
#### MS.AAD.9.1v1
808+
Risky AI agents SHALL be blocked.
809+
810+
[![Automated Check](https://img.shields.io/badge/Automated_Check-5E9732)](#key-terminology)
811+
812+
<!--Policy: MS.AAD.9.1v1; Criticality: SHALL -->
813+
- _Rationale:_ AI agents may access tenant resources using application permissions and can perform actions autonomously. Blocking high-risk AI agents reduces the risk of unauthorized access and automated misuse of resources.
814+
- _Last modified:_ March 2026
815+
- _Note:_ This policy is not applicable to Government Community Cloud (GCC) High, and Department of Defense (DoD) tenants.
816+
- _NIST SP 800-53 Rev. 5 FedRAMP High Baseline Mapping:_ AC-3, AC-6, CM-7(4)
817+
- _MITRE ATT&CK TTP Mapping:_
818+
- [T1078: Valid Accounts](https://attack.mitre.org/techniques/T1078/)
819+
- [T1078.004: Cloud Account](https://attack.mitre.org/techniques/T1078/004/)
820+
821+
### Resources
822+
823+
- [Block access by high-risk agent identities](https://learn.microsoft.com/en-us/entra/identity/conditional-access/policy-agent-block-high-risk)
824+
825+
### License Requirements
826+
827+
- Requires a Microsoft Entra ID P2 license
828+
829+
### Implementation
830+
831+
#### MS.AAD.9.1v1 Instructions
832+
833+
1. Create a conditional access policy blocking High Risk AI Agents. Configure the following policy settings in the new conditional access policy, per the values below:
834+
835+
<pre>
836+
Assignments > Users, agents (Preview) or workload identities > What does this policy apply to? > Agents (Preview) > Include > <b>All agent identities (Preview) </b>
837+
838+
Target resources > Include > <b>All resources (formerly 'All cloud apps') </b>
839+
840+
Conditions > Agent risk (Preview) > Configure > Yes > Configure agent risk levels needed for policy to be enforced > <b>High </b>
841+
</pre>
842+
802843
803844
# Acknowledgements
804845

0 commit comments

Comments
 (0)