Rules/APP-006.risky-delegated-grants.psd1

@{
    Id          = 'APP-006'
    Title       = 'No risky delegated consent grants'
    Area        = 'ConsentGrants'
    Severity    = 'Medium'
    References  = @(
        'https://learn.microsoft.com/defender-cloud-apps/investigate-risky-oauth'
    )
    Rationale   = 'Delegated grants with write access to mail or files plus offline_access are the classic footprint of illicit consent-grant attacks - durable API access in user context, invisible to password resets.'
    Remediation = 'Review the flagged grants (who consented, is the app known?) and revoke unexpected ones; restrict future user consent (see ID-005).'
    Test        = {
        param($Snapshot)
        $riskyScopes = @('Mail.ReadWrite', 'Mail.Send', 'MailboxSettings.ReadWrite', 'Files.ReadWrite.All',
            'Sites.ReadWrite.All', 'Directory.ReadWrite.All', 'Contacts.ReadWrite', 'Notes.ReadWrite.All')
        $hits = [System.Collections.Generic.List[object]]::new()
        $microsoftSkipped = 0
        foreach ($grant in @($Snapshot.ConsentGrants.delegatedGrants)) {
            $scopeTokens = @(([string]$grant.scope) -split '\s+' | Where-Object { $_ })
            $matched = @($scopeTokens | Where-Object { $riskyScopes -contains $_ })
            if ($matched.Count -eq 0) { continue }
            # Microsoft first-party apps (SharePoint, Photos, ...) get broad
            # scopes as normal platform behavior - not consent-grant risk.
            if ($grant.PSObject.Properties['_tlIsMicrosoftApp'] -and $grant._tlIsMicrosoftApp) {
                $microsoftSkipped++
                continue
            }
            $clientLabel = $(if ($grant.PSObject.Properties['_tlClientName'] -and $grant._tlClientName) { $grant._tlClientName } else { $grant.clientId })
            $hits.Add(("{0}: {1} ({2})" -f $clientLabel, ($matched -join ', '), $grant.consentType))
        }
        $notes = @()
        if ($microsoftSkipped -gt 0) {
            $notes = @("$microsoftSkipped Microsoft first-party grant(s) with broad scopes skipped as expected platform behavior.")
        }
        if ($hits.Count -gt 0) {
            New-TLRuleResult -Status Fail -Evidence (@($hits | Select-Object -First 15) + $notes)
        }
        else {
            New-TLRuleResult -Status Pass -Evidence (@("No risky third-party delegated scopes across {0} consent grants." -f @($Snapshot.ConsentGrants.delegatedGrants).Count) + $notes)
        }
    }
}