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:
202
docs/PRIVACY_HARDENING.md
Normal file
202
docs/PRIVACY_HARDENING.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# Privacy hardening and production acceptance
|
||||
|
||||
This document describes implemented controls, not a HIPAA compliance determination.
|
||||
The documents in `audit/` remain the historical audit of the earlier source snapshot.
|
||||
Do not replace their findings with an unsupported claim that all issues are resolved.
|
||||
|
||||
## What changed
|
||||
|
||||
- A new `core/privacy.py` builds an explicit field projection before results reach Qt,
|
||||
analysis/history, imports, HTML, JSON, and local CLI output. Export recomputes
|
||||
findings from minimized inventory; old findings and a privacy marker cannot bypass
|
||||
the filter. Unexpected schema fields are rejected at import; legacy command fields
|
||||
are accepted only to discard them. Timestamps are checked without optional plugins.
|
||||
- Host/domain/adapter identities, accounts, display descriptions, custom service,
|
||||
software and task names, certificate identities and unapproved paths are omitted
|
||||
or replaced with assessment-local numbered labels. Known application families,
|
||||
OS family/version/build, role identifiers, states, PIDs, ports, numeric IPs,
|
||||
storage capacities, row counts, timestamps and categorical errors remain useful.
|
||||
Unknown software is not automatically interpreted as inactive or harmless.
|
||||
- Windows queries no longer request service account/display-name fields. Task CIM
|
||||
queries request only TaskName, TaskPath and State; actions, arguments, principals,
|
||||
triggers and task XML are not requested. Disk queries omit volume labels.
|
||||
Registry reads disable environment expansion and omit publisher/install-date text.
|
||||
The PowerShell modules project names and paths before emitting JSON.
|
||||
- Certificate enumeration and SMB open-file/session enumeration are disabled.
|
||||
Certificates are explicitly `not_collected`; tasks, storage and shares have
|
||||
partial scope. Share consumers and schedule timing must be verified separately.
|
||||
Existing imported aggregate share counts can still support a finding, but their
|
||||
provenance is unverified. They do not cause the new collector to enumerate files.
|
||||
- Linux default collection no longer reads cron command files or detailed systemd
|
||||
timer properties. Timer state remains available; commands and timings need a
|
||||
separate approved scheduler review. Existing schedule parsers are retained for
|
||||
synthetic tests, but are not called by the default collection workflow.
|
||||
- Supervised collector stdout uses bounded memory pipes instead of inventory
|
||||
temporary files. Stderr is discarded. Windows child environment has an explicit
|
||||
small allowlist instead of copying all environment variables. Timeouts,
|
||||
cancellation and overflow terminate/reap the owned process.
|
||||
- Reports contain minimized data only. HTML does not create raw sidecars. Output
|
||||
is exclusive (no overwrite), restricted to the configured local report root, and
|
||||
checks common network mounts, symlinks and Windows reparse points. POSIX root
|
||||
mode is 0700 and files 0600. Windows roots require a protected DACL allowing only
|
||||
the owner, SYSTEM and Administrators; child ACLs are checked before payload write.
|
||||
No automatic privilege elevation or existing ACL changes are performed.
|
||||
- History retains at most ten assessments by default, and has a clear action.
|
||||
Clearing references is not secure memory erasure. Existing exported files are not
|
||||
automatically deleted. Scoring-policy titles are canonicalized to prevent a
|
||||
second free-text report channel.
|
||||
- Requirements now use the Widgets-capable `PySide6_Essentials`, rather than the
|
||||
umbrella package that pulls in WebEngine, PDF and Addons. Direct versions are
|
||||
pinned; observed development transitive constraints are recorded separately.
|
||||
|
||||
- Remote peer IP addresses are masked: only loopback, this host's own and wildcard
|
||||
addresses stay numeric; every other peer is reduced to `remote_scope` `private` or
|
||||
`public`, because client IPs on patient-facing servers can be identifiers. Local
|
||||
listener addresses and the host's own IPs are still retained.
|
||||
|
||||
## Configure approved metadata and report storage
|
||||
|
||||
Deployment administrators review and protect these bundled files against untrusted
|
||||
changes: `samsara/assets/privacy_policy.json` and `privacy_catalogue.json`. The
|
||||
catalogue is a finite vocabulary of infrastructure names, not executable code.
|
||||
Keep the checkout, Python environment and scripts protected by deployment controls.
|
||||
An attacker who can replace application code/policy is outside this boundary.
|
||||
|
||||
The policy defaults to no approved names/paths and an application-local `reports`
|
||||
folder. This is a laboratory default, not a CHS-approved production location. Set
|
||||
`report_directory` to an approved **absolute local** directory for deployment.
|
||||
Only files directly inside it can be saved; choose a new `.html` or `.json` filename.
|
||||
Do not put the directory under OneDrive, another sync client, a network mount or an
|
||||
unreviewed backup policy. Encrypted local storage and access/retention policy remain
|
||||
organizational requirements.
|
||||
|
||||
For example, a reviewed Windows configuration may include:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
"report_directory": "C:\\SamsaraReports",
|
||||
"approved_hostnames": [],
|
||||
"approved_share_roots": ["D:\\DepartmentShares"],
|
||||
"approved_service_names": [],
|
||||
"approved_software_names": [],
|
||||
"max_session_assessments": 10
|
||||
}
|
||||
```
|
||||
|
||||
This preserves **only the exact configured root** `D:\DepartmentShares`. It does
|
||||
not authorize any child path, filename or file-content scan. Empty allowlists are
|
||||
preferred. Add an exact hostname/custom service/software name only after privacy
|
||||
review establishes it is approved infrastructure metadata. There is no automatic
|
||||
patient-name detector: neither a path nor a free-text name can be proven safe from
|
||||
its format. Known product names are canonicalized without retaining custom suffixes.
|
||||
|
||||
On Linux, the configured root is created with mode 0700 if its parent already
|
||||
exists. An existing root with group/other permissions is rejected. Fix permissions
|
||||
on the report directory only after checking that it is the intended directory.
|
||||
|
||||
On Windows, provision a **new** local folder before exporting. The following is a
|
||||
manual setup example to be reviewed under the organization's normal approval
|
||||
process, not an action the collector performs on assessed infrastructure:
|
||||
|
||||
```powershell
|
||||
$reportRoot = 'C:\SamsaraReports'
|
||||
if (Test-Path -LiteralPath $reportRoot) { throw 'Choose a new report directory.' }
|
||||
$null = New-Item -ItemType Directory -Path $reportRoot
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
try {
|
||||
$acl = New-Object Security.AccessControl.DirectorySecurity
|
||||
$acl.SetOwner($identity.User)
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
foreach ($sid in @($identity.User, [Security.Principal.SecurityIdentifier]::new('S-1-5-18'), [Security.Principal.SecurityIdentifier]::new('S-1-5-32-544'))) {
|
||||
$rule = [Security.AccessControl.FileSystemAccessRule]::new($sid, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')
|
||||
$acl.AddAccessRule($rule)
|
||||
}
|
||||
Set-Acl -LiteralPath $reportRoot -AclObject $acl
|
||||
} finally { $identity.Dispose() }
|
||||
```
|
||||
|
||||
These commands change the chosen **local output folder's** ACL and must not be run
|
||||
against data shares. Configure the matching path in the policy. The ctypes ACL
|
||||
reader and inheritance behavior are implemented but **not tested on Windows here**.
|
||||
Before production, verify both allowed and denied cases, inherited file ACLs,
|
||||
standard-user access, reparse/mapped-drive rejection and directory replacement
|
||||
races on each intended Windows execution environment. Export fails closed when it
|
||||
cannot establish the configured checks; absence of an exception is not a complete
|
||||
filesystem-security attestation.
|
||||
|
||||
## Limits and unresolved production blockers
|
||||
|
||||
1. **No PHI-free guarantee.** Names/paths returned by CIM, Get-SmbShare, process
|
||||
listings and registry values can enter collector memory before reduction.
|
||||
Legacy JSON is parsed in memory before filtering. Explicit properties reduce
|
||||
requested fields but provider internals are unverified. Approved names/roots
|
||||
and retained numeric IPs can themselves be sensitive in organizational context.
|
||||
2. **No absolute read-only OS guarantee.** Application collection commands request
|
||||
reads, not infrastructure changes. Native providers/OS can write audit logs,
|
||||
caches or other incidental state. Python bytecode, OS paging/crash dumps, Qt and
|
||||
endpoint tools are outside the no-inventory-spool control. A bounded buffer is
|
||||
not secure memory. Disable/restrict crash dumps and use encrypted storage under
|
||||
organizational policy, rather than claiming that data never touch disk.
|
||||
3. **No complete egress guarantee.** There is no first-party upload, telemetry or
|
||||
remote collector. Filesystem checks reject common remote paths but cannot prove
|
||||
a disk has no cloud-sync/backup/virtualized backend. Native Windows providers,
|
||||
revocation behavior, domain services and OS integrations need monitored host
|
||||
validation. Use enterprise egress policy and approved local output storage.
|
||||
4. **Runtime validation outstanding.** No Windows execution host or PowerShell
|
||||
runtime was available. Revised CIM projections, least-privilege visibility,
|
||||
native synthetic tests and output ACL enforcement must pass on approved lab
|
||||
hosts. Do not substitute synthetic Python results for that evidence.
|
||||
5. **Supply chain approval outstanding.** No vulnerability database queries,
|
||||
package installations/updates or external transmissions were performed. Direct
|
||||
pins/observed constraints are not a hash-verified platform lockfile, complete
|
||||
native SBOM, signed release or CVE clearance. Existing development environments
|
||||
may still contain the former Qt umbrella/Addons packages; changing requirements
|
||||
does not uninstall them. Build clean environments through approved provisioning.
|
||||
6. **Organizational review outstanding.** CHS security/privacy approval, least-
|
||||
privilege grants, retention/deletion policy, approved metadata, endpoint and
|
||||
backup controls, incident response and HIPAA determination remain external
|
||||
acceptance gates. A low retirement score always needs historical validation,
|
||||
application ownership and administrator approval.
|
||||
|
||||
Do not import known patient-bearing legacy inventories into this tool merely to
|
||||
redact them. Have the data owner handle those artifacts using approved procedures.
|
||||
Old reports/sidecars/bundles are not scrubbed or removed by this change. The
|
||||
historical `Samsara.bundle`, if present, may predate these controls; distribute a
|
||||
reviewed build of the current source rather than an older archive.
|
||||
|
||||
## Verification and later authorized supply-chain checks
|
||||
|
||||
Run synthetic/offscreen tests from the repository root:
|
||||
|
||||
```bash
|
||||
QT_QPA_PLATFORM=offscreen .venv/bin/python -m unittest discover -s tests -v
|
||||
```
|
||||
|
||||
On an authorized Windows **lab** Server with an approved report directory:
|
||||
|
||||
```bat
|
||||
.venv\Scripts\python.exe -m unittest discover -s tests -v
|
||||
.venv\Scripts\python.exe tools\validate_windows_host.py --output-dir C:\SamsaraReports
|
||||
```
|
||||
|
||||
The second command performs a real assessment of the execution host; it is not a
|
||||
synthetic test. No real Windows host has been assessed during this change.
|
||||
|
||||
After obtaining approval for tooling/downloads and advisory-database access, in a
|
||||
separate release environment use, for example:
|
||||
|
||||
```bash
|
||||
python -m piptools compile --generate-hashes --output-file requirements.lock requirements.txt
|
||||
python -m pip install --require-hashes -r requirements.lock
|
||||
python -m pip_audit -r requirements.lock --format json --output vulnerability-results.json
|
||||
cyclonedx-py environment --output-format JSON --output-file sbom.cdx.json
|
||||
```
|
||||
|
||||
Repeat lock generation/clean installs for each target Python/OS/architecture.
|
||||
Inventory Qt native libraries/plugins and PowerShell/OS modules as well; a Python
|
||||
package SBOM alone is incomplete. SBOM/advisory checks can expose package metadata
|
||||
externally: do not run them against production inventory or without authorization.
|
||||
Triage findings against actual packages, keep advisory timestamps, sign the
|
||||
reviewed distribution, and retain acceptance evidence. These commands have not
|
||||
been run as part of the hardening work.
|
||||
Reference in New Issue
Block a user