From bc9df17627c913ea212190485d4fc8cf75aaa204 Mon Sep 17 00:00:00 2001 From: Ofir Gavish Date: Mon, 11 May 2026 22:22:31 -0400 Subject: [PATCH 1/2] Add DSPM for AI tests (MT.1172-MT.1176) Adds five Maester tests covering Microsoft Purview Data Security Posture Management for AI: - MT.1172 Unified audit log ingestion is enabled for AI activity - MT.1173 Sensitivity labels are published for files used by Microsoft 365 Copilot - MT.1174 Insider Risk Management policy for Risky AI usage is enabled - MT.1175 DLP policy is configured for the Microsoft 365 Copilot location - MT.1176 Retention policy is configured for the Microsoft Copilot location MT.1175 detects Copilot DLP coverage across the multiple shapes exposed by Get-DlpCompliancePolicy: MicrosoftCopilotLocation, Workload (MicrosoftCopilot), Locations, and EnforcementPlanes (CopilotExperiences). MT.1176 queries both retention surfaces -- legacy Get-RetentionCompliancePolicy and the newer Get-AppRetentionCompliancePolicy (User:M365Copilot) -- via the Get-MtExo cache wrapper. Passes if either surface protects Copilot. Test IDs originally proposed as MT.1152-MT.1156 were renumbered to MT.1172-MT.1176 to avoid collision with the Defender antivirus metadata batch added upstream in 7ebade57. --- powershell/Maester.psd1 | 5 +- powershell/public/cisa/exchange/Get-MtExo.ps1 | 2 + .../Test-MtPurviewAiAuditLogIngestion.md | 28 ++++ .../Test-MtPurviewAiAuditLogIngestion.ps1 | 63 +++++++ .../purview/Test-MtPurviewAiDlpPolicy.md | 30 ++++ .../purview/Test-MtPurviewAiDlpPolicy.ps1 | 115 +++++++++++++ .../Test-MtPurviewAiInsiderRiskPolicy.md | 33 ++++ .../Test-MtPurviewAiInsiderRiskPolicy.ps1 | 99 +++++++++++ .../Test-MtPurviewAiRetentionPolicy.md | 38 +++++ .../Test-MtPurviewAiRetentionPolicy.ps1 | 156 ++++++++++++++++++ ...st-MtPurviewAiSensitivityLabelsForFiles.md | 34 ++++ ...t-MtPurviewAiSensitivityLabelsForFiles.ps1 | 100 +++++++++++ .../Purview/Test-MtPurviewAi.Tests.ps1 | 36 ++++ tests/maester-config.json | 25 +++ website/docs/tests/maester/MT.1172.md | 36 ++++ website/docs/tests/maester/MT.1173.md | 36 ++++ website/docs/tests/maester/MT.1174.md | 37 +++++ website/docs/tests/maester/MT.1175.md | 38 +++++ website/docs/tests/maester/MT.1176.md | 38 +++++ 19 files changed, 948 insertions(+), 1 deletion(-) create mode 100644 powershell/public/maester/purview/Test-MtPurviewAiAuditLogIngestion.md create mode 100644 powershell/public/maester/purview/Test-MtPurviewAiAuditLogIngestion.ps1 create mode 100644 powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.md create mode 100644 powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.ps1 create mode 100644 powershell/public/maester/purview/Test-MtPurviewAiInsiderRiskPolicy.md create mode 100644 powershell/public/maester/purview/Test-MtPurviewAiInsiderRiskPolicy.ps1 create mode 100644 powershell/public/maester/purview/Test-MtPurviewAiRetentionPolicy.md create mode 100644 powershell/public/maester/purview/Test-MtPurviewAiRetentionPolicy.ps1 create mode 100644 powershell/public/maester/purview/Test-MtPurviewAiSensitivityLabelsForFiles.md create mode 100644 powershell/public/maester/purview/Test-MtPurviewAiSensitivityLabelsForFiles.ps1 create mode 100644 tests/Maester/Purview/Test-MtPurviewAi.Tests.ps1 create mode 100644 website/docs/tests/maester/MT.1172.md create mode 100644 website/docs/tests/maester/MT.1173.md create mode 100644 website/docs/tests/maester/MT.1174.md create mode 100644 website/docs/tests/maester/MT.1175.md create mode 100644 website/docs/tests/maester/MT.1176.md diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1 index a8142ff5c..04582717c 100644 --- a/powershell/Maester.psd1 +++ b/powershell/Maester.psd1 @@ -156,7 +156,10 @@ 'Test-MtMdeRetainCleanedMalware', 'Test-MtMdeScheduleScanDay', 'Test-MtMdeScriptScanning', 'Test-MtMdeSignatureBeforeScan', 'Test-MtMdeSignatureUpdateInterval', 'Test-MtMdeSubmitSamplesConsent', 'Test-MtOperationApprovalPolicies', - 'Test-MtPimAlertsExists', 'Test-MtPrivPermanentDirectoryRole', 'Test-MtSecurityGroupCreationRestricted', + 'Test-MtPimAlertsExists', 'Test-MtPrivPermanentDirectoryRole', + 'Test-MtPurviewAiAuditLogIngestion', 'Test-MtPurviewAiDlpPolicy', 'Test-MtPurviewAiInsiderRiskPolicy', + 'Test-MtPurviewAiRetentionPolicy', 'Test-MtPurviewAiSensitivityLabelsForFiles', + 'Test-MtSecurityGroupCreationRestricted', 'Test-MtServicePrincipalsForAllUsers', 'Test-MtSpExchangeAppAccessPolicy', 'Test-MtTeamsRestrictParticipantGiveRequestControl', 'Test-MtTenantCreationRestricted', 'Test-MtTenantCustomization', 'Test-MtUserAccessAdmin', 'Test-MtVaultSoftDelete', 'Test-MtWindowsDataProcessor', diff --git a/powershell/public/cisa/exchange/Get-MtExo.ps1 b/powershell/public/cisa/exchange/Get-MtExo.ps1 index 5e067738a..2f982fec7 100644 --- a/powershell/public/cisa/exchange/Get-MtExo.ps1 +++ b/powershell/public/cisa/exchange/Get-MtExo.ps1 @@ -45,6 +45,8 @@ "SharingPolicy" = "Get-SharingPolicy" "DlpComplianceRule" = "Get-DlpComplianceRule" "DlpCompliancePolicy" = "Get-DlpCompliancePolicy" + "RetentionCompliancePolicy" = "Get-RetentionCompliancePolicy" + "AppRetentionCompliancePolicy" = "Get-AppRetentionCompliancePolicy" "MalwareFilterPolicy" = "Get-MalwareFilterPolicy" "HostedContentFilterPolicy" = "Get-HostedContentFilterPolicy" "HostedConnectionFilterPolicy" = "Get-HostedConnectionFilterPolicy" diff --git a/powershell/public/maester/purview/Test-MtPurviewAiAuditLogIngestion.md b/powershell/public/maester/purview/Test-MtPurviewAiAuditLogIngestion.md new file mode 100644 index 000000000..fc5a27ce6 --- /dev/null +++ b/powershell/public/maester/purview/Test-MtPurviewAiAuditLogIngestion.md @@ -0,0 +1,28 @@ +Ensure the Microsoft 365 unified audit log is enabled so Microsoft 365 Copilot, Security Copilot, Copilot Studio and Entra-registered AI app prompts and responses are captured for downstream Purview AI controls. + +The unified audit log is the **prerequisite** for the Microsoft Purview Data Security Posture Management (DSPM) for AI activity explorer, the Risky AI usage Insider Risk Management template, eDiscovery searches that include Copilot interactions, and Communication Compliance policies that monitor Copilot prompts/responses. + +When `UnifiedAuditLogIngestionEnabled` is `False`: + +- AI prompts and responses are **not** captured anywhere in the tenant. +- DSPM for AI activity explorer will be empty. +- Risky AI usage Insider Risk policies cannot generate alerts. +- eDiscovery cannot find Copilot transcripts. +- Communication Compliance for Copilot interactions will not match. + +The test passes if `Get-AdminAuditLogConfig` returns `UnifiedAuditLogIngestionEnabled = True`. + +#### Remediation action: + +1. Connect to Exchange Online PowerShell as a Compliance Administrator or higher: `Connect-ExchangeOnline`. +2. Run: `Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true`. +3. Allow up to 60 minutes for ingestion to begin and confirm by searching the [Microsoft Purview audit search](https://purview.microsoft.com/audit/auditsearch). + +#### Related links + +- [Microsoft Learn — Turn auditing on or off](https://learn.microsoft.com/en-us/purview/audit-log-enable-disable) +- [Microsoft Learn — Data Security Posture Management for AI](https://learn.microsoft.com/en-us/purview/ai-microsoft-purview) +- [Microsoft Learn — Audit logs for Microsoft 365 Copilot interactions](https://learn.microsoft.com/en-us/purview/audit-copilot) + + +%TestResult% diff --git a/powershell/public/maester/purview/Test-MtPurviewAiAuditLogIngestion.ps1 b/powershell/public/maester/purview/Test-MtPurviewAiAuditLogIngestion.ps1 new file mode 100644 index 000000000..f1609e740 --- /dev/null +++ b/powershell/public/maester/purview/Test-MtPurviewAiAuditLogIngestion.ps1 @@ -0,0 +1,63 @@ +function Test-MtPurviewAiAuditLogIngestion { + <# + .SYNOPSIS + Ensure the Microsoft 365 unified audit log is enabled so Microsoft 365 Copilot and other AI app interactions are captured. + + .DESCRIPTION + Checks the Microsoft 365 unified audit log ingestion state via Get-AdminAuditLogConfig. + + Microsoft 365 Copilot, Security Copilot, Copilot in Fabric, Copilot Studio and Entra-registered AI apps all + flow user prompts and responses into the Microsoft Purview unified audit log. The DSPM for AI activity explorer, + Insider Risk Management Risky AI policies, eDiscovery searches for Copilot activity, and Communication Compliance + policies for Copilot interactions all depend on the unified audit log being enabled. + + Without unified audit log ingestion, Copilot prompts and responses are NOT captured, leaving organisations with no + forensic record of how generative AI is being used and breaking every downstream Purview AI control. + + The test passes if the tenant returns UnifiedAuditLogIngestionEnabled = True. + + .EXAMPLE + Test-MtPurviewAiAuditLogIngestion + + Returns true if the unified audit log is enabled. + + .LINK + https://maester.dev/docs/commands/Test-MtPurviewAiAuditLogIngestion + #> + [CmdletBinding()] + [OutputType([bool])] + param() + + if (!(Test-MtConnection ExchangeOnline)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedExchange + return $null + } + + try { + $config = Get-AdminAuditLogConfig -ErrorAction Stop + $enabled = [bool]$config.UnifiedAuditLogIngestionEnabled + + $portalLink = "https://purview.microsoft.com/audit/auditsearch" + + if ($enabled) { + $testResultMarkdown = "Well done. Your tenant has [unified audit log ingestion enabled]($portalLink), " + $testResultMarkdown += "so Microsoft 365 Copilot and other AI app prompts/responses are captured for " + $testResultMarkdown += "DSPM for AI, Insider Risk Management, eDiscovery and Communication Compliance." + } else { + $testResultMarkdown = "Your tenant does **not** have [unified audit log ingestion enabled]($portalLink).`n`n" + $testResultMarkdown += "> **Risk:** Without the unified audit log, Microsoft 365 Copilot prompts and responses are not captured. " + $testResultMarkdown += "DSPM for AI activity explorer, the Risky AI usage Insider Risk template, Copilot eDiscovery searches, " + $testResultMarkdown += "and Communication Compliance for Copilot will all be empty or non-functional." + } + + Add-MtTestResultDetail -Result $testResultMarkdown + return $enabled + } catch { + if ($_.Exception.Response -and $_.Exception.Response.StatusCode -in @(401, 403)) { + Add-MtTestResultDetail -SkippedBecause NotAuthorized + } else { + Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_ + } + return $null + } +} diff --git a/powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.md b/powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.md new file mode 100644 index 000000000..3069dfa58 --- /dev/null +++ b/powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.md @@ -0,0 +1,30 @@ +Ensure a Microsoft Purview Data Loss Prevention (DLP) policy is configured for the **Microsoft 365 Copilot** location, so Copilot is blocked from summarising or surfacing files containing sensitive information types or labelled content the requesting user has access to but should not have AI summarise. + +Microsoft 365 Copilot acts on every file, message and meeting that the requesting user can read. Without a DLP policy targeting the Copilot location, Copilot can: + +- Summarise files containing **PII, PCI, PHI, secrets or other regulated data**. +- Paraphrase content from labelled documents in chat / Word / PowerPoint generations. +- Expose oversharing risk in OneDrive / SharePoint at AI speed. + +A DLP policy on the Copilot location lets you block Copilot interactions involving files that match SITs or have specific sensitivity labels — without disabling Copilot for the user entirely. + +The test passes when at least one **enabled, non-simulation** DLP policy targets the Microsoft 365 Copilot location. Detection inspects multiple Purview schema fields (`MicrosoftCopilotLocation`, `Workload`, `Locations`, and `EnforcementPlanes`) so policies created through the older preview surface and through the current Microsoft Purview portal / PowerShell paths are both recognised. + +#### Remediation action: + +1. Open the [Microsoft Purview portal — Data Loss Prevention — Policies](https://purview.microsoft.com/datalossprevention/policies). +2. Click **+ Create policy** and choose a Custom or template-based DLP policy. +3. Under **Locations**, enable **Microsoft 365 Copilot** (you may also enable Exchange / SharePoint / OneDrive / Devices alongside). +4. Configure rules — for example, match sensitive information types (Credit Card, SSN, customer-specific SITs) or sensitivity labels (Confidential, Highly Confidential). +5. Choose actions — block Copilot from processing the matched file, notify the user, or generate an alert. +6. Set **Mode = Turn the policy on immediately** (avoid leaving it in test/simulation mode for production protection). +7. Verify with: `Get-DlpCompliancePolicy | Where-Object { $_.MicrosoftCopilotLocation -or $_.Workload -match 'Copilot' -or ($_.Locations | Out-String) -match 'Copilot' -or ($_.EnforcementPlanes | Out-String) -match 'Copilot' }`. + +#### Related links + +- [Microsoft Learn — DLP for Microsoft 365 Copilot](https://learn.microsoft.com/en-us/purview/dlp-microsoft365-copilot-learn-about) +- [Microsoft Learn — Create a DLP policy](https://learn.microsoft.com/en-us/purview/dlp-create-deploy-policy) +- [Microsoft Learn — Microsoft 365 Copilot oversharing assessment](https://learn.microsoft.com/en-us/purview/ai-microsoft-purview) + + +%TestResult% diff --git a/powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.ps1 b/powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.ps1 new file mode 100644 index 000000000..946fd7220 --- /dev/null +++ b/powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.ps1 @@ -0,0 +1,115 @@ +function Test-MtPurviewAiDlpPolicy { + <# + .SYNOPSIS + Ensure a Microsoft Purview DLP policy is configured for the Microsoft 365 Copilot location. + + .DESCRIPTION + Microsoft Purview Data Loss Prevention exposes a **Microsoft 365 Copilot** location that lets administrators + block Copilot from summarising or surfacing files containing sensitive information types (SITs) or labelled content. + + Without a DLP policy targeting the Copilot location, Microsoft 365 Copilot can summarise, paraphrase or expose + sensitive content (PII, secrets, financial data, regulated data) from any file the requesting user can already + access — accelerating oversharing risk for AI-assisted workflows. + + The test passes if at least one enabled, non-simulation DLP policy targets the Microsoft 365 Copilot location. + Detection is resilient to schema variation: it inspects `MicrosoftCopilotLocation`, `Workload`, `Locations`, + and `EnforcementPlanes` (which can surface Copilot scope as values such as `CopilotExperiences`) so that + policies created through both the older preview surface and the current Microsoft Purview portal / + PowerShell paths are recognised. + + .EXAMPLE + Test-MtPurviewAiDlpPolicy + + Returns true if a DLP policy is configured for the Microsoft 365 Copilot location. + + .LINK + https://maester.dev/docs/commands/Test-MtPurviewAiDlpPolicy + #> + [CmdletBinding()] + [OutputType([bool])] + param() + + if (!(Test-MtConnection ExchangeOnline)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedExchange + return $null + } elseif (!(Test-MtConnection SecurityCompliance)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedSecurityCompliance + return $null + } elseif ($null -eq (Get-MtLicenseInformation -Product ExoDlp)) { + Add-MtTestResultDetail -SkippedBecause NotLicensedExoDlp + return $null + } + + try { + $policies = Get-MtExo -Request DlpCompliancePolicy + } catch { + if ($_.Exception.Response -and $_.Exception.Response.StatusCode -in @(401, 403)) { + Add-MtTestResultDetail -SkippedBecause NotAuthorized + } else { + Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_ + } + return $null + } + + # Match policies that target the Microsoft 365 Copilot location. + # Microsoft Purview surfaces Copilot DLP scope through several schema fields depending on + # how the policy was created (UI vs PowerShell) and the tenant's Purview schema version: + # - MicrosoftCopilotLocation : multivalued; 'All' or specific scopes (legacy / preview shape) + # - Workload : may include 'MicrosoftCopilot' / 'Copilot' + # - Locations : newer schema; can include Copilot entries with Workload/Name 'Copilot*' + # - EnforcementPlanes : newer schema; surfaces values such as 'CopilotExperiences' + # We OR all of these so the test does not false-fail tenants whose Copilot DLP policies are + # represented through the newer schema fields. + $copilotPolicies = @($policies | Where-Object { + ($_.MicrosoftCopilotLocation -and ($_.MicrosoftCopilotLocation.DisplayName -or $_.MicrosoftCopilotLocation.Count -gt 0)) -or + ($_.Workload -match 'Copilot') -or + ($_.Locations -and (@($_.Locations) | Where-Object { + ($_ -is [string] -and $_ -match 'Copilot') -or + ($_.Workload -match 'Copilot') -or + ($_.Name -match 'Copilot') -or + ($_.DisplayName -match 'Copilot') + })) -or + ($_.EnforcementPlanes -and ( + ($_.EnforcementPlanes -is [System.Collections.IEnumerable] -and -not ($_.EnforcementPlanes -is [string]) -and (@($_.EnforcementPlanes) | Where-Object { "$_" -match 'Copilot' })) -or + ("$($_.EnforcementPlanes)" -match 'Copilot') + )) + }) + + $enabledCopilotPolicies = @($copilotPolicies | Where-Object { + $_.Enabled -and -not $_.IsSimulationPolicy + }) + + $testResult = $enabledCopilotPolicies.Count -ge 1 + + $portalLink = "https://purview.microsoft.com/datalossprevention/policies" + + if ($testResult) { + $testResultMarkdown = "Well done. Your tenant has at least one enabled [Microsoft Purview DLP policy]($portalLink) " + $testResultMarkdown += "targeting the Microsoft 365 Copilot location.`n`n%TestResult%" + } else { + $testResultMarkdown = "Your tenant does not have an enabled [Microsoft Purview DLP policy]($portalLink) " + $testResultMarkdown += "targeting the Microsoft 365 Copilot location.`n`n" + $testResultMarkdown += "> **Risk:** Microsoft 365 Copilot can summarise, paraphrase or expose files containing sensitive " + $testResultMarkdown += "information types or labelled content from any source the requesting user can access, dramatically " + $testResultMarkdown += "amplifying oversharing risk through AI-assisted workflows.`n`n%TestResult%" + } + + $passResult = "✅ Pass" + $failResult = "❌ Fail" + $result = "| Name | Status | Mode | Enabled |`n" + $result += "| --- | --- | --- | --- |`n" + if ($copilotPolicies.Count -eq 0) { + $result += "| _No DLP policies target the Microsoft 365 Copilot location_ | $failResult | - | - |`n" + } else { + foreach ($item in ($copilotPolicies | Sort-Object -Property Name)) { + $itemResult = if ($item.Enabled -and -not $item.IsSimulationPolicy) { $passResult } else { $failResult } + $mode = if ($item.IsSimulationPolicy) { "Simulation" } else { $item.Mode } + $result += "| $($item.Name) | $itemResult | $mode | $($item.Enabled) |`n" + } + } + + $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $result + + Add-MtTestResultDetail -Result $testResultMarkdown + return $testResult +} diff --git a/powershell/public/maester/purview/Test-MtPurviewAiInsiderRiskPolicy.md b/powershell/public/maester/purview/Test-MtPurviewAiInsiderRiskPolicy.md new file mode 100644 index 000000000..f2ed84f77 --- /dev/null +++ b/powershell/public/maester/purview/Test-MtPurviewAiInsiderRiskPolicy.md @@ -0,0 +1,33 @@ +Ensure a Microsoft Purview Insider Risk Management policy from the **Risky AI usage** template is enabled so risky Microsoft 365 Copilot and AI-app interactions generate triageable alerts. + +The Risky AI usage template detects: + +- Prompts attempting to **jailbreak** or bypass system prompts. +- Prompts attempting to **extract or summarise sensitive content** that falls under DLP / SIT signals. +- Generation of **harmful, unethical or otherwise risky** AI responses. +- High-volume / anomalous AI-interaction patterns from individual users. + +Without a Risky AI usage policy enabled, these signals are silently lost — Insider Risk reviewers will have no triage queue for AI misuse, and DSPM for AI cannot escalate risky users into Insider Risk Management workflows. + +The test requires Microsoft 365 E5 or the Insider Risk Management add-on. The test will skip cleanly on tenants that do not license IRM (cmdlet `Get-InsiderRiskPolicy` is unavailable). + +The test passes when at least one Insider Risk policy with an AI-related scenario / template is enabled. + +#### Remediation action: + +1. Open the [Microsoft Purview portal — Insider Risk Management — Policies](https://purview.microsoft.com/insiderriskmgmt/policies). +2. Click **+ Create policy**. +3. Choose the **Risky AI usage** template (under the AI / Generative AI category). +4. Define users in scope (typically all users or a pilot group), reviewers, and any indicator thresholds. +5. **Enable** the policy and confirm: `Get-InsiderRiskPolicy | Where-Object { $_.InsiderRiskScenario -match 'AI' -and $_.Enabled }`. + +> **Tip:** Enable Adaptive Protection so that risky AI users automatically pick up stricter DLP / Conditional Access policies once they cross a risk threshold. + +#### Related links + +- [Microsoft Learn — Insider Risk Management policies](https://learn.microsoft.com/en-us/purview/insider-risk-management-policies) +- [Microsoft Learn — Detect risky use of AI with Insider Risk Management](https://learn.microsoft.com/en-us/purview/insider-risk-management-policy-templates) +- [Microsoft Learn — Microsoft Purview AI Hub & DSPM for AI](https://learn.microsoft.com/en-us/purview/ai-microsoft-purview) + + +%TestResult% diff --git a/powershell/public/maester/purview/Test-MtPurviewAiInsiderRiskPolicy.ps1 b/powershell/public/maester/purview/Test-MtPurviewAiInsiderRiskPolicy.ps1 new file mode 100644 index 000000000..ee574cdbb --- /dev/null +++ b/powershell/public/maester/purview/Test-MtPurviewAiInsiderRiskPolicy.ps1 @@ -0,0 +1,99 @@ +function Test-MtPurviewAiInsiderRiskPolicy { + <# + .SYNOPSIS + Ensure a Microsoft Purview Insider Risk Management policy from the Risky AI usage template is configured and enabled. + + .DESCRIPTION + Microsoft Purview Insider Risk Management ships a dedicated **Risky AI usage** policy template that detects + risky prompts and responses inside Microsoft 365 Copilot and other AI apps captured by DSPM for AI — for example + prompts attempting jailbreaks, harmful content generation, or extraction of sensitive information. + + Without a Risky AI usage policy enabled, alerts on risky Copilot/AI app interactions are not generated and + Insider Risk reviewers have no triage queue for AI misuse. + + The test passes if at least one Insider Risk Management policy based on the Risky AI usage template is enabled. + + Requires Microsoft 365 E5 or the Insider Risk Management add-on. + + .EXAMPLE + Test-MtPurviewAiInsiderRiskPolicy + + Returns true if a Risky AI usage Insider Risk policy is enabled. + + .LINK + https://maester.dev/docs/commands/Test-MtPurviewAiInsiderRiskPolicy + #> + [CmdletBinding()] + [OutputType([bool])] + param() + + if (!(Test-MtConnection SecurityCompliance)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedSecurityCompliance + return $null + } + + try { + $policies = Get-InsiderRiskPolicy -ErrorAction Stop + } catch { + $exception = $_.Exception + $errorId = $_.FullyQualifiedErrorId + $message = $exception.Message + + if ($exception.Response -and $exception.Response.StatusCode -in @(401, 403)) { + Add-MtTestResultDetail -SkippedBecause NotAuthorized + } elseif ( + $exception -is [System.Management.Automation.CommandNotFoundException] -or + $errorId -match 'CommandNotFoundException' -or + $message -match "The term 'Get-InsiderRiskPolicy' is not recognized" -or + $message -match 'Get-InsiderRiskPolicy.*(is unavailable|not available|was not found)' + ) { + # IRM cmdlets are unavailable when the tenant is not licensed for Insider Risk Management. + Add-MtTestResultDetail -SkippedBecause Custom -SkippedCustomReason "Get-InsiderRiskPolicy is unavailable. Microsoft 365 E5 or the Insider Risk Management add-on is required." + } else { + Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_ + } + return $null + } + + # The Risky AI usage template surfaces as an InsiderRiskScenario value such as 'RiskyAIUsage'. + # Match permissively on policy name or scenario to handle SKU/template renames over time. + $aiPolicies = @($policies | Where-Object { + $_.InsiderRiskScenario -match 'AI' -or + $_.Name -match 'Risky\s*AI' -or + $_.Name -match 'AI\s*Usage' -or + $_.PolicyTemplate -match 'AI' + }) + + $enabledAiPolicies = @($aiPolicies | Where-Object { $_.Enabled -eq $true }) + $testResult = $enabledAiPolicies.Count -ge 1 + + $portalLink = "https://purview.microsoft.com/insiderriskmgmt/policies" + + if ($testResult) { + $testResultMarkdown = "Well done. Your tenant has at least one [Insider Risk Management policy]($portalLink) " + $testResultMarkdown += "based on the Risky AI usage template enabled.`n`n%TestResult%" + } else { + $testResultMarkdown = "Your tenant does not have an enabled [Insider Risk Management policy]($portalLink) " + $testResultMarkdown += "based on the Risky AI usage template.`n`n" + $testResultMarkdown += "> **Risk:** Risky prompts and responses inside Microsoft 365 Copilot and other AI apps will not " + $testResultMarkdown += "generate Insider Risk alerts, and reviewers will have no triage queue for AI misuse.`n`n%TestResult%" + } + + $passResult = "✅ Pass" + $failResult = "❌ Fail" + $result = "| Name | Status | Scenario | Enabled |`n" + $result += "| --- | --- | --- | --- |`n" + if ($aiPolicies.Count -eq 0) { + $result += "| _No Risky AI usage policies found_ | $failResult | - | - |`n" + } else { + foreach ($item in ($aiPolicies | Sort-Object -Property Name)) { + $itemResult = if ($item.Enabled) { $passResult } else { $failResult } + $result += "| $($item.Name) | $itemResult | $($item.InsiderRiskScenario) | $($item.Enabled) |`n" + } + } + + $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $result + + Add-MtTestResultDetail -Result $testResultMarkdown + return $testResult +} diff --git a/powershell/public/maester/purview/Test-MtPurviewAiRetentionPolicy.md b/powershell/public/maester/purview/Test-MtPurviewAiRetentionPolicy.md new file mode 100644 index 000000000..f955b75e7 --- /dev/null +++ b/powershell/public/maester/purview/Test-MtPurviewAiRetentionPolicy.md @@ -0,0 +1,38 @@ +Ensure a Microsoft Purview retention policy is configured for the **Microsoft Copilot** location to govern how Microsoft 365 Copilot prompts and AI-generated responses are retained or deleted. + +Copilot interactions (the user prompt + the AI response) are stored in the user's Exchange mailbox in a hidden folder and are subject to Purview retention. Without a retention policy targeting the Microsoft Copilot location: + +- Copilot transcripts may be retained **indefinitely**, increasing data subject access request and breach blast-radius. +- The organisation may **not satisfy regulatory obligations** that require defensible AI interaction retention or disposal (EU AI Act record-keeping, sector-specific regulations). +- eDiscovery and legal-hold workflows may have inconsistent coverage of AI activity. + +Microsoft Purview now exposes Copilot retention through two surfaces: + +- **Legacy retention** (`Get-RetentionCompliancePolicy`) — historically used for the *Teams chats and Copilot interactions* location. +- **App retention** (`Get-AppRetentionCompliancePolicy`) — the current surface for **Microsoft Copilot experiences** (Microsoft 365 Copilot, Security Copilot, Copilot Studio, Copilot in Fabric) and **Enterprise AI apps**, with application identifiers such as `User:M365Copilot`. + +The test passes when at least one enabled Microsoft Purview retention policy targets Microsoft Copilot interactions on **either** surface. + +#### Remediation action: + +1. Open the [Microsoft Purview portal — Data Lifecycle Management — Policies](https://purview.microsoft.com/datalifecyclemanagement/policies). +2. Click **+ New retention policy**. +3. Choose the locations and enable **Microsoft Copilot** (under the AI category). +4. Define a retention duration (for example, retain for 1 year then delete) aligned to your regulatory and records-retention strategy. +5. Apply to all users or scoped pilot groups, and turn the policy on. +6. Verify with PowerShell, checking both surfaces: + + ```powershell + Connect-IPPSSession + Get-RetentionCompliancePolicy | Where-Object { $_.MicrosoftCopilotLocation -or $_.Workload -match 'Copilot' } + Get-AppRetentionCompliancePolicy | Where-Object { $_.Workload -match 'Copilot' -or ($_.Applications | Out-String) -match 'Copilot' -or ($_.Locations | Out-String) -match 'Copilot' } + ``` + +#### Related links + +- [Microsoft Learn — Retention policies for Microsoft 365 Copilot](https://learn.microsoft.com/en-us/purview/retention-policies-copilot) +- [Microsoft Learn — Learn about retention](https://learn.microsoft.com/en-us/purview/retention) +- [Microsoft Learn — Audit and eDiscovery for Microsoft 365 Copilot](https://learn.microsoft.com/en-us/purview/audit-copilot) + + +%TestResult% diff --git a/powershell/public/maester/purview/Test-MtPurviewAiRetentionPolicy.ps1 b/powershell/public/maester/purview/Test-MtPurviewAiRetentionPolicy.ps1 new file mode 100644 index 000000000..5392b5c1f --- /dev/null +++ b/powershell/public/maester/purview/Test-MtPurviewAiRetentionPolicy.ps1 @@ -0,0 +1,156 @@ +function Test-MtPurviewAiRetentionPolicy { + <# + .SYNOPSIS + Ensure a Microsoft Purview retention policy is configured for Microsoft 365 Copilot interactions. + + .DESCRIPTION + Microsoft Purview retention exposes Copilot interaction retention through two surfaces: + the legacy retention surface (`Get-RetentionCompliancePolicy`, historically used for the + "Teams chats and Copilot interactions" location) and the newer app-retention surface + (`Get-AppRetentionCompliancePolicy`) used for Microsoft Copilot experiences such as + Microsoft 365 Copilot, Security Copilot, Copilot Studio, and Copilot in Fabric, with + application identifiers such as `User:M365Copilot`. + + Without a retention policy on either surface, organisations cannot satisfy regulatory + obligations for AI interaction retention, cannot defensibly dispose of stale Copilot + transcripts, and may have gaps in eDiscovery or legal hold workflows. + + The test passes if at least one enabled Microsoft Purview retention policy targets + Microsoft Copilot interactions on EITHER the legacy retention surface or the + app-retention surface. + + .EXAMPLE + Test-MtPurviewAiRetentionPolicy + + Returns true if a retention policy is configured for the Microsoft Copilot location. + + .LINK + https://maester.dev/docs/commands/Test-MtPurviewAiRetentionPolicy + #> + [CmdletBinding()] + [OutputType([bool])] + param() + + if (!(Test-MtConnection SecurityCompliance)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedSecurityCompliance + return $null + } + + # Microsoft Purview now exposes Copilot retention through two surfaces: + # - Get-RetentionCompliancePolicy : legacy/general retention surface (Exchange/SharePoint/Teams + Copilot via the + # older 'Teams chats and Copilot interactions' location) + # - Get-AppRetentionCompliancePolicy : newer app-retention surface used by Microsoft Copilot experiences + # (M365 Copilot, Security Copilot, Copilot Studio, etc.) with application + # identifiers such as 'User:M365Copilot'. + # Tenants may have configured Copilot retention through either surface. The pass condition succeeds when EITHER + # surface contains an enabled policy targeting Microsoft Copilot interactions. + try { + $legacyPolicies = @(Get-MtExo -Request RetentionCompliancePolicy) + } catch { + if ($_.Exception.Response -and $_.Exception.Response.StatusCode -in @(401, 403)) { + Add-MtTestResultDetail -SkippedBecause NotAuthorized + } else { + Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_ + } + return $null + } + + # The app-retention cmdlet may be unavailable on older tenants / unlicensed environments. + # Treat a missing cmdlet as "no app-retention surface" rather than a hard error. + $appPolicies = @() + try { + $appPolicies = @(Get-MtExo -Request AppRetentionCompliancePolicy) + } catch { + $exception = $_.Exception + $errorId = $_.FullyQualifiedErrorId + $message = $exception.Message + if ($exception -is [System.Management.Automation.CommandNotFoundException] -or + $errorId -match 'CommandNotFoundException' -or + $message -match "The term 'Get-AppRetentionCompliancePolicy' is not recognized" -or + $message -match 'Get-AppRetentionCompliancePolicy.*(is unavailable|not available|was not found)') { + Write-Verbose "Get-AppRetentionCompliancePolicy is not available in this session/tenant; falling back to Get-RetentionCompliancePolicy only." + } elseif ($exception.Response -and $exception.Response.StatusCode -in @(401, 403)) { + # Authorization failure on the app-retention surface alone shouldn't fail the whole test — + # we still have legacy results to evaluate. + Write-Verbose "Not authorized to query Get-AppRetentionCompliancePolicy; continuing with legacy retention policies only." + } else { + Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_ + return $null + } + } + + # Match legacy retention policies that target the Microsoft Copilot location. + # Schema fields vary by tenant version: MicrosoftCopilotLocation (multivalued) and / or Workload contains 'MicrosoftCopilot'. + $copilotLegacyPolicies = @($legacyPolicies | Where-Object { + ($_.MicrosoftCopilotLocation -and ($_.MicrosoftCopilotLocation.DisplayName -or $_.MicrosoftCopilotLocation.Count -gt 0)) -or + ($_.Workload -match 'Copilot') + }) + + # Match app-retention policies that target Microsoft Copilot experiences. + # The app-retention surface uses Applications (e.g. 'User:M365Copilot') and Locations, + # and Workload may also surface a Copilot value. + $copilotAppPolicies = @($appPolicies | Where-Object { + ($_.Workload -match 'Copilot') -or + ($_.Applications -and (@($_.Applications) | Where-Object { "$_" -match 'M365Copilot|Copilot' })) -or + ($_.Locations -and (@($_.Locations) | Where-Object { + ($_ -is [string] -and $_ -match 'Copilot') -or + ($_.Workload -match 'Copilot') -or + ($_.Name -match 'Copilot') -or + ($_.DisplayName -match 'Copilot') + })) + }) + + # Tag each policy with its source surface so the markdown table can show provenance. + $taggedLegacy = @($copilotLegacyPolicies | ForEach-Object { + [pscustomobject]@{ + Name = $_.Name + Enabled = $_.Enabled + Mode = $_.Mode + Surface = 'Legacy' + } + }) + $taggedApp = @($copilotAppPolicies | ForEach-Object { + [pscustomobject]@{ + Name = $_.Name + Enabled = $_.Enabled + Mode = $_.Mode + Surface = 'App' + } + }) + $copilotPolicies = @($taggedLegacy + $taggedApp) + + $enabledCopilotPolicies = @($copilotPolicies | Where-Object { $_.Enabled -eq $true }) + + $testResult = $enabledCopilotPolicies.Count -ge 1 + + $portalLink = "https://purview.microsoft.com/datalifecyclemanagement/policies" + + if ($testResult) { + $testResultMarkdown = "Well done. Your tenant has at least one enabled [Microsoft Purview retention policy]($portalLink) " + $testResultMarkdown += "targeting Microsoft Copilot interactions (legacy retention or app-retention surface).`n`n%TestResult%" + } else { + $testResultMarkdown = "Your tenant does not have an enabled [Microsoft Purview retention policy]($portalLink) " + $testResultMarkdown += "targeting Microsoft Copilot interactions on either the legacy retention or app-retention surface.`n`n" + $testResultMarkdown += "> **Risk:** Copilot prompts and responses are not governed by a retention schedule, so the organisation " + $testResultMarkdown += "cannot satisfy regulatory obligations for AI interaction retention, cannot defensibly dispose of stale " + $testResultMarkdown += "Copilot transcripts, and may have gaps in eDiscovery or legal hold workflows for AI activity.`n`n%TestResult%" + } + + $passResult = "✅ Pass" + $failResult = "❌ Fail" + $result = "| Name | Surface | Status | Enabled | Mode |`n" + $result += "| --- | --- | --- | --- | --- |`n" + if ($copilotPolicies.Count -eq 0) { + $result += "| _No retention policies target Microsoft Copilot interactions_ | - | $failResult | - | - |`n" + } else { + foreach ($item in ($copilotPolicies | Sort-Object -Property Surface, Name)) { + $itemResult = if ($item.Enabled) { $passResult } else { $failResult } + $result += "| $($item.Name) | $($item.Surface) | $itemResult | $($item.Enabled) | $($item.Mode) |`n" + } + } + + $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $result + + Add-MtTestResultDetail -Result $testResultMarkdown + return $testResult +} diff --git a/powershell/public/maester/purview/Test-MtPurviewAiSensitivityLabelsForFiles.md b/powershell/public/maester/purview/Test-MtPurviewAiSensitivityLabelsForFiles.md new file mode 100644 index 000000000..3e679b991 --- /dev/null +++ b/powershell/public/maester/purview/Test-MtPurviewAiSensitivityLabelsForFiles.md @@ -0,0 +1,34 @@ +Ensure Microsoft Purview sensitivity labels are published to users and at least one published label is scoped to **files**, so Microsoft 365 Copilot honors and inherits labels onto AI-generated content. + +Microsoft 365 Copilot only: + +- **Honors** sensitivity labels (respecting encryption and usage rights from labelled source files). +- **Inherits** the most restrictive label from the source files used to generate a response onto the new generated content. + +…**when** sensitivity labels are actually published to users in your tenant. If no label policy is published, or no published label is scoped to files (SharePoint, OneDrive, Office documents), Copilot has no labelling signal to apply, and DSPM for AI cannot report on label-based oversharing risks. + +The test passes when: + +- At least one **label policy** is published / enforced (`Get-LabelPolicy`). +- At least one **label** has the `File` scope (`Get-Label` where `ContentType` includes `File`). + +#### Remediation action: + +1. Open the [Microsoft Purview portal — Information Protection — Labels](https://purview.microsoft.com/informationprotection/labels). +2. Create or edit a sensitivity label and ensure **Files** is selected under "Define the scope for this label". +3. Open **Label policies** and publish the label to the relevant users or groups. +4. Verify with PowerShell after a few minutes: + ```powershell + Connect-IPPSSession + Get-LabelPolicy | Where-Object { $_.Mode -eq 'Enforce' } + Get-Label | Where-Object { $_.ContentType -match 'File' } + ``` + +#### Related links + +- [Microsoft Learn — Sensitivity labels overview](https://learn.microsoft.com/en-us/purview/sensitivity-labels) +- [Microsoft Learn — Microsoft 365 Copilot data protection and sensitivity labels](https://learn.microsoft.com/en-us/copilot/microsoft-365/microsoft-365-copilot-privacy) +- [Microsoft Learn — How Copilot inherits sensitivity labels](https://learn.microsoft.com/en-us/purview/sensitivity-labels-coauthoring) + + +%TestResult% diff --git a/powershell/public/maester/purview/Test-MtPurviewAiSensitivityLabelsForFiles.ps1 b/powershell/public/maester/purview/Test-MtPurviewAiSensitivityLabelsForFiles.ps1 new file mode 100644 index 000000000..faab230ef --- /dev/null +++ b/powershell/public/maester/purview/Test-MtPurviewAiSensitivityLabelsForFiles.ps1 @@ -0,0 +1,100 @@ +function Test-MtPurviewAiSensitivityLabelsForFiles { + <# + .SYNOPSIS + Ensure Microsoft Purview sensitivity labels are published so Microsoft 365 Copilot and DSPM for AI honor and inherit them on AI-generated content. + + .DESCRIPTION + Checks that at least one sensitivity label policy is published, and that at least one published label is + scoped to files (the scope that governs SharePoint, OneDrive and Office files used by Microsoft 365 Copilot). + + Microsoft 365 Copilot only respects sensitivity labels and inherits the most restrictive label onto generated + content if labels are actually published to users. When no label is published or no published label targets + files, Copilot has no labelling signal to apply and DSPM for AI cannot report on label-based oversharing. + + The test passes if at least one published label policy exists AND at least one label has the File scope. + + .EXAMPLE + Test-MtPurviewAiSensitivityLabelsForFiles + + Returns true if a sensitivity label scoped to files is published in the tenant. + + .LINK + https://maester.dev/docs/commands/Test-MtPurviewAiSensitivityLabelsForFiles + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'Files is the Microsoft Purview sensitivity label scope being tested')] + [CmdletBinding()] + [OutputType([bool])] + param() + + if (!(Test-MtConnection SecurityCompliance)) { + Add-MtTestResultDetail -SkippedBecause NotConnectedSecurityCompliance + return $null + } + + try { + $labels = Get-Label -ErrorAction Stop + $labelPolicies = Get-LabelPolicy -ErrorAction Stop + + # A label policy is "published" when it is in Enforce mode AND, when the Enabled property is exposed by + # the SCC schema, it is also enabled. Some tenant/SCC versions omit Enabled entirely on label policies, + # in which case Enforce mode alone is the published signal. + $publishedPolicies = @($labelPolicies | Where-Object { + $_.Mode -eq 'Enforce' -and + (($_.PSObject.Properties.Match('Enabled').Count -eq 0) -or $_.Enabled) + }) + # ContentType is a multi-valued field including 'File', 'Email', 'Site', 'UnifiedGroup', 'PurviewAssets' etc. + $fileLabels = @($labels | Where-Object { $_.ContentType -match 'File' }) + + # Build the set of label identifiers actually published by the enforced (and enabled when supported) policies. + # LabelPolicy.Labels is a multi-valued collection of label IDs (GUIDs); fall back to ImmutableId/Name when available. + $publishedLabelIds = @() + foreach ($policy in $publishedPolicies) { + if ($policy.Labels) { $publishedLabelIds += @($policy.Labels) } + } + $publishedLabelIds = @($publishedLabelIds | Where-Object { $_ } | Select-Object -Unique) + + # Cross-reference: a file-scoped label is only "published" when it appears in at least one published policy. + $publishedFileLabels = @($fileLabels | Where-Object { + $label = $_ + $publishedLabelIds | Where-Object { + $_ -eq $label.Guid -or $_ -eq $label.ImmutableId -or $_ -eq $label.Name -or $_ -eq $label.DisplayName + } + }) + + $hasPublishedPolicy = $publishedPolicies.Count -ge 1 + $hasFileLabel = $fileLabels.Count -ge 1 + $hasPublishedFileLabel = $publishedFileLabels.Count -ge 1 + $testResult = $hasPublishedPolicy -and $hasPublishedFileLabel + + $portalLink = "https://purview.microsoft.com/informationprotection/labels" + + if ($testResult) { + $testResultMarkdown = "Well done. Your tenant publishes [sensitivity labels]($portalLink) scoped to files, " + $testResultMarkdown += "so Microsoft 365 Copilot honors and inherits them on AI-generated content.`n`n%TestResult%" + } else { + $testResultMarkdown = "Your tenant does not have [sensitivity labels]($portalLink) published for files.`n`n" + $testResultMarkdown += "> **Risk:** Microsoft 365 Copilot cannot honor or inherit sensitivity labels onto AI-generated content " + $testResultMarkdown += "if no published label targets files. DSPM for AI oversharing reports based on labels will also be empty.`n`n%TestResult%" + } + + $passResult = "✅ Pass" + $failResult = "❌ Fail" + $result = "| Check | Status | Details |`n" + $result += "| --- | --- | --- |`n" + $result += "| Published label policy exists | $(if ($hasPublishedPolicy) { $passResult } else { $failResult }) | $($publishedPolicies.Count) policy(ies) published |`n" + $result += "| Label with File scope exists | $(if ($hasFileLabel) { $passResult } else { $failResult }) | $($fileLabels.Count) label(s) scoped to files |`n" + $result += "| File-scoped label is published | $(if ($hasPublishedFileLabel) { $passResult } else { $failResult }) | $($publishedFileLabels.Count) published file-scoped label(s) |`n" + + $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $result + + Add-MtTestResultDetail -Result $testResultMarkdown + return $testResult + } catch { + if ($_.Exception.Response -and $_.Exception.Response.StatusCode -in @(401, 403)) { + Add-MtTestResultDetail -SkippedBecause NotAuthorized + } else { + Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_ + } + return $null + } +} diff --git a/tests/Maester/Purview/Test-MtPurviewAi.Tests.ps1 b/tests/Maester/Purview/Test-MtPurviewAi.Tests.ps1 new file mode 100644 index 000000000..8cf9399d7 --- /dev/null +++ b/tests/Maester/Purview/Test-MtPurviewAi.Tests.ps1 @@ -0,0 +1,36 @@ +Describe "Maester/Purview" -Tag "Maester", "Purview" { + It "MT.1172: Unified audit log ingestion is enabled for AI activity. See https://maester.dev/docs/tests/MT.1172" -Tag "MT.1172" { + $result = Test-MtPurviewAiAuditLogIngestion + if ($null -ne $result) { + $result | Should -Be $true -Because "the unified audit log is enabled so Microsoft 365 Copilot prompts and responses are captured." + } + } + + It "MT.1173: Sensitivity labels are published for files used by Microsoft 365 Copilot. See https://maester.dev/docs/tests/MT.1173" -Tag "MT.1173" { + $result = Test-MtPurviewAiSensitivityLabelsForFiles + if ($null -ne $result) { + $result | Should -Be $true -Because "Microsoft 365 Copilot only honors and inherits sensitivity labels when at least one published label is scoped to files." + } + } + + It "MT.1174: Insider Risk Management policy for Risky AI usage is enabled. See https://maester.dev/docs/tests/MT.1174" -Tag "MT.1174" { + $result = Test-MtPurviewAiInsiderRiskPolicy + if ($null -ne $result) { + $result | Should -Be $true -Because "risky Microsoft 365 Copilot and AI-app interactions should generate triageable alerts." + } + } + + It "MT.1175: DLP policy is configured for the Microsoft 365 Copilot location. See https://maester.dev/docs/tests/MT.1175" -Tag "MT.1175" { + $result = Test-MtPurviewAiDlpPolicy + if ($null -ne $result) { + $result | Should -Be $true -Because "Microsoft 365 Copilot should be blocked from summarising or surfacing files containing sensitive information." + } + } + + It "MT.1176: Retention policy is configured for the Microsoft Copilot location. See https://maester.dev/docs/tests/MT.1176" -Tag "MT.1176" { + $result = Test-MtPurviewAiRetentionPolicy + if ($null -ne $result) { + $result | Should -Be $true -Because "Microsoft 365 Copilot prompts and responses should be governed by a defined retention schedule." + } + } +} diff --git a/tests/maester-config.json b/tests/maester-config.json index e055960b7..f5ef62a00 100644 --- a/tests/maester-config.json +++ b/tests/maester-config.json @@ -1474,6 +1474,31 @@ "Severity": "High", "Title": "Sample Submission should send safe samples automatically" }, + { + "Id": "MT.1172", + "Severity": "High", + "Title": "Unified audit log ingestion is enabled for AI activity" + }, + { + "Id": "MT.1173", + "Severity": "Medium", + "Title": "Sensitivity labels are published for files used by Microsoft 365 Copilot" + }, + { + "Id": "MT.1174", + "Severity": "Medium", + "Title": "Insider Risk Management policy for Risky AI usage is enabled" + }, + { + "Id": "MT.1175", + "Severity": "High", + "Title": "DLP policy is configured for the Microsoft 365 Copilot location" + }, + { + "Id": "MT.1176", + "Severity": "Medium", + "Title": "Retention policy is configured for the Microsoft Copilot location" + }, { "Id": "ORCA.100", "Severity": "Medium", diff --git a/website/docs/tests/maester/MT.1172.md b/website/docs/tests/maester/MT.1172.md new file mode 100644 index 000000000..4f05bb706 --- /dev/null +++ b/website/docs/tests/maester/MT.1172.md @@ -0,0 +1,36 @@ +--- +title: MT.1172 - Unified audit log ingestion is enabled for AI activity +description: Ensures the Microsoft 365 unified audit log is enabled so Microsoft 365 Copilot, Security Copilot and other AI app prompts and responses are captured for downstream Purview AI controls. +slug: /tests/MT.1172 +sidebar_class_name: hidden +--- + +# Unified audit log ingestion is enabled for AI activity + +## Description + +Microsoft Purview Data Security Posture Management (DSPM) for AI, the Risky AI usage Insider Risk Management template, eDiscovery searches that include Copilot interactions, and Communication Compliance policies for Copilot all depend on the **unified audit log** being enabled. + +When `UnifiedAuditLogIngestionEnabled` is `False`, Microsoft 365 Copilot prompts and responses are not captured anywhere in the tenant — DSPM for AI activity explorer is empty, Risky AI usage policies cannot fire alerts, and eDiscovery cannot find Copilot transcripts. + +The test passes when `Get-AdminAuditLogConfig` returns `UnifiedAuditLogIngestionEnabled = True`. + +## How to fix + +1. Connect to Exchange Online as a Compliance Administrator: `Connect-ExchangeOnline`. +2. Run: `Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true`. +3. Allow up to 60 minutes for ingestion to begin and verify in the [Microsoft Purview audit search](https://purview.microsoft.com/audit/auditsearch). + +## Prerequisites + +This test uses Exchange Online PowerShell. + +```powershell +Connect-Maester -Service ExchangeOnline +``` + +## Learn more + +- [Turn auditing on or off](https://learn.microsoft.com/en-us/purview/audit-log-enable-disable) +- [Audit logs for Microsoft 365 Copilot interactions](https://learn.microsoft.com/en-us/purview/audit-copilot) +- [Data Security Posture Management for AI](https://learn.microsoft.com/en-us/purview/ai-microsoft-purview) diff --git a/website/docs/tests/maester/MT.1173.md b/website/docs/tests/maester/MT.1173.md new file mode 100644 index 000000000..a4ef9a451 --- /dev/null +++ b/website/docs/tests/maester/MT.1173.md @@ -0,0 +1,36 @@ +--- +title: MT.1173 - Sensitivity labels are published for files used by Microsoft 365 Copilot +description: Ensures Microsoft Purview sensitivity labels are published and at least one published label is scoped to files so Microsoft 365 Copilot honors and inherits labels onto AI-generated content. +slug: /tests/MT.1173 +sidebar_class_name: hidden +--- + +# Sensitivity labels are published for files used by Microsoft 365 Copilot + +## Description + +Microsoft 365 Copilot only honors and inherits sensitivity labels onto AI-generated content when sensitivity labels are actually published to users in your tenant and at least one published label is scoped to **files** (SharePoint, OneDrive and Office documents). + +Without a published file-scoped label, Copilot has no labelling signal to apply, the most-restrictive label inheritance behaviour cannot run, and DSPM for AI cannot report on label-based oversharing. + +The test passes when at least one label policy is published and at least one label has the `File` scope. + +## How to fix + +1. Open the [Microsoft Purview portal — Information Protection — Labels](https://purview.microsoft.com/informationprotection/labels). +2. Create or edit a sensitivity label and ensure **Files** is selected under "Define the scope for this label". +3. Open **Label policies** and publish the label to the relevant users or groups. + +## Prerequisites + +This test uses the Security & Compliance PowerShell session. + +```powershell +Connect-Maester -Service SecurityCompliance +``` + +## Learn more + +- [Sensitivity labels overview](https://learn.microsoft.com/en-us/purview/sensitivity-labels) +- [Microsoft 365 Copilot data protection and sensitivity labels](https://learn.microsoft.com/en-us/copilot/microsoft-365/microsoft-365-copilot-privacy) +- [How Copilot inherits sensitivity labels](https://learn.microsoft.com/en-us/purview/sensitivity-labels-coauthoring) diff --git a/website/docs/tests/maester/MT.1174.md b/website/docs/tests/maester/MT.1174.md new file mode 100644 index 000000000..aa167eea9 --- /dev/null +++ b/website/docs/tests/maester/MT.1174.md @@ -0,0 +1,37 @@ +--- +title: MT.1174 - Insider Risk Management policy for Risky AI usage is enabled +description: Ensures a Microsoft Purview Insider Risk Management policy from the Risky AI usage template is configured and enabled so risky Microsoft 365 Copilot and AI-app interactions generate triageable alerts. +slug: /tests/MT.1174 +sidebar_class_name: hidden +--- + +# Insider Risk Management policy for Risky AI usage is enabled + +## Description + +Microsoft Purview Insider Risk Management ships a dedicated **Risky AI usage** policy template that detects jailbreak attempts, harmful generations, sensitive-content extraction prompts and anomalous AI interaction patterns inside Microsoft 365 Copilot and other AI apps captured by DSPM for AI. + +Without this policy enabled, risky AI signals are silently lost and reviewers have no triage queue for AI misuse. + +The test passes when at least one Insider Risk policy with an AI-related scenario / template is enabled. + +## How to fix + +1. Open the [Microsoft Purview portal — Insider Risk Management — Policies](https://purview.microsoft.com/insiderriskmgmt/policies). +2. Click **+ Create policy** and choose the **Risky AI usage** template. +3. Define users in scope, reviewers, and indicator thresholds. +4. **Enable** the policy. + +## Prerequisites + +Requires Microsoft 365 E5 or the Insider Risk Management add-on. + +```powershell +Connect-Maester -Service SecurityCompliance +``` + +## Learn more + +- [Insider Risk Management policies](https://learn.microsoft.com/en-us/purview/insider-risk-management-policies) +- [Detect risky use of AI with Insider Risk Management](https://learn.microsoft.com/en-us/purview/insider-risk-management-policy-templates) +- [Microsoft Purview AI Hub & DSPM for AI](https://learn.microsoft.com/en-us/purview/ai-microsoft-purview) diff --git a/website/docs/tests/maester/MT.1175.md b/website/docs/tests/maester/MT.1175.md new file mode 100644 index 000000000..561ade71c --- /dev/null +++ b/website/docs/tests/maester/MT.1175.md @@ -0,0 +1,38 @@ +--- +title: MT.1175 - DLP policy is configured for the Microsoft 365 Copilot location +description: Ensures a Microsoft Purview Data Loss Prevention policy is enabled for the Microsoft 365 Copilot location to block Copilot from summarising or surfacing files containing sensitive information. +slug: /tests/MT.1175 +sidebar_class_name: hidden +--- + +# DLP policy is configured for the Microsoft 365 Copilot location + +## Description + +Microsoft Purview Data Loss Prevention exposes a **Microsoft 365 Copilot** location that lets you block Copilot from summarising or surfacing files containing sensitive information types (PII, PCI, PHI, secrets, regulated data) or labelled content the requesting user can access but should not have AI summarise. + +Without a DLP policy on the Copilot location, Copilot can paraphrase and expose sensitive content from any file the requesting user can read — accelerating oversharing risk through AI-assisted workflows. + +The test passes when at least one **enabled, non-simulation** DLP policy targets the Microsoft 365 Copilot location. + +## How to fix + +1. Open the [Microsoft Purview portal — Data Loss Prevention — Policies](https://purview.microsoft.com/datalossprevention/policies). +2. Click **+ Create policy**. +3. Under **Locations**, enable **Microsoft 365 Copilot**. +4. Configure rules matching your sensitive information types or sensitivity labels (Confidential, Highly Confidential). +5. Set **Mode** to **Turn the policy on immediately**. + +## Prerequisites + +Requires Exchange Online + Security & Compliance PowerShell sessions and a Microsoft Purview DLP licence. + +```powershell +Connect-Maester -Service ExchangeOnline,SecurityCompliance +``` + +## Learn more + +- [DLP for Microsoft 365 Copilot](https://learn.microsoft.com/en-us/purview/dlp-microsoft365-copilot-learn-about) +- [Create a DLP policy](https://learn.microsoft.com/en-us/purview/dlp-create-deploy-policy) +- [Microsoft 365 Copilot oversharing assessment](https://learn.microsoft.com/en-us/purview/ai-microsoft-purview) diff --git a/website/docs/tests/maester/MT.1176.md b/website/docs/tests/maester/MT.1176.md new file mode 100644 index 000000000..c24d6039e --- /dev/null +++ b/website/docs/tests/maester/MT.1176.md @@ -0,0 +1,38 @@ +--- +title: MT.1176 - Retention policy is configured for the Microsoft Copilot location +description: Ensures a Microsoft Purview retention policy is enabled for the Microsoft Copilot location to govern how Microsoft 365 Copilot prompts and AI-generated responses are retained or deleted. +slug: /tests/MT.1176 +sidebar_class_name: hidden +--- + +# Retention policy is configured for the Microsoft Copilot location + +## Description + +Copilot prompts and AI-generated responses are stored in the user's Exchange mailbox and are subject to Microsoft Purview retention. A retention policy targeting the **Microsoft Copilot** location lets the organisation retain Copilot transcripts for a defined period and defensibly dispose of them afterwards. + +Without one, Copilot interactions may be retained indefinitely (increasing data subject access request and breach blast-radius), the organisation may fail regulatory obligations for AI interaction record-keeping, and eDiscovery / legal-hold workflows may have inconsistent coverage of AI activity. + +The test passes when at least one enabled retention policy targets the Microsoft Copilot location. + +## How to fix + +1. Open the [Microsoft Purview portal — Data Lifecycle Management — Policies](https://purview.microsoft.com/datalifecyclemanagement/policies). +2. Click **+ New retention policy**. +3. Enable the **Microsoft Copilot** location. +4. Define a retention duration (for example, retain for 1 year then delete) aligned to your records-retention strategy. +5. Apply the policy and turn it on. + +## Prerequisites + +This test uses the Security & Compliance PowerShell session. + +```powershell +Connect-Maester -Service SecurityCompliance +``` + +## Learn more + +- [Retention policies for Microsoft 365 Copilot](https://learn.microsoft.com/en-us/purview/retention-policies-copilot) +- [Learn about retention](https://learn.microsoft.com/en-us/purview/retention) +- [Audit and eDiscovery for Microsoft 365 Copilot](https://learn.microsoft.com/en-us/purview/audit-copilot) From 0cba15cfc9be855925ab6ced716e460565ada57c Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Tue, 12 May 2026 14:23:46 -0400 Subject: [PATCH 2/2] Add Write-Verbose to satisfy Common.Tests.ps1 build-validation The build-validation Pester test "Should contain Write-Verbose logging" in powershell/tests/functions/Common.Tests.ps1 requires every exported public function to contain at least one Write-Verbose call. Four of the five new DSPM for AI tests were missing this, causing 4 failures across ubuntu-latest, macOS-latest and windows-latest runners. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../maester/purview/Test-MtPurviewAiAuditLogIngestion.ps1 | 2 ++ powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.ps1 | 2 ++ .../maester/purview/Test-MtPurviewAiInsiderRiskPolicy.ps1 | 2 ++ .../purview/Test-MtPurviewAiSensitivityLabelsForFiles.ps1 | 2 ++ 4 files changed, 8 insertions(+) diff --git a/powershell/public/maester/purview/Test-MtPurviewAiAuditLogIngestion.ps1 b/powershell/public/maester/purview/Test-MtPurviewAiAuditLogIngestion.ps1 index f1609e740..28ebacef2 100644 --- a/powershell/public/maester/purview/Test-MtPurviewAiAuditLogIngestion.ps1 +++ b/powershell/public/maester/purview/Test-MtPurviewAiAuditLogIngestion.ps1 @@ -28,6 +28,8 @@ [OutputType([bool])] param() + Write-Verbose "Test-MtPurviewAiAuditLogIngestion: Checking if the Microsoft 365 unified audit log is enabled." + if (!(Test-MtConnection ExchangeOnline)) { Add-MtTestResultDetail -SkippedBecause NotConnectedExchange return $null diff --git a/powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.ps1 b/powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.ps1 index 946fd7220..621b478fb 100644 --- a/powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.ps1 +++ b/powershell/public/maester/purview/Test-MtPurviewAiDlpPolicy.ps1 @@ -29,6 +29,8 @@ [OutputType([bool])] param() + Write-Verbose "Test-MtPurviewAiDlpPolicy: Checking for DLP policies targeting the Microsoft 365 Copilot location." + if (!(Test-MtConnection ExchangeOnline)) { Add-MtTestResultDetail -SkippedBecause NotConnectedExchange return $null diff --git a/powershell/public/maester/purview/Test-MtPurviewAiInsiderRiskPolicy.ps1 b/powershell/public/maester/purview/Test-MtPurviewAiInsiderRiskPolicy.ps1 index ee574cdbb..53b9bcc14 100644 --- a/powershell/public/maester/purview/Test-MtPurviewAiInsiderRiskPolicy.ps1 +++ b/powershell/public/maester/purview/Test-MtPurviewAiInsiderRiskPolicy.ps1 @@ -27,6 +27,8 @@ [OutputType([bool])] param() + Write-Verbose "Test-MtPurviewAiInsiderRiskPolicy: Checking for Insider Risk Management policies using the Risky AI usage template." + if (!(Test-MtConnection SecurityCompliance)) { Add-MtTestResultDetail -SkippedBecause NotConnectedSecurityCompliance return $null diff --git a/powershell/public/maester/purview/Test-MtPurviewAiSensitivityLabelsForFiles.ps1 b/powershell/public/maester/purview/Test-MtPurviewAiSensitivityLabelsForFiles.ps1 index faab230ef..682799f86 100644 --- a/powershell/public/maester/purview/Test-MtPurviewAiSensitivityLabelsForFiles.ps1 +++ b/powershell/public/maester/purview/Test-MtPurviewAiSensitivityLabelsForFiles.ps1 @@ -26,6 +26,8 @@ [OutputType([bool])] param() + Write-Verbose "Test-MtPurviewAiSensitivityLabelsForFiles: Checking for sensitivity labels published with the File scope." + if (!(Test-MtConnection SecurityCompliance)) { Add-MtTestResultDetail -SkippedBecause NotConnectedSecurityCompliance return $null