Files
truf-server/tests/test_synthetic_llm_pipeline.py
2026-09-30 20:30:56 +03:00

352 lines
14 KiB
Python

import hashlib
import io
import os
from pathlib import Path
import subprocess
import sys
import tarfile
import tempfile
import unittest
from unittest import mock
ROOT = Path(__file__).resolve().parents[1]
APP_DIR = ROOT / 'app'
sys.path.insert(0, str(APP_DIR))
from keycheck_candidates import extract_candidates
from parity_helpers import (
bundle_evidence_difference_paths, configured_trufflehog,
native_streamed_command, normalized_bundle_evidence,
)
from result_bundle import BundleReservation, ResultBundleReader
from runtime_security import ensure_private_directory
import scan_execution
import scanner
import scanner_db
TRUFFLEHOG = configured_trufflehog()
def synthetic_material(label, length, alphabet):
seed = hashlib.sha512(label.encode('ascii')).hexdigest()
output = ''
while len(output) < length:
output += ''.join(
alphabet[int(seed[index:index + 2], 16) % len(alphabet)]
for index in range(0, len(seed), 2)
)
seed = hashlib.sha512(seed.encode('ascii')).hexdigest()
return output[:length]
def synthetic_llm_tokens():
alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
return {
'openai': (
'sk-proj-'
+ synthetic_material('local-openai-canary-prefix', 24, alphabet)
+ 'T3BlbkFJ'
+ synthetic_material('local-openai-canary-suffix', 24, alphabet)
),
'openrouter': (
'sk-or-v1-'
+ synthetic_material('local-openrouter-canary', 64, '0123456789abcdef')
),
'anthropic': (
'sk-ant-api03-'
+ synthetic_material('local-anthropic-canary', 93, alphabet + '-_')
+ 'AA'
),
}
def run_synthetic_scan(root):
fixture_path = os.path.join(root, 'synthetic.env')
with open(fixture_path, 'w', encoding='utf-8', newline='\n') as handle:
for name, value in synthetic_llm_tokens().items():
handle.write(f'{name.upper()}_API_KEY={value}\n')
completed = subprocess.run(
[
str(TRUFFLEHOG), 'filesystem', root, '--json', '--no-update',
'--no-verification',
],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
timeout=60,
)
if completed.returncode != 0:
raise AssertionError(f'TruffleHog exited with {completed.returncode}')
result = {'findings': [], 'errors': []}
scanner.append_trufflehog_findings(
result,
[line.decode('utf-8', errors='replace') for line in completed.stdout.splitlines()],
)
scanner.attach_nearby_context(result)
scanner.apply_finding_filters(result, root)
return result
@unittest.skipUnless(TRUFFLEHOG, 'configured TruffleHog binary is unavailable')
class SyntheticLLMPipelineTests(unittest.TestCase):
def test_real_scanner_detects_and_routes_synthetic_llm_keys(self):
with tempfile.TemporaryDirectory() as root:
result = run_synthetic_scan(root)
scanner.strip_nearby_context_for_persistence(result)
detectors = {
str(finding.get('DetectorName') or finding.get('DetectorType') or '')
for finding in result['findings']
}
services = {
candidate.service
for finding in result['findings']
for candidate in extract_candidates(finding)
}
self.assertEqual(detectors, {'OpenAI', 'OpenRouter', 'Anthropic'})
self.assertEqual(services, {'openai', 'openrouter', 'anthropic'})
def test_synthetic_llm_candidates_survive_durable_bundle_staging(self):
with tempfile.TemporaryDirectory() as root:
bundle_root = os.path.join(root, 'bundles')
ensure_private_directory(bundle_root, reject_reparse=True)
result = run_synthetic_scan(root)
result.update({
'scan_event_id': 'a' * 32,
'target': 'https://example.invalid/synthetic-llm-fixture',
'scan_type': 'github',
'timestamp': '2026-09-07T00:00:00+00:00',
'scan_started_at': '2026-09-07T00:00:00+00:00',
'duration_sec': 1.0,
})
scanner.assign_finding_uids(result)
reservation = BundleReservation(
reservation_id=7,
reservation_token='synthetic-reservation-token',
bundle_id='b' * 32,
scan_event_id=result['scan_event_id'],
queue_id=11,
claim_lease_token='synthetic-claim-token',
declared_bytes=4 * 1024 * 1024,
ready_path='ready/bb/' + ('b' * 32) + '.trb',
source='github',
platform='github',
target=result['target'],
normalized_target=result['target'],
)
staged = scanner.stage_result_bundle(
result,
reservation,
bundle_root,
{'no_verification': True},
{'queue_status': 'done', 'queue_error': None, 'available_after': None},
candidate_max_items=10,
candidate_max_bytes=1024 * 1024,
)
self.assertEqual(result['findings'], [])
reader = ResultBundleReader(
os.path.join(bundle_root, *staged.relative_path.split('/'))
)
metadata = reader.validate()
candidate_services = {
str(candidate.get('service') or '') for candidate in reader.iter_candidates()
}
self.assertEqual(metadata.finding_count, 3)
self.assertEqual(metadata.candidate_count, 3)
self.assertEqual(candidate_services, {'openai', 'openrouter', 'anthropic'})
def test_docker_layer_fallback_detects_the_same_synthetic_llm_keys(self):
payload = '\n'.join(
f'{name.upper()}_API_KEY={value}'
for name, value in synthetic_llm_tokens().items()
).encode('utf-8')
archive_buffer = io.BytesIO()
with tarfile.open(fileobj=archive_buffer, mode='w:gz') as archive:
member = tarfile.TarInfo('app/synthetic.env')
member.size = len(payload)
member.mode = 0o600
archive.addfile(member, io.BytesIO(payload))
layer_blob = archive_buffer.getvalue()
limits = {
'config_max_bytes': 1024,
'layer_max_bytes': 1024 * 1024,
'image_max_bytes': 2 * 1024 * 1024,
'max_layers': 1,
'archive_max_size_bytes': 1024 * 1024,
'archive_max_depth': 4,
'archive_timeout_sec': 10,
'blob_timeout_sec': 30,
'filesystem_concurrency': 1,
'blob_max_attempts': 3,
}
config_blob = b'{}'
plan = {
'version': 2,
'image': 'owner/synthetic@sha256:' + ('a' * 64),
'repository': 'owner/synthetic',
'manifest_digest': 'sha256:' + ('a' * 64),
'platform_os': 'linux',
'platform_arch': 'amd64',
'manifest_media_type': 'application/vnd.oci.image.manifest.v1+json',
'limits': limits,
'selector_version': scanner_db.DOCKER_ADAPTIVE_SELECTOR_VERSION,
'selection_policy_sha256': scanner_db.docker_layer_selection_policy_sha256(limits),
'scan_policy_sha256': 'c' * 64,
'execution_policy_sha256': scanner_db.docker_layer_execution_policy_sha256(
'c' * 64, limits,
),
'checkpoint': {'max_blobs': 1, 'max_bytes': 1024 * 1024},
'descriptors': [
{
'digest': 'sha256:' + hashlib.sha256(config_blob).hexdigest(),
'size': len(config_blob),
'media_type': 'application/vnd.oci.image.config.v1+json',
'kind': 'config',
'position': 0,
'payload_class': 'config',
'selected': True,
'selection_reason': 'already_covered',
'coverage_state': 'covered',
'lease_token': None,
'attempt': 0,
'max_attempts': 3,
},
{
'digest': 'sha256:' + hashlib.sha256(layer_blob).hexdigest(),
'size': len(layer_blob),
'media_type': 'application/vnd.oci.image.layer.v1.tar+gzip',
'kind': 'layer',
'position': 1,
'payload_class': 'copy_add',
'selected': True,
'selection_reason': 'selected_copy_add',
'coverage_state': 'leased',
'lease_token': 'l' * 43,
'attempt': 1,
'max_attempts': 3,
},
],
}
plan = scanner_db.validate_docker_layer_plan(plan)
class StreamResponse:
status_code = 200
headers = {
'Content-Length': str(len(layer_blob)),
'Content-Encoding': 'identity',
}
url = 'https://registry-1.docker.io/v2/owner/synthetic/blobs/private'
@staticmethod
def iter_content(chunk_size=1):
del chunk_size
yield layer_blob
@staticmethod
def close():
return None
plan_sha256 = hashlib.sha256(
scanner_db.canonical_docker_layer_plan_bytes(plan)
).hexdigest()
work = {
'plan': plan, 'plan_sha256': plan_sha256,
'bearer_auth': scanner.DockerRegistryAuth(token=''),
'min_free_bytes': 0,
}
claim = BundleReservation(
reservation_id=9, reservation_token='docker-reservation-token',
bundle_id='d' * 32, scan_event_id='e' * 32, queue_id=13,
claim_lease_token='docker-claim-token', declared_bytes=4 * 1024 * 1024,
ready_path='ready/dd/' + ('d' * 32) + '.trb', source='docker',
platform='docker', query='fixture', target=plan['image'],
normalized_target=scanner.normalize_target(plan['image'], 'docker'),
)
kwargs = {
'timeout_sec': 60, 'docker_layer_work': work,
'no_verification': True,
}
with tempfile.TemporaryDirectory() as root:
work_root = os.path.join(root, 'work')
local_root = os.path.join(root, 'local')
remote_root = os.path.join(root, 'remote')
for path in (work_root, local_root, remote_root):
ensure_private_directory(path, reject_reparse=True)
with mock.patch.object(
scanner, 'get_work_dir', return_value=work_root,
), mock.patch.object(
scanner, 'get_trufflehog_cmd', return_value=str(TRUFFLEHOG),
), mock.patch.object(
scanner, '_docker_registry_blob_response',
return_value=(StreamResponse(), scanner.DockerRegistryAuth(token='')),
), mock.patch.object(
scanner, 'run_command_streamed', side_effect=native_streamed_command,
), mock.patch.object(
scanner, 'require_scanner_runtime_initialized', return_value=None,
):
local_result = scanner.scan_target_result(
claim.target, 'docker', claim.scan_event_id, kwargs,
)
local = scan_execution.stage_scan_result_in_scope(
local_result, claim, local_root, {},
scan_execution.QueueDispositionPolicy(), attempts=1,
)
remote = scan_execution.execute_planned_result_in_scope(
claim, remote_root, kwargs, {},
scan_execution.QueueDispositionPolicy(), attempts=1,
)
local_path = os.path.join(local_root, local.relative_path)
remote_path = os.path.join(remote_root, remote.relative_path)
local_metadata = ResultBundleReader(local_path).metadata()
execution = scanner_db.validate_docker_layer_execution(
local_metadata['docker_layer_execution'], plan, plan_sha256,
)
for bundle_path in (local_path, remote_path):
reader = ResultBundleReader(bundle_path)
finding_uids = {
finding['finding_uid'] for finding in reader.iter_findings()
}
self.assertEqual(len(finding_uids), 3)
for candidate in reader.iter_candidates():
attribution = candidate['attribution']
self.assertIn(attribution['finding_uid'], finding_uids)
self.assertEqual(
candidate['metadata']['finding_uid'],
attribution['finding_uid'],
)
local_evidence = normalized_bundle_evidence(local_path)
remote_evidence = normalized_bundle_evidence(remote_path)
self.assertEqual(
bundle_evidence_difference_paths(local_evidence, remote_evidence), [],
)
self.assertEqual(local.queue_status, remote.queue_status)
self.assertEqual(local.queue_status, 'done')
detectors = {
str(finding.get('DetectorName') or finding.get('DetectorType') or '')
for finding in local_evidence['findings']
}
services = {
str(candidate.get('service') or '')
for candidate in local_evidence['candidates']
}
diagnostics = {
'errors': local_evidence['errors'],
'metadata': local_evidence['metadata'],
}
self.assertEqual(
detectors, {'OpenAI', 'OpenRouter', 'Anthropic'}, diagnostics,
)
self.assertEqual(services, {'openai', 'openrouter', 'anthropic'})
self.assertEqual(execution['blobs'][0]['status'], 'covered')
self.assertEqual(execution['blobs'][0]['finding_count'], 3)
self.assertEqual(execution['blobs'][0]['lease_token'], 'l' * 43)
if __name__ == '__main__':
unittest.main()