- 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>
119 lines
6.0 KiB
Python
119 lines
6.0 KiB
Python
"""Exclusive private report files in a deployment-configured local directory.
|
|
|
|
Network-mounted storage detection is best effort; enterprise egress controls remain
|
|
required. Windows roots must be provisioned with a protected, restricted DACL.
|
|
"""
|
|
import ctypes
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import stat
|
|
|
|
from samsara.core.privacy import load_privacy_policy, PROJECT_ROOT
|
|
|
|
|
|
def require_local_path(path):
|
|
value = os.fspath(path)
|
|
if not value or value.startswith(('\\\\','//')) or '://' in value:
|
|
raise ValueError('Network and URI paths are not permitted.')
|
|
candidate = Path(os.path.abspath(value))
|
|
if os.name == 'nt':
|
|
kernel = ctypes.WinDLL('kernel32',use_last_error=True,winmode=0x800)
|
|
kernel.GetDriveTypeW.argtypes=[ctypes.c_wchar_p]
|
|
if kernel.GetDriveTypeW(candidate.anchor) != 3:
|
|
raise ValueError('A fixed local drive is required.')
|
|
else:
|
|
# Inspect mount metadata only; do not probe remote mounts.
|
|
for line in Path('/proc/self/mountinfo').read_text().splitlines():
|
|
before,after=line.split(' - ',1)
|
|
mount=re.sub(r'\\([0-7]{3})',lambda m: chr(int(m[1],8)),before.split()[4])
|
|
fs=after.split()[0]
|
|
if (fs.startswith('fuse.') or fs in {'nfs','nfs4','cifs','smb3','9p','ceph','afs','glusterfs','davfs','fuse.sshfs','fuse.rclone'}) and (candidate==Path(mount) or Path(mount) in candidate.parents):
|
|
raise ValueError('Network-backed paths are not permitted.')
|
|
for parent in reversed([candidate,*candidate.parents]):
|
|
try:
|
|
info = parent.lstat()
|
|
except FileNotFoundError:
|
|
continue
|
|
if stat.S_ISLNK(info.st_mode) or getattr(info,'st_file_attributes',0)&0x400:
|
|
raise ValueError('Symbolic links and reparse points are not permitted.')
|
|
return candidate
|
|
|
|
|
|
def _verify_windows_acl(path, *, protected=True):
|
|
"""Fail closed: protected DACL; allow ACEs only owner, SYSTEM, Administrators.
|
|
|
|
No ACL changes, elevation or shell commands. Native Windows validation pending.
|
|
"""
|
|
from ctypes import wintypes as w
|
|
adv=ctypes.WinDLL('advapi32',use_last_error=True,winmode=0x800); kernel=ctypes.WinDLL('kernel32',use_last_error=True,winmode=0x800)
|
|
ptr=ctypes.c_void_p
|
|
adv.GetNamedSecurityInfoW.argtypes=[w.LPWSTR,w.DWORD,w.DWORD,ctypes.POINTER(ptr),ptr,ctypes.POINTER(ptr),ptr,ctypes.POINTER(ptr)]
|
|
adv.GetNamedSecurityInfoW.restype=w.DWORD
|
|
adv.GetSecurityDescriptorControl.argtypes=[ptr,ctypes.POINTER(w.WORD),ctypes.POINTER(w.DWORD)]
|
|
adv.ConvertSidToStringSidW.argtypes=[ptr,ctypes.POINTER(w.LPWSTR)]
|
|
adv.GetAce.argtypes=[ptr,w.DWORD,ctypes.POINTER(ptr)]
|
|
kernel.LocalFree.argtypes=[ptr];kernel.LocalFree.restype=ptr
|
|
owner=ptr();acl=ptr();descriptor=ptr()
|
|
result=adv.GetNamedSecurityInfoW(str(path),1,5,ctypes.byref(owner),None,ctypes.byref(acl),None,ctypes.byref(descriptor))
|
|
if result: raise ValueError('Cannot verify report-directory security.')
|
|
def sid_text(sid):
|
|
value=w.LPWSTR()
|
|
if not adv.ConvertSidToStringSidW(sid,ctypes.byref(value)): raise ValueError('Invalid report ACL.')
|
|
try:return value.value
|
|
finally:kernel.LocalFree(ctypes.cast(value,ptr))
|
|
try:
|
|
control=w.WORD();revision=w.DWORD()
|
|
if not adv.GetSecurityDescriptorControl(descriptor,ctypes.byref(control),ctypes.byref(revision)) or (protected and not(control.value & 0x1000)) or not acl.value:
|
|
raise ValueError('Report directory requires a protected DACL.')
|
|
permitted={sid_text(owner),'S-1-5-18','S-1-5-32-544'}
|
|
# ACL header: revision byte, reserved byte, size WORD, ACE count WORD.
|
|
count=ctypes.c_ushort.from_address(acl.value+4).value
|
|
if count==0: raise ValueError('Report directory has no usable access grants.')
|
|
for index in range(count):
|
|
ace=ptr()
|
|
if not adv.GetAce(acl,index,ctypes.byref(ace)): raise ValueError('Cannot verify report ACL.')
|
|
kind=ctypes.c_ubyte.from_address(ace.value).value
|
|
if kind!=0 or sid_text(ptr(ace.value+8)) not in permitted:
|
|
raise ValueError('Report directory grants access outside owner, SYSTEM and Administrators.')
|
|
finally:kernel.LocalFree(descriptor)
|
|
|
|
|
|
def report_directory():
|
|
configured=Path(load_privacy_policy()['report_directory'])
|
|
root=require_local_path(configured if configured.is_absolute() else PROJECT_ROOT/configured)
|
|
if os.name=='nt':
|
|
if not root.is_dir():
|
|
raise ValueError('An administrator must provision the approved private report directory first.')
|
|
_verify_windows_acl(root)
|
|
else:
|
|
root.mkdir(mode=0o700,exist_ok=True)
|
|
info=root.stat()
|
|
if info.st_uid!=os.getuid() or stat.S_IMODE(info.st_mode)&0o077:
|
|
raise ValueError('Report directory must be owned by the current user with mode 0700.')
|
|
return root
|
|
|
|
|
|
def write_private(path, payload):
|
|
destination=require_local_path(path)
|
|
root=report_directory()
|
|
if destination.parent!=root or destination.suffix.lower() not in ('.json','.html'):
|
|
raise ValueError('Save reports directly inside the configured private report directory.')
|
|
flags=os.O_WRONLY|os.O_CREAT|os.O_EXCL|getattr(os,'O_NOFOLLOW',0)
|
|
if os.name=='nt':
|
|
# Child inherits the previously verified restricted directory ACL.
|
|
fd=os.open(destination,flags|getattr(os,'O_BINARY',0),0o600)
|
|
else:
|
|
directory_fd=os.open(root,os.O_RDONLY|os.O_DIRECTORY|os.O_NOFOLLOW)
|
|
try:
|
|
info=os.fstat(directory_fd)
|
|
if info.st_uid!=os.getuid() or stat.S_IMODE(info.st_mode)&0o077:
|
|
raise ValueError('Report directory permissions changed.')
|
|
fd=os.open(destination.name,flags,0o600,dir_fd=directory_fd)
|
|
finally:os.close(directory_fd)
|
|
with os.fdopen(fd,'wb') as output:
|
|
if os.name=='nt':
|
|
_verify_windows_acl(destination, protected=False)
|
|
output.write(payload if isinstance(payload,bytes) else payload.encode('utf-8'))
|
|
return destination
|