From 2bab84f9f9728a33874514495df3ed5d97ac9db5 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 20:21:39 +0000 Subject: [PATCH] =?UTF-8?q?feat(dlp):=20add=20enforcement=20=E2=80=94=20US?= =?UTF-8?q?B=20write-block,=20print=20cancel,=20clipboard=20clear?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2.5: when DLP policy rule has action="block", the collector now actively prevents the action instead of just logging: - USB: Set-Disk -IsReadOnly via Get-Partition/Get-Disk pipeline - Print: Remove-CimInstance Win32_PrintJob for matching jobs - Clipboard: Set-Clipboard -Value $null to clear sensitive content Each enforcement adds enforced=true/false to incident telemetry. Windows balloon notification shown to user on every block action. Backward-compatible: existing action="alert" rules unchanged. Co-Authored-By: Fashion Lisa --- docs/dlp-enforcement.md | 126 ++++++++++++++++++ docs/dlp-gap-analysis.md | 9 ++ .../dlp-endpoint-signals-collector.ps1 | 120 ++++++++++++++++- windows/dlp-endpoint-signals-collector.ps1 | 120 ++++++++++++++++- 4 files changed, 369 insertions(+), 6 deletions(-) create mode 100644 docs/dlp-enforcement.md diff --git a/docs/dlp-enforcement.md b/docs/dlp-enforcement.md new file mode 100644 index 0000000..2751da0 --- /dev/null +++ b/docs/dlp-enforcement.md @@ -0,0 +1,126 @@ +# DLP Enforcement (action: "block") + +## Обзор + +Phase 2.5 расширяет DLP endpoint collector функциями **активного предотвращения** (enforcement). +При `action: "block"` в правиле DLP-политики коллектор не только регистрирует инцидент, но и выполняет блокирующее действие: + +| Канал | Действие при `block` | +|-----------|-----------------------------------------------------------| +| clipboard | Очистка буфера обмена (`Set-Clipboard -Value $null`) | +| usb | Перевод USB-диска в read-only (`Set-Disk -IsReadOnly`) | +| print | Отмена задания печати (`Remove-CimInstance Win32_PrintJob`)| + +Во всех случаях пользователь получает Windows-уведомление (balloon notification) с описанием причины блокировки. + +## Конфигурация политики + +Формат `dlp-policy.json` не изменился — поле `action` в правиле теперь поддерживает значение `"block"` наряду с `"alert"` (по умолчанию). + +### Пример: блокировка USB записи + +```json +{ + "defaults": { + "enabled": true, + "action": "alert", + "severity": "medium", + "cooldownSeconds": 300 + }, + "endpoint": { + "usb": [ + { + "id": "block-all-usb-write", + "action": "block", + "severity": "high", + "message": "Запись на USB-носитель заблокирована политикой DLP" + } + ], + "clipboard": [ + { + "id": "block-pdn-clipboard", + "action": "block", + "severity": "high", + "regexPatterns": [ + "\\b\\d{3}-\\d{3}-\\d{3}\\s?\\d{2}\\b", + "\\b\\d{4}\\s?\\d{6}\\b" + ], + "minLength": 8, + "message": "Буфер обмена очищен: обнаружены персональные данные (СНИЛС/паспорт)" + } + ], + "print": [ + { + "id": "block-confidential-print", + "action": "block", + "severity": "high", + "documentRegex": "(?i)(конфиденциально|секретно|confidential|restricted)", + "message": "Печать заблокирована: документ содержит метку конфиденциальности" + } + ] + } +} +``` + +### Пример: только мониторинг (без блокировки) + +```json +{ + "endpoint": { + "usb": [ + { + "id": "monitor-usb", + "action": "alert", + "severity": "medium", + "message": "Обнаружено подключение USB-носителя" + } + ] + } +} +``` + +## Телеметрия + +Каждый инцидент с enforcement записывается в bucket `aw-dlp-incidents_` с дополнительным полем: + +```json +{ + "ruleId": "block-all-usb-write", + "action": "block", + "severity": "high", + "signalType": "usb_insert", + "enforced": true, + "driveLetter": "E:", + "volumeName": "FLASH_DRIVE" +} +``` + +- `enforced: true` — блокировка выполнена успешно +- `enforced: false` — блокировка не удалась (недостаточно прав, устройство недоступно и т.д.) + +## Требования + +- **Clipboard block**: Не требует повышенных прав. +- **USB write-block**: Требует запуск от имени администратора (для `Set-Disk -IsReadOnly`). При запуске без прав блокировка не сработает, но инцидент будет зарегистрирован с `enforced: false`. +- **Print block**: Требует права на отмену заданий печати (обычно — SYSTEM или администратор принт-сервера). + +## Уведомления + +При каждой блокировке пользователю показывается Windows balloon notification: + +| Канал | Заголовок | +|-----------|--------------------------------------| +| clipboard | `DLP: буфер обмена очищен` | +| usb | `DLP: USB заблокирован для записи` | +| print | `DLP: печать заблокирована` | + +Текст уведомления берётся из поля `message` правила политики. + +## Rollback + +Для отключения enforcement без изменения кода — смените `action` с `"block"` на `"alert"` в `dlp-policy.json`. Все правила продолжат мониторинг без блокировки. + +Для USB, переведённого в read-only, восстановление: +```powershell +Get-Disk | Where-Object { $_.BusType -eq 'USB' -and $_.IsReadOnly } | Set-Disk -IsReadOnly $false +``` diff --git a/docs/dlp-gap-analysis.md b/docs/dlp-gap-analysis.md index cd0525e..4580910 100644 --- a/docs/dlp-gap-analysis.md +++ b/docs/dlp-gap-analysis.md @@ -32,6 +32,15 @@ - File-operation telemetry (create/copy/archive/upload hints) — в backlog. - Central incident aggregation/export — в backlog. +### Phase 2.5 — Enforcement (внедрено) + +- USB write-block (`Set-Disk -IsReadOnly`) при `action: "block"` — внедрено. +- Print job cancel (`Remove-CimInstance Win32_PrintJob`) при `action: "block"` — внедрено. +- Clipboard clear (`Set-Clipboard -Value $null`) при `action: "block"` — внедрено. +- Windows balloon notification пользователю при блокировке — внедрено. +- Телеметрия enforcement (`enforced: true/false` в incident heartbeat) — внедрено. +- Документация: `docs/dlp-enforcement.md`. + ### Phase 3 - Policy engine service (server-side), versioned policies, approval workflow. diff --git a/install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 b/install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 index 8ee44f5..de84fdf 100644 --- a/install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 +++ b/install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 @@ -215,6 +215,99 @@ function Capture-IncidentScreenshot { } } +# --------------------------------------------------------------------------- +# Enforcement functions (action = "block") +# --------------------------------------------------------------------------- + +function Show-EnforcementNotification { + param( + [Parameter(Mandatory = $true)][string]$Title, + [Parameter(Mandatory = $true)][string]$Body + ) + try { + Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue + $icon = New-Object System.Windows.Forms.NotifyIcon + $icon.Icon = [System.Drawing.SystemIcons]::Warning + $icon.BalloonTipTitle = $Title + $icon.BalloonTipText = $Body + $icon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Warning + $icon.Visible = $true + $icon.ShowBalloonTip(5000) + Start-Sleep -Milliseconds 200 + $icon.Dispose() + } + catch { + Write-EndpointLog ("notification failed: {0}" -f $_.Exception.Message) + } +} + +function Invoke-ClipboardEnforcement { + [OutputType([bool])] + param() + try { + Set-Clipboard -Value $null -ErrorAction Stop + Write-EndpointLog "enforcement: clipboard cleared" + return $true + } + catch { + Write-EndpointLog ("enforcement: clipboard clear failed: {0}" -f $_.Exception.Message) + return $false + } +} + +function Invoke-UsbWriteBlockEnforcement { + [OutputType([bool])] + param( + [Parameter(Mandatory = $true)][string]$DriveLetter + ) + try { + $partition = Get-Partition -DriveLetter ($DriveLetter.TrimEnd(':')) -ErrorAction Stop + $disk = Get-Disk -Number $partition.DiskNumber -ErrorAction Stop + if ($disk.BusType -ne 'USB') { + Write-EndpointLog ("enforcement: skip non-USB disk {0} bus={1}" -f $disk.Number, $disk.BusType) + return $false + } + if (-not $disk.IsReadOnly) { + Set-Disk -Number $disk.Number -IsReadOnly $true -ErrorAction Stop + Write-EndpointLog ("enforcement: USB disk {0} ({1}) set read-only" -f $disk.Number, $DriveLetter) + } + return $true + } + catch { + Write-EndpointLog ("enforcement: USB write-block failed drive={0}: {1}" -f $DriveLetter, $_.Exception.Message) + return $false + } +} + +function Invoke-PrintJobEnforcement { + [OutputType([bool])] + param( + [Parameter(Mandatory = $true)][string]$PrinterName, + [string]$DocumentName, + [string]$Owner + ) + $cancelled = $false + try { + $jobs = Get-CimInstance Win32_PrintJob -ErrorAction SilentlyContinue + foreach ($job in @($jobs)) { + $jobPrinter = [string]$job.Name + $jobOwner = [string]$job.Owner + $jobDoc = [string]$job.Document + $matchPrinter = ($jobPrinter -like "*$PrinterName*") + $matchOwner = (-not $Owner) -or ($jobOwner -like "*$Owner*") -or ($jobOwner -like "*$env:USERNAME*") + if ($matchPrinter -and $matchOwner) { + Remove-CimInstance -InputObject $job -ErrorAction Stop + Write-EndpointLog ("enforcement: print job cancelled id={0} printer={1} doc={2}" -f $job.JobId, $jobPrinter, $jobDoc) + $cancelled = $true + } + } + } + catch { + Write-EndpointLog ("enforcement: print cancel failed printer={0}: {1}" -f $PrinterName, $_.Exception.Message) + } + return $cancelled +} + function Get-StringHash { param([AllowNull()][string]$Value) if ($null -eq $Value) { return $null } @@ -321,11 +414,18 @@ function Evaluate-ClipboardRules { $severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity } $message = if ($rule.message) { [string]$rule.message } else { "Clipboard rule matched: $ruleId" } + $enforced = $false + if ($action -eq 'block') { + $enforced = Invoke-ClipboardEnforcement + Show-EnforcementNotification -Title 'DLP: буфер обмена очищен' -Body $message + } + Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'clipboard' -Data @{ clipboardHash = $ClipboardHash clipboardLength = $ClipboardText.Length + enforced = $enforced } - Write-EndpointLog ("incident clipboard rule={0} action={1} severity={2}" -f $ruleId, $action, $severity) + Write-EndpointLog ("incident clipboard rule={0} action={1} severity={2} enforced={3}" -f $ruleId, $action, $severity, $enforced) } } @@ -349,11 +449,18 @@ function Evaluate-UsbRules { $severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity } $message = if ($rule.message) { [string]$rule.message } else { "USB rule matched: $ruleId" } + $enforced = $false + if ($action -eq 'block') { + $enforced = Invoke-UsbWriteBlockEnforcement -DriveLetter $DriveLetter + Show-EnforcementNotification -Title 'DLP: USB заблокирован для записи' -Body $message + } + Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'usb_insert' -Data @{ driveLetter = $DriveLetter volumeName = $VolumeName + enforced = $enforced } - Write-EndpointLog ("incident usb rule={0} action={1} severity={2} drive={3}" -f $ruleId, $action, $severity, $DriveLetter) + Write-EndpointLog ("incident usb rule={0} action={1} severity={2} drive={3} enforced={4}" -f $ruleId, $action, $severity, $DriveLetter, $enforced) } } @@ -387,12 +494,19 @@ function Evaluate-PrintRules { $severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity } $message = if ($rule.message) { [string]$rule.message } else { "Print rule matched: $ruleId" } + $enforced = $false + if ($action -eq 'block') { + $enforced = Invoke-PrintJobEnforcement -PrinterName $PrinterName -DocumentName $DocumentName -Owner $Owner + Show-EnforcementNotification -Title 'DLP: печать заблокирована' -Body $message + } + Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'print_job' -Data @{ printerName = $PrinterName documentName = $DocumentName owner = $Owner + enforced = $enforced } - Write-EndpointLog ("incident print rule={0} action={1} severity={2} printer={3}" -f $ruleId, $action, $severity, $PrinterName) + Write-EndpointLog ("incident print rule={0} action={1} severity={2} printer={3} enforced={4}" -f $ruleId, $action, $severity, $PrinterName, $enforced) } } diff --git a/windows/dlp-endpoint-signals-collector.ps1 b/windows/dlp-endpoint-signals-collector.ps1 index 8ee44f5..de84fdf 100644 --- a/windows/dlp-endpoint-signals-collector.ps1 +++ b/windows/dlp-endpoint-signals-collector.ps1 @@ -215,6 +215,99 @@ function Capture-IncidentScreenshot { } } +# --------------------------------------------------------------------------- +# Enforcement functions (action = "block") +# --------------------------------------------------------------------------- + +function Show-EnforcementNotification { + param( + [Parameter(Mandatory = $true)][string]$Title, + [Parameter(Mandatory = $true)][string]$Body + ) + try { + Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue + $icon = New-Object System.Windows.Forms.NotifyIcon + $icon.Icon = [System.Drawing.SystemIcons]::Warning + $icon.BalloonTipTitle = $Title + $icon.BalloonTipText = $Body + $icon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Warning + $icon.Visible = $true + $icon.ShowBalloonTip(5000) + Start-Sleep -Milliseconds 200 + $icon.Dispose() + } + catch { + Write-EndpointLog ("notification failed: {0}" -f $_.Exception.Message) + } +} + +function Invoke-ClipboardEnforcement { + [OutputType([bool])] + param() + try { + Set-Clipboard -Value $null -ErrorAction Stop + Write-EndpointLog "enforcement: clipboard cleared" + return $true + } + catch { + Write-EndpointLog ("enforcement: clipboard clear failed: {0}" -f $_.Exception.Message) + return $false + } +} + +function Invoke-UsbWriteBlockEnforcement { + [OutputType([bool])] + param( + [Parameter(Mandatory = $true)][string]$DriveLetter + ) + try { + $partition = Get-Partition -DriveLetter ($DriveLetter.TrimEnd(':')) -ErrorAction Stop + $disk = Get-Disk -Number $partition.DiskNumber -ErrorAction Stop + if ($disk.BusType -ne 'USB') { + Write-EndpointLog ("enforcement: skip non-USB disk {0} bus={1}" -f $disk.Number, $disk.BusType) + return $false + } + if (-not $disk.IsReadOnly) { + Set-Disk -Number $disk.Number -IsReadOnly $true -ErrorAction Stop + Write-EndpointLog ("enforcement: USB disk {0} ({1}) set read-only" -f $disk.Number, $DriveLetter) + } + return $true + } + catch { + Write-EndpointLog ("enforcement: USB write-block failed drive={0}: {1}" -f $DriveLetter, $_.Exception.Message) + return $false + } +} + +function Invoke-PrintJobEnforcement { + [OutputType([bool])] + param( + [Parameter(Mandatory = $true)][string]$PrinterName, + [string]$DocumentName, + [string]$Owner + ) + $cancelled = $false + try { + $jobs = Get-CimInstance Win32_PrintJob -ErrorAction SilentlyContinue + foreach ($job in @($jobs)) { + $jobPrinter = [string]$job.Name + $jobOwner = [string]$job.Owner + $jobDoc = [string]$job.Document + $matchPrinter = ($jobPrinter -like "*$PrinterName*") + $matchOwner = (-not $Owner) -or ($jobOwner -like "*$Owner*") -or ($jobOwner -like "*$env:USERNAME*") + if ($matchPrinter -and $matchOwner) { + Remove-CimInstance -InputObject $job -ErrorAction Stop + Write-EndpointLog ("enforcement: print job cancelled id={0} printer={1} doc={2}" -f $job.JobId, $jobPrinter, $jobDoc) + $cancelled = $true + } + } + } + catch { + Write-EndpointLog ("enforcement: print cancel failed printer={0}: {1}" -f $PrinterName, $_.Exception.Message) + } + return $cancelled +} + function Get-StringHash { param([AllowNull()][string]$Value) if ($null -eq $Value) { return $null } @@ -321,11 +414,18 @@ function Evaluate-ClipboardRules { $severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity } $message = if ($rule.message) { [string]$rule.message } else { "Clipboard rule matched: $ruleId" } + $enforced = $false + if ($action -eq 'block') { + $enforced = Invoke-ClipboardEnforcement + Show-EnforcementNotification -Title 'DLP: буфер обмена очищен' -Body $message + } + Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'clipboard' -Data @{ clipboardHash = $ClipboardHash clipboardLength = $ClipboardText.Length + enforced = $enforced } - Write-EndpointLog ("incident clipboard rule={0} action={1} severity={2}" -f $ruleId, $action, $severity) + Write-EndpointLog ("incident clipboard rule={0} action={1} severity={2} enforced={3}" -f $ruleId, $action, $severity, $enforced) } } @@ -349,11 +449,18 @@ function Evaluate-UsbRules { $severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity } $message = if ($rule.message) { [string]$rule.message } else { "USB rule matched: $ruleId" } + $enforced = $false + if ($action -eq 'block') { + $enforced = Invoke-UsbWriteBlockEnforcement -DriveLetter $DriveLetter + Show-EnforcementNotification -Title 'DLP: USB заблокирован для записи' -Body $message + } + Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'usb_insert' -Data @{ driveLetter = $DriveLetter volumeName = $VolumeName + enforced = $enforced } - Write-EndpointLog ("incident usb rule={0} action={1} severity={2} drive={3}" -f $ruleId, $action, $severity, $DriveLetter) + Write-EndpointLog ("incident usb rule={0} action={1} severity={2} drive={3} enforced={4}" -f $ruleId, $action, $severity, $DriveLetter, $enforced) } } @@ -387,12 +494,19 @@ function Evaluate-PrintRules { $severity = if ($rule.severity) { [string]$rule.severity } else { [string]$script:Policy.defaults.severity } $message = if ($rule.message) { [string]$rule.message } else { "Print rule matched: $ruleId" } + $enforced = $false + if ($action -eq 'block') { + $enforced = Invoke-PrintJobEnforcement -PrinterName $PrinterName -DocumentName $DocumentName -Owner $Owner + Show-EnforcementNotification -Title 'DLP: печать заблокирована' -Body $message + } + Send-DlpIncidentHeartbeat -RuleId $ruleId -Action $action -Severity $severity -Message $message -SignalType 'print_job' -Data @{ printerName = $PrinterName documentName = $DocumentName owner = $Owner + enforced = $enforced } - Write-EndpointLog ("incident print rule={0} action={1} severity={2} printer={3}" -f $ruleId, $action, $severity, $PrinterName) + Write-EndpointLog ("incident print rule={0} action={1} severity={2} printer={3} enforced={4}" -f $ruleId, $action, $severity, $PrinterName, $enforced) } }