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

@@ -8,7 +8,7 @@ from samsara.collectors.linux import (collect_local_linux, parse_connections,
parse_units, run_command, CollectionCancelled)
from samsara.collectors.demo import collect_demo, LINUX_DEMO_HOST
from samsara.analyzers.assessment import assess
from samsara.reporting.export import export_html
from report_helpers import export_html
class LinuxTests(unittest.TestCase):
@@ -83,7 +83,44 @@ class LinuxTests(unittest.TestCase):
@unittest.skipUnless(__import__('sys').platform.startswith('linux'), 'Linux command test')
def test_command_timeout_and_cancellation(self):
with self.assertRaisesRegex(RuntimeError, 'timed out'):
with self.assertRaisesRegex(RuntimeError, 'timeout'):
run_command(['sleep', '1'], timeout=.05)
with self.assertRaises(CollectionCancelled):
run_command(['sleep', '1'], cancelled=lambda: True)
class RegressionReviewTests(unittest.TestCase):
def test_ss_states_map_to_canonical_names(self):
from samsara.collectors.linux import parse_connections
rows = parse_connections('TIME-WAIT 0 0 10.0.0.5:44321 10.0.0.9:443\nCLOSE-WAIT 0 0 10.0.0.5:1 10.0.0.9:2\nFIN-WAIT-1 0 0 10.0.0.5:3 10.0.0.9:4\n')
self.assertEqual([r['state'] for r in rows], ['TimeWait', 'CloseWait', 'FinWait1'])
def test_non_ascii_digits_and_cpp_product_label(self):
import json
from pathlib import Path
from samsara.core.privacy import minimize_inventory, numeric_version, safe_time
self.assertEqual(numeric_version('١٢.٣٤'), 'Not collected')
self.assertIsNone(safe_time('٢٠٢٦-٠١-٠١T٠٠:٠٠:٠٠Z'))
data = json.loads(Path('samsara/assets/demo_inventory.json').read_text())
data['server']['os_build'] = '١٢٣٤٥'
data['inventory']['software'] = [{'name': 'Microsoft Visual C++ 2015-2022 Redistributable (x64)', 'version': '14.38.33135'}]
clean = minimize_inventory(data)
self.assertNotEqual(clean['server']['os_build'], '١٢٣٤٥')
self.assertEqual(clean['inventory']['software'][0]['name'], 'Microsoft Visual C++')
def test_remote_peer_addresses_are_masked_but_scoring_survives(self):
import copy
from samsara.core.privacy import minimize_inventory
from samsara.models.inventory import validate_inventory
data = json.loads(Path('tests/fixtures/scoring/active_sql_server.json').read_text())
before = assess(minimize_inventory(copy.deepcopy(data)))['score']
for row in data['inventory']['network']:
if row.get('remote_address') not in ('', '0.0.0.0', '::'):
row['remote_address'] = '93.184.216.34'
clean = minimize_inventory(data)
validate_inventory(clean)
self.assertNotIn('93.184.216.34', json.dumps(clean))
self.assertEqual(minimize_inventory(clean), clean)
self.assertEqual(assess(clean)['score'], before)
self.assertNotIn('93.184.216.34', json.dumps(assess(clean)))
self.assertIn('public', {r['remote_scope'] for r in clean['inventory']['network']})