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:
@@ -1,9 +1,11 @@
|
||||
"""Actionable assessment report, with full inventory preserved as a separate JSON artifact."""
|
||||
"""Actionable reports from privacy-minimized evidence, without raw sidecars."""
|
||||
import hashlib
|
||||
import html
|
||||
import json
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
from samsara.core.privacy import minimize_inventory
|
||||
from samsara.core.storage import write_private
|
||||
from samsara.analyzers.assessment import assess
|
||||
from samsara.analyzers.summary import summarize
|
||||
|
||||
MANUAL_STEPS = ['Confirm application and business owners and obtain administrator retirement approval.',
|
||||
@@ -12,21 +14,30 @@ MANUAL_STEPS = ['Confirm application and business owners and obtain administrato
|
||||
'Verify custom tasks and share consumers; resolve incomplete checks and confirm rollback/retention requirements.']
|
||||
|
||||
|
||||
def private_assessment(data, assessment):
|
||||
# Never trust a marker or cached finding to authorize raw data disclosure.
|
||||
source=data.get('server',{}).get('collection_source')
|
||||
clean=minimize_inventory(data, trusted_source=source)
|
||||
policy=assessment.get('scoring',{}).get('policy') if isinstance(assessment,dict) else None
|
||||
if policy is not None:
|
||||
import copy
|
||||
policy=copy.deepcopy(policy)
|
||||
policy['name']='Deployment scoring policy'
|
||||
return clean, assess(clean, policy=policy)
|
||||
|
||||
|
||||
def export_json(path, data, assessment):
|
||||
Path(path).write_text(json.dumps({'inventory_result':data,'assessment':assessment},indent=2),encoding='utf-8')
|
||||
data, assessment=private_assessment(data, assessment)
|
||||
write_private(path,json.dumps({'inventory_result':data,'assessment':assessment},indent=2))
|
||||
|
||||
|
||||
def inventory_artifact(path, data):
|
||||
payload = (json.dumps(data,indent=2,ensure_ascii=True)+'\n').encode('utf-8')
|
||||
digest = hashlib.sha256(payload).hexdigest()
|
||||
destination = Path(path).with_name(Path(path).stem + '.inventory-' + digest[:12] + '.json')
|
||||
try:
|
||||
with destination.open('xb') as output:
|
||||
output.write(payload)
|
||||
except FileExistsError:
|
||||
if destination.read_bytes() != payload:
|
||||
raise FileExistsError('Existing inventory artifact differs; choose a new report filename.') from None
|
||||
return destination, digest
|
||||
"""Explicit minimized inventory export; never writes a raw sidecar."""
|
||||
data=minimize_inventory(data)
|
||||
payload=(json.dumps(data,indent=2,ensure_ascii=True)+'\n').encode('utf-8')
|
||||
destination=Path(path).with_suffix('.inventory.json')
|
||||
write_private(destination,payload)
|
||||
return destination, hashlib.sha256(payload).hexdigest()
|
||||
|
||||
|
||||
def table(headers, rows):
|
||||
@@ -38,7 +49,7 @@ def table(headers, rows):
|
||||
|
||||
def export_html(path, data, assessment):
|
||||
escape = lambda value: html.escape(str(value))
|
||||
artifact, digest = inventory_artifact(path,data)
|
||||
data, assessment = private_assessment(data, assessment)
|
||||
source = 'SYNTHETIC DEMO — no real collection performed.' if data['synthetic'] else 'IMPORTED INVENTORY — source assertions not independently verified.'
|
||||
for platform in ('Linux','Windows'):
|
||||
if not data['synthetic'] and data['server'].get('collection_source') == f'Local {platform} read-only collection':
|
||||
@@ -78,9 +89,7 @@ def export_html(path, data, assessment):
|
||||
checks.append((name,row['status'],row.get('started_at','Not supplied'),row.get('completed_at','Not supplied'),row['message'],errors,'; '.join(row.get('permission_limitations',[])),'; '.join(row.get('limitations',[]))))
|
||||
content += table(['Check','Status','started_at (UTC)','completed_at (UTC)','Result','Errors','permission_limitations','Scope limitations'],checks)
|
||||
content += '<h2>Recommended manual verification</h2><ul>'+''.join('<li>'+escape(step)+'</li>' for step in MANUAL_STEPS)+'</ul>'
|
||||
content += '<h2>Complete raw inventory</h2><p>Every collected inventory row is preserved separately, including routine services and administrative shares. The analyzer has not changed the collected evidence.</p>'
|
||||
content += '<p><a href="'+escape(quote(artifact.name))+'">Download / open complete inventory JSON</a></p><p>Inventory SHA-256: '+digest+'</p>'
|
||||
content += '<h2>Privacy and omitted evidence</h2><p>Identifying names, accounts, task commands, certificate identities and unapproved paths are omitted. No raw inventory sidecar is generated. JSON export contains the same minimized evidence. Numeric IP addresses remain confidential infrastructure metadata. This report does not establish PHI-free operation or HIPAA compliance.</p>'
|
||||
content += table(['Inventory section','Rows'],[(name,len(rows)) for name,rows in data['inventory'].items()])
|
||||
content += '<p>JSON assessment export also preserves all raw inventory, classifications, evidence references and policy. Keep the inventory file alongside this HTML when sharing or archiving.</p>'
|
||||
css = 'body{font:15px system-ui;max-width:1180px;margin:32px auto;padding:20px;color:#233044;line-height:1.55}h1,h2{color:#173c60}.muted{color:#667}.source,.card,details{background:#eef3f8;padding:16px;border-radius:7px}.table{overflow-x:auto}table{border-collapse:collapse;width:100%;margin:12px 0}th,td{border:1px solid #d9e0e8;text-align:left;vertical-align:top;padding:9px;white-space:pre-wrap;overflow-wrap:anywhere}th{background:#eef3f8}pre{white-space:pre-wrap;overflow-wrap:anywhere}a{color:#1263a3}'
|
||||
Path(path).write_text('<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Samsara</title><style>'+css+'</style></head><body>'+content+'</body></html>',encoding='utf-8')
|
||||
write_private(path,'<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Samsara</title><style>'+css+'</style></head><body>'+content+'</body></html>')
|
||||
|
||||
Reference in New Issue
Block a user