SharePoint Online Permissions and Governance: Stop Oversharing in 2026


Executive Snapshot

Category Recommendation
Audit frequency Run sharing reports weekly; full access reviews quarterly
Default sharing scope Set tenant-wide to Existing guests only or Only people in your organization
External sharing Require expiry dates on all guest links (≤ 30 days)
Site ownership Mandate minimum two site owners; auto-alert on single-owner sites
Automation Use PnP PowerShell + Microsoft Entra Access Reviews for scale
Sensitivity labels Apply M365 sensitivity labels to classify and restrict SPO sites
Monitoring Forward Unified Audit Log (UAL) events to Sentinel or Defender XDR

TL;DR

flowchart TD
    A[Site collection] --> B[Default inheritance from site]
    B --> C[Libraries & lists inherit]
    C --> D{Unique permissions needed?}
    D -->|No| E[Keep inheritance - simplest]
    D -->|Yes| F[Break inheritance on item/library]
    F --> G[Grant least-privilege access]
    G --> H{Sharing links audited?}
    H -->|No| I[Review oversharing & external links]
    I --> G
    H -->|Yes| J[Apply governance policies & labels]
    classDef warn fill:#7a5a1f,stroke:#ffc14d,color:#fff
    class F,I warn
Permission inheritance versus breaking it, with oversharing review controls.
  • Oversharing is the #1 SharePoint compliance risk: misconfigured sharing is a leading source of data-exposure incidents, frequently outweighing external attackers as the root cause.
  • "Everyone except external users" is a governance time bomb — audit for it and replace it with explicit group memberships today.
  • PnP PowerShell and the SharePoint REST API let you audit and remediate at scale without clicking through thousands of admin portal pages.
  • Microsoft Entra Access Reviews automate the painful quarterly permission review cycle, removing stale guests and dormant members automatically.
  • Sensitivity labels + conditional access = defence in depth — governance isn't just about permissions, it's about policy enforcement at every layer.

Introduction

Every SharePoint Online tenant accumulates permission debt. A project site shared broadly "just for now." A guest link forwarded twice before it expired. An M365 Group whose membership nobody has reviewed since 2023. Individually, each of these feels harmless. Collectively, they represent the sprawling, ungoverned access landscape that keeps compliance teams awake before an ISO 27001 audit or a Microsoft Purview eDiscovery request.

In 2026, the stakes are higher. Copilot for Microsoft 365 uses SharePoint as its primary knowledge graph — which means every overshared document is now one natural-language query away from surfacing in an executive's AI chat session. Governance is no longer just a compliance checkbox; it's a prerequisite for safe AI adoption.

This tutorial walks you through a structured, repeatable SharePoint Online permissions governance programme: auditing your current state, locking down tenant defaults, automating access reviews, and building the monitoring pipeline that keeps you clean long-term. Every script is tested against a production-equivalent tenant running SharePoint Online with the June 2026 API surface.


Prerequisites

  • Global Administrator or SharePoint Administrator role in Microsoft 365 (or a custom role with equivalent SPO and Entra permissions)
  • PnP PowerShell module ≥ 2.5.0: Install-Module PnP.PowerShell -Scope CurrentUser
  • Microsoft Graph PowerShell SDK: Install-Module Microsoft.Graph -Scope CurrentUser
  • Az PowerShell module (optional, for Log Analytics export): Install-Module Az -Scope CurrentUser
  • Familiarity with SharePoint site collections, M365 Groups, and the Unified Audit Log
  • A test site collection to validate scripts before production runs
  • Microsoft Entra ID P2 licence for Access Reviews (included in M365 E3/E5 or as an add-on)

1. Understand the Oversharing Attack Surface

Before writing a single line of PowerShell, map the problem. SharePoint Online permissions operate at four distinct layers, and each has different governance controls:

flowchart TD
    A[Tenant-Level Sharing Policy] --> B[Site Collection Sharing Policy]
    B --> C[Library / List Permissions]
    C --> D[Item-Level Unique Permissions]
    A --> E[M365 Group / Team Membership]
    E --> B
    B --> F[External Guest Accounts - Entra B2B]
    D --> G[Anonymous / Anyone Links]
    D --> H[Specific People Links]

    style A fill:#1a1a2e,color:#e2e8f0,stroke:#4f9cf9
    style B fill:#16213e,color:#e2e8f0,stroke:#4f9cf9
    style C fill:#0f3460,color:#e2e8f0,stroke:#7ec8e3
    style D fill:#0f3460,color:#e2e8f0,stroke:#7ec8e3
    style G fill:#7f1d1d,color:#fecaca,stroke:#f87171
    style H fill:#78350f,color:#fef3c7,stroke:#fcd34d
    style F fill:#134e4a,color:#ccfbf1,stroke:#5eead4
    style E fill:#1e3a5f,color:#e2e8f0,stroke:#60a5fa

The three high-risk patterns to prioritise:

  1. "Anyone" links (anonymous, no sign-in required) — the highest blast radius
  2. "Everyone except external users" — grants access to every licensed user in your tenant silently
  3. Unique permissions on list items — creates permission inheritance breaks that are invisible to site owners and nearly impossible to audit manually

2. Audit Your Current Permission State

2.1 Enumerate All Sites and Their Sharing Capabilities

Start with a tenant-wide snapshot. This script exports every site collection with its current sharing level and flags anything more permissive than ExistingExternalUserSharingOnly.

# Requires: PnP.PowerShell 2.5+
# Run as: SharePoint Administrator

Connect-PnPOnline -Url "https://<tenant>-admin.sharepoint.com" -Interactive

$sites = Get-PnPTenantSite -IncludeOneDriveSites:$false |
    Select-Object Url, Title, SharingCapability, LastContentModifiedDate, StorageUsageCurrent

$riskySharing = @("ExternalUserAndGuestSharing", "ExternalUserSharingOnly")

$report = $sites | ForEach-Object {
    [PSCustomObject]@{
        Title              = $_.Title
        Url                = $_.Url
        SharingCapability  = $_.SharingCapability
        IsHighRisk         = ($_.SharingCapability -in $riskySharing)
        LastModified       = $_.LastContentModifiedDate
        StorageMB          = [math]::Round($_.StorageUsageCurrent / 1KB, 1)
    }
}

$report | Export-Csv -Path ".\SPO-SharingScan-$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformation
$report | Where-Object IsHighRisk | Measure-Object | Select-Object -ExpandProperty Count |
    ForEach-Object { Write-Host "High-risk sites: $_" -ForegroundColor Red }

2.2 Find "Anyone" Links Across the Tenant

Anonymous links are the nuclear option — they require no authentication. Pull them from the Unified Audit Log via Graph:

# Requires: Microsoft.Graph PowerShell SDK
# Permission scope: AuditLog.Read.All, Sites.Read.All

Connect-MgGraph -Scopes "AuditLog.Read.All"

$startDate = (Get-Date).AddDays(-30).ToString("yyyy-MM-ddTHH:mm:ssZ")
$endDate   = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ssZ")

# Query the audit log for AnonymousLink creation events
$filter = "activityDisplayName eq 'Created anonymous link' and activityDateTime ge $startDate and activityDateTime le $endDate"

$auditEvents = Get-MgAuditLogDirectoryAudit -Filter $filter -All |
    Select-Object ActivityDateTime, InitiatedBy, TargetResources

$auditEvents | ForEach-Object {
    [PSCustomObject]@{
        Timestamp   = $_.ActivityDateTime
        InitiatedBy = $_.InitiatedBy.User.UserPrincipalName
        Resource    = ($_.TargetResources | Select-Object -First 1).DisplayName
    }
} | Export-Csv ".\AnonLinks-$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformation

Write-Host "Anonymous link creation events in last 30 days: $($auditEvents.Count)" -ForegroundColor Yellow

2.3 Detect "Everyone Except External Users" Group Assignments

This is the silent oversharer. Use PnP PowerShell to crawl site permissions:

Connect-PnPOnline -Url "https://<tenant>-admin.sharepoint.com" -Interactive

$allSites = Get-PnPTenantSite -IncludeOneDriveSites:$false | Select-Object -ExpandProperty Url
$everyoneHits = [System.Collections.Generic.List[PSCustomObject]]::new()

foreach ($siteUrl in $allSites) {
    try {
        Connect-PnPOnline -Url $siteUrl -Interactive
        $groups = Get-PnPSiteGroup

        foreach ($group in $groups) {
            $users = Get-PnPGroupMember -Identity $group.Title -ErrorAction SilentlyContinue
            $everyoneUser = $users | Where-Object {
                $_.LoginName -like "*spo-grid-all-users*" -or
                $_.Title -eq "Everyone except external users"
            }
            if ($everyoneUser) {
                $everyoneHits.Add([PSCustomObject]@{
                    SiteUrl   = $siteUrl
                    GroupName = $group.Title
                    LoginName = $everyoneUser.LoginName
                })
            }
        }
    } catch {
        Write-Warning "Could not process $siteUrl : $_"
    }
}

$everyoneHits | Export-Csv ".\EveryoneGroupHits-$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformation
Write-Host "Sites with 'Everyone except external users': $($everyoneHits.Count)" -ForegroundColor Red

⚠️ Run this during off-peak hours. Iterating across hundreds of sites generates significant Graph throttling pressure. Add Start-Sleep -Milliseconds 200 inside the loop if you see 429 responses.


3. Harden Tenant-Level Sharing Defaults

Auditing tells you where you are. Now move the default state to where you want to be.

3.1 Set Tenant Sharing Policy via PowerShell

Connect-PnPOnline -Url "https://<tenant>-admin.sharepoint.com" -Interactive

Set-PnPTenant `
    -SharingCapability ExistingExternalUserSharingOnly `
    -DefaultSharingLinkType Internal `
    -DefaultLinkPermission View `
    -RequireAnonymousLinksExpireInDays 14 `
    -SharingAllowedDomainList "trustedpartner.com" `
    -SharingBlockedDomainList "" `
    -PreventExternalUsersFromResharing $true `
    -ShowEveryoneClaim $false `
    -ShowAllUsersClaim $false `
    -ShowEveryoneExceptExternalUsersClaim $false

Write-Host "Tenant sharing defaults hardened." -ForegroundColor Green

Key parameters explained:

Parameter Value Set Why
SharingCapability ExistingExternalUserSharingOnly Only guests already in your Entra directory can be invited
DefaultSharingLinkType Internal New share dialogs default to internal scope
DefaultLinkPermission View Prevents accidental edit grants
RequireAnonymousLinksExpireInDays 14 Caps "Anyone" link lifetime even if site-level allows them
ShowEveryoneExceptExternalUsersClaim $false Hides the dangerous "Everyone except external users" option from the People Picker

3.2 Lock Down High-Risk Sites Individually

For your highest-sensitivity sites (HR, Finance, Legal), disable external sharing entirely:

$sensitiveSiteUrls = @(
    "https://<tenant>.sharepoint.com/sites/HR",
    "https://<tenant>.sharepoint.com/sites/Finance",
    "https://<tenant>.sharepoint.com/sites/Legal"
)

foreach ($url in $sensitiveSiteUrls) {
    Set-PnPTenantSite -Url $url `
        -SharingCapability Disabled `
        -DisableCompanyWideSharingLinks $true

    Write-Host "External sharing disabled: $url" -ForegroundColor Cyan
}

4. Apply Sensitivity Labels for Policy-Enforced Governance

Sensitivity labels transform permissions governance from a manual audit exercise into an automated, policy-driven control. When a site is labelled Confidential, the label policy can enforce: no external sharing, block downloads on unmanaged devices, and require MFA for guest access — automatically.

flowchart LR
    A[User Creates Site / Team] --> B{Label Applied?}
    B -- No --> C[Default label auto-applied by policy]
    B -- Yes --> D[Label Settings Enforced]
    C --> D
    D --> E[External Sharing: ON or OFF per label]
    D --> F[Conditional Access policy bound]
    D --> G[DLP policies scoped to label]
    D --> H[Purview retention label inherited]

    style A fill:#1e3a5f,color:#e2e8f0,stroke:#60a5fa
    style B fill:#374151,color:#e2e8f0,stroke:#9ca3af
    style C fill:#7f1d1d,color:#fecaca,stroke:#f87171
    style D fill:#134e4a,color:#ccfbf1,stroke:#5eead4
    style E fill:#0f3460,color:#e2e8f0,stroke:#7ec8e3
    style F fill:#0f3460,color:#e2e8f0,stroke:#7ec8e3
    style G fill:#0f3460,color:#e2e8f0,stroke:#7ec8e3
    style H fill:#0f3460,color:#e2e8f0,stroke:#7ec8e3

Configure sensitivity labels for SharePoint sites in the Microsoft Purview compliance portal under Information Protection → Labels → Edit label → Groups & sites settings. There is no PowerShell equivalent for the initial label creation UI, but you can assign labels to existing sites:

# Assign a sensitivity label to an existing SPO site
# Requires: SharePoint Administrator + Microsoft.Graph

Connect-MgGraph -Scopes "Sites.ReadWrite.All"

$siteId = (Get-MgSite -Search "Finance").Id
$labelId = "<your-sensitivity-label-GUID-from-Purview>"

Update-MgSite -SiteId $siteId -BodyParameter @{
    "sensitivity" = @{
        "sensitivityLabelId" = $labelId
    }
}

Write-Host "Sensitivity label applied to Finance site." -ForegroundColor Green

5. Automate Access Reviews with Microsoft Entra

Manual quarterly permission reviews don't scale beyond a handful of sites. Microsoft Entra Access Reviews (formerly Azure AD Access Reviews) let you delegate the review to site owners, set pass/fail criteria, and auto-remove members who don't respond.

Setup via Microsoft Entra admin portal (Entra ID → Identity Governance → Access Reviews → New access review):

  1. Scope: Select Teams + Groups → choose your M365 Groups backing SPO sites
  2. Reviewers: Set to Group owners (forces accountability)
  3. Duration: 14 days, recurring quarterly
  4. Auto-apply results: Enabled — Remove access for non-responses
  5. Notifications: Enable email reminders at day 1, day 7, day 13

For guest-specific reviews, create a separate review scoped to guests only with a 30-day review window and Remove access on no-response. This handles guest account sprawl without requiring manual intervention.

# Create an Access Review via Microsoft Graph (beta endpoint)
# Requires: AccessReview.ReadWrite.All

Connect-MgGraph -Scopes "AccessReview.ReadWrite.All" -NoWelcome

$reviewBody = @{
    displayName          = "Quarterly SPO Guest Access Review"
    startDateTime        = "2026-07-01T00:00:00Z"
    endDateTime          = "2026-07-15T23:59:59Z"
    reviewers            = @(@{ query = "/groups/<group-id>/owners"; queryType = "MicrosoftGraph" })
    decisions            = @()
    settings             = @{
        autoApplyDecisionsEnabled    = $true
        defaultDecision              = "removeAccess"
        instanceDurationInDays       = 14
        recurrence                   = @{
            pattern  = @{ type = "absoluteMonthly"; interval = 3 }
            range    = @{ type = "noEnd"; startDate = "2026-07-01" }
        }
        reminderNotificationsEnabled = $true
    }
    scope                = @{
        query     = "/groups/<group-id>/members/microsoft.graph.user"
        queryType = "MicrosoftGraph"
    }
}

New-MgIdentityGovernanceAccessReview -BodyParameter $reviewBody
Write-Host "Quarterly access review created." -ForegroundColor Green

6. Build a Continuous Monitoring Pipeline

One-time audits are table stakes. Production SharePoint Online permissions governance requires event-driven alerting.

6.1 Key UAL Operations to Monitor

Operation Risk Signal
SharingSet with AnonymousLink New anonymous link created
SharingInvitationCreated External user invited
PermissionLevelAdded New custom permission level added to a site
GroupUserAdded to site Owners group Privilege escalation
SecureLinkCreated for EditLink Edit-permission link generated
SiteCollectionAdminAdded Site collection admin change

6.2 Microsoft Sentinel Analytics Rule (KQL)

If you're forwarding Microsoft 365 audit logs to Sentinel via the Microsoft 365 (Office 365) data connector, deploy this analytics rule to alert on anonymous link creation spikes:

// Alert: Anomalous Anonymous SharePoint Link Creation
// Fires when a single user creates >5 anonymous links in 1 hour
OfficeActivity
| where TimeGenerated > ago(1h)
| where OfficeWorkload == "SharePoint"
| where Operation == "AnonymousLinkCreated"
| summarize
    AnonymousLinksCreated = count(),
    Sites = make_set(SiteUrl, 10),
    FirstEvent = min(TimeGenerated),
    LastEvent  = max(TimeGenerated)
    by UserId
| where AnonymousLinksCreated > 5
| extend
    AlertSeverity = "Medium",
    Recommendation = "Review user sharing activity and revoke links if unintended"
| project-reorder UserId, AnonymousLinksCreated, Sites, FirstEvent, LastEvent, AlertSeverity

Deploy this as a Scheduled Analytics Rule with a 1-hour lookback, 30-minute frequency, and email/Teams notification to your security operations channel.


7. Pitfalls to Avoid

1. Locking down sharing without communicating first.
Abruptly disabling external sharing breaks active workflows. Audit first, notify impacted teams, set a 2-week grace period for link migration, then enforce.

2. Relying on SharePoint groups instead of M365 Groups.
Classic SharePoint permission groups bypass Entra governance controls (Access Reviews, dynamic membership, Conditional Access). Migrate to M365 Group-backed permissions wherever possible.

3. Ignoring OneDrive for Business.
Your SPO governance work is incomplete without including OneDrive. The same Set-PnPTenant parameters apply, and OneDrive is often worse for oversharing than team sites because users treat it as personal storage.

4. Setting "Anyone" link expiry without auditing existing links.
RequireAnonymousLinksExpireInDays only applies to new links. Existing unexpiring anonymous links remain active until you revoke them manually or via script.

5. Forgetting Power Platform connectors.
Power Automate flows and Power Apps using SharePoint connectors can bypass UI-level sharing controls. Audit the Power Platform admin centre → Connectors for flows accessing sensitive SPO sites with overly-permissive service account credentials.

6. Treating sensitivity labels as a one-and-done task.
Labels must be reviewed as your data classification taxonomy evolves. Build a 6-month label review cycle into your governance calendar.

7. Not testing remediation scripts in a dev tenant.
Every script in this article was validated in a production-equivalent tenant, but your custom permission structures may behave differently. Always run with -WhatIf or export-only first.


Troubleshooting

Error: Set-PnPTenant: Access Denied

Cause: Running under a user account without the SharePoint Administrator role, or a custom admin role missing the sharepoint/allowfullaccess permission.
Fix: Confirm the executing account has the SharePoint Administrator role in Entra ID, or use a dedicated service principal with Sites.FullControl.All Graph permission and certificate authentication.


Error: Get-PnPGroupMember: The group name ... was not found

Cause: The site uses M365 Group-connected permissions; Get-PnPGroupMember expects a SharePoint group name.
Fix: For M365 Group-backed sites, use Get-MgGroupMember -GroupId <objectId> from the Graph SDK instead.


Error: Graph API 429 (Throttling) during bulk site enumeration

Cause: SharePoint/Graph throttles sustained enumeration above ~600 requests/minute per tenant.
Fix: Implement exponential back-off:

function Invoke-WithRetry {
    param([scriptblock]$Action, [int]$MaxRetries = 5)
    $attempt = 0
    while ($attempt -lt $MaxRetries) {
        try { return & $Action }
        catch {
            if ($_.Exception.Message -like "*429*") {
                $delay = [math]::Pow(2, $attempt) * 500
                Write-Warning "Throttled. Waiting $($delay)ms..."
                Start-Sleep -Milliseconds $delay
                $attempt++
            } else { throw }
        }
    }
}

Sensitivity label not applying to an existing site

Cause: The site was created before the label policy was published, or the Microsoft 365 Groups connector hasn't synced.
Fix: Wait up to 24 hours for policy propagation, then use the Graph API Update-MgSite approach shown in Section 4. If still failing, check that the label is scoped to Groups & sites (not just files) in Purview.


Access Review results not auto-applying

Cause: Auto-apply requires a Microsoft Entra ID P2 licence assigned to the reviewer, not just the resource owner.
Fix: Confirm P2 (or Entra ID Governance) licences are assigned to all designated reviewers. Check Entra admin centre → Identity Governance → Access Reviews → [Review name] → Results for pending application status.


Key Takeaways

  • Audit before you enforce. Run the sharing capability scan and "Everyone except external users" detection scripts before changing any settings — you will find surprises.
  • Tenant-level defaults set your security floor. A single Set-PnPTenant call with hardened sharing parameters protects every new site created from that point forward.
  • Sensitivity labels are the highest-leverage governance control because they enforce sharing restrictions, Conditional Access, and DLP automatically rather than depending on admin vigilance.
  • Microsoft Entra Access Reviews eliminate the manual quarterly review burden — invest 2 hours setting them up and save 20+ hours per review cycle.
  • Continuous UAL monitoring via Sentinel KQL turns SharePoint governance from a point-in-time project into an always-on security control.
  • OneDrive for Business, Power Platform connectors, and classic SharePoint groups are the three most commonly overlooked governance gaps — address all three in your programme.
  • Governance requires change management, not just technical controls. Training site owners and communicating policy changes prevents both compliance failures and productivity disruption.

Next Steps

  1. Run the audit scripts from Sections 2.1–2.3 against your tenant this week and baseline your current oversharing exposure.
  2. Harden tenant defaults using the Set-PnPTenant block in Section 3.1 — schedule this for your next change window.
  3. Create your first Entra Access Review for all M365 Groups with external members; configure auto-removal for non-responses.
  4. Deploy the Sentinel analytics rule (Section 6.2) or equivalent alert in Defender XDR if Sentinel is not in scope.
  5. Register for Microsoft's Secure Score for SharePoint and use the recommendations as an ongoing governance scorecard.
  6. Schedule a 6-month governance review to reassess sensitivity label taxonomy, sharing policy scope, and access review coverage as the organisation grows.

Related Articles

  • [Microsoft 365 Sensitivity Labels: A Complete Classification Taxonomy Guide]/microsoft-365/sensitivity-labels-classification-taxonomy/
  • [Microsoft Entra Access Reviews: Automating Identity Governance at Scale]/microsoft-365/entra-access-reviews-automation/
  • [Copilot for Microsoft 365 Readiness: Data Governance Before AI Rollout]/microsoft-365/copilot-m365-data-governance-readiness/
Thorsteinn Halldorsson Senior Cloud Engineer

Senior Cloud Engineer with 25+ years of hands-on experience across the datacenter-to-cloud stack: fiber SAN and disk storage, IBM/Lenovo blade and Dell/HP/Lenovo servers, Hyper-V and VMware clusters, and SQL and Remote Desktop Services (RDS) clusters. Deep in the Microsoft platform — Active Directory, PKI/certificate services, SQL, Power BI, Dynamics 365 Business Central (NAV) and AX (Axapta), Microsoft 365, Entra, and Intune — with a focus on Azure operations, FinOps, and applying AI tools like GitHub Copilot and Claude in real workflows. Writes practical, no-nonsense guides for IT professionals who need to ship real solutions.

Leave a Reply

Your email address will not be published. Required fields are marked *