Files
archy/scripts/tests/test_security_dependency_inventory.py
archipelago 3a27b6095b security: resolve Python graph and trace dependency license evidence
Inventory all four pinned Python runtime/build roots and 20 resolved artifacts with hashes and environment evidence. Reject stale reports; retain custom license names and exact per-file obligations. Trace all eleven prior missing declarations to exact artifacts, registry metadata or parent POMs. Record the newly exposed high ecdsa advisory without suppression and refresh integrated input/BOM hashes while retaining prior receipts. Four pure inventory regressions pass; no host install, release build or live node operation.
2026-11-09 17:43:26 -05:00

91 lines
4.7 KiB
Python

"""Pure inventory fixtures: no application imports, network, installs or builds."""
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
SCRIPT = Path(__file__).resolve().parents[1] / 'security-dependency-inventory.py'
class PythonInventoryTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name)
for path in ['core/Cargo.lock', 'Android/rust/archy-fips-core/Cargo.lock']:
self.write(path, 'version = 3\npackage = []\n')
for path in ['neode-ui/package-lock.json', 'apps/router/package-lock.json',
'examples/archipelago-app-starter/package-lock.json']:
self.write(path, '{"packages": {}}')
for path in ['aiui/pnpm-lock.yaml', 'docker/gashboard/pnpm-lock.yaml']:
self.write(path, 'packages: {}\n')
self.write('reticulum-daemon/requirements.txt', 'rns==1.3.5\nlxmf==1.0.1\n')
self.write('reticulum-daemon/requirements-build.txt', 'pyinstaller==6.21.0\n')
for path in ['reticulum-daemon/build.sh', 'reticulum-daemon/build-esptool.sh']:
self.write(path, ".venv/bin/pip install 'esptool==4.8.1'\n")
self.report = {'version': '1', 'environment': {'python_version': '3.13', 'sys_platform': 'linux'},
'install': []}
for name, version in [('rns', '1.3.5'), ('lxmf', '1.0.1'), ('pyinstaller', '6.21.0'),
('esptool', '4.8.1'), ('PyYAML', '6.0.3')]:
self.report['install'].append({
'metadata': {'name': name, 'version': version, 'license': 'Reticulum License' if name == 'rns' else 'MIT'},
'requested': name != 'PyYAML',
'download_info': {'url': 'https://example.invalid/' + name + '.whl',
'archive_info': {'hashes': {'sha256': 'a' * 64}}},
})
def write(self, path, content):
target = self.root / path
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content)
def run_inventory(self, with_report=True):
command = [sys.executable, str(SCRIPT), '--repo', str(self.root), '--output', str(self.root / 'out')]
if with_report:
self.write('pip-report.json', json.dumps(self.report))
command += ['--python-report', str(self.root / 'pip-report.json')]
return subprocess.run(command, capture_output=True, text=True)
def test_exact_transitives_hashes_and_custom_license_names(self):
result = self.run_inventory()
self.assertEqual(result.returncode, 0, result.stderr)
bom = json.loads((self.root / 'out/source-bom.cdx.json').read_text())
components = {item['purl']: item for item in bom['components']}
self.assertEqual(len(components), 5)
self.assertIn('pkg:pypi/pyyaml@6.0.3', components)
rns = components['pkg:pypi/rns@1.3.5']
self.assertEqual(rns['licenses'], [{'license': {'name': 'Reticulum License'}}])
self.assertEqual(rns['hashes'], [{'alg': 'SHA-256', 'content': 'a' * 64}])
summary = json.loads((self.root / 'out/source-inventory.json').read_text())
self.assertEqual(summary['python_resolution']['components'], 5)
self.assertIn('esptool==4.8.1', summary['python_resolution']['roots'])
self.assertTrue(any('other-platform graph' in entry['reason'] for entry in summary['unresolved']))
def test_stale_report_rejected_after_build_pin_changes(self):
self.write('reticulum-daemon/build-esptool.sh', "pip install 'esptool==5.0.0'\n")
result = self.run_inventory()
self.assertNotEqual(result.returncode, 0)
self.assertIn('does not match reticulum-daemon/build-esptool.sh', result.stderr)
self.assertFalse((self.root / 'out/source-bom.cdx.json').exists())
def test_missing_artifact_hash_is_not_exact_evidence(self):
self.report['install'][0]['download_info']['archive_info']['hashes'] = {}
result = self.run_inventory()
self.assertNotEqual(result.returncode, 0)
self.assertIn('lacks exact SHA-256 artifact evidence', result.stderr)
def test_without_report_build_only_root_remains_unresolved(self):
result = self.run_inventory(with_report=False)
self.assertEqual(result.returncode, 0, result.stderr)
summary = json.loads((self.root / 'out/source-inventory.json').read_text())
esptool = [x for x in summary['unresolved'] if x.get('constraint') == 'esptool==4.8.1']
self.assertEqual({x['input'] for x in esptool},
{'reticulum-daemon/build.sh', 'reticulum-daemon/build-esptool.sh'})
if __name__ == '__main__':
unittest.main()