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

@@ -1,28 +1,6 @@
function Get-SamsaraCertificates {
param($Result)
$collection = @{ stores = @() }
Invoke-SamsaraRead $Result 'Local machine certificate store list' {
$collection.stores = @(Get-ChildItem -LiteralPath 'Cert:\LocalMachine' -ErrorAction Stop)
}
# Listing store names is not evidence that any listed store was readable.
if ($collection.stores.Count -gt 0) { $Result.successful_subchecks-- }
foreach ($store in $collection.stores) {
# Certificate provider PSPath may omit LocalMachine (including on 5.1).
# Construct an explicit provider path from the enumerated store name.
$storeName = [string]$store.PSChildName
Invoke-SamsaraRead $Result 'Certificate store query' {
if ($storeName -notmatch '^[A-Za-z0-9_. -]+$') { throw 'Invalid store name' }
$certificates = @(Get-ChildItem -LiteralPath ('Cert:\LocalMachine\' + $storeName) -ErrorAction Stop)
foreach ($certificate in $certificates) {
Invoke-SamsaraRead $Result 'Certificate metadata projection' {
$row = [pscustomobject]@{ subject = [string]$certificate.Subject; issuer = [string]$certificate.Issuer
expires = $certificate.NotAfter.ToUniversalTime().ToString('o'); thumbprint = [string]$certificate.Thumbprint
store = $storeName }
$null = ConvertTo-Json -InputObject $row -Depth 4 -ErrorAction Stop
$Result.data += $row
}
}
}
}
$Result.limitations += 'Public LocalMachine certificate metadata only. Private keys, password-protected files, CurrentUser stores, application bindings and off-store certificates are not read.'
$Result.status = 'not_collected'
$Result.message = 'Certificate enumeration disabled by privacy policy.'
$Result.limitations += 'Subjects, issuers, extensions and certificate objects may contain identifying information. Use a separately approved certificate review; no certificate provider is invoked.'
}

View File

@@ -18,6 +18,7 @@ if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) {
exit 2
}
. (Join-Path $PSScriptRoot 'Inventory.Common.ps1')
Get-SamsaraPrivacyConfiguration
foreach ($file in @('System','Roles','Services','Network','Tasks','Software','Storage','Certificates')) {
. (Join-Path $PSScriptRoot ('Get-Server' + $file + '.ps1'))
}

View File

@@ -9,7 +9,7 @@ function Get-SamsaraNetwork {
$processName = if ($processes.ContainsKey([int]$_.OwningProcess)) { $processes[[int]$_.OwningProcess] } else { 'Unavailable or process exited' }
[pscustomobject]@{ state = [string]$_.State; local_address = [string]$_.LocalAddress; local_port = [int]$_.LocalPort
remote_address = [string]$_.RemoteAddress; remote_port = [int]$_.RemotePort
pid = [int]$_.OwningProcess; process = $processName; direction = 'Unknown' }
pid = [int]$_.OwningProcess; process = Get-SamsaraPublicName 'processes' $processName ([int]$_.OwningProcess); direction = 'Unknown' }
})
}
$Result.limitations += 'Point-in-time TCP only. Connection direction, historical activity, UDP and other network compartments are unverified. Process names may race process exit or be hidden.'

View File

@@ -1,8 +1,10 @@
function Get-SamsaraRoles {
param($Result)
Invoke-SamsaraRead $Result 'Server roles and features' {
$index = 0
$Result.data = @(Get-WindowsFeature -ErrorAction Stop | Where-Object { $_.Installed } | ForEach-Object {
[pscustomobject]@{ name = [string]$_.Name; display_name = [string]$_.DisplayName
$index++
[pscustomobject]@{ name = Get-SamsaraPublicName 'roles' ([string]$_.Name) $index
state = [string]$_.InstallState; feature_type = [string]$_.FeatureType }
})
}

View File

@@ -2,9 +2,11 @@ function Get-SamsaraServices {
param($Result)
Invoke-SamsaraRead $Result 'Windows services' {
# Do not query PathName: it can include secret-bearing command arguments.
$Result.data = @(Get-CimInstance -ClassName Win32_Service -Property Name,DisplayName,State,StartMode,StartName,ProcessId -ErrorAction Stop | ForEach-Object {
[pscustomobject]@{ name = [string]$_.Name; display_name = [string]$_.DisplayName
state = [string]$_.State; startup = [string]$_.StartMode; account = [string]$_.StartName; pid = [int]$_.ProcessId }
$index = 0
$Result.data = @(Get-CimInstance -ClassName Win32_Service -Property Name,State,StartMode,ProcessId -ErrorAction Stop | ForEach-Object {
$index++
[pscustomobject]@{ name = Get-SamsaraPublicName 'services' ([string]$_.Name) $index
state = [string]$_.State; startup = [string]$_.StartMode; account = 'Omitted by privacy policy'; pid = [int]$_.ProcessId }
})
}
$Result.limitations += 'All visible service states are inventoried, including running services. Executable command lines and credentials are not collected; service accounts do not prove consumers.'

View File

@@ -5,11 +5,12 @@ function Read-SamsaraSoftwareEntries {
try {
$entry = $Uninstall.OpenSubKey($name, $false)
if ($null -eq $entry) { throw 'Software entry not readable.' }
$display = [string]$entry.GetValue('DisplayName')
$display = [string]$entry.GetValue('DisplayName', $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
if ($display) {
$Result.data += [pscustomobject]@{ name = $display; publisher = [string]$entry.GetValue('Publisher')
version = [string]$entry.GetValue('DisplayVersion'); installation_date = [string]$entry.GetValue('InstallDate')
registry_view = $View }
$version = [string]$entry.GetValue('DisplayVersion', $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
if ($version -match '^(\d{1,5}(?:\.\d{1,5}){0,3})(?![\d.])') { $version = $Matches[1] } else { $version = 'Not collected' }
$Result.data += [pscustomobject]@{ name = Get-SamsaraPublicName 'products' $display ($Result.data.Count + 1)
version = $version; registry_view = $View }
}
} catch { Add-SamsaraError $Result $_ 'Machine software entry' }
finally { if ($entry) { $entry.Dispose() } }
@@ -33,5 +34,5 @@ function Get-SamsaraSoftware {
}
}
}
$Result.limitations += 'Machine uninstall registrations in both registry views only. Per-user, portable, container and unregistered software may be absent. Install dates are raw registry values and may be empty. No installer consistency checks are triggered.'
$Result.limitations += 'Machine uninstall registrations in both registry views only. Per-user, portable, container and unregistered software may be absent. Publisher, installation date, uninstall commands and environment expansion are omitted. No installer consistency checks are triggered.'
}

View File

@@ -1,47 +1,30 @@
function Get-SamsaraStorage {
param($Result)
Invoke-SamsaraRead $Result 'Local volumes' {
$Result.data = @(Get-Volume -ErrorAction Stop | ForEach-Object {
[pscustomobject]@{ type = 'Volume'; name = [string]$_.FileSystemLabel
path = if ($_.DriveLetter) { [string]$_.DriveLetter + ':\' } else { [string]$_.Path }
Invoke-SamsaraRead $Result 'Fixed local disk metadata' {
$index = 0
$Result.data = @(Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DriveType = 3' -Property DeviceID,Size,FreeSpace,FileSystem -ErrorAction Stop | ForEach-Object {
$index++
[pscustomobject]@{ type = 'Volume'; name = 'Volume-' + $index
path = Get-SamsaraApprovedRoot (([string]$_.DeviceID) + '\')
filesystem = [string]$_.FileSystem; capacity_gib = [math]::Round(([double]$_.Size / 1GB), 2)
free_gib = [math]::Round(([double]$_.SizeRemaining / 1GB), 2) }
free_gib = [math]::Round(([double]$_.FreeSpace / 1GB), 2) }
})
}
$Result.limitations += 'Local volume metadata only. Share contents, mounted remote storage, storage-array dependencies, snapshots and backups are not inspected.'
$Result.status = 'partial'
$Result.limitations += 'Fixed drive-letter disks only; labels and child paths not read. Mount-only volumes and external storage dependencies need review.'
}
function Get-SamsaraShares {
param($Result)
Invoke-SamsaraRead $Result 'SMB shares' {
Invoke-SamsaraRead $Result 'Configured SMB share roots' {
$index = 0
$Result.data = @(Get-SmbShare -ErrorAction Stop | ForEach-Object {
[pscustomobject]@{ type = 'SMB Share'; name = [string]$_.Name; path = [string]$_.Path
$index++
$name = if ([string]$_.Name -match '^(ADMIN|IPC|[A-Z])\$$') { ([string]$_.Name).ToUpperInvariant() } else { 'Share-' + $index }
[pscustomobject]@{ type = 'SMB Share'; name = $name; path = Get-SamsaraApprovedRoot ([string]$_.Path)
capacity_gib = $null; free_gib = $null; special = [bool]$_.Special }
})
}
Invoke-SamsaraRead $Result 'SMB open-file activity' {
$openFiles = @(Get-SmbOpenFile -ErrorAction Stop)
$observedAt = Get-SamsaraTimestamp
foreach ($share in $Result.data) {
if ($share.name -match '^(ADMIN|IPC|[A-Z])\$$' -or -not $share.path) { continue }
$prefix = $share.path.TrimEnd([char]'\') + '\'
$matching = @($openFiles | Where-Object {
# Path alone also matches a file opened through C$/D$. Require
# Path = share root + ShareRelativePath to distinguish that case.
$_.Path -and $_.ShareRelativePath -and
([string]$_.Path).Equals(($prefix + ([string]$_.ShareRelativePath).TrimStart([char]'\')), [StringComparison]::OrdinalIgnoreCase)
})
$adminAliases = @($Result.data | Where-Object {
$_.name -match '^(ADMIN|IPC|[A-Z])\$$' -and ([string]$_.path).TrimEnd([char]'\').Equals($share.path.TrimEnd([char]'\'), [StringComparison]::OrdinalIgnoreCase)
})
# Only aggregated, path-correlated activity is exported. Never serialize
# open-file objects, file names, session identities or user names.
$share | Add-Member -NotePropertyName observed_open_files -NotePropertyValue $matching.Count
$clients = @($matching | Select-Object -ExpandProperty ClientComputerName -Unique)
$share | Add-Member -NotePropertyName observed_client_count -NotePropertyValue $clients.Count
$share | Add-Member -NotePropertyName activity_source -NotePropertyValue $(if ($adminAliases.Count) { 'Get-SmbOpenFile (administrative alias ambiguous)' } else { 'Get-SmbOpenFile (share-root-correlated)' })
$share | Add-Member -NotePropertyName activity_observed_at -NotePropertyValue $observedAt
}
}
$Result.limitations += 'Configured SMB shares, including administrative shares, and aggregate open-file counts share-root-correlated to business shares. No file contents, open-file paths, session identities, ACLs or credentials are exported. Zero open files does not prove a share unused. Same-root aliases are not individually distinguishable; an administrative same-root alias is marked ambiguous and never confirms business-share use. All business shares remain one workload group.'
$Result.status = 'partial'
$Result.limitations += 'Configured roots only; exact approved local roots may be retained. No file enumeration, open-file activity, filenames, child paths, sessions or client identities are queried. Active share consumers remain unverified.'
}

View File

@@ -1,19 +1,21 @@
function Get-SamsaraSystem {
param($Result)
$Result.data = [ordered]@{
hostname = [Environment]::MachineName; platform = 'windows'
hostname = 'Host-0001'; platform = 'windows'
collection_source = 'Local Windows read-only collection'
operating_system = 'Not collected'; os_version = 'Not collected'; os_build = 'Not collected'
product_type = 0; last_boot = 'Not collected'; ip_addresses = @(); network_adapters = @()
domain = 'Not collected'; domain_joined = $null
}
Get-SamsaraPrivacyConfiguration
if ($script:SamsaraPrivacy.approved_hostnames -ccontains [Environment]::MachineName) { $Result.data.hostname = [Environment]::MachineName }
Invoke-SamsaraRead $Result 'OS identity' {
$os = Get-CimInstance -ClassName Win32_OperatingSystem -Property Caption,Version,BuildNumber,LastBootUpTime,ProductType -ErrorAction Stop
$os = Get-CimInstance -ClassName Win32_OperatingSystem -Property Version,BuildNumber,LastBootUpTime,ProductType -ErrorAction Stop
$years = @{ '14393' = '2016'; '17763' = '2019'; '20348' = '2022'; '26100' = '2025' }
if ([int]$os.ProductType -notin @(2,3) -or -not $years.ContainsKey([string]$os.BuildNumber)) {
throw 'Unsupported Windows release or workstation edition.'
}
$Result.data.operating_system = [string]$os.Caption
$Result.data.operating_system = 'Windows Server ' + $years[[string]$os.BuildNumber]
$Result.data.os_version = $years[[string]$os.BuildNumber]
$Result.data.os_build = [string]$os.BuildNumber
$Result.data.kernel_version = [string]$os.Version
@@ -21,13 +23,13 @@ function Get-SamsaraSystem {
$Result.data.last_boot = $os.LastBootUpTime.ToUniversalTime().ToString('o')
}
Invoke-SamsaraRead $Result 'Computer domain membership' {
$computer = Get-CimInstance -ClassName Win32_ComputerSystem -Property Domain,PartOfDomain -ErrorAction Stop
$Result.data.domain = [string]$computer.Domain
$computer = Get-CimInstance -ClassName Win32_ComputerSystem -Property PartOfDomain -ErrorAction Stop
$Result.data.domain = 'Domain identity omitted'
$Result.data.domain_joined = [bool]$computer.PartOfDomain
}
Invoke-SamsaraRead $Result 'Network adapter configuration' {
$adapters = @(Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter 'IPEnabled = True' -Property Description,IPAddress -ErrorAction Stop)
$Result.data.network_adapters = @($adapters | ForEach-Object { [string]$_.Description })
$adapters = @(Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter 'IPEnabled = True' -Property IPAddress -ErrorAction Stop)
$Result.data.network_adapters = @($adapters | ForEach-Object -Begin { $i = 0 } -Process { $i++; 'Adapter-' + $i })
$Result.data.ip_addresses = @($adapters | ForEach-Object { foreach ($address in $_.IPAddress) { [string]$address } })
}
$Result.limitations += 'Identity and addresses are local observations. No DNS probes or Active Directory enrichment were performed.'

View File

@@ -1,25 +1,18 @@
function Get-SamsaraTasks {
param($Result)
Invoke-SamsaraRead $Result 'Scheduled tasks' {
$tasks = @(Get-ScheduledTask -ErrorAction Stop)
foreach ($task in $tasks) {
Invoke-SamsaraRead $Result 'Scheduled task metadata projection' {
if ($null -eq $task -or [string]::IsNullOrEmpty([string]$task.TaskName)) { throw 'Missing task identity' }
$triggers = @($task.Triggers | ForEach-Object {
$type = if ($_.CimClass) { [string]$_.CimClass.CimClassName } else { 'Unknown trigger' }
# Select only known scheduler metadata; never serialize entire CIM objects.
$repetition = if ($_.Repetition) { [string]$_.Repetition.Interval } else { '' }
'{0}; start={1}; enabled={2}; daysInterval={3}; weeksInterval={4}; daysOfWeek={5}; repeat={6}' -f $type, $_.StartBoundary, $_.Enabled, $_.DaysInterval, $_.WeeksInterval, $_.DaysOfWeek, $repetition
})
$executables = @($task.Actions | ForEach-Object { Get-SamsaraSafeExecutable ([string]$_.Execute) })
$row = [pscustomobject]@{ name = [string]$task.TaskName; path = [string]$task.TaskPath
enabled = [bool]$task.Settings.Enabled; state = [string]$task.State
account = if ($task.Principal.UserId) { [string]$task.Principal.UserId } else { [string]$task.Principal.GroupId }; executables = ($executables -join '; ')
triggers = if ($triggers.Count) { $triggers -join ' | ' } else { 'No trigger metadata visible; manual or external invocation remains possible' } }
$null = ConvertTo-Json -InputObject $row -Depth 4 -ErrorAction Stop
$Result.data += $row
}
}
Invoke-SamsaraRead $Result 'Minimal scheduler metadata' {
$index = 0
$Result.data = @(Get-CimInstance -Namespace 'Root/Microsoft/Windows/TaskScheduler' -ClassName MSFT_ScheduledTask -Property TaskName,TaskPath,State -ErrorAction Stop | ForEach-Object {
$index++
$routine = ([string]$_.TaskPath).StartsWith('\Microsoft\Windows\', [StringComparison]::OrdinalIgnoreCase) -and
-not ([string]$_.TaskPath).StartsWith('\Microsoft\Windows\PowerShell\ScheduledJobs', [StringComparison]::OrdinalIgnoreCase)
$state = switch ([int]$_.State) { 1 {'Disabled'} 2 {'Queued'} 3 {'Ready'} 4 {'Running'} default {'Unknown'} }
[pscustomobject]@{ name = $(if ($routine) { 'Maintenance-' + $index } else { 'Task-' + $index })
path = $(if ($routine) { '\Microsoft\Windows\Maintenance\' } else { 'Task folder omitted' })
enabled = ([int]$_.State -in @(2,3,4)); state = $state
account = 'Omitted by privacy policy'; triggers = 'Schedule details omitted; authorized scheduler review required.' }
})
}
$Result.limitations += 'Visible Task Scheduler registrations only. Arguments, inline scripts, task XML, event queries and execution history are omitted. Non-executable actions may lack an executable name. Trigger fields are descriptive, not a prediction of execution.'
$Result.status = 'partial'
$Result.limitations += 'Only projected task identity/category and state requested. Actions, arguments, principals, triggers and task XML are not requested. Schedule timing and actual execution remain unverified. Provider internals remain outside application guarantees.'
}

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'
}

View File

@@ -1,29 +1,38 @@
# Local Windows Server inventory
# Local Windows Server collection
Targets: Windows Server 2016/2019/2022/2025, Windows PowerShell 5.1, 64-bit execution. **Real Windows host validation is still pending in this Linux development workspace.** No remoting or credentials are used.
Read-only collection commands for supported Server release builds 2016/2019/2022/2025.
The Python workflow launches fixed sections using system Windows PowerShell 5.1,
NoProfile, fixed argument lists, bounded memory output and time/cancellation limits.
No remoting/configuration, execution-policy bypass, elevation or credential storage.
Actual Windows runtime/least-privilege acceptance is still pending.
`Get-ServerInventory.ps1` is the fixed JSON driver. With no arguments it emits a full v1 inventory envelope. `-Section system|roles|services|network|tasks|software|storage|shares|certificates` emits an internal per-section JSON result. The Python collector invokes these independently to enforce time/output limits while preserving other sections after failures.
The bundled `samsara/assets/privacy_policy.json` and `privacy_catalogue.json` are
required. Protect the deployment against unauthorized changes. Name/path projection
occurs before PowerShell JSON output and again at Python UI/report boundaries.
| Module | Local read-only source |
| --- | --- |
| Get-ServerSystem.ps1 | CIM OS/product type/build, boot, domain membership, adapter configuration |
| Get-ServerRoles.ps1 | Installed Get-WindowsFeature entries |
| Get-ServerServices.ps1 | Win32_Service metadata and service identities; no PathName/arguments |
| Get-ServerNetwork.ps1 | Get-NetTCPConnection and Get-Process names/IDs |
| Get-ServerTasks.ps1 | Get-ScheduledTask visible definitions, accounts, safe executable basenames, selected triggers |
| Get-ServerSoftware.ps1 | HKLM uninstall registrations using explicit Registry32/Registry64 read-only handles |
| Get-ServerStorage.ps1 | Get-Volume, Get-SmbShare and aggregate Get-SmbOpenFile share-root correlation; volumes/shares share the existing storage inventory category |
| Get-ServerCertificates.ps1 | Public metadata in accessible LocalMachine certificate stores |
| Inventory.Common.ps1 | Section timing, safe errors and permission limitations |
| Module | Default queries | Privacy/coverage limits |
| --- | --- | --- |
| System | Local OS/build/boot, domain-joined boolean, adapter IPs | Fixed OS labels, opaque identities; no domain string/adapter description requested |
| Roles | Get-WindowsFeature installed identifiers | Canonical/opaque names; no active-use assertion |
| Services | Win32_Service Name, State, StartMode, ProcessId | No StartName, DisplayName, PathName; names canonical/opaque |
| Network | Get-Process names/PIDs, Get-NetTCPConnection | No command lines; names canonical/opaque; historical direction unverified |
| Tasks | MSFT_ScheduledTask TaskName, TaskPath, State only | Opaque identities/category; no Actions, Principals, Triggers, Settings, XML or arguments requested; partial |
| Software | Read-only HKLM uninstall registry views | DoNotExpandEnvironmentNames; canonical/opaque display name, numeric version; no installer query |
| Storage | Fixed Win32_LogicalDisk size/free/filesystem/drive metadata | No labels, mount-only volumes or child paths; partial |
| Shares | Get-SmbShare configured roots | Only exact reviewed local roots retained; no Get-SmbOpenFile or session enumeration; partial |
| Certificates | No provider call | not_collected pending a separately approved minimal design |
No Win32_Product, installer operations, private-key export, task execution, service changes, registry writes or remoting setup. No execution-policy bypass; use approved script signing/distribution if enterprise policy blocks scripts. Non-elevated sensitive checks are conservatively partial even if their visible query succeeds. Elevation is never attempted automatically. Domain Administrator privileges are not required; assess with the least-privileged authorized account and treat omitted evidence explicitly.
Provider-returned strings may enter process memory before projection; explicit
properties do not prove that native provider internals process no other metadata.
The application cannot promise PHI-free memory or zero incidental OS writes.
Permission failures have categorical errors, timestamps and limitations; no raw
exception messages are returned. Missing evidence must never imply inactivity.
The Windows Server gate uses CIM ProductType 2/3 plus the release build map; Windows 10/11 are not Server targets even where their kernel builds overlap. The gate is enforced by Python and by full-envelope PowerShell use. `-Section system` can return failed/partial identity details for diagnostics, but Python will not accept an unverified identity as an assessment.
Prefer the Python CLI and its configured private local report folder. Direct driver
output is not supervised by Python's bounds or secure writer: do not use arbitrary
shell redirects on production hosts. See [privacy setup and native validation](../docs/PRIVACY_HARDENING.md).
Direct full-script use has no Python timeout/cancellation supervisor; use the Python CLI or GUI for bounded execution. Both paths honor local script execution policy. Every script emits only selected public/administrative metadata, never raw CIM objects, exception text, task XML, task arguments or service command lines.
See README.md for exact GUI, headless and real-host validation instructions. `tests/powershell/Test-Collectors.ps1` parses scripts and mocks their data sources; this is synthetic testing, not real host validation.
Implementation references: [Get-WindowsFeature](https://learn.microsoft.com/en-us/powershell/module/servermanager/get-windowsfeature), [Win32_Service](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-service), [Get-NetTCPConnection](https://learn.microsoft.com/en-us/powershell/module/nettcpip/get-nettcpconnection), [Get-ScheduledTask](https://learn.microsoft.com/en-us/powershell/module/scheduledtasks/get-scheduledtask), [registry software inventory](https://learn.microsoft.com/en-us/powershell/scripting/samples/working-with-software-installations), [certificate provider](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/about/about_certificate_provider).
Scoring v2 excludes default administrative shares, but keeps them in inventory. Business-share client confirmation requires share-root-correlated open-file counts, not just TCP 445 activity. The read-only activity query exports no file names, session users or client names; same-root administrative aliases remain ambiguous. See docs/SCORING.md. This new query still requires native Windows validation.
`tests/powershell/Test-Collectors.ps1` parses ASTs and invokes synthetic mocked
providers only. It makes forbidden certificate, open-file, full-task and label
enumeration fail. Run on an authorized lab host with its existing script policy;
no real collector runs in that test. A passing mock test is not real-host acceptance.