feat(dlp): capture screenshot on incident

This commit is contained in:
igor04091968
2026-04-27 03:29:19 +03:00
parent 5584144978
commit 63c3d96a19
3 changed files with 207 additions and 2 deletions
@@ -360,6 +360,11 @@ function New-ActivityWatchDeploymentConfig {
logging = [pscustomobject]@{
localAgentLogsEnabled = $LocalAgentLogsEnabled
}
incidentCapture = [pscustomobject]@{
enabled = $true
screenshotEnabled = $true
artifactsRoot = (Join-Path $StateRoot 'incident-artifacts')
}
sessionEvents = [pscustomobject]@{
logonEnabled = $LogonMarkerEnabled
bucketPrefix = 'aw-session-events'
+101 -1
View File
@@ -60,6 +60,8 @@ $resolvedLogsRoot = if ($deploymentConfig) { [string]$deploymentConfig.paths.log
$resolvedLogPath = if ($LogPath) { $LogPath } else { Join-Path $resolvedLogsRoot ("browser-domains-{0}.log" -f $env:USERNAME) }
$resolvedIncidentLogPath = if ($IncidentLogPath) { $IncidentLogPath } else { Join-Path $resolvedLogsRoot ("dlp-incidents-{0}.log" -f $env:USERNAME) }
$resolvedLocalAgentLogsEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'logging' -and $deploymentConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$deploymentConfig.logging.localAgentLogsEnabled } else { $true }
$resolvedIncidentArtifactsRoot = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$deploymentConfig.incidentCapture.artifactsRoot } else { Join-Path $env:LOCALAPPDATA 'ActivityWatch-Phase2\\incident-artifacts' }
$resolvedIncidentScreenshotEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$deploymentConfig.incidentCapture.screenshotEnabled } else { $true }
if ($resolvedLocalAgentLogsEnabled -and -not (Test-Path -LiteralPath $resolvedLogsRoot)) {
New-Item -Path $resolvedLogsRoot -ItemType Directory -Force | Out-Null
@@ -72,6 +74,9 @@ $script:KnownBuckets = @{}
$script:LocalAgentLogsEnabled = $resolvedLocalAgentLogsEnabled
$script:LogPath = $resolvedLogPath
$script:IncidentLogPath = $resolvedIncidentLogPath
$script:IncidentArtifactsRoot = $resolvedIncidentArtifactsRoot
$script:IncidentScreenshotEnabled = $resolvedIncidentScreenshotEnabled
$script:ScreenshotTypesLoaded = $false
$script:IncidentState = @{}
$script:DlpRules = @()
$script:DlpDefaults = [ordered]@{
@@ -504,6 +509,15 @@ function Send-DlpIncidentHeartbeat {
$bucketId = 'aw-dlp-incidents_' + $script:Hostname
Ensure-Bucket -BucketId $bucketId -ClientName 'aw-dlp-incidents' -BucketType 'aw.dlp.incident'
$captureData = @{}
if ($script:IncidentScreenshotEnabled) {
try {
$captureData = Capture-IncidentScreenshot -RuleId ([string]$Decision.id) -SignalType 'web'
}
catch {
}
}
$event = @{
timestamp = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
duration = 0
@@ -524,12 +538,98 @@ function Send-DlpIncidentHeartbeat {
hostname = $script:Hostname
sessionId = $script:SessionId
source = 'uia-native-dlp'
}
} + $captureData
} | ConvertTo-Json -Depth 5 -Compress
Invoke-RestMethod -Method Post -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$resolvedPulseSeconds" -ContentType 'application/json' -Body $event | Out-Null
}
function Get-FileSha256Hex {
param([Parameter(Mandatory = $true)][string]$Path)
try {
$sha = [Security.Cryptography.SHA256]::Create()
$stream = [IO.File]::OpenRead($Path)
try {
($sha.ComputeHash($stream) | ForEach-Object { $_.ToString('x2') }) -join ''
}
finally {
$stream.Dispose()
$sha.Dispose()
}
}
catch {
return $null
}
}
function Ensure-Directory {
param([Parameter(Mandatory = $true)][string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item -Path $Path -ItemType Directory -Force | Out-Null
}
}
function Get-IncidentScreenshotPath {
param(
[Parameter(Mandatory = $true)][string]$RuleId,
[Parameter(Mandatory = $true)][string]$SignalType
)
$safeUser = ($env:USERNAME -replace '[^A-Za-z0-9_.-]', '_')
$safeRule = ($RuleId -replace '[^A-Za-z0-9_.-]', '_')
$safeType = ($SignalType -replace '[^A-Za-z0-9_.-]', '_')
$stamp = (Get-Date).ToUniversalTime().ToString('yyyyMMdd_HHmmss_fff')
$file = '{0}_{1}_sid{2}_{3}_{4}.png' -f $script:Hostname, $safeUser, $script:SessionId, $safeType, $safeRule
$file = '{0}_{1}' -f $stamp, $file
return (Join-Path $script:IncidentArtifactsRoot $file)
}
function Ensure-ScreenshotTypesLoaded {
if ($script:ScreenshotTypesLoaded) {
return
}
Add-Type -AssemblyName System.Windows.Forms | Out-Null
Add-Type -AssemblyName System.Drawing | Out-Null
$script:ScreenshotTypesLoaded = $true
}
function Capture-IncidentScreenshot {
param(
[Parameter(Mandatory = $true)][string]$RuleId,
[Parameter(Mandatory = $true)][string]$SignalType
)
try {
Ensure-Directory -Path $script:IncidentArtifactsRoot
Ensure-ScreenshotTypesLoaded
$vs = [System.Windows.Forms.SystemInformation]::VirtualScreen
$bmp = New-Object System.Drawing.Bitmap ([int]$vs.Width), ([int]$vs.Height)
$gfx = [System.Drawing.Graphics]::FromImage($bmp)
try {
$gfx.CopyFromScreen([int]$vs.Left, [int]$vs.Top, 0, 0, $bmp.Size)
$path = Get-IncidentScreenshotPath -RuleId $RuleId -SignalType $SignalType
$bmp.Save($path, [System.Drawing.Imaging.ImageFormat]::Png)
}
finally {
$gfx.Dispose()
$bmp.Dispose()
}
return @{
screenshotPath = $path
screenshotFormat = 'png'
screenshotWidth = [int]$vs.Width
screenshotHeight = [int]$vs.Height
screenshotSha256 = (Get-FileSha256Hex -Path $path)
}
}
catch {
Write-CollectorLog ("screenshot capture failed: {0}" -f $_.Exception.Message)
return @{}
}
}
function Get-ForegroundWindowContext {
$handle = [NativeAwMethods]::GetForegroundWindow()
if ($handle -eq [IntPtr]::Zero) {
+101 -1
View File
@@ -101,6 +101,15 @@ function Send-DlpIncidentHeartbeat {
$bucketId = 'aw-dlp-incidents_' + $script:Hostname
Ensure-Bucket -BucketId $bucketId -ClientName 'aw-dlp-incidents' -BucketType 'aw.dlp.incident'
$captureData = @{}
if ($script:IncidentScreenshotEnabled) {
try {
$captureData = Capture-IncidentScreenshot -RuleId $RuleId -SignalType $SignalType
}
catch {
}
}
$payload = @{
timestamp = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
duration = 0
@@ -114,12 +123,98 @@ function Send-DlpIncidentHeartbeat {
sessionId = $script:SessionId
hostname = $script:Hostname
source = 'endpoint-signals-phase2'
} + $Data
} + $Data + $captureData
} | ConvertTo-Json -Depth 7 -Compress
Invoke-AwJsonPost -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$script:PulseSeconds" -Json $payload
}
function Get-FileSha256Hex {
param([Parameter(Mandatory = $true)][string]$Path)
try {
$sha = [Security.Cryptography.SHA256]::Create()
$stream = [IO.File]::OpenRead($Path)
try {
($sha.ComputeHash($stream) | ForEach-Object { $_.ToString('x2') }) -join ''
}
finally {
$stream.Dispose()
$sha.Dispose()
}
}
catch {
return $null
}
}
function Ensure-Directory {
param([Parameter(Mandatory = $true)][string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item -Path $Path -ItemType Directory -Force | Out-Null
}
}
function Get-IncidentScreenshotPath {
param(
[Parameter(Mandatory = $true)][string]$RuleId,
[Parameter(Mandatory = $true)][string]$SignalType
)
$safeUser = ($env:USERNAME -replace '[^A-Za-z0-9_.-]', '_')
$safeRule = ($RuleId -replace '[^A-Za-z0-9_.-]', '_')
$safeType = ($SignalType -replace '[^A-Za-z0-9_.-]', '_')
$stamp = (Get-Date).ToUniversalTime().ToString('yyyyMMdd_HHmmss_fff')
$file = '{0}_{1}_sid{2}_{3}_{4}.png' -f $script:Hostname, $safeUser, $script:SessionId, $safeType, $safeRule
$file = '{0}_{1}' -f $stamp, $file
return (Join-Path $script:IncidentArtifactsRoot $file)
}
function Ensure-ScreenshotTypesLoaded {
if ($script:ScreenshotTypesLoaded) {
return
}
Add-Type -AssemblyName System.Windows.Forms | Out-Null
Add-Type -AssemblyName System.Drawing | Out-Null
$script:ScreenshotTypesLoaded = $true
}
function Capture-IncidentScreenshot {
param(
[Parameter(Mandatory = $true)][string]$RuleId,
[Parameter(Mandatory = $true)][string]$SignalType
)
try {
Ensure-Directory -Path $script:IncidentArtifactsRoot
Ensure-ScreenshotTypesLoaded
$vs = [System.Windows.Forms.SystemInformation]::VirtualScreen
$bmp = New-Object System.Drawing.Bitmap ([int]$vs.Width), ([int]$vs.Height)
$gfx = [System.Drawing.Graphics]::FromImage($bmp)
try {
$gfx.CopyFromScreen([int]$vs.Left, [int]$vs.Top, 0, 0, $bmp.Size)
$path = Get-IncidentScreenshotPath -RuleId $RuleId -SignalType $SignalType
$bmp.Save($path, [System.Drawing.Imaging.ImageFormat]::Png)
}
finally {
$gfx.Dispose()
$bmp.Dispose()
}
return @{
screenshotPath = $path
screenshotFormat = 'png'
screenshotWidth = [int]$vs.Width
screenshotHeight = [int]$vs.Height
screenshotSha256 = (Get-FileSha256Hex -Path $path)
}
}
catch {
Write-EndpointLog ("screenshot capture failed: {0}" -f $_.Exception.Message)
return @{}
}
}
function Get-StringHash {
param([AllowNull()][string]$Value)
if ($null -eq $Value) { return $null }
@@ -463,6 +558,8 @@ $resolvedPollSeconds = if ($PSBoundParameters.ContainsKey('PollSeconds')) { $Pol
$resolvedLogsRoot = if ($deploymentConfig) { [string]$deploymentConfig.paths.logsRoot } else { 'C:\ProgramData\ActivityWatch\logs' }
$resolvedLogPath = if ($LogPath) { $LogPath } else { Join-Path $resolvedLogsRoot ("endpoint-signals-{0}.log" -f $env:USERNAME) }
$resolvedLocalAgentLogsEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'logging' -and $deploymentConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$deploymentConfig.logging.localAgentLogsEnabled } else { $true }
$resolvedIncidentArtifactsRoot = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$deploymentConfig.incidentCapture.artifactsRoot } else { Join-Path $env:LOCALAPPDATA 'ActivityWatch-Phase2\\incident-artifacts' }
$resolvedIncidentScreenshotEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$deploymentConfig.incidentCapture.screenshotEnabled } else { $true }
if ($resolvedLocalAgentLogsEnabled -and -not (Test-Path -LiteralPath $resolvedLogsRoot)) {
New-Item -Path $resolvedLogsRoot -ItemType Directory -Force | Out-Null
@@ -480,6 +577,9 @@ $script:LastClipboardHash = $null
$script:PulseSeconds = [Math]::Max($resolvedPollSeconds * 3, 30)
$script:LocalAgentLogsEnabled = $resolvedLocalAgentLogsEnabled
$script:LogPath = $resolvedLogPath
$script:IncidentArtifactsRoot = $resolvedIncidentArtifactsRoot
$script:IncidentScreenshotEnabled = $resolvedIncidentScreenshotEnabled
$script:ScreenshotTypesLoaded = $false
Load-DlpPolicy -Path $resolvedPolicyPath
Write-EndpointLog ("endpoint collector started against {0}" -f $script:ApiBase)