tests/Test-Assessment.27028.ps1

<#
.SYNOPSIS
    A web content filtering policy governs Copilot Studio agent traffic through the baseline profile
 
.DESCRIPTION
    Evaluates whether the Global Secure Access baseline profile is enabled and linked to an enabled,
    administrator-configured web content filtering policy. The baseline profile is the only supported
    enforcement path for Copilot Studio agent traffic, so without such a policy an agent's HTTP node
    action or connector can reach web destinations the organization intended to block.
 
.NOTES
    Test ID: 27028
    Pillar: Network
    Risk Level: High
    SFI Pillar: Protect networks
    Required API: networkAccess/filteringProfiles (beta)
#>


function Test-Assessment-27028 {
    [ZtTest(
        Category = 'AI Gateway',
        ImplementationCost = 'Medium',
        Service = ('Graph'),
        CompatibleLicense = ('Entra_Premium_Internet_Access'),
        Pillar = 'Network',
        RiskLevel = 'High',
        SfiPillar = 'Protect networks',
        TenantType = ('Workforce'),
        TestId = 27028,
        Title = 'A web content filtering policy governs Copilot Studio agent traffic through the baseline profile',
        UserImpact = 'Medium'
    )]
    [CmdletBinding()]
    param()

    # The baseline profile is identified by its fixed priority and is the only profile supported for agent traffic.
    [int]$baselineProfilePriority = 65000
    # Allow-all placeholder policy that ships with every tenant; it isn't an administrator-configured restriction.
    [string]$defaultPolicyName = 'All websites'

    #region Data Collection
    Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose

    $activity = 'Evaluating web content filtering on the Global Secure Access baseline profile'
    Write-ZtProgress -Activity $activity -Status 'Querying filtering profiles'

    # Q1: Get the baseline profile and its linked policies
    $filteringProfiles = @()
    $errorMsg = $null
    $httpStatusCode = $null

    try {
        $filteringProfiles = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/filteringProfiles' -QueryParameters @{
            '$filter' = "priority eq $baselineProfilePriority"
            '$select' = 'id,name,state,priority'
            '$expand' = 'policies($select=id,state;$expand=policy($select=id,name,version))'
        } -ApiVersion beta -ErrorAction Stop
    }
    catch {
        $errorMsg = $_
        $httpStatusCode = Get-ZtHttpStatusCode -ErrorRecord $_
        Write-PSFMessage "Failed to retrieve filtering profiles (HTTP $httpStatusCode): $errorMsg" -Tag Test -Level Warning
    }
    #endregion Data Collection

    #region Assessment Logic
    $passed = $false
    $customStatus = $null
    $testResultMarkdown = ''
    $baselineState = 'Not found'
    $enabledPolicyNames = @()

    if ($httpStatusCode -eq 404) {
        # The filtering profile resource is unavailable, so the required enforcement path is absent: same outcome as an empty result.
        Write-PSFMessage 'Global Secure Access filtering profiles are not available in this tenant.' -Tag Test -Level Verbose
    }

    if ($errorMsg -and $httpStatusCode -ne 404) {
        $customStatus = 'Investigate'
        $testResultMarkdown = if ($httpStatusCode -in 401, 403) {
            '⚠️ Unable to read the Global Secure Access baseline profile due to insufficient permissions. Grant the **NetworkAccess.Read.All** Microsoft Graph permission and assign the **Global Secure Access Administrator** or **Security Reader** role, then rerun the assessment.'
        }
        else {
            '⚠️ Unable to retrieve the Global Secure Access filtering profiles due to an API error. Please rerun the assessment.'
        }
    }
    else {
        $baselineProfile = $filteringProfiles | Where-Object { $_.priority -eq $baselineProfilePriority } | Select-Object -First 1

        if ($baselineProfile) {
            $baselineState = $baselineProfile.state
            $enabledPolicyNames = @($baselineProfile.policies | Where-Object {
                    $_.'@odata.type' -eq '#microsoft.graph.networkaccess.filteringPolicyLink' -and
                    $_.state -eq 'enabled' -and
                    $_.policy.name -and
                    $_.policy.name -ne $defaultPolicyName
                } | ForEach-Object { $_.policy.name })
        }

        $passed = $baselineState -eq 'enabled' -and $enabledPolicyNames.Count -gt 0

        if ($passed) {
            $testResultMarkdown = "✅ The Global Secure Access baseline profile is enabled and linked to an administrator-configured web content filtering policy that governs Copilot Studio agent traffic.`n`n%TestResult%"
        }
        else {
            $testResultMarkdown = "❌ The Global Secure Access baseline profile isn't enabled or lacks an enabled administrator-configured web content filtering policy, leaving Copilot Studio agent web traffic unrestricted by that policy.`n`n%TestResult%"
        }
    }
    #endregion Assessment Logic

    #region Report Generation
    $mdInfo = ''

    if (-not $customStatus) {
        $baselineStateDisplay = if ($baselineState -eq 'enabled') { '✅ Enabled' } else { "❌ $baselineState" }
        $policyNamesDisplay = if ($enabledPolicyNames.Count -gt 0) {
            ($enabledPolicyNames | Sort-Object -Unique | ForEach-Object { Get-SafeMarkdown $_ }) -join ', '
        }
        else {
            'None'
        }
        $statusDisplay = if ($passed) { '✅ Pass' } else { '❌ Fail' }

        $formatTemplate = @'
## [Global Secure Access Security Profiles]({0})
 
| Baseline profile state | Enabled web content filtering policy name(s) | Status |
| :--------------------- | :------------------------------------------- | :----- |
{1}
'@


        $portalLink = 'https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/FilteringPolicyProfiles.ReactView'
        $tableRows = "| $baselineStateDisplay | $policyNamesDisplay | $statusDisplay |`n"
        $mdInfo = $formatTemplate -f $portalLink, $tableRows
    }

    $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
    #endregion Report Generation

    $params = @{
        TestId = '27028'
        Title  = 'A web content filtering policy governs Copilot Studio agent traffic through the baseline profile'
        Status = $passed
        Result = $testResultMarkdown
    }
    if ($customStatus) {
        $params.CustomStatus = $customStatus
    }
    Add-ZtTestResultDetail @params
}