Privacy hardening, remote-peer IP masking and review fixes

- Add privacy projection (core/privacy.py), private report storage, bounded
  process capture; omit identities, free text and unapproved paths
- Mask remote peer IPs: only loopback/host/wildcard stay numeric, others are
  reduced to remote_scope private/public (schema + scoring updated)
- Map ss TCP states to canonical names; ASCII-only numeric fields; fix
  "Microsoft Visual C++" product matching (Python and PowerShell)
- Merge 7fed3b9: -Diagnostic switch, serialization exit code 3, PS7 module
  path, UI error detail; keep hardened task/certificate collectors
- Add audit docs, privacy docs and regression tests

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 18:50:24 -08:00
parent 7fed3b9ab9
commit 610a3bb447
51 changed files with 2719 additions and 604 deletions

View File

@@ -44,16 +44,6 @@ function ConvertTo-SamsaraJson {
}
}
function Get-SamsaraSafeExecutable {
param([string]$Value)
# Task action arguments, scripts and environment values are deliberately omitted.
# Scheduler action values need not be valid filesystem paths. Path.GetFileName
# throws on malformed/quoted values in .NET Framework (PowerShell 5.1).
$name = (($Value -replace '\\', '/') -split '/')[-1]
if ($name -match '^[A-Za-z0-9_. -]+\.(exe|com|cmd|bat|ps1|vbs|js|wsf|msc)$') { return $name }
return '[executable name omitted]'
}
function Invoke-SamsaraSection {
param([string]$Name, [scriptblock]$Collector)
$result = [ordered]@{
@@ -84,3 +74,43 @@ function ConvertTo-SamsaraCheck {
limitations = @($Section.limitations)
}
}
function Get-SamsaraPrivacyConfiguration {
if ($null -eq $script:SamsaraPrivacy) {
$assets = Join-Path (Split-Path $PSScriptRoot -Parent) 'samsara\assets'
$script:SamsaraPrivacy = Get-Content -LiteralPath (Join-Path $assets 'privacy_policy.json') -Raw -ErrorAction Stop | ConvertFrom-Json
$script:SamsaraCatalogue = Get-Content -LiteralPath (Join-Path $assets 'privacy_catalogue.json') -Raw -ErrorAction Stop | ConvertFrom-Json
if ($script:SamsaraPrivacy.version -ne '1.0') { throw 'Invalid privacy policy.' }
}
}
function Get-SamsaraPublicName {
param([string]$Kind, [string]$Value, [int]$Index)
Get-SamsaraPrivacyConfiguration
$normalized = $Value.ToLowerInvariant() -replace '\.(exe|service)$', ''
if ($Kind -eq 'services' -and $script:SamsaraPrivacy.approved_service_names -ccontains $Value) { return $Value }
if ($Kind -eq 'products') {
if ($script:SamsaraPrivacy.approved_software_names -ccontains $Value) { return $Value }
foreach ($product in $script:SamsaraCatalogue.products) {
if ($Value -match ('^' + [regex]::Escape($product) + '(?![A-Za-z0-9])')) { return $product }
}
} elseif ($script:SamsaraCatalogue.$Kind -contains $normalized) { return $normalized }
if ($Kind -eq 'services') {
foreach ($family in $script:SamsaraCatalogue.families.PSObject.Properties) {
foreach ($prefix in $family.Value.prefixes) {
if ($normalized.StartsWith($prefix, [StringComparison]::Ordinal)) { return $family.Value.representative }
}
}
}
return $Kind + '-' + $Index
}
function Get-SamsaraApprovedRoot {
param([string]$Value)
Get-SamsaraPrivacyConfiguration
if ($Value -notmatch '^[A-Za-z]:\\' -or $Value -match '(^|[\\/])\.\.([\\/]|$)' -or $Value -match '[\x00-\x1f]') { return 'Omitted by privacy policy' }
foreach ($root in $script:SamsaraPrivacy.approved_share_roots) {
if ($root -match '^[A-Za-z]:\\' -and $Value.TrimEnd([char]'\').Equals(([string]$root).TrimEnd([char]'\'), [StringComparison]::OrdinalIgnoreCase)) { return $Value }
}
return 'Omitted by privacy policy'
}