public/maester/aiagent/Test-MtAIAgentAuthorAuthentication.ps1

<#
.SYNOPSIS
    Tests if AI agents use author (maker) authentication for their connector tools.

.DESCRIPTION
    Checks all Copilot Studio agents for connector tools that use author (maker)
    authentication instead of end-user authentication. When a connection uses
    author authentication, the agent accesses external services (SharePoint, SQL,
    etc.) using the bot maker's stored credentials rather than requiring the end
    user to authenticate. This creates a privilege escalation risk — the agent
    operates with the maker's permissions regardless of who is chatting with it.

    Reference: https://www.microsoft.com/en-us/security/blog/2026/02/12/copilot-studio-agent-security-top-10-risks-detect-prevent/

.OUTPUTS
    [bool] - Returns $true if no agents use author/maker authentication,
    $false if any agent has connections using the maker's credentials,
    $null if data is unavailable.

.EXAMPLE
    Test-MtAIAgentAuthorAuthentication

.LINK
    https://maester.dev/docs/commands/Test-MtAIAgentAuthorAuthentication
#>


function Test-MtAIAgentAuthorAuthentication {
    [CmdletBinding()]
    [OutputType([bool])]
    param()

    $agents = Get-MtAIAgentInfo
    if ($null -eq $agents) {
        Add-MtTestResultDetail -SkippedBecause 'Custom' -SkippedCustomReason 'No Copilot Studio agent data available. Ensure DataverseEnvironmentUrl is configured in maester-config.json and Connect-Maester -Service Dataverse has been run. See https://maester.dev/docs/tests/MT.1118 for prerequisites.'
        return $null
    }

    # Detect author (maker) authentication on connector tools.
    # In Copilot Studio, tools using user authentication have 'connectionProperties: mode: Invoker'
    # in their YAML data. Tools with a connectionReference but WITHOUT mode: Invoker
    # use the maker's embedded connection (author authentication).
    $failedAgents = @()

    foreach ($agent in $agents) {
        if ([string]::IsNullOrEmpty($agent.AgentToolsDetails)) {
            continue
        }

        # Parse the tools JSON to inspect each tool's YAML data
        $tools = $null
        try {
            if ($agent.AgentToolsDetails -is [string]) {
                $tools = $agent.AgentToolsDetails | ConvertFrom-Json -ErrorAction Stop
            } else {
                $tools = @($agent.AgentToolsDetails)
            }
        } catch {
            Write-Verbose "Could not parse AgentToolsDetails for agent $($agent.AIAgentName): $_"
            continue
        }

        # Ensure $tools is always an array
        if ($tools -isnot [System.Array]) { $tools = @($tools) }

        $makerAuthTools = @()
        foreach ($tool in $tools) {
            $data = $tool.Data
            if ([string]::IsNullOrEmpty($data)) { continue }

            # Check if the tool has a connection reference (uses a connector)
            if ($data -match 'connectionReference:') {
                # If it does NOT have mode: Invoker, it uses maker auth
                if ($data -notmatch 'mode:\s*Invoker') {
                    $makerAuthTools += $tool.Name
                }
            }
        }

        if ($makerAuthTools.Count -gt 0) {
            $failedAgents += [PSCustomObject]@{
                AIAgentName        = $agent.AIAgentName
                EnvironmentId      = $agent.EnvironmentId
                MakerAuthTools     = ($makerAuthTools -join ', ')
            }
        }
    }

    if ($failedAgents.Count -eq 0) {
        $testResultMarkdown = "Well done. No AI agents are using author (maker) authentication for their connections."
    } else {
        $testResultMarkdown = "Found $($failedAgents.Count) AI agent(s) using author (maker) authentication for connections.`n`n%TestResult%"
        $result = "| Agent Name | Environment | Maker Auth Tools |`n"
        $result += "| --- | --- | --- |`n"
        foreach ($agent in $failedAgents) {
            $result += "| $($agent.AIAgentName) | $($agent.EnvironmentId) | $($agent.MakerAuthTools) |`n"
        }
        $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $result
    }

    Add-MtTestResultDetail -Result $testResultMarkdown -Severity "High"
    return ($failedAgents.Count -eq 0)
}