In this module
MSA1.8 Stale Identities and Access Accumulation
You've seen stale accounts in every tenant you've worked with — the contractor who left six months ago but whose account is still active, the service account for an application that was decommissioned last year, the guest accounts from a project that ended in 2023. MSA1.7 showed you how the organization's lifecycle gaps create these accounts systematically. This sub teaches you to find every stale identity across every category, quantify the risk with specific numbers, and implement the immediate remediation for each category. Finding stale identities is the diagnostic. Preventing future accumulation (MSA1.7's lifecycle automation plus MSA12's access reviews) is the treatment.
Every stale identity in the directory is dormant access that an attacker can reactivate. A disabled account with 14 group memberships is one Set-MgUser -AccountEnabled $true away from having full access to 14 groups' worth of resources. A stale guest account with PendingAcceptance status from 2022 is an invitation that an attacker could accept if they compromise the guest's email. A service account with expired credentials but active permissions might still have valid tokens in circulation. Each stale identity is a liability — a trust relationship, a permission grant, or a credential that serves no current purpose but creates ongoing risk. This sub teaches you to detect every category of stale identity using systematic queries, quantify the risk for your environment, implement immediate remediation, and design the architectural controls that prevent future accumulation.
Estimated time: 50 minutes.
The signInActivity property — how Entra ID tracks usage
The signInActivity property on user objects is the primary staleness indicator. It exposes four timestamps that tell you when and how an identity was last used:
lastSignInDateTime — the most recent interactive sign-in (the user opened a browser or app and actively authenticated). lastNonInteractiveSignInDateTime — the most recent background token refresh (an app silently renewed a token on the user's behalf). lastSuccessfulSignInDateTime — the most recent sign-in that completed successfully (excludes blocked sign-ins and failed attempts). lastSignInRequestId — the correlation ID of the most recent sign-in event (useful for cross-referencing with the full sign-in log).
The signInActivity property requires an Entra ID P1 license for the tenant (not per-user) and the AuditLog.Read.All + User.Read.All permissions for the querying application. The timestamps update within 24 hours of the event — they're not real-time but are accurate enough for staleness analysis.
Connect-MgGraph -Scopes "User.Read.All","AuditLog.Read.All"
# Examine a single user's signInActivity to understand the structure
$phil = # Pick any active user in your tenant to examine signInActivity
$sampleUser = Get-MgUser -All -Property DisplayName, SignInActivity -Top 1
$sampleUser = Get-MgUser -UserId $sampleUser.Id `
-Property DisplayName, SignInActivity
Write-Host "User: $($phil.DisplayName)"
Write-Host "Last interactive: $($phil.SignInActivity.LastSignInDateTime)"
Write-Host "Last non-interactive: $($phil.SignInActivity.LastNonInteractiveSignInDateTime)"
Write-Host "Last successful: $($phil.SignInActivity.LastSuccessfulSignInDateTime)"
Write-Host "Last request ID: $($phil.SignInActivity.LastSignInRequestId)"{
"User": "the IT Director",
"LastInteractive": "2026-05-05T07:33:18Z",
"LastNonInteractive": "2026-05-05T08:14:22Z",
"LastSuccessful": "2026-05-05T08:14:22Z",
"LastRequestId": "a3b4c5d6-e7f8-9012-abcd-ef1234567890"
}Phil signed in interactively this morning and had a non-interactive token refresh 41 minutes later (Outlook refreshing silently). Both the interactive and the most recent successful sign-in are today. This is an active, healthy identity.
The LastNonInteractive timestamp is often more recent than LastInteractive because applications refresh tokens in the background without user interaction. A user who signed in interactively once on Monday morning might have LastNonInteractive timestamps updating throughout the week as Outlook, Teams, and OneDrive silently refresh their tokens. If LastNonInteractive is recent but LastInteractive is weeks old, the user's applications are maintaining sessions but the user hasn't actively authenticated — this could be normal (they stay signed in on a compliant device) or concerning (a stolen session token is being refreshed).
Stale member accounts — the 90-day query
The standard staleness threshold for member accounts is 90 days — a user who hasn't signed in for 90 days is unlikely to be using the account. Organizations with seasonal patterns (academic institutions, project-based companies) may need a longer threshold. the organization's workforce is full-time, so 90 days is appropriate.
$threshold = (Get-Date).AddDays(-90).ToUniversalTime()
$staleMembers = Get-MgUser -All `
-Property DisplayName, UserPrincipalName, UserType, AccountEnabled,
OnPremisesSyncEnabled, SignInActivity, CreatedDateTime, Department `
-ConsistencyLevel eventual |
Where-Object {
$_.AccountEnabled -eq $true -and
$_.UserType -eq 'Member' -and
(
$_.SignInActivity.LastSignInDateTime -lt $threshold -or
-not $_.SignInActivity.LastSignInDateTime
)
}
# Exclude known inactive-by-design accounts (break-glass)
# Exclude your break-glass accounts (replace with your actual UPNs)
$breakGlass = @("bg01@$((Get-MgOrganization).VerifiedDomains.Where({$_.IsDefault}).Name)", "bg02@$((Get-MgOrganization).VerifiedDomains.Where({$_.IsDefault}).Name)")
$staleMembers = $staleMembers | Where-Object {
$_.UserPrincipalName -notin $breakGlass
}
Write-Host "Active member accounts, no sign-in in 90+ days (excl. break-glass): $($staleMembers.Count)"
Write-Host ""
$staleMembers | Sort-Object { $_.SignInActivity.LastSignInDateTime } |
Select-Object DisplayName, UserPrincipalName,
@{N='LastSignIn';E={
if ($_.SignInActivity.LastSignInDateTime) {
$_.SignInActivity.LastSignInDateTime.ToString("yyyy-MM-dd")
} else { "Never" }
}},
@{N='DaysSinceSignIn';E={
if ($_.SignInActivity.LastSignInDateTime) {
[int]((Get-Date).ToUniversalTime() - $_.SignInActivity.LastSignInDateTime).TotalDays
} else { "Never" }
}},
@{N='Synced';E={if($_.OnPremisesSyncEnabled){"Yes"}else{"No"}}},
Department -First 10Active member accounts, no sign-in in 90+ days (excl. break-glass): 21
DisplayName UPN LastSignIn DaysSince Synced Department
----------- --- ---------- --------- ------ ----------
James Barker j.barker@yourtenant.onmicrosoft.com Never Never Yes Engineering
Linda Chen l.chen@yourtenant.onmicrosoft.com Never Never Yes Operations
Martin O'Brien m.obrien@yourtenant.onmicrosoft.com 2024-11-18 534 Yes (null)
Karen Walsh k.walsh@yourtenant.onmicrosoft.com 2025-01-14 477 Yes Engineering
Steve Hartley s.hartley@yourtenant.onmicrosoft.com 2025-01-22 469 Yes (null)
Fiona Campbell f.campbell@yourtenant.onmicrosoft.com 2025-02-03 457 Yes Facilities
David Morton d.morton@yourtenant.onmicrosoft.com 2025-02-08 452 No IT
Rebecca Turner r.turner@yourtenant.onmicrosoft.com 2025-02-14 446 Yes (null)
Patrick Willis p.willis@yourtenant.onmicrosoft.com 2025-02-19 441 Yes Sales
Janet Okafor j.okafor@yourtenant.onmicrosoft.com 2025-03-04 428 Yes FinanceReading your results:
If your stale count is zero, your lifecycle management is either effective or your tenant is new (users haven't had time to go stale). A developer tenant with accounts created this week will show zero stale users — that's expected.
If you find stale accounts, look for patterns. "Never signed in" accounts may be synced from on-premises AD but never used cloud services. Accounts with a null Department attribute are invisible to attribute-driven automation — they fall through every dynamic group and lifecycle workflow. Accounts inactive for 400+ days are almost certainly stale — no legitimate employee goes a year without signing in.
If your tenant is cloud-only, you won't see synced accounts in the results. Focus on cloud-only accounts with old LastSignIn dates and any accounts that were never used.
Review your stale account results. Common patterns: accounts synced from AD that were never used in the cloud (never-signed-in), accounts with null Department attributes that are invisible to automation, and long-inactive accounts from incomplete leaver processes. Each stale account is a potential credential stuffing or password spray target with residual group memberships that grant access.
Entra Admin Center
View inactive users:
Identity → Users → All users → click the Activity column header to sort
Users with no recent sign-in show a high day count or "Never." Click any user → Sign-in logs for detailed authentication history.
Recommendations:
Identity → Overview → Recommendations
The "Address inactive users" recommendation automatically identifies stale accounts and suggests remediation.
Stale guest accounts — the forgotten trust relationships
Guest accounts have a different staleness pattern. Member users should be signing in daily. Guest users may only access your tenant during active collaboration periods — a 90-day gap might be normal between project phases. The standard guest staleness threshold is 180 days, but the right number depends on your collaboration pattern.
$guestThreshold = (Get-Date).AddDays(-180).ToUniversalTime()
$allGuests = Get-MgUser -All -Filter "userType eq 'Guest'" `
-Property DisplayName, Mail, ExternalUserState, CreatedDateTime,
SignInActivity, AccountEnabled, CreationType
$staleGuests = $allGuests | Where-Object {
$_.AccountEnabled -eq $true -and
(
$_.SignInActivity.LastSignInDateTime -lt $guestThreshold -or
-not $_.SignInActivity.LastSignInDateTime
)
}
Write-Host "Total guests: $($allGuests.Count)"
Write-Host "Stale guests (180d): $($staleGuests.Count) ($([math]::Round($staleGuests.Count/$allGuests.Count*100))%)"
Write-Host ""
# Break down by category
$pending = $staleGuests | Where-Object { $_.ExternalUserState -eq 'PendingAcceptance' }
$accepted = $staleGuests | Where-Object { $_.ExternalUserState -eq 'Accepted' }
$blank = $staleGuests | Where-Object { -not $_.ExternalUserState }
Write-Host "Category breakdown:"
Write-Host " PendingAcceptance (never completed invitation): $($pending.Count)"
Write-Host " Accepted (authenticated once, now inactive): $($accepted.Count)"
Write-Host " Blank state (created via implicit sharing): $($blank.Count)"Total guests: 147
Stale guests (180d): 98 (67%)
Category breakdown:
PendingAcceptance (never completed invitation): 34
Accepted (authenticated once, now inactive): 52
Blank state (created via implicit sharing): 1267% of all guest accounts are stale. Each category has a different risk profile and a different remediation:
PendingAcceptance (34 accounts): These invitations were sent but never completed. The identity object exists in the organization's tenant with whatever access was intended, but the external user never authenticated. If the invitation link is still valid (or if the guest's email is compromised), someone could accept it and gain access. Remediation: delete guests with pending invitations older than 90 days.
# Remediation: Remove stale pending invitations (>90 days old)
$pendingThreshold = (Get-Date).AddDays(-90).ToUniversalTime()
$stalePending = $pending | Where-Object { $_.CreatedDateTime -lt $pendingThreshold }
Write-Host "Pending invitations older than 90 days: $($stalePending.Count)"
Write-Host ""
# Preview before deletion
$stalePending | Select-Object DisplayName, Mail,
@{N='Created';E={$_.CreatedDateTime.ToString("yyyy-MM-dd")}},
@{N='AgeDays';E={[int]((Get-Date) - $_.CreatedDateTime).TotalDays}} |
Sort-Object AgeDays -Descending | Format-Table -AutoSize
# To delete (uncomment after review):
# $stalePending | ForEach-Object {
# Remove-MgUser -UserId $_.Id
# Write-Host "Deleted: $($_.DisplayName) ($($_.Mail))"
# }Pending invitations older than 90 days: 31
DisplayName Mail Created AgeDays
----------- ---- ------- -------
Sarah Chen s.chen@parkfield.co.uk 2022-11-08 1274
David Kowalski d.kowalski@tmeacons.de 2023-01-22 1199
...Blank state (12 accounts): These were created through implicit sharing — someone shared a SharePoint document or Teams channel with an external email, creating a guest identity without a formal invitation. The creationType is null and ExternalUserState is blank. These guests may have never authenticated, or they may have accessed the shared resource once. Remediation: disable, then delete after confirming no active sharing dependencies.
Accepted but inactive (52 accounts): These guests completed the invitation and authenticated at least once, but haven't signed in for 180+ days. They may return (seasonal collaboration) or they may be permanently inactive. Remediation: disable the account (don't delete — the guest may need to be re-enabled). Schedule a review with the resource owner (the person who originally invited the guest) to confirm whether the access is still needed.
Entra Admin Center
Review guest accounts:
Identity → Users → All users → filter User type to Guest
Sort by Activity column. Guests with "Never" or high day counts are candidates for review.
Configure guest expiration policy:
Identity → External identities → External collaboration settings
The Guest user access restrictions setting controls guest permissions (MSA1.3 documented this). The guest invite settings control who can invite guests and whether admin approval is required.
Stale application registrations — credential status + sign-in correlation
Application registrations become stale when their credentials expire and nobody renews them, or when the application they represent is no longer in use. The previous credential age analysis in MSA1.2 identified 9 expired credentials. This section correlates credential status with service principal sign-in activity to determine which applications are truly abandoned.
Connect-MgGraph -Scopes "Application.Read.All","AuditLog.Read.All"
$apps = Get-MgApplication -All -Property DisplayName, AppId, CreatedDateTime,
PasswordCredentials, KeyCredentials
$analysis = foreach ($app in $apps) {
$sp = Get-MgServicePrincipal -Filter "appId eq '$($app.AppId)'" `
-Property DisplayName, SignInActivity, AccountEnabled -ErrorAction SilentlyContinue
$allCreds = @()
$app.PasswordCredentials | ForEach-Object {
$allCreds += [PSCustomObject]@{
Type='Secret'; Expires=$_.EndDateTime;
Expired=($_.EndDateTime -lt (Get-Date).ToUniversalTime())
}
}
$app.KeyCredentials | ForEach-Object {
$allCreds += [PSCustomObject]@{
Type='Cert'; Expires=$_.EndDateTime;
Expired=($_.EndDateTime -lt (Get-Date).ToUniversalTime())
}
}
$allExpired = $allCreds.Count -gt 0 -and ($allCreds | Where-Object { -not $_.Expired }).Count -eq 0
$lastSP = if ($sp.SignInActivity.LastSignInDateTime) {
$sp.SignInActivity.LastSignInDateTime.ToString("yyyy-MM-dd")
} else { "Never" }
[PSCustomObject]@{
App = $app.DisplayName
Created = $app.CreatedDateTime.ToString("yyyy-MM-dd")
Creds = $allCreds.Count
AllExpired = $allExpired
NoCreds = ($allCreds.Count -eq 0)
LastSPSignIn = $lastSP
SPEnabled = if ($sp) { $sp.AccountEnabled } else { "No SP" }
Category = if ($allExpired -and $lastSP -eq "Never") { "Abandoned" }
elseif ($allExpired -and $lastSP -ne "Never") { "Expired-Active" }
elseif (-not $allExpired -and $allCreds.Count -gt 0) { "Healthy" }
elseif ($allCreds.Count -eq 0) { "No credentials" }
else { "Unknown" }
}
}
Write-Host "Application registration analysis:"
$analysis | Group-Object Category |
Select-Object @{N='Category';E={$_.Name}}, Count | Format-Table -AutoSize
Write-Host ""
$analysis | Where-Object { $_.Category -ne "Healthy" } |
Sort-Object Created |
Select-Object App, Created, Creds, AllExpired, LastSPSignIn, Category |
Format-Table -AutoSizeApplication registration analysis:
Category Count
-------- -----
Abandoned 4
Expired-Active 4
Healthy 3
No credentials 1
App Created Creds AllExpired LastSPSignIn Category
--- ------- ----- ---------- ------------ --------
CRM Integration 2021-08-14 1 True Never Abandoned
Backup Service 2022-05-09 1 True Never Abandoned
Reporting Dashboard 2023-03-14 1 True Never Abandoned
Visitor Management 2024-01-08 1 True Never Abandoned
ERP Connector 2021-11-02 2 True 2024-08-22 Expired-Active
SharePoint Automation 2022-02-18 1 True 2024-01-15 Expired-Active
HR Data Sync 2022-09-27 1 True 2025-06-14 Expired-Active
IT Helpdesk Bot 2023-07-22 1 True 2024-11-03 Expired-Active
NE Sentinel Connector 2024-09-22 0 False 2026-05-05 No credentialsFour distinct categories:
Abandoned (4 apps): All credentials expired AND the service principal has never signed in. These applications were registered, credentials were created, but the application never successfully authenticated through its service principal. They're abandoned registrations — likely from development projects that were started and never completed, or integrations that were replaced before going live. Remediation: Delete these registrations. They serve no purpose and each one is a potential attack surface if someone creates a new valid credential for the registration.
Expired-Active (4 apps): All credentials expired BUT the service principal had sign-in activity in the past. These applications were functional at some point but their credentials expired and they stopped authenticating. The HR Data Sync app is interesting — its credentials expired but it had a sign-in as recently as June 2025. This could indicate a second credential that wasn't captured (a certificate in a Key Vault), a long-lived token that was still being refreshed, or delegated authentication on behalf of a user. Remediation: Investigate each one. Determine whether the application is still needed. If yes, rotate credentials and review permissions. If no, delete.
No credentials (1 app): The NE Sentinel Connector has zero app-owned credentials but authenticates daily. This is the managed identity pattern from MSA1.2 — it authenticates without app-owned secrets because Azure manages the credential. This is the architecturally ideal pattern.
Entra Admin Center
Review app registration credentials:
Identity → Applications → App registrations → All applications
Click any application → Certificates & secrets to view credential status and expiration dates. Expired credentials show a "Expired" badge.
Review service principal sign-in activity:
Identity → Monitoring & health → Sign-in logs → switch to Service principal sign-ins tab
Filter by application name to see authentication events. If no entries appear, the service principal hasn't authenticated in the log retention period (default: 30 days).
Disabled accounts with residual access — the leaver cleanup gap
MSA1.2 identified 31 disabled synced accounts. These are on-premises AD accounts that were disabled (the standard leaver action) and whose disabled state was synced to Entra ID. The accounts can't authenticate — but their group memberships, application assignments, and Teams memberships persist. If someone re-enables the account (in on-premises AD, where it propagates via sync), the user immediately has all their previous access.
$disabledWithGroups = Get-MgUser -All `
-Filter "accountEnabled eq false" `
-ConsistencyLevel eventual `
-Property DisplayName, UserPrincipalName, AccountEnabled, OnPremisesSyncEnabled |
ForEach-Object {
$user = $_
$groups = Get-MgUserMemberOf -UserId $_.Id -All |
Where-Object { $_.AdditionalProperties['@odata.type'] -eq '#microsoft.graph.group' }
if ($groups.Count -gt 0) {
[PSCustomObject]@{
DisplayName = $user.DisplayName
UPN = $user.UserPrincipalName
Synced = if ($user.OnPremisesSyncEnabled) { "Yes" } else { "No" }
Groups = $groups.Count
GroupNames = ($groups | ForEach-Object {
$_.AdditionalProperties['displayName']
}) -join "; "
}
}
}
Write-Host "Disabled accounts with group memberships: $($disabledWithGroups.Count)"
Write-Host ""
$disabledWithGroups | Sort-Object Groups -Descending |
Select-Object DisplayName, Groups, Synced -First 10Disabled accounts with group memberships: 27
DisplayName Groups Synced
----------- ------ ------
Robert Finch 14 Yes
Angela Morrison 11 Yes
Daniel Okafor 9 Yes
Michelle Torres 8 Yes
Kevin Hartley 8 Yes
Laura Bennett 7 Yes
Christopher Adams 6 Yes
Sarah Dawson 6 Yes
Thomas Reid 5 Yes
Helen McGregor 4 Yes27 disabled accounts with active group memberships. Robert Finch has 14 — meaning if his on-premises AD account is accidentally re-enabled (or an attacker re-enables it), he immediately has access to 14 groups' worth of resources. The leaver process disabled the account but never cleaned up the access.
Remediation — clean up group memberships for disabled accounts:
# Clean up all group memberships for disabled accounts
# WARNING: This removes all group memberships — review before executing
foreach ($account in $disabledWithGroups) {
$userId = (Get-MgUser -UserId $account.UPN).Id
$groups = Get-MgUserMemberOf -UserId $userId -All |
Where-Object { $_.AdditionalProperties['@odata.type'] -eq '#microsoft.graph.group' }
Write-Host "Cleaning $($account.DisplayName) ($($groups.Count) groups)..."
foreach ($group in $groups) {
try {
Remove-MgGroupMemberByRef -GroupId $group.Id -DirectoryObjectId $userId
Write-Host " Removed from: $($group.AdditionalProperties['displayName'])"
} catch {
Write-Host " FAILED: $($group.AdditionalProperties['displayName']) — $($_.Exception.Message)"
}
}
}The try/catch handles groups where the membership can't be removed — dynamic groups (membership is controlled by the rule, not direct removal), role-assignable groups (requires specific permissions), and on-premises synced groups (membership is managed in AD, not Entra ID). For synced groups, the cleanup must happen in on-premises AD.
the organization's stale identity summary and risk register
Category Count Risk Remediation Risk Register
---------------------------- ----- ------ ---------------------------- ----------------
Stale members (90d, active) 21 Medium Investigate. Disable if MSA1-RISK-004
confirmed stale.
Stale guests — pending >90d 31 High Delete. Invitation expired. MSA1-RISK-005
Stale guests — blank state 12 Medium Disable. Review sharing. (included in 005)
Stale guests — inactive 180d 52 Medium Disable. Review with owner. (included in 005)
Abandoned app registrations 4 Medium Delete. No function. MSA1-RISK-006
Expired-active apps 4 High Investigate. Rotate or delete. MSA1-RISK-007
Disabled accts with groups 27 Medium Remove group memberships. MSA1-RISK-008
TOTAL stale identities 151 5 risk entries151 stale identities out of 1,006 in the census — 15.0%. One in seven identities in the organization's tenant is stale. Each is dormant access, a trust relationship, or a credential that serves no current purpose.
The five risk register entries feed directly into MSA1.11 (NE Identity Assessment) where they're prioritized alongside the other identity gaps documented across MSA1.1–1.8.
Architectural controls that prevent future accumulation
Detecting stale identities is necessary but not sufficient. The architectural goal is preventing accumulation in the first place. Five controls, each designed in a later module:
Lifecycle Workflows (MSA1.7, MSA12): Automated leaver workflows that remove group memberships (not just disable accounts), revoke licenses, and clean up application assignments. Automated mover workflows that adjust access when roles change. This is the primary prevention mechanism.
Access reviews (MSA12): Recurring reviews of group membership, application assignments, and guest access. Reviewers confirm or deny each identity's continued need. Denied identities are automatically disabled or removed. Quarterly for sensitive resources, annually for standard.
Guest invitation policies and expiration (MSA3, MSA12): Restrict who can invite guests (admin-only for unverified domains). Require approval for guest invitations from external organizations. Configure automatic guest access expiration (Entra ID can expire guest access after a configurable period — 30, 60, 90, or 180 days).
Application credential monitoring (MSA4, MSA9): Alert when app registration credentials approach expiration. Alert when an app registration's last valid credential expires. Mandate certificate-based authentication over client secrets, with auto-rotation via Key Vault. Require managed identities for all Azure workloads.
Stale identity detection automation (MSA9): Schedule the queries from this sub as a recurring health check — weekly stale member scan, monthly stale guest scan, monthly app credential scan. Integrate findings with the governance dashboard. This transforms a point-in-time diagnostic into continuous monitoring.
The IT team runs a "stale user cleanup" once a year, usually before an audit. They query users who haven't signed in for 90 days and disable the obvious ones. They don't check guest accounts — nobody owns the guest population. They don't check app registrations — developers created those. They don't check group memberships on disabled accounts — why would they, the account is disabled. They miss the legacy service accounts, the implicit guests from SharePoint sharing, and the abandoned app registrations with expired credentials. The annual cleanup catches 30% of stale identities and calls it done. The remaining 70% persist until the next cycle. The architectural fix isn't a better annual cleanup — it's automation that prevents staleness from accumulating and continuous monitoring that detects it when it does.
Before moving on, verify your understanding: Run the stale member query against your developer tenant. If you have break-glass accounts, confirm they're excluded from the results. Explain why break-glass accounts should be excluded from staleness reporting even though they appear stale. Run the stale guest query against your tenant. 34 are PendingAcceptance. Explain the specific attack scenario for a 3-year-old pending invitation — who could accept it, and what would they gain?
Reusable script — the commands from this sub assembled for operational use:
Copy the NE stale identity summary and the five risk register entries into your architecture package at 01-identity/stale-identity-report.md. For each category, include the query, the count, the risk rating, and the specific remediation action.
Run the queries against your own tenant. Compare your stale identity percentage to the organization's 15.0% baseline. In our experience assessing tenants, 15% is typical for a mid-sized organization with manual lifecycle management. Organizations with no lifecycle automation and no access reviews often exceed 25%. Organizations with mature lifecycle automation and quarterly access reviews typically operate below 5%.
The stale identity report is the input for two downstream deliverables: MSA1.11 (NE Identity Assessment) consolidates this report with all other MSA1 findings into the complete identity architecture gap analysis. MSA12 (Governance Architecture) designs the controls that reduce this number toward zero over time.
You're reading the free modules of m365-security-architecture
The full course continues with advanced topics, production detection rules, worked investigation scenarios, and deployable artifacts.