Files

80 lines
3.1 KiB
Python

"""Read-only security fixtures; execution deferred at the user's request."""
import json
import stat
from types import SimpleNamespace
from proxydoctor.security import collect, inspect_path, update_evidence
def metadata(mode,uid=0,modified=3600):
return SimpleNamespace(st_mode=mode,st_uid=uid,st_gid=42,st_mtime=modified)
def test_shadow_group_read_is_not_a_finding():
row = inspect_path('/etc/shadow',True,lstat=lambda _:metadata(stat.S_IFREG|0o640))
assert row['status'] == 'observed'
def test_world_readable_shadow_and_writable_configuration_require_review():
row = inspect_path('/etc/shadow',True,lstat=lambda _:metadata(stat.S_IFREG|0o644))
assert row['reasons'] == ['SENSITIVE_WORLD_READABLE']
row = inspect_path('/etc/sudoers',lstat=lambda _:metadata(stat.S_IFREG|0o666,uid=1000))
assert 'WORLD_WRITABLE' in row['reasons']
assert 'GROUP_WRITABLE_REVIEW' in row['reasons']
assert 'OWNER_NOT_ROOT_REVIEW' in row['reasons']
def test_symlinks_are_not_followed_or_called_safe():
row = inspect_path('/etc/shadow',True,lstat=lambda _:metadata(stat.S_IFLNK|0o777))
assert row['status'] == 'unknown'
assert row['error_code'] == 'SYMLINK_REQUIRES_REVIEW'
def test_permission_error_does_not_expose_exception_text():
def denied(_):
raise PermissionError('secret-path-token')
row = inspect_path('/etc/shadow',True,lstat=denied)
assert row['error_code'] == 'METADATA_UNAVAILABLE'
assert 'secret-path-token' not in json.dumps(row)
def test_missing_cache_does_not_mean_zero_updates():
def missing(_):
raise FileNotFoundError
row = update_evidence(lstat=missing)
assert row['status'] == 'unknown'
assert row['pending_updates'] is None
def test_cache_is_timestamped_observation_not_patch_verdict():
row = update_evidence(lstat=lambda _:metadata(stat.S_IFREG|0o644),now=lambda:7200,
read=lambda _:'12 updates can be applied immediately.\n3 of these updates are standard security updates.')
assert row['pending_updates'] == 12
assert row['security_updates'] == 3
assert row['sources'][0]['age_hours'] == 1
assert row['status'] == 'cached_observation'
def test_localized_summary_stays_unknown_and_raw_text_is_not_retained():
row = update_evidence(lstat=lambda _:metadata(stat.S_IFREG|0o644),now=lambda:7200,
read=lambda _:'中文缓存 secret-key')
assert row['pending_updates'] is None
assert 'secret-key' not in json.dumps(row)
def test_collect_does_not_read_password_file_contents():
reads = []
def read(path):
reads.append(path)
assert path == '/var/lib/update-notifier/updates-available'
return None
row = collect(system='Linux',lstat=lambda _:metadata(stat.S_IFREG|0o640),read=read,now=lambda:7200)
assert len(row['files']) == 9
assert reads == ['/var/lib/update-notifier/updates-available']
def test_non_linux_does_not_inspect_host_files():
def unexpected(_):
raise AssertionError('Linux paths should not be read')
assert collect(system='Windows',lstat=unexpected,read=unexpected)['status'] == 'unsupported'