Checks/Teams/MET-Teams004-ZAPForTeams.ps1

try {
    $teamsPolicy = Get-TeamsProtectionPolicy -ErrorAction Stop
}
catch {
    New-METCheckResult -CheckId 'MET-Teams004' -Category Teams -Name 'ZAP for Teams' `
        -Result Fail -Severity High -AffectedObject 'Teams Protection Policy' `
        -Finding 'Unable to retrieve Teams protection policy' `
        -Recommendation 'Ensure the account has Security Reader or higher permissions and that Defender for Office 365 Plan 1 or Plan 2 is licensed.' `
        -ReferenceUrl 'https://aka.ms/mdo-teams-zap' -ErrorMessage $_.ToString()
    return
}

if (-not $teamsPolicy) {
    New-METCheckResult -CheckId 'MET-Teams004' -Category Teams -Name 'ZAP for Teams' `
        -Result Fail -Severity High -AffectedObject 'Teams Protection Policy' `
        -Finding 'No Teams protection policy found' `
        -Recommendation 'Configure the Teams protection policy in the Microsoft Defender portal at security.microsoft.com/securitysettings/teamsProtectionPolicy.' `
        -ReferenceUrl 'https://aka.ms/mdo-teams-zap'
    return
}

function Test-QuarantineTagPermission {
    param([string]$TagName, [string]$Label)

    if (-not $TagName) {
        return "No quarantine policy is assigned for $Label - the tenant default may allow users to self-release"
    }

    if ($TagName -eq 'AdminOnlyAccessPolicy') {
        return $null
    }

    try {
        $policy = Get-QuarantinePolicy -Identity $TagName -ErrorAction Stop
    }
    catch {
        return "Unable to retrieve quarantine policy '$TagName' for $Label - cannot verify user release permissions"
    }

    if ($policy.EndUserQuarantinePermissions.PermissionToRelease) {
        return "$Label quarantine policy '$TagName' allows users to self-release quarantined messages - set PermissionToRelease to false or use AdminOnlyAccessPolicy"
    }

    return $null
}

$issues = [System.Collections.Generic.List[string]]::new()

if (-not $teamsPolicy.ZapEnabled) {
    $issues.Add('Zero-hour auto purge (ZAP) for Teams is disabled - malicious messages already delivered to Teams chats are not retroactively removed')
}

$malwareIssue = Test-QuarantineTagPermission -TagName $teamsPolicy.MalwareQuarantineTag -Label 'Malware'
if ($malwareIssue) { $issues.Add($malwareIssue) }

$hcpIssue = Test-QuarantineTagPermission -TagName $teamsPolicy.HighConfidencePhishQuarantineTag -Label 'High-confidence phish'
if ($hcpIssue) { $issues.Add($hcpIssue) }

$warningIssues = [System.Collections.Generic.List[string]]::new()

try {
    $protectionRules = @(Get-TeamsProtectionPolicyRule -ErrorAction Stop)
    $rulesWithExceptions = @($protectionRules | Where-Object {
        $_.State -eq 'Enabled' -and (
            @($_.ExceptIfSentTo | Where-Object { $_ }).Count -gt 0 -or
            @($_.ExceptIfSentToMemberOf | Where-Object { $_ }).Count -gt 0 -or
            @($_.ExceptIfRecipientDomainIs | Where-Object { $_ }).Count -gt 0
        )
    })
    foreach ($rule in $rulesWithExceptions) {
        $exceptedRecipients = @($rule.ExceptIfSentTo | Where-Object { $_ }).Count
        $exceptedGroups = @($rule.ExceptIfSentToMemberOf | Where-Object { $_ }).Count
        $exceptedDomains = @($rule.ExceptIfRecipientDomainIs | Where-Object { $_ }).Count
        $exceptionParts = [System.Collections.Generic.List[string]]::new()
        if ($exceptedRecipients -gt 0) { $exceptionParts.Add("$exceptedRecipients recipient(s)") }
        if ($exceptedGroups -gt 0) { $exceptionParts.Add("$exceptedGroups group(s)") }
        if ($exceptedDomains -gt 0) { $exceptionParts.Add("$exceptedDomains domain(s)") }
        $warningIssues.Add("Teams protection rule '$($rule.Name)' excepts $($exceptionParts -join ', ') from Teams ZAP protection - excluded recipients do not receive retroactive removal of malicious messages")
    }
}
catch {
    Write-Verbose "Could not retrieve Teams protection policy rules - skipping rule exception check: $_"
}

if ($issues.Count -gt 0) {
    New-METCheckResult -CheckId 'MET-Teams004' -Category Teams -Name 'ZAP for Teams' `
        -Result Fail -Severity High -AffectedObject 'Teams Protection Policy' `
        -Finding (($issues + $warningIssues) -join '; ') `
        -Recommendation 'Enable ZAP for Teams: Set-TeamsProtectionPolicy -ZapEnabled $true. Ensure MalwareQuarantineTag and HighConfidencePhishQuarantineTag use AdminOnlyAccessPolicy or a custom policy with PermissionToRelease disabled.' `
        -ReferenceUrl 'https://aka.ms/mdo-teams-zap'
}
elseif ($warningIssues.Count -gt 0) {
    New-METCheckResult -CheckId 'MET-Teams004' -Category Teams -Name 'ZAP for Teams' `
        -Result Warning -Severity High -AffectedObject 'Teams Protection Policy' `
        -Finding ($warningIssues -join '; ') `
        -Recommendation 'Review Teams protection policy rule exceptions (ExceptIfSentTo, ExceptIfSentToMemberOf, ExceptIfRecipientDomainIs) and remove any that are not intentional - excluded recipients do not benefit from ZAP for Teams.' `
        -ReferenceUrl 'https://aka.ms/mdo-teams-zap'
}
else {
    New-METCheckResult -CheckId 'MET-Teams004' -Category Teams -Name 'ZAP for Teams' `
        -Result Pass -Severity High -AffectedObject 'Teams Protection Policy' `
        -Finding 'ZAP for Teams is enabled and quarantine policies do not allow user self-release' `
        -ReferenceUrl 'https://aka.ms/mdo-teams-zap'
}