ReferenceCheck catalog
Microsoft Entra ID
All 142 Microsoft Entra ID checks - what each one checks, why, and the access used to evaluate it.
142 checks. Column "Access used" lists the application permissions and collected data sources this check's evaluation reads; "None" means Watchtower reads no tenant data for it.
| Check | Severity | What it checks | Why it matters | Access used |
|---|---|---|---|---|
Administrative accounts are cloud-onlywt.entra.admin-accounts-cloud-only | CRITICAL | Checks that accounts holding privileged roles are not synchronised from on-premises Active Directory, ensuring administrative identities are managed exclusively in Entra ID. | Hybrid-synced admin accounts are exposed to on-premises attack paths. A single compromised domain controller or pass-the-hash attack against an on-prem credential can escalate directly to cloud Global Administrator, enabling full tenant takeover and data exfiltration. | RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory, User.Read.All - via privilegedUsers |
Access to the Entra admin center is restrictedwt.entra.admin-center-access-restricted | MEDIUM | Checks that non-administrator users are prevented from accessing the Entra admin centre and browsing directory data through that interface. | Unrestricted access to the Entra admin portal allows any user to enumerate users, groups, applications, and service principals - providing attackers with reconnaissance data useful for targeted phishing, privilege escalation, and lateral movement. | None - no tenant data read (not automatable via API, reported as a manual/indeterminate result) |
Administrative accounts use licenses with a reduced application footprintwt.entra.admin-reduced-footprint-licenses | MEDIUM | Verifies that privileged accounts are assigned only licenses with a minimal application footprint (such as Entra ID P2 or Microsoft 365 F1), rather than full Microsoft 365 E3/E5 licenses that include productivity apps. | Full productivity licenses on admin accounts increase the attack surface: a compromised admin credential can be used to access email, SharePoint, Teams, and other data stores, enabling large-scale data exfiltration in addition to privilege abuse. Reducing the license footprint limits the blast radius of an account compromise. | RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory, User.Read.All - via privilegedUsers |
Admin consent workflow is enabledwt.entra.app.admin-consent-workflow-enabled | HIGH | Verifies that the admin consent workflow is enabled so users can request administrator approval for OAuth app permissions rather than consenting directly, with reviewer notifications and reminders active. | Without a consent workflow, users who cannot grant consent may turn to social engineering or shadow IT to access apps. The workflow ensures all OAuth permission requests are reviewed, preventing attackers from abusing OAuth phishing - a technique that grants persistent access to mailboxes and data without requiring a password. | Policy.Read.All - via adminConsentRequestPolicy |
Application certificates expire within 180 dayswt.entra.app.cert-expiry-bounded | LOW | Checks that no app registration or service principal holds a certificate credential valid for more than 180 more days (per Microsoft Zero Trust Assessment v2.4.0). Complements wt.entra.app.cert-rotation, which flags certificates issued more than 180 days ago; this check bounds remaining validity so rotation policy has teeth. | A certificate that stays valid for years gives a stolen private key a years-long useful life, regardless of how diligently new certificates are issued. Bounding expiry to a rotation-sized window means a leaked key ages out on a schedule instead of waiting to be noticed. | Directory.Read.All - via applications, servicePrincipals |
Application certificates are rotated regularlywt.entra.app.cert-rotation | LOW | Checks that no app registration holds a certificate credential issued more than 180 days ago (per Microsoft Zero Trust Assessment v2.4.0). Certificates should be rotated on a regular cadence and superseded ones removed. Client secrets are not assessed by this check. | A long-lived application certificate is a durable, high-value credential: the longer it lives, the larger the window for it to be copied from a build server, key store, or backup and used to impersonate the application. Regular rotation bounds that exposure window and forces stale or forgotten credentials to surface and be removed. | Directory.Read.All - via applications |
Tenant app management policy enforces credential standardswt.entra.app.credential-standards-enforced | MEDIUM | Checks that the tenant default app management policy exists, is enabled, and carries at least one enabled credential restriction (e.g. blocking new client secrets, capping password or certificate lifetimes) across its application and service-principal restriction sets. | Every other app-credential check in this catalog reports violations after they exist; the app management policy is the one tenant-wide control that prevents them - a blocked long-lived secret is never leaked, rotated late, or found by an attacker. A policy that exists but is disabled, or whose restrictions are all switched off, enforces nothing while looking configured. | Policy.Read.All - via defaultAppManagementPolicy |
Multitenant apps configure the app instance property lockwt.entra.app.instance-property-lock | MEDIUM | Checks that every multitenant app registration (sign-in audience AzureADMultipleOrgs or AzureADandPersonalMicrosoftAccount) has the app instance property lock configured, making sensitive properties of its service-principal instances immutable in consuming tenants. Tenants with no multitenant apps pass. | When your multitenant app is consented elsewhere, each consuming tenant holds a service-principal instance of it. Without the property lock, a compromised consuming tenant can add credentials to that instance and sign in as your app - inheriting whatever trust other tenants have granted it. The lock closes a cross-tenant impersonation path you otherwise cannot see or control. | Directory.Read.All - via applications |
The legacy Azure AD PowerShell module is blockedwt.entra.app.legacy-aad-powershell-blocked | MEDIUM | Checks that the retired Azure AD PowerShell module's enterprise application (well-known appId 1b730954-...) exists in the tenant with user sign-in disabled. An absent service principal fails - nothing blocks the module; requiring app-role assignment instead of disabling fails with a review warning (upstream flags Investigate). | The deprecated module authenticates through a first-party app that predates modern token protections and is a staple of tenant-reconnaissance tooling (ROADtools-style enumeration rides it). Disabling its enterprise app's sign-in is the one switch that shuts the door tenant-wide. | Directory.Read.All - via servicePrincipals |
No applications authenticate via the deprecated ADAL librarywt.entra.app.no-adal-usage | MEDIUM | Checks that Entra's adalToMsalMigration recommendation reports no applications still authenticating through ADAL (the Azure AD Authentication Library, end-of-life since 2023). | ADAL receives no security fixes and predates MSAL's token-protection features (CAE, token binding, managed identity flows). An app on ADAL is an app whose authentication stack is frozen at its last pre-2023 patch - and Entra's sign-in telemetry knows exactly which apps those are. | DirectoryRecommendations.Read.All - via directoryRecommendations |
Applications do not use client secretswt.entra.app.no-client-secrets | LOW | Checks that no app registration or service principal carries a client secret (password credential). Per Microsoft Zero Trust Assessment v2.4.0 the target state is certificate credentials, workload identity federation, or managed identities everywhere; this is an aspirational hardening bar most tenants will fail until secrets are migrated. | A client secret is a static string with none of the binding properties of a certificate or federated credential: it pastes into source control, CI variables, and chat, and whoever holds it IS the application. Eliminating secrets removes the most commonly leaked credential class from the tenant's application estate. | Directory.Read.All - via applications, servicePrincipals |
App registrations use safe redirect URIswt.entra.app.safe-redirect-uris | MEDIUM | Checks that no app registration configures an unsafe redirect URI - a wildcard, or plaintext http:// to a non-loopback host. (Watchtower's redirect-URI-hygiene interpretation of the ZTA control; subdomain-takeover-prone hosts such as *.azurewebsites.net are flagged in remediation.) | The OAuth redirect URI is where authorization codes and tokens are delivered. A wildcard entry lets an attacker register a matching subdomain and intercept the code; a plaintext http redirect exposes the code on the wire. Both convert a phishing or network-position foothold into a full token theft, bypassing the strength of the authentication itself. | Directory.Read.All - via applications |
Third-party integrated applications are not allowedwt.entra.app.user-app-registration-disabled | HIGH | Checks that non-administrator users are prevented from registering applications in Entra ID, ensuring all app registrations go through an approved process. | When users can freely register applications, attackers with compromised user accounts can create malicious OAuth apps, register service principals, or establish persistent access tokens that survive password resets. Restricting app registration forces all third-party integrations through an administrative review process. | Policy.Read.All - via authorizationPolicy |
User consent to apps accessing company data on their behalf is not allowedwt.entra.app.user-consent-disabled | HIGH | Checks that users cannot grant OAuth consent to applications requesting access to company data, requiring administrator approval for all application permissions. | OAuth consent phishing is a widely-used technique where attackers trick users into granting a malicious application access to their mailbox, files, or calendar. Disabling user consent breaks this attack chain by ensuring all OAuth grants require administrator review before access is permitted. | Policy.Read.All - via userConsentSettings |
Only organizationally managed/approved public groups existwt.entra.approved-public-groups-only | MEDIUM | Checks that no Microsoft 365 Unified groups are set to public visibility unless explicitly approved, preventing any user from browsing group content and membership without authorisation. | Public Microsoft 365 groups expose their membership list, files, emails, and conversations to any user in the tenant. Attackers with access to a compromised low-privilege account can harvest sensitive information from public groups to facilitate targeted phishing and lateral movement. | Group.Read.All - via groups |
Privileged users have phishing-resistant methods registeredwt.entra.auth.admins-phishing-resistant-registered | HIGH | Checks that every directory-role holder has actually registered a phishing-resistant credential - a device-bound passkey (passKeyDeviceBound, passKeyDeviceBoundAuthenticator) or Windows Hello for Business - per the authentication-methods registration report. Complements wt.entra.ca.phishing-resistant-mfa-admins, which enforces the policy; this verifies the roster behind it. | A Conditional Access policy requiring phishing-resistant authentication only bites if admins have the credential to satisfy it - otherwise it locks them out, or gets scoped with exclusions that quietly become the attack surface. An admin still authenticating on phishable methods is the single highest-value phishing target in the tenant: one AiTM page against that account defeats every downstream control. | AuditLog.Read.All, RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory, User.Read.All, UserAuthenticationMethod.Read.All - via privilegedUsers, userRegistrationDetails |
All member users are MFA capablewt.entra.auth.all-users-mfa-capable | CRITICAL | Checks that every member user account has at least one MFA method registered and is therefore capable of completing a multi-factor authentication challenge. | Users without a registered MFA method cannot satisfy Conditional Access MFA requirements and may be blocked from signing in or, worse, fall back to single-factor authentication if policies are misconfigured. Unregistered users are also the most vulnerable to credential-stuffing and password-spray attacks targeting accounts that have never been protected by MFA. | AuditLog.Read.All, UserAuthenticationMethod.Read.All - via userRegistrationDetails |
Microsoft Authenticator is configured to protect against MFA fatiguewt.entra.auth.authenticator-fatigue-protection | MEDIUM | Checks that Microsoft Authenticator is configured with number matching and additional context, reducing the effectiveness of MFA fatigue (push-bombing) attacks. | MFA fatigue attacks bombard users with push notifications until they approve one by mistake. Number matching requires users to enter a code displayed during sign-in, while additional context shows the application and location - together these controls make it impractical for attackers to obtain fraudulent MFA approvals even with the correct password. | Policy.Read.All - via authMethodsPolicy |
Custom banned passwords lists are usedwt.entra.auth.custom-banned-passwords | MEDIUM | Checks that a custom banned password list is configured in Entra Password Protection, supplementing Microsoft's global list with organisation-specific terms that attackers are likely to guess. | Password-spray attacks succeed by guessing passwords related to the organisation - its name, products, locations, or common seasonal variations. A custom banned list prevents users from choosing passwords that are predictable in the context of the specific organisation, reducing spray attack success rates. | Directory.Read.All - via passwordProtectionSettings |
Email OTP authentication method is disabledwt.entra.auth.email-otp-disabled | MEDIUM | Checks that the email one-time passcode authentication method is disabled, preventing users and guests from using email-delivered codes as an MFA factor. | Email OTP is the weakest MFA method because it delivers codes to an inbox that may itself be compromised. An attacker who has already obtained access to a user's email account can intercept OTP codes to satisfy MFA challenges, bypassing the second factor entirely. Disabling this method forces use of stronger alternatives such as TOTP apps or phishing-resistant methods. | Policy.Read.All - via authMethodsPolicy |
FIDO2 security-key attestation is enforcedwt.entra.auth.fido2-attestation-enforced | MEDIUM | Checks that the FIDO2 authentication method enforces attestation (isAttestationEnforced = true), so only genuine, approved hardware authenticators can be registered as security keys. | Without attestation enforcement, a user can register a software-emulated or unapproved security key, undermining the hardware-bound guarantee that makes FIDO2 phishing-resistant. Enforcing attestation ensures every registered key is verifiably genuine hardware from a vendor you trust. | Policy.Read.All - via authMethodsPolicy |
FIDO2 security keys are enabled as an authentication methodwt.entra.auth.fido2-enabled | MEDIUM | Checks that FIDO2 security keys are enabled in the authentication methods policy, so users can register a phishing-resistant credential. | Phishing-resistant Conditional Access has nothing to enforce if no phishing-resistant method is available to register. Enabling FIDO2 is the prerequisite that lets users and admins onboard hardware-bound credentials, which are the strongest defence against adversary-in-the-middle phishing. | Policy.Read.All - via authMethodsPolicy |
Legacy MFA and SSPR policies are migratedwt.entra.auth.legacy-policy-migration-complete | LOW | Checks that the migration from the legacy per-service MFA and SSPR policies to the unified authentication methods policy is complete (policyMigrationState is migrationComplete, or absent because the tenant never had legacy policies). | While the migration is pending, authentication methods are governed from two places at once: the modern policy and the legacy MFA/SSPR portals. Methods disabled in one can remain enabled in the other, so a weak method you believe is off (SMS, voice) may still be registrable - and combined security-info registration cannot be fully enforced. | Policy.Read.All - via authMethodsPolicy |
Password protection is enabled for on-premises Active Directorywt.entra.auth.onprem-password-protection | MEDIUM | Checks that Microsoft Entra Password Protection is deployed and enforced on on-premises Active Directory domain controllers, applying the cloud banned-password list locally. | On-premises AD credentials are prime targets for password-spray and brute-force attacks. Without Entra Password Protection on domain controllers, weak or organisation-predictable passwords can be set in AD and subsequently used to access synced cloud accounts, enabling lateral movement from on-prem to cloud. | Directory.Read.All - via passwordProtectionSettings |
Passkeys are enabled and assigned to userswt.entra.auth.passkey-enabled | MEDIUM | Checks that the FIDO2 method (which delivers passkeys in Entra) is enabled and has at least one include target, so passkeys are actually available to users rather than merely toggled on with no scope. | Passkeys are the most usable phishing-resistant credential and the practical path to broad phishing-resistant coverage. Enabling the method but assigning it to no one leaves it unusable; requiring both enablement and an include target confirms passkeys are genuinely deployable in the tenant. | Policy.Read.All - via authMethodsPolicy |
Passwordless methods reduce the password surface areawt.entra.auth.passwordless-methods-configured | LOW | Checks that both passwordless pillars are deployed: FIDO2 security keys enabled with include targets, and Microsoft Authenticator enabled with include targets whose authentication mode permits passwordless phone sign-in (any or deviceBasedPush). Per Microsoft Zero Trust Assessment v2.4.0. | Every password a user still types is phishable, reusable, and spillable regardless of MFA on top. Passwordless sign-in removes the password from the flow entirely - but only where it is actually enabled and assigned. A method toggled on with no targets, or Authenticator locked to push-only, leaves users typing passwords day to day. | Policy.Read.All - via authMethodsPolicy |
Smart lockout duration is at least 60 secondswt.entra.auth.smart-lockout-duration | MEDIUM | Checks that the smart lockout duration in the 'Password Rule Settings' directory settings template is 60 seconds or more. An absent template or absent value passes - Entra then applies its 60-second default; only an explicit value below 60 fails (mirrors the upstream ZTA test). | The lockout duration is the cost an attacker pays per failed burst. Shortening it below the default turns smart lockout into a metronome: a spray tool simply waits out the window and resumes, cycling through the password list at a predictable rate while real users barely notice anything is wrong. | Directory.Read.All - via passwordRuleSettings |
Smart lockout threshold is 10 attempts or fewerwt.entra.auth.smart-lockout-threshold | MEDIUM | Checks that the smart lockout threshold in the 'Password Rule Settings' directory settings template is explicitly configured at 10 or fewer failed attempts. Unlike the duration check, a missing template or missing value fails - the upstream ZTA test requires the threshold to be deliberately set, not left implicit. | The threshold is how many guesses an attacker gets per lockout window per account. At the default 10 a spray campaign already gets ten free tries per cycle; tenants that raised it to reduce helpdesk tickets have quietly multiplied the attacker's budget. Setting it explicitly - and low - is the half of smart lockout that is actually a choice. | Directory.Read.All - via passwordRuleSettings |
System-preferred multifactor authentication is enabledwt.entra.auth.system-preferred-mfa | MEDIUM | Checks that the system-preferred MFA feature is enabled, which automatically nudges users to authenticate with their strongest registered MFA method rather than the method they last used. | When users are free to choose their MFA method, they typically default to the weakest option they have registered (e.g. SMS OTP) even when a stronger method like Microsoft Authenticator is available. System-preferred MFA steers users toward phishing-resistant or harder-to-intercept methods without requiring administrator intervention per user. | Policy.Read.All - via authMethodsPolicy |
Temporary Access Pass is enabledwt.entra.auth.tap-enabled | LOW | Checks that Temporary Access Pass (TAP) is enabled in the authentication methods policy, providing a time-limited passcode for onboarding or recovering phishing-resistant credentials. | Without TAP, the fallback for a user who has lost their credential or is registering their first strong method is often a phishable method such as SMS. A Temporary Access Pass is a short-lived, admin-issued passcode that lets users bootstrap a phishing-resistant credential without weakening the tenant's method mix. | Policy.Read.All - via authMethodsPolicy |
Temporary Access Pass is restricted to single usewt.entra.auth.tap-single-use | MEDIUM | Checks that the Temporary Access Pass configuration is set to one-time use (isUsableOnce = true), so a TAP cannot be replayed after its first use. | A reusable Temporary Access Pass is effectively a standing bypass credential for the duration of its lifetime: if it is intercepted or leaked, it can be used repeatedly until it expires. Restricting TAP to single use consumes it on first authentication, bounding a leaked pass to at most one sign-in. | Policy.Read.All - via authMethodsPolicy |
Weak authentication methods are disabledwt.entra.auth.weak-methods-disabled | CRITICAL | Checks that SMS, voice call, and hardware OATH token authentication methods are disabled in the tenant-wide authentication methods policy, ensuring users cannot register or use these weak second factors. | SMS and voice OTP codes are susceptible to SIM-swapping, real-time phishing kits (adversary-in-the-middle proxies), and SS7 interception - all of which allow attackers to capture the code and satisfy MFA challenges. Disabling these methods forces users onto cryptographic, phishing-resistant alternatives. | Policy.Read.All - via authMethodsPolicy |
Emergency access account activity is monitoredwt.entra.breakglass-activity-monitored | HIGH | Checks that alert policies are configured to notify administrators immediately whenever an emergency access (break-glass) account is used to sign in. | Break-glass accounts are excluded from MFA and Conditional Access policies and hold Global Administrator privileges - making them the highest-value target in the tenant. Any sign-in to these accounts during normal operations is an indicator of compromise or insider threat and must trigger an immediate security response. | None - no tenant data read (not automatable via API, reported as a manual/indeterminate result) |
Sign-in frequency is enabled and browser sessions are not persistent for administrative userswt.entra.ca.admin-signin-frequency | HIGH | Verifies that a Conditional Access policy enforces a 4-hour sign-in frequency and non-persistent browser sessions for all users holding privileged administrative roles. | Stolen browser session tokens allow attackers to impersonate administrators without knowing their password or completing MFA. Limiting session lifetime to 4 hours and preventing persistent browser sessions reduces the window during which a stolen token remains valid, limiting the blast radius of session hijacking attacks against high-privilege accounts. | Policy.Read.All - via conditionalAccessPolicies |
App-enforced restrictions limit access from unmanaged deviceswt.entra.ca.app-enforced-restrictions | MEDIUM | Checks that an active Conditional Access policy applies app-enforced restrictions to Office 365 for all users, so browser access from unmanaged devices is limited (web-only, no download / print / sync). | App-enforced restrictions let SharePoint and Exchange downgrade the session on an unmanaged or non-compliant device to a limited web experience, preventing data from being downloaded onto a device the organisation does not control. It is the Zero Trust data-containment control for the common case where you allow access but not local copies. | Policy.Read.All - via conditionalAccessPolicies |
Authentication transfer is blocked by Conditional Accesswt.entra.ca.auth-transfer-blocked | MEDIUM | Checks that an active Conditional Access policy blocks the authentication transfer flow (the cross-device sign-in hand-off, e.g. QR-code / 'sign in from another device'), scoped to all users. | Authentication transfer lets a sign-in started on one device be completed on another. Attackers abuse it in device-code and QR-code phishing to move an authenticated session onto attacker-controlled hardware. Blocking the transfer flow unless explicitly needed closes that lateral sign-in path, consistent with the device-code-flow control. | Policy.Read.All - via conditionalAccessPolicies |
Device code sign-in flow is blockedwt.entra.ca.device-code-blocked | HIGH | Checks that a Conditional Access policy blocks the device code authentication flow for all users, preventing this rarely-needed flow from being exploited in social-engineering attacks. | Device code phishing (also known as illicit consent or device code flow abuse) is an attack where a threat actor tricks a user into entering a device code on a legitimate Microsoft page, granting the attacker a persistent access token. Since most users and applications do not need this flow, blocking it eliminates this attack vector entirely. | Policy.Read.All - via conditionalAccessPolicies |
Guests do not have long-lived sign-in sessionswt.entra.ca.guest-session-lifetime-limited | MEDIUM | Checks that an active Conditional Access policy targeting guest / external users enforces a sign-in frequency of at most 24 hours (or re-authentication every time), so external identities cannot hold indefinitely-persistent sessions. | Guest and external accounts are outside your identity governance: you do not control their device hygiene, their credential rotation, or their offboarding. A long-lived session for a guest means a stolen or stale external token stays valid for weeks. Bounding the guest sign-in frequency forces periodic re-evaluation of Conditional Access for exactly the population you have the least visibility into. | Policy.Read.All - via conditionalAccessPolicies |
Idle session timeout is set to 3 hours or less (CA policy)wt.entra.ca.idle-session-timeout | HIGH | Verifies that a Conditional Access policy enforces app-enforced restrictions for browser sessions to Office 365, enabling SharePoint and Exchange Online to apply idle session timeouts of 3 hours or less. | Unattended browser sessions that remain authenticated indefinitely are vulnerable to physical access attacks and session token theft. Enforcing idle timeouts reduces the window for an attacker to exploit an unlocked workstation or a stolen session cookie to access email, files, and other sensitive data. | Policy.Read.All - via conditionalAccessPolicies |
Conditional Access policies block legacy authenticationwt.entra.ca.legacy-auth-blocked | CRITICAL | Checks that a Conditional Access policy blocks all legacy authentication protocols - Exchange ActiveSync and other basic-auth clients - for all users and all cloud applications. Only break-glass account exclusions are acceptable. | Legacy authentication protocols do not support modern MFA challenges. Any account that can authenticate via these protocols is permanently unprotected by MFA, regardless of any Conditional Access policies in place. Over 99% of password-spray attacks against Microsoft tenants exploit legacy auth precisely because it bypasses MFA entirely. | Policy.Read.All - via conditionalAccessPolicies |
Managed device required for authenticationwt.entra.ca.managed-device-required-for-auth | CRITICAL | Checks that a Conditional Access policy requires all users to authenticate from a compliant (Intune-enrolled) or domain-joined device to access any cloud application. | Unmanaged devices lack endpoint security controls and may be compromised with keyloggers, credential-stealers, or session-hijacking malware. Requiring managed devices closes a major attack path where stolen credentials can be used from an attacker-controlled machine to access corporate data. | Policy.Read.All - via conditionalAccessPolicies |
Managed device required to register security informationwt.entra.ca.managed-device-required-for-security-info | HIGH | Checks that a Conditional Access policy requires users to be on a managed device before they can register or modify their MFA methods and security information. | If MFA registration can be performed from any device, an attacker who has stolen a user's password can register their own MFA device before the legitimate user notices, locking them out and taking over the account. Requiring a managed device for registration prevents this account-takeover path. | Policy.Read.All - via conditionalAccessPolicies |
Multifactor authentication is enabled for all users in administrative roleswt.entra.ca.mfa-admins | CRITICAL | Checks that every user holding a privileged administrative role has MFA capability and is subject to a Conditional Access policy that enforces MFA for all applications. | Administrative accounts are the primary target in tenant takeover attacks. A compromised admin account without MFA gives an attacker unrestricted control over the entire Microsoft 365 environment - including the ability to disable security policies, exfiltrate all data, and create persistent backdoors. | AuditLog.Read.All, RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory, User.Read.All, UserAuthenticationMethod.Read.All - via privilegedUsers, userRegistrationDetails |
Multifactor authentication is enabled for all userswt.entra.ca.mfa-all-users | CRITICAL | Checks that an active Conditional Access policy requires MFA for all users across all cloud applications with no application exclusions (other than break-glass accounts). | MFA is the single most effective control for preventing account takeover from credential theft. Policies with application exclusions leave gaps that attackers can exploit - accessing unprotected apps first and then pivoting to protected resources using tokens or internal access paths. | Policy.Read.All - via conditionalAccessPolicies |
MFA is required for device join and device registrationwt.entra.ca.mfa-device-registration | MEDIUM | Checks that an active Conditional Access policy requires MFA for the Register or join devices user action, so a device cannot be registered or Entra-joined on the strength of a password alone. | Device registration and join establish a durable device identity that later Conditional Access decisions trust. If an attacker with only a stolen password can register a device, they mint a trusted device they can then use to satisfy device-based access policies. Requiring MFA at the registration/join gate stops a password-only compromise from bootstrapping a trusted device. | Policy.Read.All - via conditionalAccessPolicies |
Privileged roles are restricted to Privileged Access Workstationswt.entra.ca.paw-policies-configured | HIGH | Checks for BOTH halves of the PAW pattern in enabled Conditional Access policies targeting Entra-flagged privileged roles: one policy requiring a compliant device, and one block policy with a device filter in exclude mode (block everything that is not a PAW). Also reads the privilegedRoleDefinitions source. | Admin credentials are only as safe as the keyboard they're typed on: phished session tokens and keyloggers live on everyday workstations. The PAW pattern makes privileged role use physically conditional - a compromised admin password on a random laptop meets a block policy instead of the Entra portal. | Policy.Read.All, RoleManagement.Read.Directory - via conditionalAccessPolicies, privilegedRoleDefinitions |
Phishing-resistant MFA strength is required for administratorswt.entra.ca.phishing-resistant-mfa-admins | CRITICAL | Checks that a Conditional Access policy requiring a phishing-resistant MFA authentication strength (FIDO2, Windows Hello for Business, or certificate-based authentication) applies to all users in privileged administrative roles. | Adversary-in-the-middle (AiTM) phishing kits can intercept TOTP and push notification MFA codes in real time, bypassing conventional MFA for admin accounts. Phishing-resistant methods use cryptographic binding to the origin, making real-time interception impossible and protecting the highest-value accounts from this increasingly common attack. | Policy.Read.All, RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory, User.Read.All - via conditionalAccessPolicies, privilegedUsers |
Phishing-resistant MFA strength is required for all userswt.entra.ca.phishing-resistant-mfa-all-users | HIGH | Checks that an active Conditional Access policy requires the phishing-resistant MFA authentication strength (FIDO2, Windows Hello for Business, or certificate-based auth) for all users across all cloud apps, not only administrators. | Zero Trust treats phishing-resistant authentication as the baseline for every user, not a privilege reserved for admins. Adversary-in-the-middle phishing kits intercept TOTP and push MFA in real time; extending the phishing-resistant strength to the whole workforce removes the interceptable-MFA gap that attackers pivot through after compromising a standard account. | Policy.Read.All - via conditionalAccessPolicies |
Protected actions guard Conditional Access policy changeswt.entra.ca.protected-actions-enforced | HIGH | Checks that all four CA-management protected actions (create/update/delete Conditional Access policies, rebind authentication contexts) are bound to an authentication context, that at least one enabled CA policy targets those contexts, and that the targeting policies uniformly demand an authentication strength (with a phishing-resistant combination available) or a sign-in-frequency step-up. Also reads the conditionalAccessPolicies source. | Disabling or gutting CA policies is the first move after an admin-account compromise - it disarms every other control at once. Protected actions force a fresh, phishing-resistant step-up at exactly that moment, so a stolen admin session or token is not enough to switch the tenant's defences off. | Policy.Read.All, RoleManagement.Read.Directory - via conditionalAccessPolicies, protectedActions |
Sign-in frequency for Intune Enrollment is set to Every timewt.entra.ca.signin-frequency-intune-enrollment | HIGH | Checks that a Conditional Access policy requires MFA at every sign-in to the Microsoft Intune Enrollment application, ensuring device enrolment always requires fresh authentication. | If a user's session token is compromised, an attacker could enrol a rogue device into Intune without MFA, gaining a managed (and therefore trusted) foothold in the environment. Requiring MFA on every Intune enrolment prevents an attacker from using a cached token to silently enrol a malicious device. | Policy.Read.All - via conditionalAccessPolicies |
Sign-in risk is blocked for medium and high riskwt.entra.ca.signin-risk-blocked | CRITICAL | Checks that a Conditional Access policy blocks sign-in attempts assessed as medium or high risk by Entra Identity Protection, such as anonymous IP addresses, impossible travel, or known malicious IPs. | Blocking medium and high sign-in risk stops in-progress account compromise attempts in real time. When Identity Protection detects characteristics of credential theft (e.g. an attacker using an anonymising proxy or logging in from an impossible location), blocking immediately prevents access before any damage is done. | Policy.Read.All - via conditionalAccessPolicies |
Identity Protection sign-in risk policies are enabledwt.entra.ca.signin-risk-policy-enabled | CRITICAL | Checks that a Conditional Access sign-in risk policy is active, requiring step-up MFA at every attempt for sign-ins rated medium or high risk by Entra Identity Protection. | Sign-in risk policies provide a real-time defence against credential compromise by detecting suspicious authentication patterns and requiring additional verification before granting access. Without this, attackers using stolen credentials from a risky source (Tor exit node, unfamiliar location) may authenticate successfully without challenge. | Policy.Read.All - via conditionalAccessPolicies |
Token protection is enabled by Conditional Accesswt.entra.ca.token-protection-enabled | MEDIUM | Checks that an active Conditional Access policy enables token protection (the secure sign-in session control), which cryptographically binds sign-in session tokens to the device they were issued to. | Refresh- and access-token theft is the dominant post-MFA attack: an adversary who exfiltrates a token from a compromised device replays it from their own machine and inherits the session without re-authenticating. Token protection binds the token to the originating device's hardware, so a stolen token is useless off that device - closing the gap that MFA alone does not cover. | Policy.Read.All - via conditionalAccessPolicies |
Token protection is applied to Windows desktop sign-inswt.entra.ca.token-protection-signin | MEDIUM | Checks for the documented token-protection policy shape: an enabled Conditional Access policy scoped to exactly the mobileAppsAndDesktopClients client-app type and exactly the Windows platform, with the secure sign-in session (token protection) control enabled. The companion check wt.entra.ca.token-protection-enabled (ZTA 21941) verifies any token-protection control exists; this one verifies the specific supported scoping. | Token protection cryptographically binds sign-in session tokens to the device, killing the token-replay playbook - but the control is only supported for Windows desktop/mobile clients, so a policy scoped any wider silently applies to nothing. The exact shape is the difference between having the control and having a checkbox. | Policy.Read.All - via conditionalAccessPolicies |
At least one trusted named location is configuredwt.entra.ca.trusted-named-locations | MEDIUM | Checks that at least one Conditional Access named location is marked trusted. Trusted locations anchor Entra's sign-in risk engine and location-based Conditional Access. The upstream ZTA test skips unlicensed (non-P1) tenants; Watchtower evaluates every tenant, since the gap - no location intelligence - is real either way. | Without a trusted-network anchor, 'unfamiliar location' risk detections degrade into noise and every location-conditioned CA policy has nothing to reference. Marking the corporate egress ranges trusted is one of the highest-leverage low-effort inputs to the whole risk pipeline - it sharpens detections for every user at once. | Policy.Read.All - via namedLocations |
Identity Protection user risk policies are enabledwt.entra.ca.user-risk-policy-enabled | CRITICAL | Checks that an active user risk policy requires high-risk users to complete MFA and change their password, responding automatically when Entra Identity Protection determines an account is likely compromised. | User risk is elevated when Identity Protection detects that an account's credentials may have been leaked (e.g. found in a breach database or correlated with multiple anomalous sign-ins). Without an automated remediation policy, compromised accounts remain active until a manual investigation is completed - potentially allowing prolonged attacker access and data exfiltration. | Policy.Read.All - via conditionalAccessPolicies |
Credentialed workload identities are location-restrictedwt.entra.ca.workload-identity-locations | MEDIUM | Checks that every own-tenant service principal carrying credentials (on the SP or its single-tenant app registration) is covered by an enabled workload-identity Conditional Access policy with a location condition - a global ServicePrincipalsInMyTenant policy with locations passes everything at once. Also reads the applications, conditionalAccessPolicies, and namedLocations sources. Upstream gates on P1 + Workload ID licenses; Watchtower evaluates every tenant, since the gap is real either way. | Service principal secrets leak - into repos, pipelines, laptops. A location-conditioned workload policy is the difference between a leaked secret that works from anywhere on earth and one that only works from your egress ranges, where the attacker isn't. | Directory.Read.All, Policy.Read.All - via applications, conditionalAccessPolicies, namedLocations, servicePrincipals |
Workload identities are covered by Conditional Accesswt.entra.ca.workload-identity-policies | MEDIUM | Checks that at least one enabled Conditional Access policy targets service principals (a workload-identity policy with clientApplications.includeServicePrincipals). The upstream ZTA test skips tenants without the Workload ID license; the policy read works regardless, so Watchtower evaluates every tenant. | Every user-facing CA policy is invisible to service principals - they authenticate through a plane those policies never touch. Workload-identity CA is the only mechanism that can refuse a token to a compromised app credential at issuance time, based on risk or location, before any API sees a request. | Policy.Read.All - via conditionalAccessPolicies |
Outbound cross-tenant access is blocked by defaultwt.entra.cross-tenant.outbound-access-blocked | MEDIUM | Checks that the default cross-tenant access policy blocks outbound access for both B2B collaboration and B2B direct connect - all users and all applications - so external collaboration happens only through explicit per-partner allowances. Per Microsoft Zero Trust Assessment v2.4.0; this is a strict default-deny bar. | Outbound defaults govern where your users can take their corporate identities. Left open, any user can accept an invitation into any external tenant - including an attacker-controlled one - and sign in there with their work account, creating a data-movement and consent-phishing path entirely outside your visibility. Default-deny plus explicit partner allowances turns external collaboration into an inventoried, reviewable surface. | Policy.Read.All - via crossTenantAccessPolicyDefault |
Tenant restrictions v2 blocks external identities by defaultwt.entra.cross-tenant.tenant-restrictions-v2 | MEDIUM | Checks that tenant restrictions v2 is configured on the default cross-tenant access policy to block all external users and all external applications, so accounts from foreign tenants cannot be used from your network and managed devices except where explicitly allowed. | Cross-tenant outbound settings control your OWN identities; tenant restrictions control FOREIGN ones. Without it, a user (or malware) on a corporate device can sign in with a private or attacker-tenant account and move data to it - a channel that bypasses every control scoped to your tenant. Default-blocking external identities closes the anonymous-tenant exfiltration path. | Policy.Read.All - via crossTenantAccessPolicyDefault |
Users are restricted from recovering BitLocker keyswt.entra.device.bitlocker-recovery-restricted | HIGH | Checks that standard users cannot self-serve BitLocker recovery keys for their own devices through the MyAccount portal, ensuring key retrieval requires administrator involvement. | A user whose account has been compromised could be coerced or tricked into retrieving their BitLocker recovery key, allowing an attacker with physical access to the device to bypass full-disk encryption. Restricting self-service key recovery to administrators limits this data-exfiltration and device-bypass path. | Policy.Read.All - via authorizationPolicy |
GA role is not added as local administrator during Entra joinwt.entra.device.ga-not-local-admin-on-join | MEDIUM | Checks that Global Administrators are not automatically added as local administrators on devices when they join Entra ID, reducing unnecessary privilege escalation paths on endpoints. | When Global Admins are automatically local admins on every Entra-joined device, a single compromised endpoint becomes a pivot point: local admin access can be used to dump credentials, disable endpoint security, and facilitate lateral movement. Removing this automatic assignment enforces least-privilege at the device level. | Policy.Read.DeviceConfiguration - via deviceRegistrationPolicy |
The ability to join devices to Entra is restrictedwt.entra.device.join-restricted | MEDIUM | Checks that the ability to join devices to Entra ID is restricted to administrators or a specific approved group, rather than being open to all users. | If any user can join devices to Entra, a compromised account or a malicious insider can register attacker-controlled devices that appear as managed in the directory. These devices could then be used to satisfy device-compliance Conditional Access requirements, bypassing controls that assume managed devices are trustworthy. | Policy.Read.DeviceConfiguration - via deviceRegistrationPolicy |
Local Administrator Password Solution (LAPS) is enabledwt.entra.device.laps-enabled | HIGH | Checks that Microsoft Entra LAPS is enabled, which automatically rotates and stores unique local administrator passwords for each Entra-joined device. | When all devices share the same local administrator password, a single compromised endpoint exposes every device in the fleet to pass-the-hash lateral movement attacks. LAPS ensures each device has a unique, rotated local admin credential, limiting the blast radius of a single endpoint compromise. | Policy.Read.DeviceConfiguration - via deviceRegistrationPolicy |
Local administrator assignment is limited during Entra joinwt.entra.device.local-admin-restricted | MEDIUM | Checks that the set of accounts automatically granted local administrator rights on Entra-joined devices is explicitly restricted rather than defaulting to all directory roles or all joining users. | Unrestricted local admin assignment during device join means any user who joins a device is also a local admin on it, giving them the ability to install software, modify security settings, and extract local credentials. This broadens the attack surface for privilege escalation and lateral movement. | Policy.Read.DeviceConfiguration - via deviceRegistrationPolicy |
Maximum number of devices per user is limitedwt.entra.device.quota-limited | MEDIUM | Checks that the maximum number of devices a single user can register or join to Entra ID is limited to 20 or fewer, reducing the potential for device-flooding abuse. | Without a per-user device quota, a compromised account or malicious insider can register a large number of rogue devices into the tenant, each appearing as a managed device. This can be used to circumvent device-based Conditional Access policies or to conduct reconnaissance from multiple persistent device identities. | Policy.Read.DeviceConfiguration - via deviceRegistrationPolicy |
External access package requests require approvalwt.entra.em.external-approval-required | MEDIUM | Checks that every request-enabled entitlement management assignment policy targeting external users has approval required. Policies that don't accept requests are out of scope. Requires Entra ID P2; without it this check reports INDETERMINATE. | Without approval, an external-facing access package is a vending machine: any in-scope external identity requests and immediately receives the package's groups, apps, and sites. The approval step is the single human checkpoint between 'partner asked' and 'partner has access'. | EntitlementManagement.Read.All - via entitlementAssignmentPolicies |
External access packages are scoped to connected organizationswt.entra.em.external-connected-orgs-only | MEDIUM | Checks that no entitlement management assignment policy targeting external users uses the allExternalUsers scope. Policies open to any connected organization pass with a review warning (upstream flags them Investigate); specific-organization scoping passes clean. Requires Entra ID P2; without it this check reports INDETERMINATE. | An access package scoped to all external users is self-service onboarding for the entire internet's worth of Entra identities - the connected-organization boundary is the only thing making 'external collaboration' mean partners rather than anyone. Every other guest control assumes that boundary holds. | EntitlementManagement.Read.All - via entitlementAssignmentPolicies |
Guest access packages expire or carry access reviewswt.entra.em.guest-packages-governed | MEDIUM | Checks that every self-service external assignment policy carries at least one lifecycle control: an expiration (anything but noExpiration) or an enabled access review with a recurrence schedule. Requires Entra ID P2; without it this check reports INDETERMINATE. | Self-service plus external plus no lifecycle control is the guest-sprawl recipe: partners request access once and hold it forever, surviving project end, contract end, and their own offboarding at the home tenant. Either the access expires on its own or somebody is scheduled to look - one of the two must exist. | EntitlementManagement.Read.All - via entitlementAssignmentPolicies |
Access package assignment policies expirewt.entra.em.policies-have-expiration | MEDIUM | Checks that every entitlement management assignment policy expires - either after a set duration or at a set date. Policies with no expiration fail. Requires Entra ID P2; without it this check reports INDETERMINATE. | Entitlement management exists to make access temporary by default; a policy without expiration quietly opts back into permanent access while keeping the governance branding. Expiry is the mechanism that forces the 'do they still need this?' question to answer itself. | EntitlementManagement.Read.All - via entitlementAssignmentPolicies |
Two break-glass emergency access accounts existwt.entra.emergency-access-configured | HIGH | Checks that exactly two emergency access (break-glass) accounts exist per Microsoft guidance: permanent cloud-only Global Administrators whose only real credentials are phishing-resistant (FIDO2 or certificate; the password entry is ignored) and which are excluded from every enabled Conditional Access policy. Fewer than two fails; more than two also fails (that many CA-exempt GAs is its own risk - upstream flags Investigate). Also reads the conditionalAccessPolicies source. Auth method TYPES only are collected - never method details. | The day a Conditional Access mistake locks every admin out - or the MFA provider has an outage - the break-glass accounts are the only way back in. They must be exempt from CA (or the lockout catches them too), cloud-only (or the on-prem outage catches them), and secured by hardware credentials (or their CA exemption becomes the tenant's weakest door). Two, because one is a single point of failure. | Directory.Read.All, Policy.Read.All, RoleManagement.Read.Directory, User.Read.All, UserAuthenticationMethod.Read.All - via conditionalAccessPolicies, emergencyAccessProfile |
GA and GSA Administrator rosters are tightly limitedwt.entra.ga-gsa-role-hygiene | HIGH | Checks the Global Administrator and Global Secure Access Administrator rosters: no assignments to groups, service principals, or guests (fail); disabled member accounts or more than five assignments per role pass with a review warning (upstream flags Investigate); a missing GA role definition fails outright. | These two roles can rewire what the tenant trusts at the network layer. A group assignment makes the roster as wide as the group's owners want it to be; a service principal makes it as safe as one credential; a guest puts it under someone else's security programme. The roster itself is the control. | Directory.Read.All, RoleManagement.Read.Directory - via gaGsaRoleAssignments |
Between two and four global admins are designatedwt.entra.global-admin-count-limited | CRITICAL | Checks that the number of active Global Administrator accounts is between two and four - enough for redundancy without creating excessive attack surface. | Too few Global Admins creates a single point of failure if an account is locked out or compromised; too many increases the attack surface since each is a potential target for account takeover leading to full tenant compromise. The 2-4 range balances availability with least privilege. | RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory, User.Read.All - via privilegedUsers |
Global Administrators are a minority of privileged userswt.entra.global-admin-ratio-bounded | MEDIUM | Checks that users holding Global Administrator make up less than 30% of all users holding Entra-flagged privileged roles (per Microsoft Zero Trust Assessment v2.4.0). Complements wt.entra.global-admin-count-limited, which bounds the absolute GA count. | A high GA share means administration that should run on scoped roles (User, Exchange, Security Administrator) is running on the tenant's most powerful role instead. Every unnecessary GA multiplies the blast radius of a single phished admin and defeats the point of Entra's role model - if most admins are GA, role-based least privilege exists on paper only. | RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory, User.Read.All - via privilegedUsers |
AI Gateway prompt policies protect generative AI trafficwt.entra.gsa.ai-prompt-protection | MEDIUM | Checks that at least one Global Secure Access prompt policy exists and is linked to an enabled, enforced filtering profile (baseline, or a security profile bound by an enabled Conditional Access policy) - putting the AI Gateway's prompt-injection protection in the path of generative AI traffic. | Prompt injection is the emerging path by which a poisoned page or document turns an enterprise AI assistant against its user - exfiltrating context or triggering actions the user never asked for. The AI Gateway can screen prompts in transit, but like all GSA controls it protects nothing until a policy is linked into an enforced profile. | NetworkAccess.Read.All, Policy.Read.All - via conditionalAccessPolicies, gsaFilteringPosture |
Global Secure Access signaling for Conditional Access is enabledwt.entra.gsa.ca-signaling-enabled | MEDIUM | Checks that Global Secure Access Conditional Access signaling (source IP restoration) is enabled, so policies and risk detection evaluate the user's real source IP instead of the Security Service Edge proxy's egress address. Requires the NetworkAccess.Read.All permission (added to the consent surface 2026-07-03); until a tenant re-consents this check reports INDETERMINATE. | With GSA in the traffic path and signaling off, every named-location, trusted-IP, and impossible-travel evaluation sees Microsoft's proxy IP - location-based Conditional Access silently stops distinguishing the office from an attacker's VPS. Signaling restores the original source IP to Entra ID so the network controls keep meaning what they say. | NetworkAccess.Read.All - via gsaSettings |
GSA client is deployed across managed endpointswt.entra.gsa.client-deployment | HIGH | Compares the devices seen by Global Secure Access in the last 7 days against the tenant's Entra-joined and hybrid-joined device count (read as a count only - no device records enter evidence). Coverage of 90% or more passes; 70-90% and inventory inconsistencies fail with a review prompt; below 70% fails outright. Requires the NetworkAccess.Read.All permission (added to the consent surface 2026-07-03); until a tenant re-consents this check reports INDETERMINATE. | Every network control in this pillar rides the GSA client: an unmanaged gap in client deployment is a population of endpoints whose traffic bypasses filtering, TLS inspection, and compliant-network checks entirely. Coverage percentage is the honest measure of how much of the fleet the controls actually reach. | Directory.Read.All, NetworkAccess.Read.All - via gsaUsageReports |
Cloud firewall protects branch office internet trafficwt.entra.gsa.cloud-firewall-branch | HIGH | For tenants with remote networks (branch sites) configured, checks that the baseline profile carries at least one enabled cloud firewall policy with at least one enabled rule. Reports as not applicable when no remote networks exist. | Branch sites route their internet traffic through GSA without a per-device client - the cloud firewall on the baseline profile is the only filter that traffic gets. A branch connected to GSA with no enabled firewall rules egresses to the internet with a false sense of protection. | Directory.Read.All, NetworkAccess.Read.All - via gsaUsageReports |
Conditional Access enforces the compliant network checkwt.entra.gsa.compliant-network-ca | MEDIUM | Checks the full compliant-network chain: Global Secure Access signaling enabled, an all-tenant compliant network named location present, and at least one enabled Conditional Access policy that blocks access from every location except the compliant network. Policies that reference the location in weaker patterns are flagged for review instead of passing. | The compliant network condition is GSA's answer to token theft: even a valid, MFA-backed session is refused unless the traffic arrives through the tenant's own Security Service Edge. A stolen token replayed from an attacker's infrastructure fails the network check regardless of the credential's validity. | NetworkAccess.Read.All, Policy.Read.All - via conditionalAccessPolicies, gsaSettings, namedLocations |
GSA deployment logs show no failed deploymentswt.entra.gsa.deployment-logs-healthy | MEDIUM | Checks the Global Secure Access deployment log for the last 30 days: no configuration deployment may be in the failed stage. Reports as not applicable when GSA has no enabled forwarding profile. | A failed GSA deployment means the running Security Service Edge does not match the policy the portal displays - filtering rules an admin believes are active may never have reached the edge. The deployment log is where that divergence is visible; unreviewed failures compound. | Directory.Read.All, NetworkAccess.Read.All - via gsaForwardingProfiles, gsaUsageReports |
GSA file transfer policies prevent data exfiltrationwt.entra.gsa.file-transfer-policies | MEDIUM | Checks that at least one Global Secure Access file policy exists and is actively enforced - linked with an enabled link to an enabled filtering profile that is either the baseline profile or a security profile bound by an enabled Conditional Access policy. | File policies are the Secure Web Gateway's data-exfiltration control: without one, any file can leave through the tunnel GSA itself provides. An unenforced file policy gives the dashboard-level appearance of transfer monitoring while every upload passes unexamined. | NetworkAccess.Read.All, Policy.Read.All - via conditionalAccessPolicies, gsaFilteringPosture |
All GSA traffic forwarding profiles are enabledwt.entra.gsa.forwarding-profiles-enabled | HIGH | Checks that every Global Secure Access traffic forwarding profile (Microsoft 365, private access, internet access) is enabled. No profiles at all means GSA is not configured; a mix of enabled and disabled profiles means only part of the traffic is protected. Requires the NetworkAccess.Read.All permission (added to the consent surface 2026-07-03); until a tenant re-consents this check reports INDETERMINATE. | Each disabled forwarding profile is an entire traffic class - all of Microsoft 365, all private apps, or all web traffic - flowing outside the Security Service Edge. Every filtering, inspection, and network-signal control in the pillar applies only to traffic a profile actually captures. | Directory.Read.All, NetworkAccess.Read.All - via gsaForwardingProfiles |
GSA internet access forwarding profile is enabled and assignedwt.entra.gsa.internet-profile-enabled | HIGH | Checks that the Global Secure Access internet access traffic forwarding profile exists, is enabled, and is available to users (assignment not required, or at least one user or group assigned). | The internet profile is what places general web traffic behind GSA's Secure Web Gateway. Web content filtering, TLS inspection, and threat protection policies are all downstream of it - configured beautifully, they protect nothing until this profile captures traffic. | Directory.Read.All, NetworkAccess.Read.All - via gsaForwardingProfiles |
Global Secure Access licenses exist and are assignedwt.entra.gsa.licenses-assigned | LOW | Checks that an enabled SKU containing a Global Secure Access service plan (Entra Internet Access or Private Access) exists and has at least one seat consumed. Watchtower reads the SKU ledger's consumedUnits; the upstream test counts assigned users - the same fact from the license side. | Every Network-pillar control downstream of this one assumes GSA is actually running. Licenses that exist but sit unassigned are the classic gap between 'we bought Zero Trust networking' and any packet flowing through it. | Organization.Read.All - via subscribedSkus |
Microsoft 365 traffic is actively flowing through GSAwt.entra.gsa.m365-traffic-flowing | MEDIUM | Checks that the Microsoft 365 traffic forwarding profile is enabled and that the 7-day transaction summary shows Microsoft 365 traffic actually flowing through Global Secure Access. Low transaction volume or few active devices pass with warnings. | Configuration and reality can diverge silently: an enabled forwarding profile with zero observed transactions means clients are not tunneling - wrong client version, profile not reaching devices, or local bypasses. Traffic volume is the ground truth that the control is live. | Directory.Read.All, NetworkAccess.Read.All - via gsaForwardingProfiles, gsaUsageReports |
Every Private Access app has user or group assignmentswt.entra.gsa.private-access-assignments | MEDIUM | Checks that every ZTNA-published Private Access application has at least one user or group assignment (assignment presence only). No Private Access apps at all fails with a review warning (Private Access is not deployed). | Per-app assignment is where GSA's least-privilege story becomes real: each published app is a tunnel to a specific private segment, and its assignee list is that segment's access policy. An unassigned app is a segment whose policy is 'whatever comes along later' - segmentation in name only. | Directory.Read.All - via gsaTaggedApps |
GSA forwarding profiles are scoped to users or groupswt.entra.gsa.profile-assignments | HIGH | Checks that every enabled traffic forwarding profile is actually available to someone: either assignment is not required (all users) or at least one user or group is assigned. Watchtower records assignment counts only - assignee identities never enter evidence. | An enabled profile with zero assignments is protection on paper: the dashboard shows Global Secure Access as on, while nobody's traffic is being captured. The gap is invisible precisely because everything keeps working - traffic just flows unprotected. | Directory.Read.All, NetworkAccess.Read.All - via gsaForwardingProfiles |
Quick Access requires and has user assignmentswt.entra.gsa.quick-access-assignments | MEDIUM | Checks that the GSA Quick Access application requires app-role assignment AND has assignees (assignment presence only - assignee identities are not collected). A missing Quick Access app fails with a review warning. | Quick Access without required assignment is private-network access for the entire directory - every user, every guest, whatever CA allows. Requiring assignment turns the front door into a guest list; having assignees proves the list is actually in use rather than a lockout. | Directory.Read.All - via gsaTaggedApps |
Quick Access is bound to a strong Conditional Access policywt.entra.gsa.quick-access-ca | HIGH | Checks that the GSA Quick Access application exists and at least one enabled Conditional Access policy targets it (by app or All apps) with a strong grant control - MFA, compliant device, domain-joined device, or approved app. Also reads the conditionalAccessPolicies source. A missing Quick Access app fails (Private Access is not deployed). | Quick Access is the single published front door to everything GSA tunnels privately. Without a CA binding, that door opens on password strength alone - reproducing, at the network layer, exactly the VPN-with-a-password anti-pattern Zero Trust networking exists to retire. | Directory.Read.All, Policy.Read.All - via conditionalAccessPolicies, gsaTaggedApps |
GSA security profiles are applied through Conditional Accesswt.entra.gsa.security-profiles-ca | HIGH | Checks that at least one enabled Conditional Access policy carries an enabled Global Secure Access security profile session control referencing an enabled filtering profile. Requires the NetworkAccess.Read.All permission (added to the consent surface 2026-07-03); until a tenant re-consents this check reports INDETERMINATE. | A GSA security profile is a bundle of filtering policies with no audience of its own - Conditional Access is the delivery mechanism. Profiles that no enabled policy applies are configuration theatre: the filtering rules exist, and no user's session is subject to them. | NetworkAccess.Read.All, Policy.Read.All - via conditionalAccessPolicies, gsaFilteringPosture |
Custom TLS bypass rules don't duplicate system bypasseswt.entra.gsa.tls-bypass-not-redundant | LOW | Checks that no custom TLS bypass destination duplicates an entry Global Secure Access already bypasses by default - the published system bypass list (certificate-pinned services like Apple, Teams, WhatsApp) and the four system-bypassed web categories. Matching (exact, wildcard, and subdomain forms) happens at collection against a vendored copy of the published list; custom destinations that do NOT match it never enter evidence. | Redundant bypass rules are inert today and misleading tomorrow: they inflate the bypass list an auditor must reason about, and when Microsoft changes the system list, a redundant custom copy silently becomes the only thing keeping a destination uninspected. A minimal custom list is one someone can actually review. | NetworkAccess.Read.All - via gsaFilteringPosture, gsaTlsInspection |
TLS inspection bypass rules are reviewed regularlywt.entra.gsa.tls-bypass-reviewed | MEDIUM | Checks that every TLS inspection policy carrying custom bypass rules (auto-created system rules excluded) has been modified within the last 90 days - the upstream proxy for 'someone reviewed whether these bypasses are still justified'. On tenants without TLS inspection this check reports as not applicable rather than failing. | Each bypass rule is a deliberate hole in inspection, usually punched for one incompatible app under deadline. Unreviewed, the holes outlive the apps that justified them - and attackers only need one forgotten bypass domain to move traffic invisibly. | NetworkAccess.Read.All - via gsaFilteringPosture, gsaTlsInspection |
TLS inspection certificates have sufficient remaining validitywt.entra.gsa.tls-certificates-valid | MEDIUM | Checks that no in-use TLS inspection CA certificate is expired, marked expiring, or within 90 days of expiry (certificates still enrolling or disabled are skipped, per upstream). Watchtower records only each certificate's status and expiry date - never subjects, thumbprints, or key material. Reports as not applicable on tenants without TLS inspection. | The inspection CA certificate is a single point of failure for the whole inspection layer: when it expires, inspection either fails open - traffic silently reverts to uninspected - or breaks user connectivity tenant-wide. Either failure lands as an emergency; a 90-day renewal window turns it into a calendar entry. | NetworkAccess.Read.All - via gsaFilteringPosture, gsaTlsInspection |
TLS inspection is enabled for outbound trafficwt.entra.gsa.tls-inspection-enabled | HIGH | Checks that TLS inspection policies exist and that at least one is linked (enabled link) to an enabled, enforced filtering profile - the baseline profile, or a security profile bound by an enabled Conditional Access policy. Requires the NetworkAccess.Read.All permission (added to the consent surface 2026-07-03); until a tenant re-consents this check reports INDETERMINATE. | Without TLS inspection the Secure Web Gateway grades traffic by hostname alone - malware payloads, C2 channels, and exfiltration inside HTTPS pass unexamined. And like every GSA control, an inspection policy linked to no enforced profile inspects nothing. | NetworkAccess.Read.All, Policy.Read.All - via conditionalAccessPolicies, gsaFilteringPosture, gsaTlsInspection |
Universal Continuous Access Evaluation is in effect for GSA trafficwt.entra.gsa.universal-cae | HIGH | Checks that Global Secure Access Conditional Access signaling is enabled (the prerequisite for Universal CAE) and that no enabled Conditional Access policy targeting All applications sets its continuous access evaluation session control to disabled. | Continuous Access Evaluation is what lets Entra kill a session the moment network context changes, instead of waiting up to an hour for the token to expire. One broad policy that disables CAE reopens that whole window: a stolen token keeps working after the user leaves the compliant network. | NetworkAccess.Read.All, Policy.Read.All - via conditionalAccessPolicies, gsaSettings |
Universal tenant restrictions block unauthorized external tenantswt.entra.gsa.universal-tenant-restrictions | HIGH | Checks that Global Secure Access network packet tagging is enabled and that the default tenant restrictions v2 policy blocks all users from accessing all applications in unauthorized external tenants. | Without universal tenant restrictions, anyone on a managed device can sign in to an arbitrary external Entra tenant - their own, or an attacker's - and move corporate data there with sanctioned tools. Packet tagging makes the restriction travel with the network traffic, so the block holds even for connections that never touch a corporate proxy PAC file. | NetworkAccess.Read.All, Policy.Read.All - via crossTenantAccessPolicyDefault, gsaSettings |
Web content filtering policies are configured and appliedwt.entra.gsa.wcf-applied | HIGH | Checks that at least one web content filtering policy (beyond the built-in catch-all) is applied: linked with an enabled link to an enabled filtering profile that is actually enforced - the baseline profile, or a security profile bound by an enabled Conditional Access policy. | Web content filtering is the Secure Web Gateway's first control: without an applied policy, users behind GSA browse with no category or destination restrictions at all. A policy that exists but is linked nowhere - or linked to a profile no Conditional Access policy delivers - filters nothing. | NetworkAccess.Read.All, Policy.Read.All - via conditionalAccessPolicies, gsaFilteringPosture |
Web content filtering uses category-based ruleswt.entra.gsa.wcf-category-rules | MEDIUM | Checks that at least one web content filtering policy carrying web category rules is linked to an enforced filtering profile (the baseline profile, or a security profile bound by an enabled Conditional Access policy). Watchtower records only the category names on the rules; custom FQDN and URL destination lists never enter evidence. | Category rules are maintained by Microsoft's classifier and keep pace as sites appear, move, and change purpose. A filtering posture built purely on hand-curated FQDN lists rots silently: yesterday's block list says nothing about today's phishing domain. | NetworkAccess.Read.All, Policy.Read.All - via conditionalAccessPolicies, gsaFilteringPosture |
Web content filtering policies are linked to security profileswt.entra.gsa.wcf-enforced | HIGH | Checks that web content filtering policies and filtering profiles both exist, and that the policies reach users: either the baseline profile carries a filtering-policy link, or a security profile carrying one has Conditional Access policies attached. The upstream test reads the profile's attached policies; Watchtower evaluates the same relationship from the Conditional Access side. | The chain from filtering policy to filtered packet has three links - policy, profile, delivery - and severing any one of them silently disables the control while every object still looks configured in the portal. This check verifies the chain end to end rather than the objects in isolation. | NetworkAccess.Read.All, Policy.Read.All - via conditionalAccessPolicies, gsaFilteringPosture |
High-risk web categories are blockedwt.entra.gsa.wcf-high-risk-blocked | HIGH | Checks that the three high-risk web categories - Criminal activity, Hacking, and Illegal software - are each blocked by the effective filtering profile. Mirroring the upstream evaluation, candidates are ordered by profile priority then rule priority, and the first enforced profile (baseline, or Conditional-Access-bound security profile) decides each category's fate. | These categories are where drive-by malware, exploit kits, and credential-theft tooling live. Priority ordering matters as much as the rule itself: a lower-priority block is meaningless if a higher-priority profile allows the same category first - which is exactly the misconfiguration this check catches. | NetworkAccess.Read.All, Policy.Read.All - via conditionalAccessPolicies, gsaFilteringPosture |
Guest user access is restrictedwt.entra.guest.access-restricted | MEDIUM | Checks that guest users are assigned a restricted role (Guest User or Restricted Guest User) rather than the same permissions as member users, limiting their ability to enumerate directory objects. | Guest accounts with member-level directory permissions can enumerate all users, groups, and applications in the tenant - providing attackers who compromise a guest account with the reconnaissance needed for targeted phishing, social engineering, and privilege escalation attacks. | Policy.Read.All - via authorizationPolicy |
Collaboration invitations are sent to allowed domains onlywt.entra.guest.allowed-domains-only | MEDIUM | Checks that external collaboration invitations (B2B) can only be sent to email addresses belonging to explicitly approved domains, preventing invitations to arbitrary external parties. | Unrestricted B2B invitations allow any internal user to invite anyone from any domain, including personal email providers and known threat-actor infrastructure. This enables data exfiltration via deliberate sharing and increases the risk of insider threats sharing sensitive data with unauthorised external parties. | None - no tenant data read (not automatable via API, reported as a manual/indeterminate result) |
A dynamic group for guest users is createdwt.entra.guest.dynamic-group-exists | MEDIUM | Checks that at least one dynamic group exists with a membership rule that automatically captures all guest users in the tenant, enabling consistent policy targeting. | Without a dynamic guest group, Conditional Access policies, access reviews, and lifecycle governance must target individual guest accounts manually. Missed accounts create security gaps where guest users escape oversight - a particular risk for data exfiltration and prolonged unauthorised access after collaboration has ended. | Group.Read.All - via groups |
Inactive guest accounts are disabled or removedwt.entra.guest.inactive-disabled | MEDIUM | Checks that no enabled guest account has been inactive for over 90 days, measured from the last successful sign-in and falling back to the account's creation date for guests who never signed in. Requires Entra ID P1 (sign-in activity); without it this check reports INDETERMINATE. | Guests outlive the collaborations that created them: the project ends, the account stays, and its group memberships and sharing links stay with it - watched by nobody on either side of the federation. Phished external mailboxes are exactly how those dormant accounts come back to life. | AuditLog.Read.All, User.Read.All - via guestUserActivity |
Guest user invitations are limited to the Guest Inviter rolewt.entra.guest.invitations-restricted | HIGH | Checks that only administrators and users assigned the Guest Inviter role can invite external users, preventing all member users from inviting guests independently. | When any member user can invite guests, the tenant's B2B surface grows in an uncontrolled manner. Attackers who compromise a member account can invite external identities they control, creating persistent backdoor access that survives password resets on the compromised account. | Policy.Read.All - via authorizationPolicy |
Guests own no applications or service principalswt.entra.guest.no-app-ownership | MEDIUM | Checks that no guest user owns an application or service principal in the tenant. The guest-owner join is resolved at collection time; evidence carries only the offending pairs. | An app owner can add credentials to it. A guest owner is therefore an external identity - secured by someone else's tenant, phishable through someone else's defences - holding a credential-minting right in yours. Whatever that app can access, the guest's home-tenant compromise can access too. | Directory.Read.All, User.Read.All - via guestOwnedObjects |
Guests hold no privileged directory roleswt.entra.guest.no-privileged-roles | HIGH | Checks that no guest account (userType Guest) holds an Entra-flagged privileged directory role. External identities administering the tenant are governed by their home tenant's security posture, not yours. | A guest with a privileged role means another organisation's account can administer your tenant - under that organisation's password policy, MFA enforcement, and offboarding process, none of which you can see or control. If the guest's home account is compromised, or simply never disabled after they leave that organisation, the attacker or ex-employee still holds privileged access to YOUR tenant. | RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory, User.Read.All - via privilegedUsers |
Guest self-service sign-up via user flows is disabledwt.entra.guest.self-signup-disabled | MEDIUM | Checks that the tenant's authentication flows policy reports self-service sign-up via user flows disabled. When enabled, anyone holding a sign-up flow link can create a guest account in the tenant without an invitation. | Every other guest control in this catalog assumes guests arrive through an invitation someone approved. A self-service sign-up flow is a standing side door: the link circulates in mail threads and wikis indefinitely, and each use mints a guest identity with whatever access the flow's app grants - no inviter, no review, no expiry decision. | Policy.Read.All - via authenticationFlowsPolicy |
Every guest account has a sponsorwt.entra.guest.sponsors-assigned | LOW | Checks that every guest account has at least one sponsor - the internal user or group accountable for the guest's continued access. Watchtower records sponsor COUNTS only, never who. | The sponsor field is what makes guest governance actionable: access reviews route to the sponsor, offboarding asks the sponsor, and the inactive-guest sweep escalates to the sponsor. A sponsorless guest is unowned on both sides of the federation - nobody internal is answerable for what it can reach. | User.Read.All - via guestSponsors |
Entra Connect syncs with service principal credentialswt.entra.hybrid.connect-sp-credentials | HIGH | Checks that when on-premises sync is enabled, no enabled user account holds the Directory Synchronization Accounts role - modern Entra Connect authenticates through an application (service principal) identity instead. Cloud-only tenants pass automatically. | The legacy sync account is a password-bearing user with standing directory-write power and a name every attacker knows to look for (Sync_*). It cannot do MFA, is excluded from CA by design, and its compromise is tenant-wide. Application-based authentication removes the password from the equation entirely. | Directory.Read.All - via entraConnectSyncAccounts |
Password hash sync is enabled for hybrid deploymentswt.entra.hybrid.password-hash-sync | HIGH | Checks that Password Hash Synchronisation is enabled in Entra Connect for hybrid environments, allowing Entra Identity Protection to detect leaked credentials and enabling cloud authentication fallback. | Without password hash sync, Entra Identity Protection cannot evaluate whether on-premises credentials have been exposed in breach databases, leaving the tenant blind to leaked-credential attacks. Password hash sync also enables cloud-only authentication fallback if on-premises AD becomes unavailable, preventing service disruption from ransomware or AD outages. | OnPremDirectorySynchronization.Read.All - via onPremisesSynchronization |
LinkedIn account connections is disabledwt.entra.linkedin-connections-disabled | MEDIUM | Checks that the LinkedIn account connections feature is disabled, preventing users from linking their work Microsoft account to their LinkedIn profile. | Linking work accounts to LinkedIn exposes organisational relationship data and user profiles to a third-party social network. This data can be harvested by threat actors for targeted spear-phishing, social engineering, and business email compromise by building detailed profiles of employees and their relationships. | None - no tenant data read (not automatable via API, reported as a manual/indeterminate result) |
Password expiration policy is set to never expirewt.entra.password.never-expires | LOW | Checks that verified domain password policies are configured to never expire, aligning with NIST SP 800-63B guidance that routine expiration increases security risk rather than reducing it. | Mandatory password rotation leads users to choose predictable incremental passwords (e.g. Password1!, Password2!) and to reuse passwords across systems - both of which make credential attacks more effective. NIST and Microsoft guidance favour non-expiring passwords combined with MFA and breach-detection monitoring instead. | Domain.Read.All - via domains |
Per-user MFA is disabledwt.entra.per-user-mfa-disabled | CRITICAL | Checks that per-user MFA (the legacy enforcement method) is in a disabled state for all accounts, confirming that MFA is managed exclusively through Conditional Access policies rather than the legacy per-user toggle. | Per-user MFA and Conditional Access MFA can conflict, creating gaps where accounts appear protected but can bypass MFA under certain conditions. Per-user MFA also lacks the granularity of Conditional Access and does not support modern authentication strength requirements. Running both simultaneously increases misconfiguration risk. | User.Read.All - via perUserMfaStates |
Privileged role assignment notifications reach someonewt.entra.pim.assignment-alerts | MEDIUM | Checks that across every privileged role's PIM policy, the full set of nine notification rules (admin / assignee / approver, for eligibility, active assignment, and activation) is not silenced - each configured rule keeps default recipients or explicit addresses. Requires Entra ID P2; without it this check reports INDETERMINATE. | Assignment notifications are the audit trail humans actually read: a new eligible or active assignment to a privileged role lands in someone's inbox the moment it happens. Silencing them is exactly what an attacker who gains role-management rights would do first - and what well-meaning admins do to reduce noise, leaving privilege changes visible only to whoever thinks to query the audit log. | RoleManagement.Read.Directory - via pimNotificationRules |
Global Administrator activations raise an alertwt.entra.pim.ga-activation-alert | MEDIUM | Checks that the Global Administrator role's PIM policy sends its activation notification to someone - default recipients enabled or explicit addresses configured. Requires Entra ID P2; without it this check reports INDETERMINATE. | GA activation is the single highest-signal event in the tenant: legitimate ones are rare and planned, so an unexpected one IS the incident. If the activation alert reaches no one, an attacker activating an eligible GA assignment - or a compromised admin quietly self-elevating - happens with zero eyes on it. | RoleManagement.Read.Directory - via pimNotificationRules |
Access reviews for guest users are configuredwt.entra.pim.guest-access-reviews | MEDIUM | Checks that recurring access reviews are configured in Entra Identity Governance to periodically validate that all guest user accounts still require access to the tenant. | Guest accounts that are no longer needed represent a persistent access risk - a former collaborator's account that remains active can be exploited if their personal email or identity provider is compromised. Regular access reviews ensure stale guest accounts are identified and removed, reducing the tenant's attack surface. | None - no tenant data read (not automatable via API, reported as a manual/indeterminate result) |
Approval is required for Privileged Role Administrator activationwt.entra.pim.pra-approval-required | HIGH | Checks that the Privileged Role Administrator role in PIM is configured to require explicit approval before activation, preventing unilateral role assignment without a second authorised approver. | The Privileged Role Administrator role can assign any directory role to any user - including Global Administrator. Without approval gating, a compromised account with this role can immediately escalate to full tenant control. Requiring approval creates a mandatory human review step that detects and blocks unauthorised privilege escalation. | PrivilegedAccess.Read.AzureAD, RoleManagement.Read.Directory - via praRoleManagementPolicyRules |
All privileged role activations raise an alertwt.entra.pim.privileged-activation-alerts | MEDIUM | Checks that every Entra-flagged privileged role has a PIM policy whose activation notification reaches someone. A role with no PIM policy, or with the alert silenced (default recipients off, no custom recipients), fails. Requires Entra ID P2; without it this check reports INDETERMINATE. | Attackers avoid the watched roles: if Global Administrator activations page the security team but Privileged Authentication Administrator activations are silent, that is where the escalation happens - it resets any admin's credentials just the same. Alert coverage has to match the privileged surface, not just its most famous member. | RoleManagement.Read.Directory - via pimNotificationRules |
Access reviews for privileged roles are configuredwt.entra.pim.privileged-role-reviews | HIGH | Checks that recurring access reviews are configured in PIM for privileged Entra directory roles, ensuring role assignments are periodically validated by an authorised reviewer. | Stale or unnecessary privileged role assignments accumulate over time through role creep and forgotten one-off assignments. Each unreviewed privileged account is a potential account-takeover target that grants an attacker elevated access. Regular reviews ensure only personnel with a current need hold privileged roles. | None - no tenant data read (not automatable via API, reported as a manual/indeterminate result) |
Privileged Identity Management is used to manage roleswt.entra.pim.used-for-roles | HIGH | Checks that privileged Entra directory roles are managed through Privileged Identity Management (PIM) as eligible assignments rather than permanent active assignments. | Permanent active role assignments mean privileged accounts hold elevated privileges continuously - every session, every token, every minute. If such an account is compromised, the attacker immediately has full administrative access. PIM's just-in-time model ensures privileges are active only when explicitly activated, drastically reducing the window of exposure for credential theft. | RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory, User.Read.All - via privilegedUsers |
All Entra recommendations are addressedwt.entra.recommendations.all-addressed | LOW | Checks that no Microsoft Entra recommendation is in the active or postponed state. Recommendations are Microsoft's own telemetry-driven findings about the tenant, each with a documented fix. | An unaddressed recommendation is a known gap that Microsoft has already diagnosed and written the remediation for - the cheapest security work available. Postponing them indefinitely turns the recommendations blade into a list of accepted risks nobody actually accepted. | DirectoryRecommendations.Read.All - via directoryRecommendations |
High-priority Entra recommendations are addressedwt.entra.recommendations.high-priority-addressed | MEDIUM | Checks that no high-priority Microsoft Entra recommendation is in the active or postponed state. The companion check wt.entra.recommendations.all-addressed covers the full list; this one isolates the subset Microsoft's own triage rates most urgent. | High-priority recommendations flag things like admins without MFA and legacy authentication still enabled - the exact findings an attacker's first reconnaissance pass would produce. When Microsoft's triage and an attacker's triage agree, that item does not belong in a backlog. | DirectoryRecommendations.Read.All - via directoryRecommendations |
High-risk sign-in detections are triagedwt.entra.risk.signins-triaged | HIGH | Checks that no risk detection sits at riskState atRisk with riskLevel high in Identity Protection. Watchtower records only the COUNT. Requires Entra ID P2; without it this check reports INDETERMINATE. | Each untriaged high-risk detection is a specific event - impossible travel, anonymized IP, leaked credential match - that the platform flagged and no human ruled on. The detections queue is where 'suspicious' becomes either 'incident' or 'noise'; leaving it to accumulate makes both answers invisible. | IdentityRiskEvent.Read.All, IdentityRiskyUser.Read.All - via userRiskState |
High-risk users are triagedwt.entra.risk.users-triaged | HIGH | Checks that no user sits at riskState atRisk with riskLevel high in Identity Protection. Watchtower records only the COUNT - risky-user identities never enter evidence. Requires Entra ID P2; without it this check reports INDETERMINATE. | A high-risk atRisk user is Entra's machine-learning saying 'this account is probably compromised' - and the queue entry means nobody has answered. Detection without triage is just logging: the account keeps working, keeps its sessions, and keeps whatever the attacker is doing with it. | IdentityRiskEvent.Read.All, IdentityRiskyUser.Read.All - via userRiskState |
Risky workload identities are triagedwt.entra.risk.workload-identities-triaged | HIGH | Checks that no service principal and no service-principal risk detection sits at riskState atRisk. Requires the Entra Workload ID license; without it this check reports INDETERMINATE. | A compromised user eventually notices - password stops working, MFA prompts appear. A compromised service principal notices nothing, reports nothing, and keeps authenticating. The workload-identity risk queue is the only surface where that compromise ever becomes visible, which makes an unworked queue equivalent to no detection at all. | IdentityRiskEvent.Read.All, IdentityRiskyServicePrincipal.Read.All - via workloadIdentityRiskState |
Risky workload identity sign-ins are triagedwt.entra.risk.workload-signins-triaged | HIGH | Checks that no service-principal risk detection with activity signIn sits at riskState atRisk - the subset of workload risk where the suspicious credential was actually USED to authenticate. Requires the Entra Workload ID license; without it this check reports INDETERMINATE. | A risky workload sign-in is past-tense compromise evidence: the flagged credential authenticated from suspicious infrastructure. Every hour it stays untriaged is an hour an attacker's token factory keeps running under a legitimate app's name. | IdentityRiskEvent.Read.All, IdentityRiskyServicePrincipal.Read.All - via workloadIdentityRiskState |
Users cannot create security groupswt.entra.security-group-creation-restricted | MEDIUM | Checks that standard users are prevented from creating new security groups in Entra ID, ensuring group creation is controlled by administrators. | Unrestricted security group creation allows users - including compromised accounts - to create groups that can be used to grant application permissions, bypass Conditional Access scope restrictions, or establish unauthorised privilege escalation paths through group-based role assignments. | Policy.Read.All - via authorizationPolicy |
Open-assignment SSO apps use scoped provisioningwt.entra.sp.assignment-or-scoped-provisioning | MEDIUM | Checks that every SSO-enabled enterprise app that does not require user assignment compensates with scoped provisioning: provisioning jobs exist and every job's object mappings carry a scoping filter. Unreadable schemas count as unscoped (mirrors upstream). Requires Synchronization.Read.All (added to the consent surface 2026-07-03); until a tenant re-consents this check reports INDETERMINATE. | No assignment requirement plus unscoped provisioning is the whole-directory export: every user - guests, service accounts, the lot - gets pushed into the downstream system the moment sync runs. Either the app's front door has a guest list (assignment required) or its sync has one (scoping filters); having neither is a data-governance incident on a schedule. | Synchronization.Read.All - via spProvisioning |
High-privilege applications have at least two ownerswt.entra.sp.high-privilege-owners | HIGH | Checks that every enterprise application holding a high-privilege Microsoft Graph permission (Directory.ReadWrite.All, Mail.*, RoleManagement.ReadWrite.Directory, and the rest of the upstream ZTA classification) has at least two owners. Managed identities are out of scope - owners cannot be assigned to them. Apps with only lower-risk permissions are assessed by wt.entra.sp.owners-assigned. | An app that can rewrite the directory or read every mailbox, with nobody accountable for it, is the tenant's most attractive unwatched credential: no owner reviews its consent, notices a freshly added secret, or answers 'is this still needed?'. Requiring two owners also survives the single owner leaving the organisation - the moment ownership drops to zero, the app governs itself. | Directory.Read.All - via servicePrincipals |
Inactive applications hold no high-privilege Graph permissionswt.entra.sp.inactive-apps-no-high-privileges | HIGH | Checks that every application holding a high-privilege Microsoft Graph permission (per the upstream ZTA risk classification) has sign-in activity on record. Unlike the ownership checks, managed identities are in scope. Also reads the servicePrincipals source. | The permissions plane twin of the privileged-roles check: Directory.ReadWrite.All granted to an app nobody has run in months is a skeleton key in a drawer. Consent reviews rarely revisit old grants, so the inactive-plus-high-privilege intersection is where over-permissioning accumulates unnoticed. | AuditLog.Read.All, Directory.Read.All - via servicePrincipalSignInActivities, servicePrincipals |
Inactive applications hold no privileged directory roleswt.entra.sp.inactive-apps-no-privileged-roles | HIGH | Checks that every privileged directory role held by a service principal belongs to an application with sign-in activity on record. An app that holds Global Administrator-class privilege but never signs in fails. Also reads the directoryRoles and servicePrincipals sources. | An application that hasn't signed in within the report window isn't using its privileged role - but its credentials still unlock it. Dormant privileged apps are the quietest escalation path in the tenant: no user notices them, no sign-in alerts fire on them, and their secrets sit in old pipelines and key vaults waiting to be found. | AuditLog.Read.All, Directory.Read.All, RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory - via directoryRoles, servicePrincipalSignInActivities, servicePrincipals |
Microsoft services applications have no credentials configuredwt.entra.sp.microsoft-services-no-credentials | HIGH | Checks that no Microsoft first-party service principal (owned by the Microsoft Services tenant) carries a client secret or certificate. These SPs authenticate through Microsoft's own infrastructure and never need tenant-local credentials. | First-party Microsoft service principals are pre-consented and often highly privileged. An attacker who adds a credential to one gains sign-in as that app - persistence that survives user password resets and MFA, and blends into first-party traffic. This exact technique was used in the Solorigate/NOBELIUM intrusions; a credential on a Microsoft services SP is a compromise indicator, not a hygiene finding. | Directory.Read.All - via servicePrincipals |
Service principals do not carry credentials on the SP objectwt.entra.sp.no-credentials | MEDIUM | Checks that no service principal carries a client secret or certificate directly on the SP object. Credentials belong on the app registration, where the owning tenant manages them; Microsoft first-party service principals are assessed by wt.entra.sp.microsoft-services-no-credentials. | A credential added to a service principal is invisible to the app's owner and survives rotation of the app registration's own credentials - which is why it is a favoured persistence technique after tenant compromise. Even when added legitimately, it splits credential inventory across two objects and defeats rotation policy applied at the registration. | Directory.Read.All - via servicePrincipals |
Workload identities hold no privileged directory roleswt.entra.sp.no-privileged-roles | HIGH | Checks that no service principal or managed identity holds an Entra-flagged privileged directory role. Workload identities should carry least-privilege Graph application permissions, not directory roles. | A workload identity with a privileged role is a standing non-human administrator: whoever can authenticate as the app - via a leaked secret, a stolen certificate, or a compromised consuming tenant - gets the role with no MFA, no Conditional Access, and no sign-in risk evaluation in the way. Combined with credential-addition techniques, it is a complete, quiet tenant-takeover path. | Directory.Read.All, RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory - via directoryRoles, servicePrincipals |
Enterprise applications have at least two ownerswt.entra.sp.owners-assigned | MEDIUM | Checks that every enterprise application holding any Microsoft Graph permission whose risk classifies below high (medium, low, or unranked in the upstream ZTA table) has at least two owners. Managed identities are out of scope - owners cannot be assigned to them. High-privilege apps are assessed by wt.entra.sp.high-privilege-owners. | Ownerless applications rot: consent granted years ago with nobody to ask whether it is still needed, nobody notified when a credential is added, nobody to name in an access review. Ownership is the cheapest governance control there is - one field - and the prerequisite for every other app-lifecycle process working. | Directory.Read.All - via servicePrincipals |
SSO apps that support provisioning have it configuredwt.entra.sp.provisioning-configured | MEDIUM | Checks that every SSO-enabled enterprise application whose vendor publishes provisioning templates actually has a provisioning job configured. Requires the Synchronization.Read.All permission (added to the consent surface 2026-07-03); until a tenant re-consents this check reports INDETERMINATE. | Automatic provisioning is what makes offboarding real in SaaS apps: the account dies when the directory says so. An app that supports it but runs without it accumulates manually-created accounts that outlive their owners - the classic 'ex-employee still had access to the CRM' finding. | Synchronization.Read.All - via spProvisioning |
Service principals use safe redirect URIswt.entra.sp.safe-redirect-uris | MEDIUM | Checks that no third-party enterprise app (a service principal owned by another tenant) configures an unsafe reply URL - a wildcard, or plaintext http:// to a non-loopback host. Own-tenant registrations are covered by wt.entra.app.safe-redirect-uris; Microsoft first-party service principals are out of scope. (Watchtower's redirect-URI-hygiene interpretation of the ZTA control.) | Reply URLs on a service principal receive authorization codes and SAML assertions exactly like app-registration redirect URIs - but for third-party apps YOUR tenant's sign-ins are delivered to a vendor-controlled URL list. A wildcard or plaintext-http entry there, or a URL on an abandoned host such as a deleted *.azurewebsites.net site, lets an attacker intercept your users' tokens without touching your own registrations. | Directory.Read.All - via applications, servicePrincipals |
Administrators are blocked from self-service password resetwt.entra.sspr-blocked-for-admins | MEDIUM | Checks that administrators cannot use self-service password reset (allowedToUseSSPR disabled on the authorization policy). Admin password changes should go through a controlled, auditable process, not the self-service flow. Standard-user SSPR (wt.entra.sspr-enabled-all) is unaffected. | SSPR resets a password after answering registration challenges - for an administrator, that makes the account only as strong as its weakest registered recovery method. An attacker who compromises an admin's phone number or alternate email can use the self-service flow to take over the account. Routing admin resets through a controlled process keeps a human check in front of the most privileged credentials. | Policy.Read.All - via authorizationPolicy |
Self service password reset enabled is set to Allwt.entra.sspr-enabled-all | MEDIUM | Checks that Self-Service Password Reset (SSPR) is enabled for all users, allowing them to reset their own passwords without contacting the help desk. | When SSPR is unavailable, users with forgotten or expired passwords must contact help desk staff who may use identity verification procedures susceptible to social engineering - a common attack vector for business email compromise. SSPR with properly configured verification methods reduces this human risk while improving availability. | AuditLog.Read.All, UserAuthenticationMethod.Read.All - via userRegistrationDetails |
The option to remain signed in is hiddenwt.entra.stay-signed-in-hidden | MEDIUM | Checks that the 'Stay signed in?' prompt is hidden from users during authentication, preventing the creation of persistent browser sessions on shared or unmanaged devices. | When users opt to stay signed in on shared workstations, library computers, or personal devices used outside of corporate MDM policies, their session tokens persist beyond the work session. A subsequent user of that device or an attacker with physical access can hijack the active session without needing credentials or MFA. | None - no tenant data read (not automatable via API, reported as a manual/indeterminate result) |
Restrict non-admin users from creating tenants is set to Yeswt.entra.tenant-creation-restricted | MEDIUM | Checks that standard users are prevented from creating new Microsoft Entra tenants, ensuring all tenant provisioning is controlled by administrators. | Users who can create new tenants can establish shadow IT environments outside of corporate governance, potentially storing sensitive data without security controls. Compromised accounts with tenant-creation privileges could also be used to build attacker-controlled infrastructure that appears legitimate and shares the same domain namespace. | Policy.Read.All - via authorizationPolicy |