From 4f2bf16f7780176e6d0b81b52d96a6f4de8322c5 Mon Sep 17 00:00:00 2001 From: Kshitiz Sharma Date: Thu, 8 Jan 2026 11:45:18 +0530 Subject: [PATCH 1/8] 35018 --- src/powershell/tests/Test-Assessment.35018.md | 21 ++ .../tests/Test-Assessment.35018.ps1 | 184 ++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 src/powershell/tests/Test-Assessment.35018.md create mode 100644 src/powershell/tests/Test-Assessment.35018.ps1 diff --git a/src/powershell/tests/Test-Assessment.35018.md b/src/powershell/tests/Test-Assessment.35018.md new file mode 100644 index 000000000..d9b301426 --- /dev/null +++ b/src/powershell/tests/Test-Assessment.35018.md @@ -0,0 +1,21 @@ +When sensitivity label policies do not require users to provide justification when removing or downgrading labels, users can silently reduce the classification level of sensitive content without creating an audit trail explaining why. This creates a compliance and audit risk because organizations lose visibility into intentional label downgrades that may indicate inappropriate access to sensitive data. When a user removes a "Confidential" label and replaces it with "Internal" or no label at all, organizations should require explicit justification to create an audit record of this action. Downgrade justification is a lightweight control that increases accountability for label decisions without significantly impacting user workflows. When justification is not required, compromised user accounts or departing employees could systematically downgrade labels on sensitive documents to enable data exfiltration, leaving no clear audit trail of the unauthorized changes. Configuring downgrade justification requirements on sensitivity label policies ensures that any intentional reduction in classification level is logged with a user-provided business reason, supporting both compliance audits and insider threat investigations. Downgrade justification should be configured alongside other label controls (mandatory labeling, default labels, and mandatory enforcement) to create a comprehensive data governance framework. + +**Remediation action** +1. Navigate to [Sensitivity label policies](https://purview.microsoft.com/informationprotection/labelpolicies) in Microsoft Purview +2. Create or update a policy to enable downgrade justification requirement +3. Enable: "Require users to provide justification to change a label" +4. Define predefined justification reasons +5. Set policy scope (global or specific groups) +6. Verify audit logging is enabled +7. Test with pilot users + +**Learn More:** [Require users to provide justification to change a label](https://learn.microsoft.com/en-us/purview/sensitivity-labels-office-apps#require-users-to-provide-justification-to-change-a-label) + +- [Plan for sensitivity labels](https://learn.microsoft.com/en-us/microsoft-365/compliance/sensitivity-labels#plan-for-sensitivity-labels) +- [Create and publish sensitivity labels](https://learn.microsoft.com/en-us/microsoft-365/compliance/create-sensitivity-labels) +- [Require users to provide justification to change a label](https://learn.microsoft.com/en-us/purview/sensitivity-labels-office-apps#require-users-to-provide-justification-to-change-a-label) +- [Plan your sensitivity label solution](https://learn.microsoft.com/en-us/microsoft-365/compliance/sensitivity-labels#plan-for-sensitivity-labels) +- [Search the audit log](https://learn.microsoft.com/en-us/purview/audit-log-search) + + +%TestResult% diff --git a/src/powershell/tests/Test-Assessment.35018.ps1 b/src/powershell/tests/Test-Assessment.35018.ps1 new file mode 100644 index 000000000..54b572ddd --- /dev/null +++ b/src/powershell/tests/Test-Assessment.35018.ps1 @@ -0,0 +1,184 @@ +<# +.SYNOPSIS + Downgrade Justification Required for Sensitivity Labels + +.DESCRIPTION + Sensitivity label policies should require users to provide justification when removing or downgrading labels. When downgrade justification is not required, users can silently reduce the classification level of sensitive content without creating an audit trail, creating compliance and audit risks. + +.NOTES + Test ID: 35018 + Pillar: Data + Risk Level: Medium +#> + +function Test-Assessment-35018 { + [ZtTest( + Category = 'Information Protection', + ImplementationCost = 'Low', + MinimumLicense = ('Microsoft 365 E3'), + Pillar = 'Data', + RiskLevel = 'Medium', + SfiPillar = 'Protect tenants and production systems', + TenantType = ('Workforce'), + TestId = 35018, + Title = 'Downgrade Justification Required for Sensitivity Labels', + UserImpact = 'Medium' + )] + [CmdletBinding()] + param() + + #region Data Collection + Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose + + $activity = 'Checking Sensitivity Label Policy Downgrade Justification Requirements' + Write-ZtProgress -Activity $activity -Status 'Query 1: Getting all enabled label policies' + + # Query 1: Get all enabled label policies + $labelPolicies = $null + $errorMsg = $null + $investigateReason = $null + + try { + $labelPolicies = @(Get-LabelPolicy -ErrorAction Stop | Where-Object { $_.Enabled -eq $true }) + } + catch { + $errorMsg = $_ + $investigateReason = "Unable to query Label Policies: $($_)" + Write-PSFMessage "Error querying Label Policies: $_" -Level Error + } + #endregion Data Collection + + #region Assessment Logic + $testStatus = $null + $policiesWithDowngradeJustification = @() + $policyDetails = @() + + if ($errorMsg) { + $testStatus = 'Investigate' + } + else { + Write-ZtProgress -Activity $activity -Status 'Query 2 & 3: Examining label policy downgrade justification settings' + + # For each enabled policy, examine settings for downgrade justification + foreach ($policy in $labelPolicies) { + try { + # Query 3: Get detail on specific policy settings + $policySettings = Get-LabelPolicy -Identity $policy.Identity -ErrorAction Stop | + Select-Object -ExpandProperty Settings + + # Convert Settings array to hashtable for easier querying + $settingsHash = @{} + if ($policySettings) { + foreach ($setting in $policySettings) { + # Parse [key, value] format + $match = $setting -match '^\[(.*?),\s*(.+)\]$' + if ($match) { + $key = $matches[1].ToLower().Trim() + $value = $matches[2].ToLower().Trim() + $settingsHash[$key] = $value + } + } + } + + # Query 2: Check for requiredowngradejustification setting + $hasDowngradeJustification = $settingsHash.ContainsKey('requiredowngradejustification') -and + $settingsHash['requiredowngradejustification'] -eq 'true' + + if ($hasDowngradeJustification) { + $policiesWithDowngradeJustification += $policy + } + + # Collect policy details for reporting + $policyDetail = [PSCustomObject]@{ + PolicyName = $policy.Name + Enabled = $policy.Enabled + RequireDowngradeJustification = $hasDowngradeJustification + PolicyScope = if ($policy.ExchangeLocation -and $policy.ExchangeLocation.Type.value -ne 'Tenant') { 'Scoped' } else { 'Global' } + LabelsPublishedCount = if ($policy.labels) { @($policy.labels).Count } else { 0 } + WorkloadsAffected = @($policy.Workload) -join ', ' + } + $policyDetails += $policyDetail + } + catch { + $investigateReason = "Unable to determine Settings structure or permissions prevent access for policy: $($policy.Name)" + Write-PSFMessage "Error examining policy '$($policy.Name)': $_" -Level Warning + } + } + + # Determine test status + if ($investigateReason) { + $testStatus = 'Investigate' + } + elseif ($policiesWithDowngradeJustification.Count -gt 0) { + $testStatus = 'Pass' + } + else { + $testStatus = 'Fail' + } + } + #endregion Assessment Logic + + #region Report Generation + $testResultMarkdown = "" + + if ($testStatus -eq 'Investigate') { + $testResultMarkdown += "### Investigate`n`n" + $testResultMarkdown += $investigateReason + } + elseif ($testStatus -eq 'Pass') { + $testResultMarkdown += "### βœ… Pass`n`n" + $testResultMarkdown += "Downgrade justification is required for at least one active sensitivity label policy, ensuring users must explain when removing or reducing label classification.`n`n" + } + else { + $testResultMarkdown += "### ❌ Fail`n`n" + $testResultMarkdown += "No sensitivity label policies require users to provide downgrade justification when removing or changing labels.`n`n" + } + + # Add detailed configuration data if we have policy information + if ($policyDetails.Count -gt 0) { + $testResultMarkdown += "## Downgrade Justification Configuration`n`n" + + $testResultMarkdown += "### Policy Summary`n`n" + $testResultMarkdown += "| Policy Name | Enabled | Downgrade Justification | Scope | Labels Count | Workloads |`n" + $testResultMarkdown += "|---|---|---|---|---|---|`n" + + foreach ($detail in $policyDetails) { + $downgradeStatus = if ($detail.RequireDowngradeJustification) { 'βœ… Yes' } else { '❌ No' } + $testResultMarkdown += "| $($detail.PolicyName) | $($detail.Enabled) | $downgradeStatus | $($detail.PolicyScope) | $($detail.LabelsPublishedCount) | $($detail.WorkloadsAffected) |`n" + } + + $testResultMarkdown += "`n## Summary Statistics`n`n" + $testResultMarkdown += "| Metric | Count |`n" + $testResultMarkdown += "|---|---|`n" + $testResultMarkdown += "| Total Enabled Label Policies | $($policyDetails.Count) |`n" + $testResultMarkdown += "| Policies Requiring Downgrade Justification | $($policiesWithDowngradeJustification.Count) |`n" + $testResultMarkdown += "| Policies NOT Requiring Downgrade Justification | $($policyDetails.Count - $policiesWithDowngradeJustification.Count) |`n" + + if ($policyDetails.Count -gt 0) { + $percentage = [Math]::Round(($policiesWithDowngradeJustification.Count / $policyDetails.Count) * 100, 2) + $testResultMarkdown += "| Percentage with Downgrade Justification | $percentage% |`n" + } + + if ($policiesWithDowngradeJustification.Count -gt 0) { + $testResultMarkdown += "`n## Policies with Downgrade Justification Enabled`n`n" + $testResultMarkdown += "| Policy Name | Scope | Labels |`n" + $testResultMarkdown += "|---|---|---|`n" + + foreach ($policy in $policiesWithDowngradeJustification) { + $detail = $policyDetails | Where-Object { $_.PolicyName -eq $policy.Name } + $testResultMarkdown += "| $($policy.Name) | $($detail.PolicyScope) | $($detail.LabelsPublishedCount) |`n" + } + } + } + + #endregion Report Generation + + $passed = $testStatus -eq 'Pass' + $params = @{ + TestId = '35018' + Title = 'Downgrade Justification Required for Sensitivity Labels' + Status = $passed + Result = $testResultMarkdown + } + Add-ZtTestResultDetail @params +} From 416917afb10bc97814a358f4dede7ff955bfde40 Mon Sep 17 00:00:00 2001 From: Kshitiz Sharma Date: Fri, 9 Jan 2026 09:10:06 +0530 Subject: [PATCH 2/8] 35018-removing redundant info --- src/powershell/tests/Test-Assessment.35018.ps1 | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/powershell/tests/Test-Assessment.35018.ps1 b/src/powershell/tests/Test-Assessment.35018.ps1 index 54b572ddd..55157e04c 100644 --- a/src/powershell/tests/Test-Assessment.35018.ps1 +++ b/src/powershell/tests/Test-Assessment.35018.ps1 @@ -159,16 +159,6 @@ function Test-Assessment-35018 { $testResultMarkdown += "| Percentage with Downgrade Justification | $percentage% |`n" } - if ($policiesWithDowngradeJustification.Count -gt 0) { - $testResultMarkdown += "`n## Policies with Downgrade Justification Enabled`n`n" - $testResultMarkdown += "| Policy Name | Scope | Labels |`n" - $testResultMarkdown += "|---|---|---|`n" - - foreach ($policy in $policiesWithDowngradeJustification) { - $detail = $policyDetails | Where-Object { $_.PolicyName -eq $policy.Name } - $testResultMarkdown += "| $($policy.Name) | $($detail.PolicyScope) | $($detail.LabelsPublishedCount) |`n" - } - } } #endregion Report Generation From 254a8f3f6608d036a6b374c7106e72c54071e24f Mon Sep 17 00:00:00 2001 From: Kshitiz Sharma Date: Wed, 14 Jan 2026 16:58:57 +0530 Subject: [PATCH 3/8] Feature-35018 --- src/powershell/tests/Test-Assessment.35029.md | 17 ++ .../tests/Test-Assessment.35029.ps1 | 179 ++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 src/powershell/tests/Test-Assessment.35029.md create mode 100644 src/powershell/tests/Test-Assessment.35029.ps1 diff --git a/src/powershell/tests/Test-Assessment.35029.md b/src/powershell/tests/Test-Assessment.35029.md new file mode 100644 index 000000000..a1e0325f8 --- /dev/null +++ b/src/powershell/tests/Test-Assessment.35029.md @@ -0,0 +1,17 @@ +Mail flow rules (transport rules) in Exchange Online allow organizations to automatically apply information protection policies to email messages based on conditions such as sender, recipient, content patterns, or organizational attributes. When mail flow rules with rights protection are not configured, organizations must rely solely on users to manually apply sensitivity labels or encrypt messagesβ€”a approach that is inconsistent, error-prone, and does not scale. Without automated rights protection rules, sensitive emails are frequently sent unencrypted, allowing unauthorized access, forwarding, and printing of confidential information. Rights protection rules automatically apply encryption, restriction labels, and permissions to messages matching specific criteria (e.g., emails to external domains, messages containing credit card numbers, emails from finance departments). Configuring at least one mail flow rule with rights protection for high-risk email scenarios ensures sensitive information is automatically protected at the message transport layer, reducing the risk of data exfiltration, unauthorized access, and compliance violations. + +**Remediation action** + +1. Plan mail flow rule strategy: identify high-risk scenarios (external email, financial data, healthcare records) +2. Navigate to [Mail flow rules](https://purview.microsoft.com/datalossprevention/rules) in Microsoft Purview +3. Create RMS templates or configure OME before creating rules +4. Create rule for external email protection +5. Create rules for sensitive content detection +6. Configure rule priority/order +7. Test rules with test emails +8. Enable and monitor in production + +- [Mail flow rules](https://purview.microsoft.com/datalossprevention/rules) +- [Mail flow rules in Exchange Online](https://learn.microsoft.com/en-us/exchange/security-and-compliance/mail-flow-rules/mail-flow-rules) + +%TestResult% diff --git a/src/powershell/tests/Test-Assessment.35029.ps1 b/src/powershell/tests/Test-Assessment.35029.ps1 new file mode 100644 index 000000000..f7d914fbb --- /dev/null +++ b/src/powershell/tests/Test-Assessment.35029.ps1 @@ -0,0 +1,179 @@ +<# +.SYNOPSIS + Checks for Mail Flow Rules with Rights Protection. + +.DESCRIPTION + This test validates that mail flow rules (transport rules) are configured to apply rights protection + to sensitive emails. It checks for rules that apply Office 365 Message Encryption (OME), + Rights Management Service (RMS) templates, or data classifications. + +.NOTES + Test ID: 35029 + Pillar: Data +#> +function Test-Assessment-35029 { + [ZtTest( + Category = 'Information Protection', + ImplementationCost = 'Medium', + MinimumLicense = 'Microsoft 365 E3', + Pillar = 'Data', + RiskLevel = 'Medium', + SfiPillar = 'Protect tenants and production systems', + TenantType = 'Workforce', + TestId = '35029', + Title = 'Mail Flow Rules with Rights Protection', + UserImpact = 'Medium' + )] + [CmdletBinding()] + param() + + #region Data Collection + Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose + + $activity = 'Checking Mail Flow Rules with Rights Protection' + Write-ZtProgress -Activity $activity -Status 'Querying Transport Rules' + + $rules = $null + $errorMsg = $null + + try { + if (Get-Command Get-TransportRule -ErrorAction SilentlyContinue) { + $rules = Get-TransportRule -ResultSize Unlimited | Where-Object { + $_.ApplyOME -eq $true -or + $null -ne $_.ApplyRightsProtectionTemplate -or + $null -ne $_.ApplyClassification + } + } + else { + $errorMsg = "The Get-TransportRule cmdlet is not available. Please ensure you are connected to Exchange Online." + } + } + catch { + $errorMsg = $_ + Write-PSFMessage "Failed to query transport rules: $_" -Level Error + } + #endregion Data Collection + + #region Assessment Logic + $passed = $false + $customStatus = $null + $totalRules = 0 + $enabledCount = 0 + $disabledCount = 0 + $omeCount = 0 + $rmsCount = 0 + $dlpCount = 0 + $hasExternal = $false + $hasSensitive = $false + $hasDepartment = $false + + if ($errorMsg) { + $passed = $false + $customStatus = 'Investigate' + } + else { + $totalRules = $rules.Count + $enabledRules = $rules | Where-Object { $_.State -eq 'Enabled' } + $enabledCount = $enabledRules.Count + $disabledCount = $totalRules - $enabledCount + + $omeCount = ($rules | Where-Object { $_.ApplyOME -eq $true }).Count + $rmsCount = ($rules | Where-Object { $null -ne $_.ApplyRightsProtectionTemplate }).Count + $dlpCount = ($rules | Where-Object { $null -ne $_.ApplyClassification }).Count + + foreach ($rule in $rules) { + if ($rule.SentToScope -eq 'NotInOrganization' -or ($null -ne $rule.RecipientDomainIs -and $rule.RecipientDomainIs.Count -gt 0)) { + $hasExternal = $true + } + if ($null -ne $rule.MessageContainsDataClassifications -and $rule.MessageContainsDataClassifications.Count -gt 0) { + $hasSensitive = $true + } + if (($null -ne $rule.FromMemberOf -and $rule.FromMemberOf.Count -gt 0) -or $null -ne $rule.SenderAttribute1) { + $hasDepartment = $true + } + } + + $passed = $enabledCount -ge 1 + } + #endregion Assessment Logic + + #region Report Generation + $testResultMarkdown = "" + + if ($customStatus -eq 'Investigate') { + $testResultMarkdown = "### Investigate`n`n" + $testResultMarkdown += "Unable to query transport rules: $errorMsg" + } + else { + if ($passed) { + $testResultMarkdown += "βœ… Mail flow rules with rights protection are configured to automatically protect sensitive emails.`n`n" + } + else { + $testResultMarkdown += "❌ No mail flow rules with rights protection are configured; sensitive emails are not automatically protected.`n`n" + } + + $testResultMarkdown += "### Summary`n`n" + $testResultMarkdown += "* Total Rights Protection Rules: **$totalRules**`n" + $testResultMarkdown += "* Enabled Rules: **$enabledCount**`n" + $testResultMarkdown += "* Disabled Rules: **$disabledCount**`n`n" + + $testResultMarkdown += "#### Rules by Action Type`n`n" + $testResultMarkdown += "| Action Type | Count |`n" + $testResultMarkdown += "| :--- | :--- |`n" + $testResultMarkdown += "| OME Encryption Rules | $omeCount |`n" + $testResultMarkdown += "| RMS Template Application | $rmsCount |`n" + $testResultMarkdown += "| DLP/Classification Rules | $dlpCount |`n`n" + + $testResultMarkdown += "#### Common Protection Scenarios Covered`n`n" + $testResultMarkdown += "| Scenario | Covered |`n" + $testResultMarkdown += "| :--- | :--- |`n" + $testResultMarkdown += "| External Email Protection | $(if ($hasExternal) { 'Yes' } else { 'No' }) |`n" + $testResultMarkdown += "| Sensitive Content Detection | $(if ($hasSensitive) { 'Yes' } else { 'No' }) |`n" + $testResultMarkdown += "| Department-Specific Rules | $(if ($hasDepartment) { 'Yes' } else { 'No' }) |`n" + $testResultMarkdown += "`n[Microsoft Purview portal > Data Loss Prevention > Rules](https://purview.microsoft.com/datalossprevention/rules)`n`n" + + if ($totalRules -gt 0) { + $testResultMarkdown += "### Mail Flow Rules with Rights Protection`n`n" + $testResultMarkdown += "| Rule Name | Enabled Status | Trigger Conditions | Protection Actions | Rule Priority | Last Modified Date |`n" + $testResultMarkdown += "| :--- | :--- | :--- | :--- | :--- | :--- |`n" + + foreach ($rule in $rules) { + $name = if ($rule.Name) { $rule.Name -replace '\|', '|' } else { "-" } + $status = if ($rule.State) { $rule.State } else { "-" } + + $triggers = @() + if ($rule.From) { $triggers += "Sender" } + if ($rule.SentTo) { $triggers += "Recipient" } + if ($rule.SentToScope) { $triggers += "Scope: $($rule.SentToScope)" } + if ($rule.RecipientDomainIs) { $triggers += "Recipient Domain" } + if ($rule.SubjectOrBodyContainsWords) { $triggers += "Content Keywords" } + if ($rule.MessageContainsDataClassifications) { $triggers += "Sensitive Info Types" } + if ($rule.FromMemberOf) { $triggers += "Sender Group" } + if ($triggers.Count -eq 0) { $triggers += "Other/Custom" } + $triggerStr = $triggers -join ", " + + $actions = @() + if ($rule.ApplyOME) { $actions += "OME Encryption" } + if ($rule.ApplyRightsProtectionTemplate) { $actions += "RMS Template: $($rule.ApplyRightsProtectionTemplate)" } + if ($rule.ApplyClassification) { $actions += "Classification: $($rule.ApplyClassification)" } + $actionStr = $actions -join ", " + + $priority = $rule.Priority + $modified = if ($rule.WhenChanged) { $rule.WhenChanged.ToString("yyyy-MM-dd") } else { "-" } + + $testResultMarkdown += "| $name | $status | $triggerStr | $actionStr | $priority | $modified |`n" + } + } + } + #endregion Report Generation + + $params = @{ + TestId = '35029' + Status = $passed + Result = $testResultMarkdown + } + if ($customStatus) { + $params.CustomStatus = $customStatus + } + Add-ZtTestResultDetail @params +} From aeb1dcf23c227d8722027eb9408c7777017dd938 Mon Sep 17 00:00:00 2001 From: Kshitiz Sharma Date: Wed, 14 Jan 2026 16:59:10 +0530 Subject: [PATCH 4/8] 35018 --- .../tests/Test-Assessment.35018.ps1 | 5 +- src/powershell/tests/Test-Assessment.35029.md | 17 -- .../tests/Test-Assessment.35029.ps1 | 179 ------------------ 3 files changed, 1 insertion(+), 200 deletions(-) delete mode 100644 src/powershell/tests/Test-Assessment.35029.md delete mode 100644 src/powershell/tests/Test-Assessment.35029.ps1 diff --git a/src/powershell/tests/Test-Assessment.35018.ps1 b/src/powershell/tests/Test-Assessment.35018.ps1 index 55157e04c..ef9d3f536 100644 --- a/src/powershell/tests/Test-Assessment.35018.ps1 +++ b/src/powershell/tests/Test-Assessment.35018.ps1 @@ -31,8 +31,6 @@ function Test-Assessment-35018 { Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose $activity = 'Checking Sensitivity Label Policy Downgrade Justification Requirements' - Write-ZtProgress -Activity $activity -Status 'Query 1: Getting all enabled label policies' - # Query 1: Get all enabled label policies $labelPolicies = $null $errorMsg = $null @@ -57,7 +55,6 @@ function Test-Assessment-35018 { $testStatus = 'Investigate' } else { - Write-ZtProgress -Activity $activity -Status 'Query 2 & 3: Examining label policy downgrade justification settings' # For each enabled policy, examine settings for downgrade justification foreach ($policy in $labelPolicies) { @@ -139,7 +136,7 @@ function Test-Assessment-35018 { $testResultMarkdown += "## Downgrade Justification Configuration`n`n" $testResultMarkdown += "### Policy Summary`n`n" - $testResultMarkdown += "| Policy Name | Enabled | Downgrade Justification | Scope | Labels Count | Workloads |`n" + $testResultMarkdown += "| Policy name | Enabled | Downgrade justification | Scope | Labels count | Workloads |`n" $testResultMarkdown += "|---|---|---|---|---|---|`n" foreach ($detail in $policyDetails) { diff --git a/src/powershell/tests/Test-Assessment.35029.md b/src/powershell/tests/Test-Assessment.35029.md deleted file mode 100644 index a1e0325f8..000000000 --- a/src/powershell/tests/Test-Assessment.35029.md +++ /dev/null @@ -1,17 +0,0 @@ -Mail flow rules (transport rules) in Exchange Online allow organizations to automatically apply information protection policies to email messages based on conditions such as sender, recipient, content patterns, or organizational attributes. When mail flow rules with rights protection are not configured, organizations must rely solely on users to manually apply sensitivity labels or encrypt messagesβ€”a approach that is inconsistent, error-prone, and does not scale. Without automated rights protection rules, sensitive emails are frequently sent unencrypted, allowing unauthorized access, forwarding, and printing of confidential information. Rights protection rules automatically apply encryption, restriction labels, and permissions to messages matching specific criteria (e.g., emails to external domains, messages containing credit card numbers, emails from finance departments). Configuring at least one mail flow rule with rights protection for high-risk email scenarios ensures sensitive information is automatically protected at the message transport layer, reducing the risk of data exfiltration, unauthorized access, and compliance violations. - -**Remediation action** - -1. Plan mail flow rule strategy: identify high-risk scenarios (external email, financial data, healthcare records) -2. Navigate to [Mail flow rules](https://purview.microsoft.com/datalossprevention/rules) in Microsoft Purview -3. Create RMS templates or configure OME before creating rules -4. Create rule for external email protection -5. Create rules for sensitive content detection -6. Configure rule priority/order -7. Test rules with test emails -8. Enable and monitor in production - -- [Mail flow rules](https://purview.microsoft.com/datalossprevention/rules) -- [Mail flow rules in Exchange Online](https://learn.microsoft.com/en-us/exchange/security-and-compliance/mail-flow-rules/mail-flow-rules) - -%TestResult% diff --git a/src/powershell/tests/Test-Assessment.35029.ps1 b/src/powershell/tests/Test-Assessment.35029.ps1 deleted file mode 100644 index f7d914fbb..000000000 --- a/src/powershell/tests/Test-Assessment.35029.ps1 +++ /dev/null @@ -1,179 +0,0 @@ -<# -.SYNOPSIS - Checks for Mail Flow Rules with Rights Protection. - -.DESCRIPTION - This test validates that mail flow rules (transport rules) are configured to apply rights protection - to sensitive emails. It checks for rules that apply Office 365 Message Encryption (OME), - Rights Management Service (RMS) templates, or data classifications. - -.NOTES - Test ID: 35029 - Pillar: Data -#> -function Test-Assessment-35029 { - [ZtTest( - Category = 'Information Protection', - ImplementationCost = 'Medium', - MinimumLicense = 'Microsoft 365 E3', - Pillar = 'Data', - RiskLevel = 'Medium', - SfiPillar = 'Protect tenants and production systems', - TenantType = 'Workforce', - TestId = '35029', - Title = 'Mail Flow Rules with Rights Protection', - UserImpact = 'Medium' - )] - [CmdletBinding()] - param() - - #region Data Collection - Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose - - $activity = 'Checking Mail Flow Rules with Rights Protection' - Write-ZtProgress -Activity $activity -Status 'Querying Transport Rules' - - $rules = $null - $errorMsg = $null - - try { - if (Get-Command Get-TransportRule -ErrorAction SilentlyContinue) { - $rules = Get-TransportRule -ResultSize Unlimited | Where-Object { - $_.ApplyOME -eq $true -or - $null -ne $_.ApplyRightsProtectionTemplate -or - $null -ne $_.ApplyClassification - } - } - else { - $errorMsg = "The Get-TransportRule cmdlet is not available. Please ensure you are connected to Exchange Online." - } - } - catch { - $errorMsg = $_ - Write-PSFMessage "Failed to query transport rules: $_" -Level Error - } - #endregion Data Collection - - #region Assessment Logic - $passed = $false - $customStatus = $null - $totalRules = 0 - $enabledCount = 0 - $disabledCount = 0 - $omeCount = 0 - $rmsCount = 0 - $dlpCount = 0 - $hasExternal = $false - $hasSensitive = $false - $hasDepartment = $false - - if ($errorMsg) { - $passed = $false - $customStatus = 'Investigate' - } - else { - $totalRules = $rules.Count - $enabledRules = $rules | Where-Object { $_.State -eq 'Enabled' } - $enabledCount = $enabledRules.Count - $disabledCount = $totalRules - $enabledCount - - $omeCount = ($rules | Where-Object { $_.ApplyOME -eq $true }).Count - $rmsCount = ($rules | Where-Object { $null -ne $_.ApplyRightsProtectionTemplate }).Count - $dlpCount = ($rules | Where-Object { $null -ne $_.ApplyClassification }).Count - - foreach ($rule in $rules) { - if ($rule.SentToScope -eq 'NotInOrganization' -or ($null -ne $rule.RecipientDomainIs -and $rule.RecipientDomainIs.Count -gt 0)) { - $hasExternal = $true - } - if ($null -ne $rule.MessageContainsDataClassifications -and $rule.MessageContainsDataClassifications.Count -gt 0) { - $hasSensitive = $true - } - if (($null -ne $rule.FromMemberOf -and $rule.FromMemberOf.Count -gt 0) -or $null -ne $rule.SenderAttribute1) { - $hasDepartment = $true - } - } - - $passed = $enabledCount -ge 1 - } - #endregion Assessment Logic - - #region Report Generation - $testResultMarkdown = "" - - if ($customStatus -eq 'Investigate') { - $testResultMarkdown = "### Investigate`n`n" - $testResultMarkdown += "Unable to query transport rules: $errorMsg" - } - else { - if ($passed) { - $testResultMarkdown += "βœ… Mail flow rules with rights protection are configured to automatically protect sensitive emails.`n`n" - } - else { - $testResultMarkdown += "❌ No mail flow rules with rights protection are configured; sensitive emails are not automatically protected.`n`n" - } - - $testResultMarkdown += "### Summary`n`n" - $testResultMarkdown += "* Total Rights Protection Rules: **$totalRules**`n" - $testResultMarkdown += "* Enabled Rules: **$enabledCount**`n" - $testResultMarkdown += "* Disabled Rules: **$disabledCount**`n`n" - - $testResultMarkdown += "#### Rules by Action Type`n`n" - $testResultMarkdown += "| Action Type | Count |`n" - $testResultMarkdown += "| :--- | :--- |`n" - $testResultMarkdown += "| OME Encryption Rules | $omeCount |`n" - $testResultMarkdown += "| RMS Template Application | $rmsCount |`n" - $testResultMarkdown += "| DLP/Classification Rules | $dlpCount |`n`n" - - $testResultMarkdown += "#### Common Protection Scenarios Covered`n`n" - $testResultMarkdown += "| Scenario | Covered |`n" - $testResultMarkdown += "| :--- | :--- |`n" - $testResultMarkdown += "| External Email Protection | $(if ($hasExternal) { 'Yes' } else { 'No' }) |`n" - $testResultMarkdown += "| Sensitive Content Detection | $(if ($hasSensitive) { 'Yes' } else { 'No' }) |`n" - $testResultMarkdown += "| Department-Specific Rules | $(if ($hasDepartment) { 'Yes' } else { 'No' }) |`n" - $testResultMarkdown += "`n[Microsoft Purview portal > Data Loss Prevention > Rules](https://purview.microsoft.com/datalossprevention/rules)`n`n" - - if ($totalRules -gt 0) { - $testResultMarkdown += "### Mail Flow Rules with Rights Protection`n`n" - $testResultMarkdown += "| Rule Name | Enabled Status | Trigger Conditions | Protection Actions | Rule Priority | Last Modified Date |`n" - $testResultMarkdown += "| :--- | :--- | :--- | :--- | :--- | :--- |`n" - - foreach ($rule in $rules) { - $name = if ($rule.Name) { $rule.Name -replace '\|', '|' } else { "-" } - $status = if ($rule.State) { $rule.State } else { "-" } - - $triggers = @() - if ($rule.From) { $triggers += "Sender" } - if ($rule.SentTo) { $triggers += "Recipient" } - if ($rule.SentToScope) { $triggers += "Scope: $($rule.SentToScope)" } - if ($rule.RecipientDomainIs) { $triggers += "Recipient Domain" } - if ($rule.SubjectOrBodyContainsWords) { $triggers += "Content Keywords" } - if ($rule.MessageContainsDataClassifications) { $triggers += "Sensitive Info Types" } - if ($rule.FromMemberOf) { $triggers += "Sender Group" } - if ($triggers.Count -eq 0) { $triggers += "Other/Custom" } - $triggerStr = $triggers -join ", " - - $actions = @() - if ($rule.ApplyOME) { $actions += "OME Encryption" } - if ($rule.ApplyRightsProtectionTemplate) { $actions += "RMS Template: $($rule.ApplyRightsProtectionTemplate)" } - if ($rule.ApplyClassification) { $actions += "Classification: $($rule.ApplyClassification)" } - $actionStr = $actions -join ", " - - $priority = $rule.Priority - $modified = if ($rule.WhenChanged) { $rule.WhenChanged.ToString("yyyy-MM-dd") } else { "-" } - - $testResultMarkdown += "| $name | $status | $triggerStr | $actionStr | $priority | $modified |`n" - } - } - } - #endregion Report Generation - - $params = @{ - TestId = '35029' - Status = $passed - Result = $testResultMarkdown - } - if ($customStatus) { - $params.CustomStatus = $customStatus - } - Add-ZtTestResultDetail @params -} From 13f00b8f7b5200689ec9e4cbe9310ecd4acd473a Mon Sep 17 00:00:00 2001 From: Kshitiz Sharma Date: Wed, 14 Jan 2026 22:34:42 +0530 Subject: [PATCH 5/8] 35018 --- .../tests/Test-Assessment.35018.ps1 | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/powershell/tests/Test-Assessment.35018.ps1 b/src/powershell/tests/Test-Assessment.35018.ps1 index ef9d3f536..b587b074b 100644 --- a/src/powershell/tests/Test-Assessment.35018.ps1 +++ b/src/powershell/tests/Test-Assessment.35018.ps1 @@ -48,11 +48,14 @@ function Test-Assessment-35018 { #region Assessment Logic $testStatus = $null + $passed = $false + $customStatus = $null $policiesWithDowngradeJustification = @() $policyDetails = @() if ($errorMsg) { - $testStatus = 'Investigate' + # Use CustomStatus to indicate investigation β€” do not overwrite TestStatus to preserve core pass/fail logic. + $customStatus = 'Investigate' } else { @@ -104,13 +107,16 @@ function Test-Assessment-35018 { # Determine test status if ($investigateReason) { - $testStatus = 'Investigate' + # Prefer using CustomStatus to indicate investigation; avoid setting TestStatus here. + $customStatus = 'Investigate' } elseif ($policiesWithDowngradeJustification.Count -gt 0) { $testStatus = 'Pass' + $passed = $true } else { $testStatus = 'Fail' + $passed = $false } } #endregion Assessment Logic @@ -118,16 +124,17 @@ function Test-Assessment-35018 { #region Report Generation $testResultMarkdown = "" - if ($testStatus -eq 'Investigate') { - $testResultMarkdown += "### Investigate`n`n" - $testResultMarkdown += $investigateReason + # Prefer CustomStatus 'Investigate' for reporting when present. + if ($customStatus -eq 'Investigate') { + $testResultMarkdown = "### Investigate`n`n" + $testResultMarkdown += "Unable to determine if downgrade justification is required due to policy complexity, permissions issues, or unclear Settings structure.`n`n" } elseif ($testStatus -eq 'Pass') { - $testResultMarkdown += "### βœ… Pass`n`n" + $testResultMarkdown = "### βœ… Pass`n`n" $testResultMarkdown += "Downgrade justification is required for at least one active sensitivity label policy, ensuring users must explain when removing or reducing label classification.`n`n" } else { - $testResultMarkdown += "### ❌ Fail`n`n" + $testResultMarkdown = "### ❌ Fail`n`n" $testResultMarkdown += "No sensitivity label policies require users to provide downgrade justification when removing or changing labels.`n`n" } @@ -147,9 +154,9 @@ function Test-Assessment-35018 { $testResultMarkdown += "`n## Summary Statistics`n`n" $testResultMarkdown += "| Metric | Count |`n" $testResultMarkdown += "|---|---|`n" - $testResultMarkdown += "| Total Enabled Label Policies | $($policyDetails.Count) |`n" - $testResultMarkdown += "| Policies Requiring Downgrade Justification | $($policiesWithDowngradeJustification.Count) |`n" - $testResultMarkdown += "| Policies NOT Requiring Downgrade Justification | $($policyDetails.Count - $policiesWithDowngradeJustification.Count) |`n" + $testResultMarkdown += "| Total enabled label policies | $($policyDetails.Count) |`n" + $testResultMarkdown += "| Policies requiring downgrade justification | $($policiesWithDowngradeJustification.Count) |`n" + $testResultMarkdown += "| Policies not requiring downgrade justification | $($policyDetails.Count - $policiesWithDowngradeJustification.Count) |`n" if ($policyDetails.Count -gt 0) { $percentage = [Math]::Round(($policiesWithDowngradeJustification.Count / $policyDetails.Count) * 100, 2) @@ -167,5 +174,11 @@ function Test-Assessment-35018 { Status = $passed Result = $testResultMarkdown } + + # Add CustomStatus if status is 'Investigate' + if ($null -ne $customStatus) { + $params.CustomStatus = $customStatus + } + Add-ZtTestResultDetail @params } From 7e151d86fc447704aaf1d3bd83f4ee64cc61035e Mon Sep 17 00:00:00 2001 From: Kshitiz Sharma Date: Wed, 14 Jan 2026 22:43:45 +0530 Subject: [PATCH 6/8] 35018- md fix --- src/powershell/tests/Test-Assessment.35018.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/powershell/tests/Test-Assessment.35018.md b/src/powershell/tests/Test-Assessment.35018.md index d9b301426..953d29a54 100644 --- a/src/powershell/tests/Test-Assessment.35018.md +++ b/src/powershell/tests/Test-Assessment.35018.md @@ -11,7 +11,6 @@ When sensitivity label policies do not require users to provide justification wh **Learn More:** [Require users to provide justification to change a label](https://learn.microsoft.com/en-us/purview/sensitivity-labels-office-apps#require-users-to-provide-justification-to-change-a-label) -- [Plan for sensitivity labels](https://learn.microsoft.com/en-us/microsoft-365/compliance/sensitivity-labels#plan-for-sensitivity-labels) - [Create and publish sensitivity labels](https://learn.microsoft.com/en-us/microsoft-365/compliance/create-sensitivity-labels) - [Require users to provide justification to change a label](https://learn.microsoft.com/en-us/purview/sensitivity-labels-office-apps#require-users-to-provide-justification-to-change-a-label) - [Plan your sensitivity label solution](https://learn.microsoft.com/en-us/microsoft-365/compliance/sensitivity-labels#plan-for-sensitivity-labels) From 6b39c2355ef5254a2f37bc1a9bbd8f01d10c6455 Mon Sep 17 00:00:00 2001 From: Kshitij Sharma Date: Thu, 15 Jan 2026 12:39:54 +0530 Subject: [PATCH 7/8] Code - Fix --- .../tests/Test-Assessment.35018.ps1 | 190 +++++++++--------- 1 file changed, 94 insertions(+), 96 deletions(-) diff --git a/src/powershell/tests/Test-Assessment.35018.ps1 b/src/powershell/tests/Test-Assessment.35018.ps1 index b587b074b..fc7767eb3 100644 --- a/src/powershell/tests/Test-Assessment.35018.ps1 +++ b/src/powershell/tests/Test-Assessment.35018.ps1 @@ -30,152 +30,150 @@ function Test-Assessment-35018 { #region Data Collection Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose - $activity = 'Checking Sensitivity Label Policy Downgrade Justification Requirements' - # Query 1: Get all enabled label policies - $labelPolicies = $null + $enabledPolicies = @() $errorMsg = $null - $investigateReason = $null try { - $labelPolicies = @(Get-LabelPolicy -ErrorAction Stop | Where-Object { $_.Enabled -eq $true }) + $enabledPolicies = Get-LabelPolicy -ErrorAction Stop | Where-Object { $_.Enabled -eq $true } } catch { $errorMsg = $_ - $investigateReason = "Unable to query Label Policies: $($_)" - Write-PSFMessage "Error querying Label Policies: $_" -Level Error + Write-PSFMessage "Error querying label policies: $_" -Level Error } #endregion Data Collection #region Assessment Logic - $testStatus = $null + $policyResults = @() + $policiesWithDowngradeJustification = @() + $xmlParseErrors = @() $passed = $false $customStatus = $null - $policiesWithDowngradeJustification = @() - $policyDetails = @() if ($errorMsg) { - # Use CustomStatus to indicate investigation β€” do not overwrite TestStatus to preserve core pass/fail logic. $customStatus = 'Investigate' + $testResultMarkdown = "⚠️ Unable to determine downgrade justification status due to error: $errorMsg`n`n" } else { + foreach ($policy in $enabledPolicies) { + + $requireDowngradeJustification = $false + + if (-not [string]::IsNullOrWhiteSpace($policy.PolicySettingsBlob)) { + try { + $xmlSettings = [xml]$policy.PolicySettingsBlob + + if ($xmlSettings.settings -and $xmlSettings.settings.setting) { + foreach ($setting in $xmlSettings.settings.setting) { + + if (-not $setting.key -or -not $setting.value) { continue } - # For each enabled policy, examine settings for downgrade justification - foreach ($policy in $labelPolicies) { - try { - # Query 3: Get detail on specific policy settings - $policySettings = Get-LabelPolicy -Identity $policy.Identity -ErrorAction Stop | - Select-Object -ExpandProperty Settings - - # Convert Settings array to hashtable for easier querying - $settingsHash = @{} - if ($policySettings) { - foreach ($setting in $policySettings) { - # Parse [key, value] format - $match = $setting -match '^\[(.*?),\s*(.+)\]$' - if ($match) { - $key = $matches[1].ToLower().Trim() - $value = $matches[2].ToLower().Trim() - $settingsHash[$key] = $value + if ($setting.key.ToLower() -eq 'requiredowngradejustification') { + $requireDowngradeJustification = ($setting.value.ToLower() -eq 'true') + } } } } - - # Query 2: Check for requiredowngradejustification setting - $hasDowngradeJustification = $settingsHash.ContainsKey('requiredowngradejustification') -and - $settingsHash['requiredowngradejustification'] -eq 'true' - - if ($hasDowngradeJustification) { - $policiesWithDowngradeJustification += $policy + catch { + $xmlParseErrors += [PSCustomObject]@{ + PolicyName = $policy.Name + Error = $_.Exception.Message + } } + } - # Collect policy details for reporting - $policyDetail = [PSCustomObject]@{ - PolicyName = $policy.Name - Enabled = $policy.Enabled - RequireDowngradeJustification = $hasDowngradeJustification - PolicyScope = if ($policy.ExchangeLocation -and $policy.ExchangeLocation.Type.value -ne 'Tenant') { 'Scoped' } else { 'Global' } - LabelsPublishedCount = if ($policy.labels) { @($policy.labels).Count } else { 0 } - WorkloadsAffected = @($policy.Workload) -join ', ' - } - $policyDetails += $policyDetail + # Determine scope + $isGlobal = + ($policy.ExchangeLocation -match '^All$') -or + ($policy.ModernGroupLocation -match '^All$') -or + ($policy.SharePointLocation -match '^All$') -or + ($policy.OneDriveLocation -match '^All$') -or + ($policy.SkypeLocation -match '^All$') -or + ($policy.PublicFolderLocation -match '^All$') + + # Determine workloads + $workloads = @() + if ($policy.ExchangeLocation) { $workloads += 'Exchange' } + if ($policy.SharePointLocation) { $workloads += 'SharePoint' } + if ($policy.OneDriveLocation) { $workloads += 'OneDrive' } + if ($policy.ModernGroupLocation) { $workloads += 'M365 Groups' } + if ($policy.PowerBILocation) { $workloads += 'Power BI' } + + $policyResult = [PSCustomObject]@{ + PolicyName = $policy.Name + PolicyGuid = $policy.Guid + Enabled = $policy.Enabled + RequireDowngradeJustification = $requireDowngradeJustification + Scope = if ($isGlobal) { 'Global' } else { 'Scoped' } + LabelsCount = $policy.Labels.Count + Workloads = ($workloads -join ', ') } - catch { - $investigateReason = "Unable to determine Settings structure or permissions prevent access for policy: $($policy.Name)" - Write-PSFMessage "Error examining policy '$($policy.Name)': $_" -Level Warning + + $policyResults += $policyResult + + if ($requireDowngradeJustification) { + $policiesWithDowngradeJustification += $policyResult } } - # Determine test status - if ($investigateReason) { - # Prefer using CustomStatus to indicate investigation; avoid setting TestStatus here. - $customStatus = 'Investigate' - } - elseif ($policiesWithDowngradeJustification.Count -gt 0) { - $testStatus = 'Pass' + if ($policiesWithDowngradeJustification.Count -gt 0) { $passed = $true + $testResultMarkdown = "βœ… Downgrade justification is enforced in at least one enabled sensitivity label policy.`n`n%TestResult%" } else { - $testStatus = 'Fail' $passed = $false + $testResultMarkdown = "❌ No enabled sensitivity label policies require downgrade justification.`n`n%TestResult%" } } #endregion Assessment Logic #region Report Generation - $testResultMarkdown = "" + $mdInfo = "`n`n### Downgrade Justification Configuration`n" - # Prefer CustomStatus 'Investigate' for reporting when present. - if ($customStatus -eq 'Investigate') { - $testResultMarkdown = "### Investigate`n`n" - $testResultMarkdown += "Unable to determine if downgrade justification is required due to policy complexity, permissions issues, or unclear Settings structure.`n`n" - } - elseif ($testStatus -eq 'Pass') { - $testResultMarkdown = "### βœ… Pass`n`n" - $testResultMarkdown += "Downgrade justification is required for at least one active sensitivity label policy, ensuring users must explain when removing or reducing label classification.`n`n" - } - else { - $testResultMarkdown = "### ❌ Fail`n`n" - $testResultMarkdown += "No sensitivity label policies require users to provide downgrade justification when removing or changing labels.`n`n" - } - - # Add detailed configuration data if we have policy information - if ($policyDetails.Count -gt 0) { - $testResultMarkdown += "## Downgrade Justification Configuration`n`n" + if ($policyResults.Count -gt 0) { + $mdInfo += "| Policy name | Downgrade justification | Scope | Labels | Workloads |`n" + $mdInfo += "| :--- | :--- | :--- | :--- | :--- |`n" - $testResultMarkdown += "### Policy Summary`n`n" - $testResultMarkdown += "| Policy name | Enabled | Downgrade justification | Scope | Labels count | Workloads |`n" - $testResultMarkdown += "|---|---|---|---|---|---|`n" + foreach ($policy in $policyResults) { + $policyName = Get-SafeMarkdown -Text $policy.PolicyName + $policyUrl = "https://purview.microsoft.com/informationprotection/labelpolicies/$($policy.PolicyGuid)" + $icon = if ($policy.RequireDowngradeJustification) { 'βœ…' } else { '❌' } - foreach ($detail in $policyDetails) { - $downgradeStatus = if ($detail.RequireDowngradeJustification) { 'βœ… Yes' } else { '❌ No' } - $testResultMarkdown += "| $($detail.PolicyName) | $($detail.Enabled) | $downgradeStatus | $($detail.PolicyScope) | $($detail.LabelsPublishedCount) | $($detail.WorkloadsAffected) |`n" + $mdInfo += "| [$policyName]($policyUrl) | $icon | $($policy.Scope) | $($policy.LabelsCount) | $($policy.Workloads) |`n" } - $testResultMarkdown += "`n## Summary Statistics`n`n" - $testResultMarkdown += "| Metric | Count |`n" - $testResultMarkdown += "|---|---|`n" - $testResultMarkdown += "| Total enabled label policies | $($policyDetails.Count) |`n" - $testResultMarkdown += "| Policies requiring downgrade justification | $($policiesWithDowngradeJustification.Count) |`n" - $testResultMarkdown += "| Policies not requiring downgrade justification | $($policyDetails.Count - $policiesWithDowngradeJustification.Count) |`n" - - if ($policyDetails.Count -gt 0) { - $percentage = [Math]::Round(($policiesWithDowngradeJustification.Count / $policyDetails.Count) * 100, 2) - $testResultMarkdown += "| Percentage with Downgrade Justification | $percentage% |`n" + $percentage = if ($policyResults.Count -gt 0) { + [Math]::Round(($policiesWithDowngradeJustification.Count / $policyResults.Count) * 100, 2) } + else { 0 } + + $mdInfo += "`n### Summary`n" + $mdInfo += "| Metric | Count |`n" + $mdInfo += "| :--- | :--- |`n" + $mdInfo += "| Total enabled label policies | $($policyResults.Count) |`n" + $mdInfo += "| Policies requiring downgrade justification | $($policiesWithDowngradeJustification.Count) |`n" + $mdInfo += "| Policies NOT requiring downgrade justification | $($policyResults.Count - $policiesWithDowngradeJustification.Count) |`n" + $mdInfo += "| Percentage with downgrade justification | $percentage% |" + } + if ($xmlParseErrors.Count -gt 0) { + $mdInfo += "`n`n### ⚠️ XML Parsing Errors`n" + $mdInfo += "| Policy name | Error |`n" + $mdInfo += "| :--- | :--- |`n" + foreach ($err in $xmlParseErrors) { + $mdInfo += "| $(Get-SafeMarkdown $err.PolicyName) | $(Get-SafeMarkdown $err.Error) |`n" + } } + $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo #endregion Report Generation - $passed = $testStatus -eq 'Pass' $params = @{ - TestId = '35018' - Title = 'Downgrade Justification Required for Sensitivity Labels' - Status = $passed - Result = $testResultMarkdown + TestId = '35018' + Title = 'Downgrade Justification Required for Sensitivity Labels' + Status = $passed + Result = $testResultMarkdown } - # Add CustomStatus if status is 'Investigate' if ($null -ne $customStatus) { $params.CustomStatus = $customStatus } From 094bd44e61cc253c46d7f6338b0c6945c7ed2a8a Mon Sep 17 00:00:00 2001 From: alexandair Date: Thu, 15 Jan 2026 14:50:33 +0000 Subject: [PATCH 8/8] Refactor markdown content for clarity and organization in Test-Assessment.35018.md --- src/powershell/tests/Test-Assessment.35018.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/powershell/tests/Test-Assessment.35018.md b/src/powershell/tests/Test-Assessment.35018.md index 953d29a54..a05a6b530 100644 --- a/src/powershell/tests/Test-Assessment.35018.md +++ b/src/powershell/tests/Test-Assessment.35018.md @@ -1,4 +1,8 @@ -When sensitivity label policies do not require users to provide justification when removing or downgrading labels, users can silently reduce the classification level of sensitive content without creating an audit trail explaining why. This creates a compliance and audit risk because organizations lose visibility into intentional label downgrades that may indicate inappropriate access to sensitive data. When a user removes a "Confidential" label and replaces it with "Internal" or no label at all, organizations should require explicit justification to create an audit record of this action. Downgrade justification is a lightweight control that increases accountability for label decisions without significantly impacting user workflows. When justification is not required, compromised user accounts or departing employees could systematically downgrade labels on sensitive documents to enable data exfiltration, leaving no clear audit trail of the unauthorized changes. Configuring downgrade justification requirements on sensitivity label policies ensures that any intentional reduction in classification level is logged with a user-provided business reason, supporting both compliance audits and insider threat investigations. Downgrade justification should be configured alongside other label controls (mandatory labeling, default labels, and mandatory enforcement) to create a comprehensive data governance framework. +When sensitivity label policies do not require users to provide justification when removing or downgrading labels, users can silently reduce the classification level of sensitive content without creating an audit trail explaining why. This creates a compliance and audit risk because organizations lose visibility into intentional label downgrades that may indicate inappropriate access to sensitive data. + +When a user removes a "Confidential" label and replaces it with "Internal" or no label at all, organizations should require explicit justification to create an audit record of this action. Downgrade justification is a lightweight control that increases accountability for label decisions without significantly impacting user workflows. + +When justification is not required, compromised user accounts or departing employees could systematically downgrade labels on sensitive documents to enable data exfiltration, leaving no clear audit trail of the unauthorized changes. Configuring downgrade justification requirements on sensitivity label policies ensures that any intentional reduction in classification level is logged with a user-provided business reason, supporting both compliance audits and insider threat investigations. Downgrade justification should be configured alongside other label controls (mandatory labeling, default labels, and mandatory enforcement) to create a comprehensive data governance framework. **Remediation action** 1. Navigate to [Sensitivity label policies](https://purview.microsoft.com/informationprotection/labelpolicies) in Microsoft Purview @@ -9,10 +13,9 @@ When sensitivity label policies do not require users to provide justification wh 6. Verify audit logging is enabled 7. Test with pilot users -**Learn More:** [Require users to provide justification to change a label](https://learn.microsoft.com/en-us/purview/sensitivity-labels-office-apps#require-users-to-provide-justification-to-change-a-label) - -- [Create and publish sensitivity labels](https://learn.microsoft.com/en-us/microsoft-365/compliance/create-sensitivity-labels) +**Learn More:** - [Require users to provide justification to change a label](https://learn.microsoft.com/en-us/purview/sensitivity-labels-office-apps#require-users-to-provide-justification-to-change-a-label) +- [Create and publish sensitivity labels](https://learn.microsoft.com/en-us/microsoft-365/compliance/create-sensitivity-labels) - [Plan your sensitivity label solution](https://learn.microsoft.com/en-us/microsoft-365/compliance/sensitivity-labels#plan-for-sensitivity-labels) - [Search the audit log](https://learn.microsoft.com/en-us/purview/audit-log-search)