Existing check search
Provider
Microsoft 365
New provider name
No response
Service or product area
exchange
Suggested check name
exchange_application_access_policy_restricts_mailbox_apps
Context and goal
- Security condition to validate: Every service principal in the tenant that holds any application-level Exchange mailbox permission (
Mail.Read, Mail.ReadBasic, Mail.ReadBasic.All, Mail.ReadWrite, Mail.Send, MailboxSettings.Read, MailboxSettings.ReadWrite, Calendars.Read, Calendars.ReadWrite, Contacts.Read, Contacts.ReadWrite) must be scoped by an Exchange Online Application Access Policy that lists the same AppId.
- Why it matters: Microsoft Graph application permissions like
Mail.Read grant tenant-wide mailbox access by default — the consenting app can read every mailbox in the organization. Application Access Policies (New-ApplicationAccessPolicy) restrict that access to a defined mail-enabled security group, which is the only supported way to apply least-privilege scoping to those permissions. A service principal with Mail.Read and no policy is, by definition, able to read every user's email until restricted.
- Resource involved: Microsoft Entra service principals and their
appRoleAssignments against the Microsoft Graph service principal (appId 00000003-0000-0000-c000-000000000000), plus the Exchange Online Application Access Policy collection.
Expected behavior
- Resource or scope to evaluate:
- Resolve the Microsoft Graph service principal (
appId 00000003-0000-0000-c000-000000000000) to obtain the role IDs of the Exchange-relevant application roles listed above.
- Enumerate every service principal in the tenant and read its
appRoleAssignments against the Microsoft Graph service principal. Collect the AppId of any service principal that has at least one of those role assignments.
- Run Exchange Online PowerShell
Get-ApplicationAccessPolicy and collect the AppId field of every returned policy.
- Compare: every
AppId from step 2 must appear in step 3.
- PASS when: every service principal with at least one Exchange application permission has a matching
ApplicationAccessPolicy.AppId.
- FAIL when: at least one such service principal has no matching policy. The finding should report the offending service principal's
displayName, appId, and the list of Exchange permissions it holds, so operators know exactly which app to scope.
- MANUAL when: Exchange Online PowerShell is unavailable for the run (the operator has not configured PowerShell credentials) — the check cannot complete and should report MANUAL with a status message asking to enable EXO PowerShell.
- Exclusions / edge cases:
- First-party Microsoft service principals (e.g. Microsoft-owned apps) should not be flagged. Filter by
servicePrincipalType = 'Application' and exclude principals owned by Microsoft tenants if their appOwnerOrganizationId matches Microsoft's first-party tenant IDs.
- Disabled service principals (
accountEnabled = false) are out of scope.
- The check operates only on application permissions (
Application role type), not delegated permissions (delegated access is constrained by the signed-in user).
References
Suggested severity
Medium
Additional implementation notes
- Existing patterns to follow: Prowler's
exchange service already wires Exchange Online PowerShell (prowler/providers/m365/services/exchange/exchange_service.py) for sibling checks like exchange_organization_modern_authentication_enabled. Reuse the same PowerShell session to invoke Get-ApplicationAccessPolicy. For the Graph side, the entra service already enumerates service principals and app role assignments — extend it (or share via the M365 provider) so the new check has both data sets available.
- Permissions / scopes: No additional permissions beyond Prowler's M365 baseline (
Directory.Read.All covers reading service principals and their app role assignments) plus the Exchange Online PowerShell access the exchange service already requires.
- PowerShell IS required for the
Get-ApplicationAccessPolicy half of the check. Microsoft Graph does not expose Exchange Application Access Policies; this is intentional from Microsoft and the only supported way to read them is the Exchange Online module.
- Metadata: follow the Exchange metadata schema used by sibling checks under
exchange/. Mark ServiceName as exchange and place the check directory under prowler/providers/m365/services/exchange/.
Existing check search
Provider
Microsoft 365
New provider name
No response
Service or product area
exchange
Suggested check name
exchange_application_access_policy_restricts_mailbox_appsContext and goal
Mail.Read,Mail.ReadBasic,Mail.ReadBasic.All,Mail.ReadWrite,Mail.Send,MailboxSettings.Read,MailboxSettings.ReadWrite,Calendars.Read,Calendars.ReadWrite,Contacts.Read,Contacts.ReadWrite) must be scoped by an Exchange Online Application Access Policy that lists the sameAppId.Mail.Readgrant tenant-wide mailbox access by default — the consenting app can read every mailbox in the organization. Application Access Policies (New-ApplicationAccessPolicy) restrict that access to a defined mail-enabled security group, which is the only supported way to apply least-privilege scoping to those permissions. A service principal withMail.Readand no policy is, by definition, able to read every user's email until restricted.appRoleAssignmentsagainst the Microsoft Graph service principal (appId 00000003-0000-0000-c000-000000000000), plus the Exchange Online Application Access Policy collection.Expected behavior
appId 00000003-0000-0000-c000-000000000000) to obtain the role IDs of the Exchange-relevant application roles listed above.appRoleAssignmentsagainst the Microsoft Graph service principal. Collect theAppIdof any service principal that has at least one of those role assignments.Get-ApplicationAccessPolicyand collect theAppIdfield of every returned policy.AppIdfrom step 2 must appear in step 3.ApplicationAccessPolicy.AppId.displayName,appId, and the list of Exchange permissions it holds, so operators know exactly which app to scope.servicePrincipalType = 'Application'and exclude principals owned by Microsoft tenants if theirappOwnerOrganizationIdmatches Microsoft's first-party tenant IDs.accountEnabled = false) are out of scope.Applicationrole type), not delegated permissions (delegated access is constrained by the signed-in user).References
New-ApplicationAccessPolicy: https://learn.microsoft.com/en-us/powershell/module/exchange/new-applicationaccesspolicyGet-ApplicationAccessPolicy: https://learn.microsoft.com/en-us/powershell/module/exchange/get-applicationaccesspolicyservicePrincipalresource: https://learn.microsoft.com/en-us/graph/api/resources/serviceprincipal?view=graph-rest-1.0appRoleAssignmentsfor a service principal: https://learn.microsoft.com/en-us/graph/api/serviceprincipal-list-approleassignments?view=graph-rest-1.0Suggested severity
Medium
Additional implementation notes
exchangeservice already wires Exchange Online PowerShell (prowler/providers/m365/services/exchange/exchange_service.py) for sibling checks likeexchange_organization_modern_authentication_enabled. Reuse the same PowerShell session to invokeGet-ApplicationAccessPolicy. For the Graph side, theentraservice already enumerates service principals and app role assignments — extend it (or share via the M365 provider) so the new check has both data sets available.Directory.Read.Allcovers reading service principals and their app role assignments) plus the Exchange Online PowerShell access theexchangeservice already requires.Get-ApplicationAccessPolicyhalf of the check. Microsoft Graph does not expose Exchange Application Access Policies; this is intentional from Microsoft and the only supported way to read them is the Exchange Online module.exchange/. MarkServiceNameasexchangeand place the check directory underprowler/providers/m365/services/exchange/.