Before You Give an AI Agent the Keys, Check Who Already Has Them

Apple is changing macOS permissions after a dispute over an AI agent reading private messages. Here is how to audit and tighten the same kind of access in your Microsoft 365 tenant with PowerShell on Ubuntu.

Written by: Justin

Published on: October 11, 2026

This week Apple said it will change how macOS Full Disk Access works. The trigger was a public dispute over whether Meta’s Muse AI agent could read a columnist’s private messages. Meta says Muse can only read Messages if the user turns on both Full Disk Access and a Messages connector. A macOS security expert replied that Full Disk Access makes almost any file readable by the app that holds it. Apple didn’t name Meta, but said the risks of that kind of access will grow as AI agents become more autonomous. Ars Technica has the full story.

I can’t tell you who is right about what Muse did. The lesson holds either way: an agent can only use what it has been handed, and access you grant stays granted until someone takes it back. Your Microsoft 365 tenant works the same way.

The Microsoft 365 version of this problem

In your tenant, the equivalent of Full Disk Access is consent. When someone signs in to a third-party app with their work account, the app asks for permission to act on their behalf: read mail, read files, send messages. By default, Microsoft Entra lets users approve those requests themselves for any permission that doesn’t require an administrator. Microsoft’s own example is that a user can let an app into their mailbox but can’t let it read and write every file in the company. The details are on Microsoft Learn.

Three facts make this worth an afternoon of your time:

  • Changing the consent policy only affects future requests. Grants that already exist stay in place until someone revokes them.
  • Resetting a password or requiring MFA won’t stop a malicious app that a user has already approved, because the app lives outside your organization. Microsoft says so in its illicit consent grant guidance.
  • AI assistants raise the stakes. Microsoft says Copilot only accesses data a user is authorized to access. That is reassuring until you count how much a typical user is authorized to see.

So before you invite another AI tool into the tenant, find out what you have already handed out. Here is how.

Step 1: See what has already been granted

You need PowerShell 7 and the Microsoft Graph PowerShell SDK. On Ubuntu, install PowerShell 7 from Microsoft’s Linux guide, start pwsh, and install only the three modules this script uses:

Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Applications, Microsoft.Graph.Identity.SignIns -Scope CurrentUser

The first time you run it, it asks you to sign in and approve two read-only permissions, Directory.Read.All and Policy.Read.All. Use an account that is allowed to review enterprise app permissions. Microsoft says that means at least Cloud Application Administrator. The script changes nothing. It prints how users are currently allowed to consent, then lists every app that holds delegated permissions, with the riskiest first.

# Read-only: lists the apps that hold delegated permissions in your tenant.
Connect-MgGraph -Scopes 'Directory.Read.All', 'Policy.Read.All' -NoWelcome

# How are users allowed to consent today?
(Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions.PermissionGrantPoliciesAssigned

# App names and scopes can be set by other parties, so strip control characters
$clean = { param($text) $text -replace '\p{C}' }

# Scopes that write data, touch mail, or impersonate the user
$risky = '*.ReadWrite*', 'Mail.Read*', 'Mail.Send*', '*AccessAsUser*', 'user_impersonation'

Get-MgOauth2PermissionGrant -All | Group-Object ClientId | ForEach-Object {
    $app    = Get-MgServicePrincipal -ServicePrincipalId $_.Name
    $scopes = $_.Group.Scope -split ' ' | ForEach-Object { & $clean $_ } | Where-Object { $_ } | Sort-Object -Unique
    [pscustomobject]@{
        App                = & $clean $app.DisplayName
        ServicePrincipalId = $app.Id
        Publisher          = & $clean $app.VerifiedPublisher.DisplayName
        TenantWide         = [bool]($_.Group | Where-Object ConsentType -eq 'AllPrincipals')
        Risky              = ($scopes | Where-Object { $s = $_; $risky | Where-Object { $s -like $_ } }) -join ' '
    }
} | Sort-Object @{ Expression = { [bool]$_.Risky }; Descending = $true }, App | Format-Table -AutoSize

That short version is enough to get started. The full version is in my pwsh repo on GitHub. It also covers application permissions (apps that act with no signed-in user), can hide Microsoft’s own apps, lists who consented, ranks every app High, Medium, or Low, and exports to CSV or JSON. It is free and open source, like everything else I put there.

Read the results with a skeptic’s eye:

  • TenantWide = True means an administrator approved the app for everyone. Expect this for Microsoft’s own apps. Question it for everything else.
  • A blank Publisher means Microsoft hasn’t verified who made the app. That isn’t proof of anything bad, but it is a reason to ask who installed it and why.
  • Risky lists permissions that write data, read or send mail, or impersonate the signed-in user. The patterns come from Microsoft’s list of consent grants to scrutinize.

Run it in a test tenant first if you have one. Microsoft’s own apps will show up in the list too, which is normal. The full version can hide them.

Step 2: Tighten who can say yes

The first thing the script prints is the policy that controls user consent. If you see microsoft-user-default-legacy, any user can approve any app that doesn’t require admin consent. If you see neither that nor user-default-low, users likely can’t consent on their own. Microsoft recommends allowing user consent only for apps from verified publishers. The built-in policy for that is microsoft-user-default-low, which also limits users to permissions that you classify as low impact. You get to decide what counts as low impact.

I’d make this change in the Entra admin center under Enterprise apps > Consent and permissions > User consent settings. It can be done in PowerShell too, but Microsoft warns that you must preserve any existing owned-resource consent policies when you update the list, and that is an easy mistake to make.

Then turn on the admin consent workflow. When a user hits a “no,” they can send a request with a justification instead of working around you. You choose the reviewers, and a Global Administrator turns the feature on. Allow up to an hour for it to take effect.

Step 3: Clean up and keep it clean

When you find a grant that shouldn’t exist, ask the person who installed the app before you pull it. You may break something that works. When you’re sure, look up the grant with the ServicePrincipalId from the output and remove it. The -WhatIf switch shows what would happen without doing it:

Get-MgOauth2PermissionGrant -Filter "clientId eq '<ServicePrincipalId>'"
Remove-MgOauth2PermissionGrant -OAuth2PermissionGrantId '<grant id>' -WhatIf

Two cautions from Microsoft’s guidance. First, removing a grant stops new access tokens, but tokens already issued stay valid until they expire. Second, if an app looks truly malicious, disable it rather than deleting it, or it can come back the next time someone consents. Also make sure mailbox and activity auditing are turned on before you need them. You can’t investigate what you didn’t log.

Microsoft suggests reviewing consent grants weekly in organizations with many apps and users. For a small tenant, a monthly calendar reminder is a reasonable start.

The continuous discipline

In the Army, equipment never got handed out and forgotten. You signed a hand receipt, and someone counted it on a schedule. Treat app permissions the same way. Take inventory, narrow who can approve access, and count again next month. Do that, and the next AI tool that asks for the keys will be a decision you made on purpose.

Leave a Comment

Previous

Bitwarden’s License Change Works Against the Spirit of Open Source