diff --git a/powershell/Maester.Format.ps1xml b/powershell/Maester.Format.ps1xml
index 57eb7dd89..088334462 100644
--- a/powershell/Maester.Format.ps1xml
+++ b/powershell/Maester.Format.ps1xml
@@ -85,6 +85,23 @@
}
+
+
+
+ $null -ne $_.SharePoint
+
+
+
+
+ if ($_.SharePoint) {
+ "Connected`n" +
+ "URL: $($_.SharePoint.Url)`n" +
+ "Tenant: $($_.SharePoint.Tenant)`n"
+ } else {
+ ''
+ }
+
+
diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1
index 016409af1..8062ed586 100644
--- a/powershell/Maester.psd1
+++ b/powershell/Maester.psd1
@@ -88,7 +88,7 @@
'Get-MtAzureManagementGroup', 'Get-MailAuthenticationRecord', 'Get-MtAdminPortalUrl', 'Get-MtAuthenticationMethodPolicyConfig',
'Get-MtConditionalAccessPolicy', 'Get-MtExo', 'Get-MtGraphScope', 'Get-MtGroupMember',
'Get-MtExoThreatPolicyMalware',
- 'Get-MtHtmlReport', 'Import-MtMaesterResult', 'Merge-MtMaesterResult', 'Get-MtLicenseInformation', 'Get-MtRole', 'Get-MtRoleMember', 'Get-MtSafeMarkdown', 'Get-MtSession',
+ 'Get-MtHtmlReport', 'Import-MtMaesterResult', 'Merge-MtMaesterResult', 'Get-MtLicenseInformation', 'Get-MtRole', 'Get-MtRoleMember', 'Get-MtSafeMarkdown', 'Get-MtSession', 'Get-MtSpo',
'Get-MtUser', 'Get-MtUserAuthenticationMethod', 'Get-MtUserAuthenticationMethodInfoByType',
'Install-MaesterTests',
'Invoke-Maester', 'Invoke-MtGraphRequest', 'Invoke-MtAzureRequest', 'Invoke-MtAzureResourceGraphRequest',
@@ -135,8 +135,12 @@
'Test-MtCisaPrivilegedPhishResistant', 'Test-MtCisaRequireActivationApproval', 'Test-MtCisaSafeLink',
'Test-MtCisaSafeLinkClickTracking', 'Test-MtCisaSafeLinkDownloadScan', 'Test-MtCisaSmtpAuthentication',
'Test-MtCisaSpamAction', 'Test-MtCisaSpamAlternative', 'Test-MtCisaSpamBypass', 'Test-MtCisaSpamFilter',
- 'Test-MtCisaSpfDirective', 'Test-MtCisaSpfRestriction', 'Test-MtCisaSpoSharing',
- 'Test-MtCisaSpoSharingAllowedDomain', 'Test-MtCisaUnmanagedRoleAssignment', 'Test-MtCisaWeakFactor',
+ 'Test-MtCisaSpfDirective', 'Test-MtCisaSpfRestriction',
+ 'Test-MtCisaSpoAnyoneLinkExpiration', 'Test-MtCisaSpoAnyoneLinkPermission',
+ 'Test-MtCisaSpoDefaultSharingPermission', 'Test-MtCisaSpoDefaultSharingScope',
+ 'Test-MtCisaSpoOneDriveSharing', 'Test-MtCisaSpoSharing',
+ 'Test-MtCisaSpoSharingAllowedDomain', 'Test-MtCisaSpoVerificationCodeReauth',
+ 'Test-MtCisaUnmanagedRoleAssignment', 'Test-MtCisaWeakFactor',
'Test-MtConditionalAccessWhatIf', 'Test-MtConnection', 'Test-MtDeviceComplianceSettings',
'Test-MtExoRejectDirectSend',
'Test-MtExoSetScl',
diff --git a/powershell/Maester.psm1 b/powershell/Maester.psm1
index 1cb989e9f..cffcf1fc2 100644
--- a/powershell/Maester.psm1
+++ b/powershell/Maester.psm1
@@ -25,6 +25,7 @@ $__MtSession = @{
DataverseApiBase = $null # Resolved Dataverse OData API base URL (e.g. https://org123.api.crm.dynamics.com/api/data/v9.2)
DataverseResourceUrl = $null # Dataverse resource URL for token acquisition (e.g. https://org123.crm.dynamics.com)
DataverseEnvironmentId = $null # Environment identifier for display (e.g. org123.crm.dynamics.com)
+ SpoCache = @{} # Cache for SharePoint Online tenant settings retrieved via PnP
}
New-Variable -Name __MtSession -Value $__MtSession -Scope Script -Force
diff --git a/powershell/internal/Clear-ModuleVariable.ps1 b/powershell/internal/Clear-ModuleVariable.ps1
index cf53d89f0..b9ba58318 100644
--- a/powershell/internal/Clear-ModuleVariable.ps1
+++ b/powershell/internal/Clear-ModuleVariable.ps1
@@ -20,5 +20,6 @@
Clear-MtDnsCache
Clear-MtExoCache
$__MtSession.AIAgentInfo = $null
+ $__MtSession.SpoCache = @{}
# $__MtSession.Connections = @() # Do not clear connections as they are used to track the connection state. This module variable should only be set by Connect-Maester and Disconnect-Maester.
}
diff --git a/powershell/internal/Get-MtSkippedReason.ps1 b/powershell/internal/Get-MtSkippedReason.ps1
index 8cd4212f3..14845af4f 100644
--- a/powershell/internal/Get-MtSkippedReason.ps1
+++ b/powershell/internal/Get-MtSkippedReason.ps1
@@ -14,6 +14,7 @@
"NotConnectedSecurityCompliance" { "Not connected to Security & Compliance. See [Connecting to Security & Compliance](https://maester.dev/docs/connect-maester/#connect-to-azure-exchange-online-and-teams)"; break}
"NotConnectedTeams" { "Not connected to Teams. See [Connecting to Teams](https://maester.dev/docs/connect-maester/#connect-to-azure-exchange-online-and-teams)"; break}
"NotConnectedGraph" { "Not connected to Graph. See [Connect-Maester](https://maester.dev/docs/commands/Connect-Maester#examples)"; break}
+ "NotConnectedSharePoint" { "Not connected to SharePoint Online. See [Connecting to SharePoint Online](https://maester.dev/docs/connect-maester/#connect-to-sharepoint-online)"; break}
"NotDotGovDomain" { "This test is only for federal, executive branch, departments and agencies. To override use [Test-MtCisaDmarcAggregateCisa -Force](https://maester.dev/docs/commands/Test-MtCisaDmarcAggregateCisa)"; break}
"NotLicensedEntraIDP1" { "This test is for tenants that are licensed for Entra ID P1. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break}
"NotLicensedEntraIDP2" { "This test is for tenants that are licensed for Entra ID P2. See [Entra ID licensing](https://learn.microsoft.com/entra/fundamentals/licensing)"; break}
diff --git a/powershell/public/Connect-Maester.ps1 b/powershell/public/Connect-Maester.ps1
index a0c1d4301..eb7553e08 100644
--- a/powershell/public/Connect-Maester.ps1
+++ b/powershell/public/Connect-Maester.ps1
@@ -108,15 +108,18 @@
[ValidateSet('TeamsChina', 'TeamsGCCH', 'TeamsDOD')]
[string]$TeamsEnvironmentName = $null, #ToValidate: Don't use this parameter, this is the default.
- # The services to connect to such as Azure, Dataverse (for Copilot Studio tests), and EXO. Default is Graph.
- [ValidateSet('All', 'Azure', 'Dataverse', 'ExchangeOnline', 'Graph', 'SecurityCompliance', 'Teams')]
+ # The services to connect to such as Azure, Dataverse (for Copilot Studio tests), EXO, and SharePoint Online. Default is Graph.
+ [ValidateSet('All', 'Azure', 'Dataverse', 'ExchangeOnline', 'Graph', 'SecurityCompliance', 'SharePointOnline', 'Teams')]
[string[]]$Service = 'Graph',
# The Tenant ID to connect to, if not specified the sign-in user's default tenant is used.
[string]$TenantId,
# The Client ID of the app to connect to for Graph. If not specified, the default Graph PowerShell CLI enterprise app will be used. Reference on how to create an enterprise app: https://learn.microsoft.com/en-us/powershell/microsoftgraph/authentication-commands?view=graph-powershell-1.0#use-delegated-access-with-a-custom-application-for-microsoft-graph-powershell
- [string]$GraphClientId
+ [string]$GraphClientId,
+
+ # The Client ID of the PnP Entra ID app for SharePoint Online. Required when Service includes SharePointOnline. Create the app using Register-PnPEntraIDAppForInteractiveLogin.
+ [string]$SharePointClientId
)
$__MtSession.Connections = $Service
@@ -353,4 +356,46 @@
}
} # end switch OrderedImport
+ # SharePoint Online via PnP — must run AFTER Graph to avoid Microsoft.Graph.Core DLL conflict
+ if ($Service -contains 'SharePointOnline' -or $Service -contains 'All') {
+ Write-Verbose 'Connecting to SharePoint Online via PnP'
+
+ if (-not $SharePointClientId) {
+ Write-Host "`nSharePointOnline requires the -SharePointClientId parameter. Create a PnP app registration using Register-PnPEntraIDAppForInteractiveLogin.`nFor more information see https://maester.dev/docs/sections/create-entra-app" -ForegroundColor Red
+ } else {
+ try {
+ # Resolve the SharePoint admin URL from the tenant's initial domain
+ $domains = Invoke-MtGraphRequest -RelativeUri "domains" -ApiVersion "v1.0"
+ $initialDomain = ($domains | Where-Object { $_.isInitial -eq $true }).id
+ $tenantPrefix = ($initialDomain -split '\.')[0]
+ $spoAdminUrl = "https://$tenantPrefix-admin.sharepoint.com"
+
+ Write-Verbose "Resolved SharePoint admin URL: $spoAdminUrl"
+
+ Import-Module PnP.PowerShell -ErrorAction Stop
+
+ $pnpParams = @{
+ Url = $spoAdminUrl
+ ClientId = $SharePointClientId
+ }
+
+ if ($UseDeviceCode) {
+ $pnpParams['DeviceLogin'] = $true
+ }
+
+ if ($TenantId) {
+ $pnpParams['Tenant'] = $TenantId
+ }
+
+ Connect-PnPOnline @pnpParams
+
+ } catch [Management.Automation.CommandNotFoundException] {
+ Write-Host "`nThe PnP.PowerShell module is not installed. Please install the module using the following command.`nFor more information see https://pnp.github.io/powershell/articles/installation.html" -ForegroundColor Red
+ Write-Host "`nInstall-Module PnP.PowerShell -Scope CurrentUser`n" -ForegroundColor Yellow
+ } catch {
+ Write-Host "`nFailed to connect to SharePoint Online: $($_.Exception.Message)" -ForegroundColor Red
+ }
+ }
+ }
+
} # end function Connect-Maester
diff --git a/powershell/public/Disconnect-Maester.ps1 b/powershell/public/Disconnect-Maester.ps1
index 8918dd355..0da74d4f0 100644
--- a/powershell/public/Disconnect-Maester.ps1
+++ b/powershell/public/Disconnect-Maester.ps1
@@ -49,4 +49,14 @@
Write-Verbose -Message "Disconnecting from Microsoft Teams."
Disconnect-MicrosoftTeams
}
+
+ if($__MtSession.Connections -contains "SharePointOnline" -or $__MtSession.Connections -contains "All"){
+ Write-Verbose -Message "Disconnecting from SharePoint Online (PnP)."
+ try {
+ Disconnect-PnPOnline -ErrorAction Stop
+ } catch {
+ Write-Verbose "Disconnect-PnPOnline encountered an error: $($_.Exception.Message)"
+ }
+ $__MtSession.SpoCache = @{}
+ }
}
diff --git a/powershell/public/cisa/spo/Get-MtSpo.ps1 b/powershell/public/cisa/spo/Get-MtSpo.ps1
new file mode 100644
index 000000000..6295002ea
--- /dev/null
+++ b/powershell/public/cisa/spo/Get-MtSpo.ps1
@@ -0,0 +1,45 @@
+function Get-MtSpo {
+ <#
+ .SYNOPSIS
+ Retrieves SharePoint Online tenant settings via PnP with session caching.
+
+ .DESCRIPTION
+ Returns the full SPO tenant configuration from Get-PnPTenant.
+ Results are cached in $__MtSession.SpoCache for the duration of the session.
+ Use -ClearCache to force a fresh retrieval.
+
+ .EXAMPLE
+ Get-MtSpo
+
+ Returns the cached (or freshly retrieved) SPO tenant settings.
+
+ .EXAMPLE
+ Get-MtSpo -ClearCache
+
+ Clears the cached settings and retrieves fresh data from Get-PnPTenant.
+
+ .LINK
+ https://maester.dev/docs/commands/Get-MtSpo
+ #>
+ [CmdletBinding()]
+ param(
+ # Clear the cached SPO tenant settings and retrieve fresh data.
+ [switch]$ClearCache
+ )
+
+ if ($ClearCache) {
+ $__MtSession.SpoCache = @{}
+ Write-Verbose "SPO cache cleared."
+ }
+
+ if ($null -eq $__MtSession.SpoCache.SpoTenant) {
+ Write-Verbose "SPO tenant settings not in cache, requesting."
+ $response = Get-PnPTenant -ErrorAction Stop
+ $__MtSession.SpoCache.SpoTenant = $response
+ } else {
+ Write-Verbose "SPO tenant settings in cache."
+ $response = $__MtSession.SpoCache.SpoTenant
+ }
+
+ return $response
+}
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoAnyoneLinkExpiration.md b/powershell/public/cisa/spo/Test-MtCisaSpoAnyoneLinkExpiration.md
new file mode 100644
index 000000000..2661be90b
--- /dev/null
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoAnyoneLinkExpiration.md
@@ -0,0 +1,18 @@
+Expiration days for Anyone links SHALL be set to 30 days or less.
+
+Rationale: Anyone links that do not expire or have excessively long expiration periods pose a persistent risk of unauthorized access. Setting an expiration of 30 days or less ensures that shared content access is time-limited.
+
+#### Remediation action:
+
+1. Sign in to the [SharePoint admin center](https://go.microsoft.com/fwlink/?linkid=2185219).
+2. Select Policies > Sharing.
+3. Under Advanced settings for Anyone links, check **These links must expire within this many days** and set to **30** days or less.
+4. Select Save.
+
+#### Related links
+
+* [CISA 3 Anyone Link Expiration - MS.SHAREPOINT.3.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint31v1)
+* [CISA ScubaGear Rego Reference](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Rego/SharepointConfig.rego)
+
+
+%TestResult%
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoAnyoneLinkExpiration.ps1 b/powershell/public/cisa/spo/Test-MtCisaSpoAnyoneLinkExpiration.ps1
new file mode 100644
index 000000000..0859011dd
--- /dev/null
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoAnyoneLinkExpiration.ps1
@@ -0,0 +1,54 @@
+function Test-MtCisaSpoAnyoneLinkExpiration {
+ <#
+ .SYNOPSIS
+ Checks state of Anyone link expiration for SharePoint Online
+
+ .DESCRIPTION
+ An expiration date SHOULD be set for Anyone links.
+
+ .EXAMPLE
+ Test-MtCisaSpoAnyoneLinkExpiration
+
+ Returns true if Anyone links have an expiration set
+
+ .LINK
+ https://maester.dev/docs/commands/Test-MtCisaSpoAnyoneLinkExpiration
+ #>
+ [CmdletBinding()]
+ [OutputType([bool])]
+ param()
+
+ if (!(Test-MtConnection SharePoint)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
+ }
+
+ try {
+ $spoTenant = Get-MtSpo
+
+ if ($spoTenant.SharingCapability -ne "ExternalUserAndGuestSharing") {
+ Add-MtTestResultDetail -SkippedBecause Custom -SkippedCustomReason "Anyone links are not enabled. This test only applies when sharing is set to Anyone."
+ return $null
+ }
+
+ # RequireAnonymousLinksExpireInDays: 0 = no expiration, >0 = days until expiry
+ # CISA requires >= 1 AND <= 30 days
+ $days = $spoTenant.RequireAnonymousLinksExpireInDays
+ $testResult = $days -ge 1 -and $days -le 30
+
+ if ($testResult) {
+ $testResultMarkdown = "Well done. Anyone links expire after $days day(s)."
+ } elseif ($days -eq 0) {
+ $testResultMarkdown = "Anyone links do not have an expiration date set."
+ } else {
+ $testResultMarkdown = "Anyone links expiration is set to $days day(s), which exceeds the 30-day maximum."
+ }
+
+ Add-MtTestResultDetail -Result $testResultMarkdown
+
+ return $testResult
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
+}
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoAnyoneLinkPermission.md b/powershell/public/cisa/spo/Test-MtCisaSpoAnyoneLinkPermission.md
new file mode 100644
index 000000000..6c0caf2ec
--- /dev/null
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoAnyoneLinkPermission.md
@@ -0,0 +1,18 @@
+Allowable file and folder permissions for Anyone links SHALL be set to View only.
+
+Rationale: Allowing edit permissions on Anyone links increases the risk of unauthorized modifications to shared content. Restricting to View only limits the potential impact of anonymous access.
+
+#### Remediation action:
+
+1. Sign in to the [SharePoint admin center](https://go.microsoft.com/fwlink/?linkid=2185219).
+2. Select Policies > Sharing.
+3. Under Advanced settings for Anyone links, set the file and folder permissions to **View**.
+4. Select Save.
+
+#### Related links
+
+* [CISA 3 Anyone Link Permission - MS.SHAREPOINT.3.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint32v1)
+* [CISA ScubaGear Rego Reference](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Rego/SharepointConfig.rego)
+
+
+%TestResult%
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoAnyoneLinkPermission.ps1 b/powershell/public/cisa/spo/Test-MtCisaSpoAnyoneLinkPermission.ps1
new file mode 100644
index 000000000..4d8a6f698
--- /dev/null
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoAnyoneLinkPermission.ps1
@@ -0,0 +1,51 @@
+function Test-MtCisaSpoAnyoneLinkPermission {
+ <#
+ .SYNOPSIS
+ Checks state of Anyone link permissions for SharePoint Online
+
+ .DESCRIPTION
+ Anyone link permissions SHOULD be limited to View only.
+
+ .EXAMPLE
+ Test-MtCisaSpoAnyoneLinkPermission
+
+ Returns true if Anyone links are limited to View
+
+ .LINK
+ https://maester.dev/docs/commands/Test-MtCisaSpoAnyoneLinkPermission
+ #>
+ [CmdletBinding()]
+ [OutputType([bool])]
+ param()
+
+ if (!(Test-MtConnection SharePoint)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
+ }
+
+ try {
+ $spoTenant = Get-MtSpo
+
+ if ($spoTenant.SharingCapability -ne "ExternalUserAndGuestSharing") {
+ Add-MtTestResultDetail -SkippedBecause Custom -SkippedCustomReason "Anyone links are not enabled. This test only applies when sharing is set to Anyone."
+ return $null
+ }
+
+ # FileAnonymousLinkType: 1 = View, 2 = Edit
+ # FolderAnonymousLinkType: 1 = View, 2 = Edit
+ $testResult = $spoTenant.FileAnonymousLinkType -eq 1 -and $spoTenant.FolderAnonymousLinkType -eq 1
+
+ if ($testResult) {
+ $testResultMarkdown = "Well done. Anyone link permissions are limited to View only."
+ } else {
+ $testResultMarkdown = "Anyone link permissions are not limited to View only.`n`n* File Anyone link type: ``$($spoTenant.FileAnonymousLinkType)`` `n* Folder Anyone link type: ``$($spoTenant.FolderAnonymousLinkType)``"
+ }
+
+ Add-MtTestResultDetail -Result $testResultMarkdown
+
+ return $testResult
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
+}
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoDefaultSharingPermission.md b/powershell/public/cisa/spo/Test-MtCisaSpoDefaultSharingPermission.md
new file mode 100644
index 000000000..9b0266e88
--- /dev/null
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoDefaultSharingPermission.md
@@ -0,0 +1,18 @@
+File and folder default sharing permissions SHALL be set to View only.
+
+Rationale: Setting the default permission to View reduces the risk of accidental editing of shared documents by external parties.
+
+#### Remediation action:
+
+1. Sign in to the [SharePoint admin center](https://go.microsoft.com/fwlink/?linkid=2185219).
+2. Select Policies > Sharing.
+3. Under File and folder links, set the default link permission to **View**.
+4. Select Save.
+
+#### Related links
+
+* [CISA 2 Default Sharing Permission - MS.SHAREPOINT.2.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint22v1)
+* [CISA ScubaGear Rego Reference](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Rego/SharepointConfig.rego)
+
+
+%TestResult%
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoDefaultSharingPermission.ps1 b/powershell/public/cisa/spo/Test-MtCisaSpoDefaultSharingPermission.ps1
new file mode 100644
index 000000000..69cb39a3f
--- /dev/null
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoDefaultSharingPermission.ps1
@@ -0,0 +1,46 @@
+function Test-MtCisaSpoDefaultSharingPermission {
+ <#
+ .SYNOPSIS
+ Checks state of default SharePoint Online sharing permission
+
+ .DESCRIPTION
+ Default file and folder sharing permission SHOULD be set to View.
+
+ .EXAMPLE
+ Test-MtCisaSpoDefaultSharingPermission
+
+ Returns true if default sharing permission is set to View
+
+ .LINK
+ https://maester.dev/docs/commands/Test-MtCisaSpoDefaultSharingPermission
+ #>
+ [CmdletBinding()]
+ [OutputType([bool])]
+ param()
+
+ if (!(Test-MtConnection SharePoint)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
+ }
+
+ try {
+ $spoTenant = Get-MtSpo
+
+ # DefaultLinkPermission: None = not explicitly set, View = View only, Edit = Edit
+ # CISA requires an explicit View choice — None (never set) should fail.
+ $testResult = $spoTenant.DefaultLinkPermission -eq "View"
+
+ if ($testResult) {
+ $testResultMarkdown = "Well done. Your tenant default sharing permission is set to View."
+ } else {
+ $testResultMarkdown = "Your tenant default sharing permission is not set to View.`n`n* Current setting: ``$($spoTenant.DefaultLinkPermission)``"
+ }
+
+ Add-MtTestResultDetail -Result $testResultMarkdown
+
+ return $testResult
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
+}
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoDefaultSharingScope.md b/powershell/public/cisa/spo/Test-MtCisaSpoDefaultSharingScope.md
new file mode 100644
index 000000000..a6785040d
--- /dev/null
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoDefaultSharingScope.md
@@ -0,0 +1,18 @@
+File and folder default sharing scope SHALL be set to Specific People (only the people the user specifies).
+
+Rationale: Overly permissive default sharing settings increase the risk of unintentional data exposure. Setting the default to Specific People ensures users make deliberate sharing decisions.
+
+#### Remediation action:
+
+1. Sign in to the [SharePoint admin center](https://go.microsoft.com/fwlink/?linkid=2185219).
+2. Select Policies > Sharing.
+3. Under File and folder links, set the default link type to **Specific people (only the people the user specifies)**.
+4. Select Save.
+
+#### Related links
+
+* [CISA 2 Default Sharing Scope - MS.SHAREPOINT.2.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint21v1)
+* [CISA ScubaGear Rego Reference](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Rego/SharepointConfig.rego)
+
+
+%TestResult%
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoDefaultSharingScope.ps1 b/powershell/public/cisa/spo/Test-MtCisaSpoDefaultSharingScope.ps1
new file mode 100644
index 000000000..a7a0fcb83
--- /dev/null
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoDefaultSharingScope.ps1
@@ -0,0 +1,45 @@
+function Test-MtCisaSpoDefaultSharingScope {
+ <#
+ .SYNOPSIS
+ Checks state of default SharePoint Online sharing scope
+
+ .DESCRIPTION
+ Default sharing scope SHOULD be set to Specific People (Only the people the user specifies).
+
+ .EXAMPLE
+ Test-MtCisaSpoDefaultSharingScope
+
+ Returns true if default sharing scope is restricted to specific people
+
+ .LINK
+ https://maester.dev/docs/commands/Test-MtCisaSpoDefaultSharingScope
+ #>
+ [CmdletBinding()]
+ [OutputType([bool])]
+ param()
+
+ if (!(Test-MtConnection SharePoint)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
+ }
+
+ try {
+ $spoTenant = Get-MtSpo
+
+ # DefaultSharingLinkType: None = default (not explicitly set), Direct = Specific People, Internal = Organization, AnonymousAccess = Anyone
+ $testResult = $spoTenant.DefaultSharingLinkType -eq "Direct"
+
+ if ($testResult) {
+ $testResultMarkdown = "Well done. Your tenant default sharing scope is set to Specific People."
+ } else {
+ $testResultMarkdown = "Your tenant default sharing scope is not set to Specific People.`n`n* Current setting: ``$($spoTenant.DefaultSharingLinkType)``"
+ }
+
+ Add-MtTestResultDetail -Result $testResultMarkdown
+
+ return $testResult
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
+}
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoOneDriveSharing.md b/powershell/public/cisa/spo/Test-MtCisaSpoOneDriveSharing.md
new file mode 100644
index 000000000..4ffac4ed5
--- /dev/null
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoOneDriveSharing.md
@@ -0,0 +1,18 @@
+External sharing for OneDrive SHALL be limited to Existing guests or Only People in your organization.
+
+Rationale: Restricting OneDrive sharing reduces the risk of unauthorized data exposure from individual user storage. Allowing broad external sharing of OneDrive content increases the attack surface.
+
+#### Remediation action:
+
+1. Sign in to the [SharePoint admin center](https://go.microsoft.com/fwlink/?linkid=2185219).
+2. Select Policies > Sharing.
+3. Adjust external sharing slider for OneDrive to **Existing guests** or **Only people in your organization**.
+4. Select Save.
+
+#### Related links
+
+* [CISA 1 OneDrive Sharing - MS.SHAREPOINT.1.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint12v1)
+* [CISA ScubaGear Rego Reference](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Rego/SharepointConfig.rego)
+
+
+%TestResult%
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoOneDriveSharing.ps1 b/powershell/public/cisa/spo/Test-MtCisaSpoOneDriveSharing.ps1
new file mode 100644
index 000000000..80ddf1882
--- /dev/null
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoOneDriveSharing.ps1
@@ -0,0 +1,45 @@
+function Test-MtCisaSpoOneDriveSharing {
+ <#
+ .SYNOPSIS
+ Checks state of OneDrive sharing
+
+ .DESCRIPTION
+ External sharing for OneDrive SHALL be limited to Existing guests or Only People in your organization.
+
+ .EXAMPLE
+ Test-MtCisaSpoOneDriveSharing
+
+ Returns true if OneDrive sharing is restricted
+
+ .LINK
+ https://maester.dev/docs/commands/Test-MtCisaSpoOneDriveSharing
+ #>
+ [CmdletBinding()]
+ [OutputType([bool])]
+ param()
+
+ if (!(Test-MtConnection SharePoint)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
+ }
+
+ try {
+ $spoTenant = Get-MtSpo
+
+ # OneDriveSharingCapability: Disabled, ExistingExternalUserSharingOnly, ExternalUserSharingOnly, ExternalUserAndGuestSharing
+ $testResult = $spoTenant.OneDriveSharingCapability -in @("Disabled", "ExistingExternalUserSharingOnly")
+
+ if ($testResult) {
+ $testResultMarkdown = "Well done. OneDrive sharing is restricted."
+ } else {
+ $testResultMarkdown = "OneDrive sharing is not restricted.`n`n* Current setting: ``$($spoTenant.OneDriveSharingCapability)``"
+ }
+
+ Add-MtTestResultDetail -Result $testResultMarkdown
+
+ return $testResult
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
+}
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoSharing.ps1 b/powershell/public/cisa/spo/Test-MtCisaSpoSharing.ps1
index 284413a7b..79c99d385 100644
--- a/powershell/public/cisa/spo/Test-MtCisaSpoSharing.ps1
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoSharing.ps1
@@ -18,27 +18,27 @@
[OutputType([bool])]
param()
- $policy = Invoke-MtGraphRequest -RelativeUri "admin/sharepoint/settings" -ApiVersion "v1.0"
-
- $resultPolicy = $policy | Where-Object {
- $_.sharingCapability -in @("disabled","existingExternalUserSharingOnly")
+ if (!(Test-MtConnection SharePoint)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
}
- $testResult = ($resultPolicy | Measure-Object).Count -gt 0
+ try {
+ $spoTenant = Get-MtSpo
- if ($testResult) {
- $testResultMarkdown = "Well done. Your tenant restricts SharePoint Online sharing."
- } else {
- $testResultMarkdown = "Your tenant does not restrict SharePoint Online sharing.`n`n%TestResult%"
- $policy | ForEach-Object {
- $result = "* $($_.sharingCapability)`n"
- $result | Out-Null
- }
- }
+ $testResult = $spoTenant.SharingCapability -in @("Disabled", "ExistingExternalUserSharingOnly")
- $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $result
+ if ($testResult) {
+ $testResultMarkdown = "Well done. Your tenant restricts SharePoint Online sharing."
+ } else {
+ $testResultMarkdown = "Your tenant does not restrict SharePoint Online sharing.`n`n* Current setting: ``$($spoTenant.SharingCapability)``"
+ }
- Add-MtTestResultDetail -Result $testResultMarkdown
+ Add-MtTestResultDetail -Result $testResultMarkdown
- return $testResult
+ return $testResult
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
}
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoSharingAllowedDomain.ps1 b/powershell/public/cisa/spo/Test-MtCisaSpoSharingAllowedDomain.ps1
index ddb05286d..ff72f7767 100644
--- a/powershell/public/cisa/spo/Test-MtCisaSpoSharingAllowedDomain.ps1
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoSharingAllowedDomain.ps1
@@ -7,7 +7,7 @@
External sharing SHALL be restricted to approved external domains and/or users in approved security groups per interagency collaboration needs.
.EXAMPLE
- Test-MtCisaSpoSharingAllowedDomains
+ Test-MtCisaSpoSharingAllowedDomain
Returns true if sharing uses restricted domains
@@ -18,31 +18,35 @@
[OutputType([bool])]
param()
- $policy = Invoke-MtGraphRequest -RelativeUri "admin/sharepoint/settings" -ApiVersion "v1.0"
-
- if($policy.sharingCapability -eq "disabled"){
- Add-MtTestResultDetail -SkippedBecause Custom -SkippedCustomReason "SharePoint Online external sharing is disabled."
+ if (!(Test-MtConnection SharePoint)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
return $null
}
- $resultPolicy = $policy.sharingAllowedDomainList
-
- $testResult = ($resultPolicy | Measure-Object).Count -gt 0
+ try {
+ $spoTenant = Get-MtSpo
- if ($testResult) {
- $testResultMarkdown = "Well done. Your tenant restricts SharePoint Online sharing to specific domains.`n`n%TestResult%"
- } else {
- $testResultMarkdown = "Your tenant does not restrict SharePoint Online sharing to specific domains."
- }
+ if ($spoTenant.SharingCapability -eq "Disabled") {
+ Add-MtTestResultDetail -SkippedBecause Custom -SkippedCustomReason "SharePoint Online external sharing is disabled."
+ return $null
+ }
- $resultPolicy | ForEach-Object {
- $result = "* $_`n"
- $result | Out-Null
- }
+ # SharingDomainRestrictionMode: 0 = None, 1 = AllowList, 2 = BlockList
+ $testResult = $spoTenant.SharingDomainRestrictionMode -eq 1
- $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $result
+ if ($testResult) {
+ $allowedDomains = $spoTenant.SharingAllowedDomainList -split ' ' | Where-Object { $_ -ne '' }
+ $domainList = ($allowedDomains | ForEach-Object { "* $_" }) -join "`n"
+ $testResultMarkdown = "Well done. Your tenant restricts SharePoint Online sharing to approved domains.`n`n$domainList"
+ } else {
+ $testResultMarkdown = "Your tenant does not restrict SharePoint Online sharing to approved domains.`n`n* Current restriction mode: ``$($spoTenant.SharingDomainRestrictionMode)``"
+ }
- Add-MtTestResultDetail -Result $testResultMarkdown
+ Add-MtTestResultDetail -Result $testResultMarkdown
- return $testResult
+ return $testResult
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
}
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoVerificationCodeReauth.md b/powershell/public/cisa/spo/Test-MtCisaSpoVerificationCodeReauth.md
new file mode 100644
index 000000000..74ebfb122
--- /dev/null
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoVerificationCodeReauth.md
@@ -0,0 +1,19 @@
+Reauthentication days for people who use a verification code SHALL be set to 30 days or less.
+
+Rationale: Requiring periodic reauthentication via verification codes ensures that external users maintain valid access and reduces the risk of prolonged unauthorized access through stale sessions.
+
+#### Remediation action:
+
+1. Sign in to the [SharePoint admin center](https://go.microsoft.com/fwlink/?linkid=2185219).
+2. Select Policies > Sharing.
+3. Under Advanced settings, check **Guests must sign in using the same account to which sharing invitations are sent**.
+4. Check **People who use a verification code must reauthenticate after this many days** and set to **30** days or less.
+5. Select Save.
+
+#### Related links
+
+* [CISA 3 Verification Code Reauth - MS.SHAREPOINT.3.3v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint33v1)
+* [CISA ScubaGear Rego Reference](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/Rego/SharepointConfig.rego)
+
+
+%TestResult%
diff --git a/powershell/public/cisa/spo/Test-MtCisaSpoVerificationCodeReauth.ps1 b/powershell/public/cisa/spo/Test-MtCisaSpoVerificationCodeReauth.ps1
new file mode 100644
index 000000000..3438ee05b
--- /dev/null
+++ b/powershell/public/cisa/spo/Test-MtCisaSpoVerificationCodeReauth.ps1
@@ -0,0 +1,53 @@
+function Test-MtCisaSpoVerificationCodeReauth {
+ <#
+ .SYNOPSIS
+ Checks state of verification code reauthentication for SharePoint Online
+
+ .DESCRIPTION
+ Reauthentication with verification code SHOULD be required after thirty days or less.
+
+ .EXAMPLE
+ Test-MtCisaSpoVerificationCodeReauth
+
+ Returns true if verification code reauthentication is required within 30 days or less
+
+ .LINK
+ https://maester.dev/docs/commands/Test-MtCisaSpoVerificationCodeReauth
+ #>
+ [CmdletBinding()]
+ [OutputType([bool])]
+ param()
+
+ if (!(Test-MtConnection SharePoint)) {
+ Add-MtTestResultDetail -SkippedBecause NotConnectedSharePoint
+ return $null
+ }
+
+ try {
+ $spoTenant = Get-MtSpo
+
+ # Only applicable when sharing allows external users: ExternalUserSharingOnly (1) or ExternalUserAndGuestSharing (2)
+ if ($spoTenant.SharingCapability -notin @("ExternalUserSharingOnly", "ExternalUserAndGuestSharing")) {
+ Add-MtTestResultDetail -SkippedBecause Custom -SkippedCustomReason "External sharing does not allow new or anonymous guests. Verification code reauthentication is not applicable."
+ return $null
+ }
+
+ # EmailAttestationRequired: $true = verification code required, $false = not required
+ # EmailAttestationReAuthDays: number of days before reauthentication is required
+ # CISA requires <= 30 days
+ $testResult = $spoTenant.EmailAttestationRequired -eq $true -and $spoTenant.EmailAttestationReAuthDays -le 30
+
+ if ($testResult) {
+ $testResultMarkdown = "Well done. Verification code reauthentication is required every $($spoTenant.EmailAttestationReAuthDays) day(s)."
+ } else {
+ $testResultMarkdown = "Verification code reauthentication is not properly configured.`n`n* Email attestation required: ``$($spoTenant.EmailAttestationRequired)`` `n* Reauthentication days: ``$($spoTenant.EmailAttestationReAuthDays)``"
+ }
+
+ Add-MtTestResultDetail -Result $testResultMarkdown
+
+ return $testResult
+ } catch {
+ Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_
+ return $null
+ }
+}
diff --git a/powershell/public/core/Test-MtConnection.ps1 b/powershell/public/core/Test-MtConnection.ps1
index 6994d3019..0155341b3 100644
--- a/powershell/public/core/Test-MtConnection.ps1
+++ b/powershell/public/core/Test-MtConnection.ps1
@@ -35,7 +35,7 @@
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', 'AvoidUsingWriteHost', Justification = 'Sending colorful output to host in addition to rich object output.')]
param(
# Checks if the current session is connected to the specified service
- [ValidateSet('All', 'Azure', 'ExchangeOnline', 'EOP', 'Graph', 'SecurityCompliance', 'Teams')]
+ [ValidateSet('All', 'Azure', 'ExchangeOnline', 'EOP', 'Graph', 'SecurityCompliance', 'SharePoint', 'Teams')]
[Parameter(Position = 0)]
[string[]]$Service = 'Graph',
@@ -51,6 +51,7 @@
Graph = $null
ExchangeOnline = $null
ExchangeOnlineProtection = $null
+ SharePoint = $null
Teams = $null
AllConnected = $false
}
@@ -131,6 +132,20 @@
}
#endregion Exchange Online Protection (EOP)
+ #region SharePoint
+ if ($Service -contains 'SharePoint' -or $Service -contains 'All') {
+ $IsConnected = $false
+ try {
+ $MtConnections.SharePoint = Get-PnPConnection
+ $IsConnected = $null -ne ($MtConnections.SharePoint)
+ } catch {
+ Write-Debug "SharePoint: $false"
+ }
+ Write-Verbose "SharePoint: $IsConnected"
+ if (!$IsConnected) { $ConnectionState = $false }
+ }
+ #endregion SharePoint
+
#region Teams
if ($Service -contains 'Teams' -or $Service -contains 'All') {
$IsConnected = $false
diff --git a/tests/cisa/spo/Test-MtCisaSpoAnyoneLinkExpiration.Tests.ps1 b/tests/cisa/spo/Test-MtCisaSpoAnyoneLinkExpiration.Tests.ps1
new file mode 100644
index 000000000..7c44e8f40
--- /dev/null
+++ b/tests/cisa/spo/Test-MtCisaSpoAnyoneLinkExpiration.Tests.ps1
@@ -0,0 +1,10 @@
+Describe "CISA" -Tag "MS.SHAREPOINT", "MS.SHAREPOINT.3.1", "CISA.MS.SHAREPOINT.3.1", "CISA" {
+ It "CISA.MS.SHAREPOINT.3.1: Expiration days for Anyone links SHALL be set to 30 days or less." {
+
+ $result = Test-MtCisaSpoAnyoneLinkExpiration
+
+ if ($null -ne $result) {
+ $result | Should -Be $true -Because "Anyone links expire within 30 days."
+ }
+ }
+}
diff --git a/tests/cisa/spo/Test-MtCisaSpoAnyoneLinkPermission.Tests.ps1 b/tests/cisa/spo/Test-MtCisaSpoAnyoneLinkPermission.Tests.ps1
new file mode 100644
index 000000000..b8b506f7c
--- /dev/null
+++ b/tests/cisa/spo/Test-MtCisaSpoAnyoneLinkPermission.Tests.ps1
@@ -0,0 +1,10 @@
+Describe "CISA" -Tag "MS.SHAREPOINT", "MS.SHAREPOINT.3.2", "CISA.MS.SHAREPOINT.3.2", "CISA" {
+ It "CISA.MS.SHAREPOINT.3.2: Allowable file and folder permissions for Anyone links SHALL be set to View only." {
+
+ $result = Test-MtCisaSpoAnyoneLinkPermission
+
+ if ($null -ne $result) {
+ $result | Should -Be $true -Because "Anyone link permissions are View only."
+ }
+ }
+}
diff --git a/tests/cisa/spo/Test-MtCisaSpoDefaultSharingPermission.Tests.ps1 b/tests/cisa/spo/Test-MtCisaSpoDefaultSharingPermission.Tests.ps1
new file mode 100644
index 000000000..e1dd36663
--- /dev/null
+++ b/tests/cisa/spo/Test-MtCisaSpoDefaultSharingPermission.Tests.ps1
@@ -0,0 +1,10 @@
+Describe "CISA" -Tag "MS.SHAREPOINT", "MS.SHAREPOINT.2.2", "CISA.MS.SHAREPOINT.2.2", "CISA" {
+ It "CISA.MS.SHAREPOINT.2.2: File and folder default sharing permissions SHALL be set to View only." {
+
+ $result = Test-MtCisaSpoDefaultSharingPermission
+
+ if ($null -ne $result) {
+ $result | Should -Be $true -Because "default sharing permission is View."
+ }
+ }
+}
diff --git a/tests/cisa/spo/Test-MtCisaSpoDefaultSharingScope.Tests.ps1 b/tests/cisa/spo/Test-MtCisaSpoDefaultSharingScope.Tests.ps1
new file mode 100644
index 000000000..49766959d
--- /dev/null
+++ b/tests/cisa/spo/Test-MtCisaSpoDefaultSharingScope.Tests.ps1
@@ -0,0 +1,10 @@
+Describe "CISA" -Tag "MS.SHAREPOINT", "MS.SHAREPOINT.2.1", "CISA.MS.SHAREPOINT.2.1", "CISA" {
+ It "CISA.MS.SHAREPOINT.2.1: File and folder default sharing scope SHALL be set to Specific People." {
+
+ $result = Test-MtCisaSpoDefaultSharingScope
+
+ if ($null -ne $result) {
+ $result | Should -Be $true -Because "default sharing scope is Specific People."
+ }
+ }
+}
diff --git a/tests/cisa/spo/Test-MtCisaSpoOneDriveSharing.Tests.ps1 b/tests/cisa/spo/Test-MtCisaSpoOneDriveSharing.Tests.ps1
new file mode 100644
index 000000000..fe65e5d7a
--- /dev/null
+++ b/tests/cisa/spo/Test-MtCisaSpoOneDriveSharing.Tests.ps1
@@ -0,0 +1,10 @@
+Describe "CISA" -Tag "MS.SHAREPOINT", "MS.SHAREPOINT.1.2", "CISA.MS.SHAREPOINT.1.2", "CISA" {
+ It "CISA.MS.SHAREPOINT.1.2: External sharing for OneDrive SHALL be limited to Existing guests or Only People in your organization." {
+
+ $result = Test-MtCisaSpoOneDriveSharing
+
+ if ($null -ne $result) {
+ $result | Should -Be $true -Because "OneDrive sharing is restricted."
+ }
+ }
+}
diff --git a/tests/cisa/spo/Test-MtCisaSpoVerificationCodeReauth.Tests.ps1 b/tests/cisa/spo/Test-MtCisaSpoVerificationCodeReauth.Tests.ps1
new file mode 100644
index 000000000..7e6344bb8
--- /dev/null
+++ b/tests/cisa/spo/Test-MtCisaSpoVerificationCodeReauth.Tests.ps1
@@ -0,0 +1,10 @@
+Describe "CISA" -Tag "MS.SHAREPOINT", "MS.SHAREPOINT.3.3", "CISA.MS.SHAREPOINT.3.3", "CISA" {
+ It "CISA.MS.SHAREPOINT.3.3: Reauthentication days for people who use a verification code SHALL be set to 30 days or less." {
+
+ $result = Test-MtCisaSpoVerificationCodeReauth
+
+ if ($null -ne $result) {
+ $result | Should -Be $true -Because "verification code reauthentication is within 30 days."
+ }
+ }
+}
diff --git a/website/docs/sections/create-entra-app.md b/website/docs/sections/create-entra-app.md
index 8d17a3b8f..7e734f068 100644
--- a/website/docs/sections/create-entra-app.md
+++ b/website/docs/sections/create-entra-app.md
@@ -102,6 +102,53 @@ $deleteAssignment = Invoke-AzRestMethod -Path "$($assignment.RoleAssignmentId)?a
```
+
+ (Optional) Grant permissions to SharePoint Online
+### (Optional) Grant permissions to SharePoint Online
+
+SharePoint Online tests require the **PnP.PowerShell** module and a dedicated PnP Entra ID app registration. The standard Maester app registration does not cover SharePoint tenant admin operations — PnP requires its own app with `Sites.FullControl.All` permissions.
+
+> The SharePoint Online permissions are necessary to support tests that validate [SharePoint Online configurations](https://maester.dev/docs/tests/cisa/spo), such as the CISA SharePoint baseline controls.
+
+#### Install PnP.PowerShell
+
+```powershell
+Install-Module PnP.PowerShell -Scope CurrentUser
+```
+
+#### Register the PnP Entra ID app
+
+PnP provides a built-in cmdlet to create the required app registration for interactive login:
+
+```powershell
+Register-PnPEntraIDAppForInteractiveLogin -ApplicationName "Maester PnP" -Tenant [yourtenant].onmicrosoft.com -SharePointDelegatePermissions "AllSites.FullControl"
+```
+
+This will:
+- Create an Entra ID app registration with the required delegated permissions
+- Prompt you to authenticate and provide consent
+- Output the **Client ID** you will need for `Connect-Maester`
+
+> **Note:** `AllSites.FullControl` (delegated) is the minimum permission required by PnP for tenant admin cmdlets like `Get-PnPTenant`. There is no read-only equivalent.
+>
+> **Important:** After registering the app, open a **new PowerShell session** before running `Connect-Maester`, as the registration process loads PnP assemblies that can conflict with Microsoft Graph.
+
+#### Connect to SharePoint Online
+
+Use the Client ID from the previous step when connecting:
+
+```powershell
+Connect-Maester -Service Graph,SharePointOnline -SharePointClientId ""
+```
+
+For device code flow (e.g. non-interactive sessions):
+
+```powershell
+Connect-Maester -Service Graph,SharePointOnline -SharePointClientId "" -UseDeviceCode
+```
+
+
+
(Optional) Grant Dataverse permissions for Copilot Studio tests
### (Optional) Grant Dataverse permissions for Copilot Studio
diff --git a/website/docs/tests/cisa/spo.md b/website/docs/tests/cisa/spo.md
index 1bca46457..1ceb11dc1 100644
--- a/website/docs/tests/cisa/spo.md
+++ b/website/docs/tests/cisa/spo.md
@@ -14,13 +14,10 @@ The tests in this section verifies that a Microsoft 365 tenant’s **SharePoint
| Cmdlet Name | CISA Control ID (Link) |
|- | - |
| Test-MtCisaSpoSharing | [MS.SHAREPOINT.1.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint11v1) |
-| Not Implemented (Not availabile in Graph) | [MS.SHAREPOINT.1.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint12v1) |
+| Test-MtCisaSpoOneDriveSharing | [MS.SHAREPOINT.1.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint12v1) |
| Test-MtCisaSpoSharingAllowedDomain | [MS.SHAREPOINT.1.3v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint13v1) |
-| Not Implemented (Deprecated setting) | [MS.SHAREPOINT.1.4v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint14v1) |
-| Not Implemented | [MS.SHAREPOINT.2.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint21v1) |
-| Not Implemented | [MS.SHAREPOINT.2.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint22v1) |
-| Not Implemented | [MS.SHAREPOINT.3.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint31v1) |
-| Not Implemented | [MS.SHAREPOINT.3.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint32v1) |
-| Not Implemented | [MS.SHAREPOINT.3.3v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint33v1) |
-| Not Implemented | [MS.SHAREPOINT.3.3v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint33v1) |
-| Not Implemented | [MS.SHAREPOINT.4.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint42v1) |
+| Test-MtCisaSpoDefaultSharingScope | [MS.SHAREPOINT.2.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint21v1) |
+| Test-MtCisaSpoDefaultSharingPermission | [MS.SHAREPOINT.2.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint22v1) |
+| Test-MtCisaSpoAnyoneLinkExpiration | [MS.SHAREPOINT.3.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint31v1) |
+| Test-MtCisaSpoAnyoneLinkPermission | [MS.SHAREPOINT.3.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint32v1) |
+| Test-MtCisaSpoVerificationCodeReauth | [MS.SHAREPOINT.3.3v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint33v1) |
diff --git a/website/versioned_docs/version-2.0.0/tests/cisa/spo.md b/website/versioned_docs/version-2.0.0/tests/cisa/spo.md
index 1bca46457..1ceb11dc1 100644
--- a/website/versioned_docs/version-2.0.0/tests/cisa/spo.md
+++ b/website/versioned_docs/version-2.0.0/tests/cisa/spo.md
@@ -14,13 +14,10 @@ The tests in this section verifies that a Microsoft 365 tenant’s **SharePoint
| Cmdlet Name | CISA Control ID (Link) |
|- | - |
| Test-MtCisaSpoSharing | [MS.SHAREPOINT.1.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint11v1) |
-| Not Implemented (Not availabile in Graph) | [MS.SHAREPOINT.1.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint12v1) |
+| Test-MtCisaSpoOneDriveSharing | [MS.SHAREPOINT.1.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint12v1) |
| Test-MtCisaSpoSharingAllowedDomain | [MS.SHAREPOINT.1.3v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint13v1) |
-| Not Implemented (Deprecated setting) | [MS.SHAREPOINT.1.4v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint14v1) |
-| Not Implemented | [MS.SHAREPOINT.2.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint21v1) |
-| Not Implemented | [MS.SHAREPOINT.2.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint22v1) |
-| Not Implemented | [MS.SHAREPOINT.3.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint31v1) |
-| Not Implemented | [MS.SHAREPOINT.3.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint32v1) |
-| Not Implemented | [MS.SHAREPOINT.3.3v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint33v1) |
-| Not Implemented | [MS.SHAREPOINT.3.3v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint33v1) |
-| Not Implemented | [MS.SHAREPOINT.4.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint42v1) |
+| Test-MtCisaSpoDefaultSharingScope | [MS.SHAREPOINT.2.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint21v1) |
+| Test-MtCisaSpoDefaultSharingPermission | [MS.SHAREPOINT.2.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint22v1) |
+| Test-MtCisaSpoAnyoneLinkExpiration | [MS.SHAREPOINT.3.1v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint31v1) |
+| Test-MtCisaSpoAnyoneLinkPermission | [MS.SHAREPOINT.3.2v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint32v1) |
+| Test-MtCisaSpoVerificationCodeReauth | [MS.SHAREPOINT.3.3v1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/sharepoint.md#mssharepoint33v1) |