prowler-cloud/prowler
View on GitHub[New Check]: Conditional Access excluded objects must be covered by another policy (no exclusion gaps)
Open
#11062 opened on May 6, 2026
feature-requestgood first issuenew-checkprovider/m365
Description
Existing check search
- I have searched existing issues, Prowler Hub, and the public roadmap, and this check does not already exist.
Provider
Microsoft 365
New provider name
No response
Service or product area
entra
Suggested check name
entra_conditional_access_policy_no_exclusion_gaps
Context and goal
- Security condition to validate: For every enabled Conditional Access policy, every object listed in any
exclude*collection (excludeUsers,excludeGroups,excludeRoles,excludeApplications,excludeServicePrincipals,excludeLocations,excludePlatforms) must appear in at least oneinclude*collection of some enabled Conditional Access policy. Excluded objects that never appear as included anywhere are "gaps" — they sit completely outside the CA control plane. - Why it matters: Excluding a principal from a policy is a common and legitimate operation (break-glass accounts, the Directory Synchronization Accounts role, certain service principals, etc.). It only stays safe when the excluded principal is also covered by another CA policy that enforces compensating controls. Otherwise the exclusion silently removes the principal from CA enforcement entirely, which is exactly how MFA bypass and lateral movement against admin accounts have happened in real incidents.
- Resource involved: Microsoft Entra Conditional Access policies and their inclusion/exclusion collections (users, groups, roles, applications, service principals, locations, platforms).
Expected behavior
- Resource or scope to evaluate: All Conditional Access policies whose
stateisenabled. Build a global "include set" by union-ing everyinclude*collection across all enabled policies (per object type). For each enabled policy, walk everyexclude*collection and compare each entry against the include set of the same type. - PASS when: every excluded object identifier exists in the global include set for its corresponding object type. Also PASS when no enabled policy exists or no policy uses any exclusion.
- FAIL when: at least one excluded object identifier does not appear in the global include set of its type. The finding should report the orphaned object IDs grouped by type (users / groups / roles / apps / SPs / locations / platforms) and the policies that excluded them.
- MANUAL when: not applicable.
- Exclusions / edge cases:
- Skip the Directory Synchronization Accounts role exclusion (template ID
d29b2b05-8046-44ba-8758-1e26182fcf32) — Prowler already enforces this exclusion inentra_conditional_access_policy_directory_sync_account_excluded, and it is intended to have no fallback. - Skip exclusions that resolve to confirmed emergency-access accounts (the same accounts validated by
entra_emergency_access_exclusion); they are intentional gaps. - Treat report-only policies as out of scope: only
state = enabledpolicies count for both the exclusion side and the include set, mirroring real enforcement. - Group identifiers should be matched recursively when feasible (i.e. an exclusion of group A is "covered" if A is included; transitive group expansion is out of scope for v1).
- Skip the Directory Synchronization Accounts role exclusion (template ID
References
- Microsoft Graph v1.0 — Conditional Access policy resource: https://learn.microsoft.com/en-us/graph/api/resources/conditionalaccesspolicy?view=graph-rest-1.0
- Microsoft Graph v1.0 —
conditionalAccessUsers(include/exclude users, groups, roles): https://learn.microsoft.com/en-us/graph/api/resources/conditionalaccessusers?view=graph-rest-1.0 - Microsoft Graph v1.0 —
conditionalAccessApplications(include/exclude applications): https://learn.microsoft.com/en-us/graph/api/resources/conditionalaccessapplications?view=graph-rest-1.0 - Plan a Conditional Access deployment (gap analysis guidance): https://learn.microsoft.com/en-us/entra/identity/conditional-access/plan-conditional-access
- Secure emergency access accounts in Microsoft Entra ID: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/security-emergency-access
Suggested severity
Medium
Additional implementation notes
- Existing patterns to follow: Reuse the Conditional Access iteration pattern from
entra_conditional_access_policy_mfa_enforced_for_guest_usersandentra_emergency_access_exclusion(prowler/providers/m365/services/entra/). - Service change (
entra_service.py): theConditionalAccessPolicymodel already exposesincludeUsers/Groups/RolesandexcludeUsers/Groups/Roles; verify and add (if missing)includeApplications/excludeApplications,includeServicePrincipals/excludeServicePrincipals(underclientApplications),includeLocations/excludeLocations,includePlatforms/excludePlatforms. No new Graph endpoint needed; the policy listing already returns these collections. - Permissions / scopes: No additional permissions beyond Prowler's M365 baseline (
Policy.Read.Allalready grants read access to all Conditional Access policies). - PowerShell is NOT needed; this check uses the existing Microsoft Graph v1.0 data already loaded by the entra service.
- Related checks (do not duplicate):
entra_conditional_access_policy_directory_sync_account_excluded— wants the dir-sync role excluded with no fallback. Add it as a hard skip in this check.entra_emergency_access_exclusion— wants emergency accounts excluded. Treat them as expected gaps.
- Metadata: follow the M365 metadata schema used by sibling checks under
entra/.