checks/library/sp-multiple-sensitive-approles.ps1

# Service principals assigned more than one high-privilege Microsoft Graph app role.
$graph = @(Get-GraphAll "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=appId eq '00000003-0000-0000-c000-000000000000'&`$select=id,appRoles")[0]
$roleMap = @{}
foreach ($r in @($graph.appRoles)) { $roleMap["$($r.id)"] = $r.value }
$risky = @(
    'Directory.ReadWrite.All', 'RoleManagement.ReadWrite.Directory', 'PrivilegedAccess.ReadWrite.AzureAD',
    'AppRoleAssignment.ReadWrite.All', 'Application.ReadWrite.All', 'Organization.ReadWrite.All',
    'User.ReadWrite.All', 'Group.ReadWrite.All', 'GroupMember.ReadWrite.All', 'Mail.ReadWrite'
)
$byPrincipal = @{}
foreach ($a in Get-GraphAll "https://graph.microsoft.com/v1.0/servicePrincipals/$($graph.id)/appRoleAssignedTo?`$top=999") {
    $val = $roleMap["$($a.appRoleId)"]
    if ($val -and ($risky -contains $val)) {
        if (-not $byPrincipal.ContainsKey($a.principalId)) {
            $byPrincipal[$a.principalId] = [pscustomobject]@{ Name = $a.principalDisplayName; Roles = @() }
        }
        $byPrincipal[$a.principalId].Roles += $val
    }
}
$items = @()
foreach ($p in $byPrincipal.Values) {
    $u = @($p.Roles | Sort-Object -Unique)
    if ($u.Count -gt 1) {
        $items += [pscustomobject]@{ Title = $p.Name; Detail = "Holds $($u.Count) high-privilege Graph permissions: $($u -join ', ')" }
    }
}
[pscustomobject]@{
    Name     = 'Service principals with multiple sensitive app roles'
    Severity = 'high'
    Fix      = 'Split workloads so each principal holds only the permissions it needs; remove excess grants.'
    Items    = $items
}