Implement managed browser security, individual agents and observability

This commit is contained in:
Karl committed 2026-10-13 16:58:15 +02:00
1 parent ff482ebe23
commit ea448de2d4
172 files changed
+5404 -347

No files matched your search

+31 -1
View File
@@ -8,9 +8,11 @@ import re
import shutil
import subprocess
import tempfile
import time
import unicodedata
import zipfile
from pathlib import Path
from stage_timings import timed, stage
os.environ['HF_HUB_OFFLINE']='1'
os.environ['TRANSFORMERS_OFFLINE']='1'
@@ -22,6 +24,7 @@ _MODEL = None
_OCR = None
@timed("ocr.load")
def local_ocr():
global _OCR
if _OCR is None:
@@ -46,6 +49,7 @@ def local_ocr():
return _OCR
@timed("yara")
def scan_download(data, policy):
"""Malware gate only: never implies document/DLP coverage."""
result={"decision":"block","ruleId":"yara-unavailable","engine":"yara",
@@ -72,8 +76,26 @@ def scan_download(data, policy):
import yara
rules=yara.compile(sources=sources,includes=False,error_on_warning=True)
matches=rules.match(data=data,timeout=5)
if config.get("scanOfficeMacros",True) is not False:
from office_macros import macro_payloads
try:
with stage("macros"):
summary,payloads=macro_payloads(data)
result["officeMacros"]=summary
deadline=time.monotonic()+10
for payload in payloads:
if time.monotonic()>=deadline: raise ValueError('macro-scan-timeout')
matches.extend(rules.match(data=payload,timeout=5))
except Exception:
result["ruleId"]="office-macro-inspection-failed"
result["officeMacros"]={"office":data.startswith(b'\xd0\xcf\x11\xe0') or data.startswith(b'PK'),
"present":False,"modules":0,"complete":False}
return result
else:
result["officeMacros"]={"office":False,"present":False,"modules":0,"complete":False}
result["coverage"]["complete"]=True
result["matchedRules"]=[{"id":ids[item.namespace],"rule":item.rule} for item in matches]
result["matchedRules"]=[{"id":identifier,"rule":rule} for identifier,rule in
dict.fromkeys((ids[item.namespace],item.rule) for item in matches)]
if matches: result["ruleId"]=ids[matches[0].namespace]
else: result["decision"]="allow";result["ruleId"]=None
except ImportError: pass
@@ -81,6 +103,7 @@ def scan_download(data, policy):
return result
@timed("inspect")
def reference_document(data):
"""Return privacy-preserving registration features, without extracted plaintext."""
result={"sha256":hashlib.sha256(data).hexdigest(),"bytes":len(data),
@@ -143,6 +166,7 @@ def content_kind(data):
except UnicodeError: return "unknown"
@timed("ocr")
def ocr_image(data):
from PIL import Image, ImageSequence
image=Image.open(io.BytesIO(data))
@@ -169,6 +193,7 @@ def ocr_image(data):
return "\n".join(parts), True
@timed("parse")
def extract(data, kind):
if kind=="text": return data.decode("utf-8"), True, []
if kind=="image":
@@ -224,6 +249,7 @@ def extract(data, kind):
raise ValueError("Unsupported content type")
@timed("semantic.load")
def get_model():
global _MODEL
directory=os.environ.get("EMBEDDING_MODEL_DIR", "")
@@ -235,6 +261,7 @@ def get_model():
return _MODEL
@timed("semantic")
def embed(data, kind):
model=get_model()
if kind=="text": vectors=model.text_vectors(data.decode("utf-8"))
@@ -253,6 +280,7 @@ def aggregate_vectors(vectors):
return [value/norm for value in vector]
@timed("semantic")
def semantic_vector_features(text,media):
model=get_model();vectors=model.text_vectors(text) if text.strip() else []
text_chunks=len(vectors)
@@ -269,6 +297,7 @@ def semantic_vectors(text,media):
return semantic_vector_features(text,media)[0]
@timed("semantic")
def embedding_features(data):
kind=content_kind(data)
if len(data)>MAX_BYTES: raise ValueError("Embedding input budget exceeded")
@@ -297,6 +326,7 @@ def deterministic(text, data, policy):
return None
@timed("inspect")
def inspect_content(data, policy, encoder=embed):
kind=content_kind(data)
result={"decision":"block","ruleId":"uninspectable","contentType":kind,"coverage":{"complete":False},"model":None}
+133
View File
@@ -0,0 +1,133 @@
"""Bounded in-memory Office VBA extraction. Never executes macros or writes files.
Source matching supplements original/compiled-stream YARA and enterprise ICAP;
it is not VBA execution, deobfuscation, p-code equivalence or an AV verdict.
"""
import io
import logging
import struct
import zipfile
OLE_MAGIC = b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1'
MAX_EXPANDED = 20 * 1024 * 1024
MAX_CODE = 4 * 1024 * 1024
MAX_MODULES = 128
def bounded_decompress(data):
"""Strict MS-OVBA 2.4.1 decompression with per-chunk and total limits."""
if not data or data[0] != 1:
raise ValueError('macro-compression-invalid')
output = bytearray(); cursor = 1
while cursor < len(data):
if cursor + 2 > len(data): raise ValueError('macro-compression-invalid')
header = struct.unpack_from('<H', data, cursor)[0]
size = (header & 4095) + 3; end = cursor + size
if ((header >> 12) & 7) != 3 or end > len(data):
raise ValueError('macro-compression-invalid')
cursor += 2; start = len(output)
if not header & 0x8000:
if size != 4098: raise ValueError('macro-compression-invalid')
output.extend(data[cursor:end]); cursor = end
else:
while cursor < end:
flags = data[cursor]; cursor += 1
for bit in range(8):
if cursor >= end: break
if flags & (1 << bit):
if cursor + 2 > end: raise ValueError('macro-compression-invalid')
token = struct.unpack_from('<H', data, cursor)[0]; cursor += 2
count = len(output) - start
bits = max(4, (count - 1).bit_length())
mask = 0xffff >> bits
length = (token & mask) + 3
offset = (token >> (16 - bits)) + 1
if offset > count or count + length > 4096:
raise ValueError('macro-compression-invalid')
for _ in range(length): output.append(output[-offset])
else:
if len(output) - start >= 4096: raise ValueError('macro-compression-invalid')
output.append(data[cursor]); cursor += 1
if len(output) > MAX_CODE: raise ValueError('macro-code-limit')
return bytes(output)
def macro_payloads(data):
"""Return (metadata, YARA payloads); reject ambiguous or unsupported Office."""
summary = {'office': False, 'present': False, 'modules': 0, 'complete': True}
projects = []; payloads = []; ooxml = False; payload_bytes = 0
def append_payload(payload):
nonlocal payload_bytes
payload_bytes += len(payload)
if len(payloads) >= 512 or payload_bytes > 2 * MAX_EXPANDED:
raise ValueError('macro-payload-limit')
payloads.append(payload)
if data.startswith(OLE_MAGIC):
summary['office'] = True; projects.append(data)
elif data.startswith(b'PK'):
with zipfile.ZipFile(io.BytesIO(data)) as archive:
entries = archive.infolist()
if len(entries) > 2048 or sum(x.file_size for x in entries) > MAX_EXPANDED:
raise ValueError('macro-archive-limit')
names = [x.filename.lower() for x in entries]
if len(set(names)) != len(names): raise ValueError('macro-archive-ambiguous')
summary['office'] = any(n in names for n in ('word/document.xml', 'xl/workbook.xml', 'xl/workbook.bin', 'ppt/presentation.xml'))
if not summary['office']: return summary, []
ooxml = True
if 'xl/workbook.bin' in names: raise ValueError('macro-xlsb-unsupported')
for entry, name in zip(entries, names):
if entry.flag_bits & 1: raise ValueError('macro-encrypted')
if '/embeddings/' in name or '/macrosheets/' in name or name.endswith('vbadata.xml'):
raise ValueError('macro-format-unsupported')
if name.endswith('vbaproject.bin'):
if len(projects) >= 16 or entry.file_size > MAX_CODE: raise ValueError('macro-project-limit')
project = archive.read(entry)
if not project.startswith(OLE_MAGIC): raise ValueError('macro-project-invalid')
projects.append(project); append_payload(project)
else:
return summary, []
if not projects: return summary, payloads
import olefile
from oletools import olevba
# Silent third-party parser: module/path/code fragments must never reach logs.
for name in ('olevba', 'olefile', 'crypto', 'ppt', 'ppt_parser', 'ftguess', 'oleform', 'rtfobj'):
logging.getLogger(name).disabled = True
previous = olevba.decompress_stream
olevba.decompress_stream = bounded_decompress
total = 0
try:
for project in projects:
with olefile.OleFileIO(io.BytesIO(project), raise_defects=olefile.DEFECT_INCORRECT) as ole:
streams = ole.listdir(streams=True, storages=False)
if len(streams) > 2048 or sum(ole.get_size(s) for s in streams) > MAX_EXPANDED:
raise ValueError('macro-ole-limit')
roots = {s[0].lower() for s in streams}
if 'encryptedpackage' in roots or 'encryptioninfo' in roots:
raise ValueError('macro-encrypted')
# Unsupported legacy Excel XLM / embedded Office containers are not safe negatives.
if any(s[-1].lower() in ('workbook', 'book') for s in streams):
raise ValueError('macro-xlm-unsupported')
has_vba = any('vba' in [p.lower() for p in s] for s in streams)
if ooxml and not has_vba: raise ValueError('macro-project-invalid')
# Contiguous streams expose compiled/p-code bytes to existing rules.
# They do not establish source/p-code equivalence (VBA stomping).
for stream in streams:
append_payload(ole.openstream(stream).read())
summary['present'] = summary['present'] or has_vba
parser = olevba.VBA_Parser('memory-office', data=project, relaxed=False)
try:
before = summary['modules']
for _, _, _, code in parser.extract_macros():
if not isinstance(code, str): raise ValueError('macro-source-invalid')
encoded = code.encode('utf-8'); total += len(encoded)
summary['modules'] += 1
if summary['modules'] > MAX_MODULES or total > MAX_CODE:
raise ValueError('macro-code-limit')
append_payload(encoded)
if has_vba and summary['modules'] == before:
raise ValueError('macro-source-unavailable')
finally:
parser.close()
finally:
olevba.decompress_stream = previous
return summary, payloads
@@ -0,0 +1,4 @@
oletools==0.60.2 --hash=sha256:72ad8bd748fd0c4e7b5b4733af770d11543ebb2bf2697455f99f975fcd50cc96
olefile==0.47 --hash=sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f
colorclass==2.2.2 --hash=sha256:6f10c273a0ef7a1150b1120b6095cbdd68e5cf36dfd5d0fc957a2500bbf99a55
pyparsing==3.3.2 --hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d
+2
View File
@@ -4,3 +4,5 @@ defusedxml>=0.7,<1
yara-python==4.5.4
rapidocr==3.8.1
onnxruntime==1.24.4
# VBA extraction uses the separately hash-pinned requirements-macros.lock.txt
# installed with --no-deps (no CLI, p-code disassembler or XLM execution engine).
+9 -5
View File
@@ -44,11 +44,15 @@ def main():
data = base64.b64decode(request['data'], validate=True)
if len(data)>MAX_BYTES: raise ValueError('Document exceeds inspection budget')
route = request['route']
if route == '/inspect': result = inspect_content(data, request['policy'])
elif route == '/scan-download': result = scan_download(data, request['policy'])
elif route == '/reference-document': result = reference_document(data)
elif route == '/embed': result = embedding_features(data)
else: raise ValueError('Unknown inspection operation')
from stage_timings import collect, metadata
with collect(request.get('policy', {})) as timings:
if route == '/inspect': result = inspect_content(data, request['policy'])
elif route == '/scan-download': result = scan_download(data, request['policy'])
elif route == '/reference-document': result = reference_document(data)
elif route == '/embed': result = embedding_features(data)
else: raise ValueError('Unknown inspection operation')
diagnostic = metadata(timings)
if diagnostic is not None: result['_diagnosticStages'] = diagnostic
response.write(json.dumps(result, allow_nan=False))
response.flush()
response.close()
+5
View File
@@ -58,6 +58,11 @@ def dispatch(route, request):
raise RuntimeError('Isolated parser decision invalid')
if result['decision']=='allow' and not result['coverage']['complete']:
raise RuntimeError('Incomplete parser coverage cannot release content')
from stage_timings import enabled, validated
diagnostic = result.pop('_diagnosticStages', None)
if enabled(request.get('policy', {})):
diagnostic = validated(diagnostic)
if diagnostic is not None: result['_diagnosticStages'] = diagnostic
return result
TOKEN=os.environ.get("INSPECTOR_TOKEN", "")
+76
View File
@@ -0,0 +1,76 @@
"""Bounded in-memory debug timings; no content, logging, sockets or dependencies."""
import contextvars
import functools
import math
import time
from contextlib import contextmanager
STAGES = frozenset(('inspect', 'parse', 'ocr', 'ocr.load', 'yara', 'semantic', 'semantic.load', 'macros'))
_current = contextvars.ContextVar('inspection_timings', default=None)
def enabled(policy):
config = policy.get('diagnostics', {}) if isinstance(policy, dict) else {}
return isinstance(config, dict) and config.get('enabled') is True and config.get('mode') == 'debug'
@contextmanager
def collect(policy):
state = {'baseUnixMs': int(time.time()*1000), 'start': time.perf_counter(), 'records': [], 'stack': []} if enabled(policy) else None
token = _current.set(state)
try:
yield state
finally:
_current.reset(token)
@contextmanager
def stage(name):
state = _current.get()
record = None
if state is not None and name in STAGES and len(state['records']) < 64:
offset = (time.perf_counter()-state['start'])*1000
if 0 <= offset <= 30000:
record = {'id': len(state['records'])+1, 'parentId': state['stack'][-1] if state['stack'] else None,
'stage': name, 'startOffsetMs': offset, 'durationMs': 0, 'status': 'ok'}
state['records'].append(record)
state['stack'].append(record['id'])
try:
yield
except BaseException:
if record is not None: record['status'] = 'error'
raise
finally:
if record is not None:
record['durationMs'] = min(30000-record['startOffsetMs'], max(0, (time.perf_counter()-state['start'])*1000-record['startOffsetMs']))
state['stack'].pop()
def timed(name):
if name not in STAGES: raise ValueError('Unknown diagnostic stage')
def decorate(fn):
@functools.wraps(fn)
def wrapped(*args, **kwargs):
with stage(name): return fn(*args, **kwargs)
return wrapped
return decorate
def metadata(state):
return {'baseUnixMs': state['baseUnixMs'], 'records': state['records']} if state is not None else None
def validated(value):
"""Untrusted worker metadata is optional; malformed values are discarded."""
try:
if not isinstance(value, dict) or set(value) != {'baseUnixMs', 'records'}: return None
base = value['baseUnixMs']; records = value['records']
if type(base) is not int or not 0 < base < 100000000000000 or not isinstance(records, list) or len(records)>64: return None
seen = set()
for record in records:
if not isinstance(record, dict) or set(record) != {'id','parentId','stage','startOffsetMs','durationMs','status'}: return None
identifier = record['id']; parent = record['parentId']
if type(identifier) is not int or identifier != len(seen)+1 or (parent is not None and (type(parent) is not int or parent not in seen)): return None
if record['stage'] not in STAGES or record['status'] not in ('ok','error'): return None
for key in ('startOffsetMs','durationMs'):
number=record[key]
if type(number) not in (int,float) or not math.isfinite(number) or not 0<=number<=30000: return None
if record['startOffsetMs']+record['durationMs']>30000: return None
seen.add(identifier)
return value
except Exception:
return None
+75
View File
@@ -0,0 +1,75 @@
import base64
import hashlib
import io
from pathlib import Path
import struct
import unittest
import zipfile
from engine import scan_download
from office_macros import bounded_decompress, macro_payloads, MAX_CODE
def fixture():
path = Path(__file__).resolve().parents[2] / 'tests/fixtures/office/benign-vba-project.base64.txt'
data = base64.b64decode(path.read_text(), validate=False)
assert hashlib.sha256(data).hexdigest() == '0ced1464b3677e98f5e3a8c5d80135e18dc98dca39299f1a8cfd2a00999fbf9f'
return data
def office(project=None, extra=None):
output = io.BytesIO()
with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as archive:
archive.writestr('xl/workbook.xml', '<workbook/>')
if project is not None: archive.writestr('xl/vbaProject.bin', project)
if extra: archive.writestr(*extra)
return output.getvalue()
def policy(source='rule clean { condition: false }', enabled=True):
return {'security': {'yara': {'enabled': True, 'scanOfficeMacros': enabled,
'rules': [{'id': 'macro-marker', 'source': source}]}}}
class OfficeMacroTests(unittest.TestCase):
def test_real_compressed_source_is_scanned(self):
data = office(fixture())
self.assertNotIn(b'Hello from Python!', data)
result = scan_download(data, policy('rule source_marker { strings: $m = "Hello from Python!" condition: $m }'))
self.assertEqual(result['decision'], 'block')
self.assertEqual(result['ruleId'], 'macro-marker')
self.assertEqual(result['officeMacros']['modules'], 5)
self.assertTrue(result['officeMacros']['present'])
self.assertNotIn('Hello from Python!', str(result))
def test_benign_macros_are_not_blanket_blocked(self):
result = scan_download(office(fixture()), policy())
self.assertEqual(result['decision'], 'allow')
self.assertTrue(result['officeMacros']['complete'])
def test_corrupt_project_fails_closed_and_can_be_disabled(self):
data = office(b'bad project')
self.assertEqual(scan_download(data, policy())['ruleId'], 'office-macro-inspection-failed')
self.assertEqual(scan_download(data, policy(enabled=False))['decision'], 'allow')
def test_macro_free_office(self):
summary, payloads = macro_payloads(office())
self.assertEqual(summary, {'office': True, 'present': False, 'modules': 0, 'complete': True})
self.assertEqual(payloads, [])
def test_legacy_project(self):
self.assertEqual(macro_payloads(fixture())[0]['modules'], 5)
def test_unsupported_embedded_or_xlm(self):
for name in ('xl/macrosheets/sheet1.xml', 'word/embeddings/object.bin'):
with self.subTest(name=name):
self.assertEqual(scan_download(office(extra=(name, 'x')), policy())['decision'], 'block')
def test_strict_compression_and_size_limits(self):
self.assertEqual(bounded_decompress(b'\x01\x03\xb0\x00abc'), b'abc')
for data in (b'', b'\x01\x03\xb0\x00a', b'\x01\x03\xb0\x01\xff\xff',
b'\x01' + (struct.pack('<H', 0x3fff) + b'x' * 4096) * (MAX_CODE // 4096 + 1)):
with self.subTest(length=len(data)):
with self.assertRaises(ValueError): bounded_decompress(data)
if __name__ == '__main__': unittest.main()
+22
View File
@@ -0,0 +1,22 @@
import unittest
from stage_timings import collect, stage, metadata, validated
class StageTimingTests(unittest.TestCase):
def test_nested_private_debug_metadata(self):
with collect({'diagnostics':{'enabled':True,'mode':'debug'}}) as state:
with stage('inspect'):
with stage('parse'): pass
result=validated(metadata(state));self.assertEqual(result['records'][1]['parentId'],1);self.assertNotIn('password',str(result))
def test_off_does_not_collect(self):
for policy in ({},{'diagnostics':{'enabled':True,'mode':'operational'}}):
with collect(policy) as state:
with stage('parse'): pass
self.assertIsNone(metadata(state))
def test_error_preserves_original_exception(self):
error=ValueError('PRIVATE_ERROR_SENTINEL')
with collect({'diagnostics':{'enabled':True,'mode':'debug'}}) as state:
with self.assertRaises(ValueError) as caught:
with stage('yara'): raise error
self.assertIs(caught.exception,error);self.assertEqual(state['records'][0]['status'],'error');self.assertNotIn('PRIVATE_ERROR_SENTINEL',str(metadata(state)))
def test_malformed_optional_metadata_is_discarded(self):
self.assertIsNone(validated({'baseUnixMs':1,'records':[{'stage':'password'}]}))
if __name__=='__main__':unittest.main()
@@ -172,4 +172,15 @@ print(json.dumps(result))
with self.assertRaises(SandboxError):self.sandbox.run(sys.executable,['-c','print("x"*10000)'],max_output=100)
def test_real_compressed_office_macro_inside_sandbox(self):
from test_office_macros import fixture, office, policy
for source, expected in [('rule source_marker { strings: $m = "Hello from Python!" condition: $m }', 'block'), ('rule no_match { condition: false }', 'allow')]:
request={'route':'/scan-download','data':base64.b64encode(office(fixture())).decode(),'policy':policy(source)}
code, output=self.sandbox.run(sys.executable,[str(self.source/'sandbox_worker.py')],json.dumps(request).encode())
self.assertEqual(code,0,output.decode(errors='replace'))
result=json.loads(output);self.assertEqual(result['decision'],expected)
self.assertEqual(result['officeMacros']['modules'],5)
self.assertNotIn('Hello from Python!',output.decode())
if __name__=='__main__':unittest.main()
+131
View File
@@ -0,0 +1,131 @@
"""Private Windows upload handle lease. No filenames or contents in diagnostics."""
import base64, ctypes as c, ctypes.wintypes as w, hashlib, json, ntpath, os, re, sys
VERSION = 'safetynet-upload-lease-v1'
LIMIT = 10 * 1024 * 1024
def run():
if os.name != 'nt':
raise ValueError('platform')
k = c.WinDLL('kernel32', use_last_error=True)
k.CreateFileW.argtypes = [w.LPCWSTR,w.DWORD,w.DWORD,c.c_void_p,w.DWORD,w.DWORD,w.HANDLE]
k.CreateFileW.restype = w.HANDLE
k.CloseHandle.argtypes = [w.HANDLE]
k.GetFinalPathNameByHandleW.argtypes = [w.HANDLE,w.LPWSTR,w.DWORD,w.DWORD]
k.GetFileType.argtypes = [w.HANDLE]
k.GetDriveTypeW.argtypes = [w.LPCWSTR]
k.GetVolumeInformationW.argtypes = [w.LPCWSTR,w.LPWSTR,w.DWORD,c.POINTER(w.DWORD),c.POINTER(w.DWORD),c.POINTER(w.DWORD),w.LPWSTR,w.DWORD]
k.ReadFile.argtypes = [w.HANDLE,c.c_void_p,w.DWORD,c.POINTER(w.DWORD),c.c_void_p]
class Info(c.Structure):
_fields_ = [('attrs',w.DWORD),('created',w.FILETIME),('accessed',w.FILETIME),('written',w.FILETIME),('volume',w.DWORD),('sizeHi',w.DWORD),('sizeLo',w.DWORD),('links',w.DWORD),('indexHi',w.DWORD),('indexLo',w.DWORD)]
k.GetFileInformationByHandle.argtypes = [w.HANDLE,c.POINTER(Info)]
handles = []
copies=[]
parent=None
announced=False
data=bytearray()
buf=None
try:
line = sys.stdin.buffer.readline(65537)
if len(line) > 65536 or not line.endswith(b'\n'): raise ValueError('config')
cfg = json.loads(line)
if type(cfg) == dict and set(cfg) == {'version','parentPid','closeHandles'}:
if cfg['version'] != VERSION or type(cfg['parentPid']) != int or cfg['parentPid'] != os.getppid() or type(cfg['closeHandles']) != list or not 1 <= len(cfg['closeHandles']) <= 4096 or not all(type(v)==str and re.fullmatch(r'[1-9][0-9]{0,19}',v) and int(v)<2**64 for v in cfg['closeHandles']): raise ValueError('cleanup')
k.OpenProcess.argtypes=[w.DWORD,w.BOOL,w.DWORD]; k.OpenProcess.restype=w.HANDLE
k.DuplicateHandle.argtypes=[w.HANDLE,w.HANDLE,w.HANDLE,c.POINTER(w.HANDLE),w.DWORD,w.BOOL,w.DWORD]
k.GetCurrentProcess.restype=w.HANDLE
parent=k.OpenProcess(0x40,False,cfg['parentPid'])
if not parent: raise ValueError('parent')
handles.append(parent)
for value in cfg['closeHandles']:
duplicate=w.HANDLE()
if not k.DuplicateHandle(parent,w.HANDLE(int(value)),k.GetCurrentProcess(),c.byref(duplicate),0,False,3): raise ValueError('cleanup')
k.CloseHandle(duplicate)
print(json.dumps({'version':VERSION,'released':True}),flush=True)
return
if set(cfg) != {'version','paths','maxBytes','parentPid'} or cfg['version'] != VERSION: raise ValueError('config')
maximum = cfg['maxBytes']
if type(maximum) != int or not 1 <= maximum <= LIMIT: raise ValueError('config')
if type(cfg['paths']) != list or not 1 <= len(cfg['paths']) <= 16: raise ValueError('config')
k.OpenProcess.argtypes=[w.DWORD,w.BOOL,w.DWORD]; k.OpenProcess.restype=w.HANDLE
k.DuplicateHandle.argtypes=[w.HANDLE,w.HANDLE,w.HANDLE,c.POINTER(w.HANDLE),w.DWORD,w.BOOL,w.DWORD]
k.GetCurrentProcess.restype=w.HANDLE
if type(cfg['parentPid']) != int or cfg['parentPid'] != os.getppid(): raise ValueError('parent')
parent=k.OpenProcess(0x40,False,cfg['parentPid'])
if not parent: raise ValueError('parent')
handles.append(parent)
copies=[]
files, total = [], 0
for path in cfg['paths']:
if type(path) != str or len(path) > 240 or not re.fullmatch(r'[A-Za-z]:\\[^\x00-\x1f:]*', path): raise ValueError('path')
canonical = ntpath.normpath(path)
if canonical != path or any(part.endswith((' ','.')) for part in path[3:].split('\\')): raise ValueError('path')
root = path[:3]
if k.GetDriveTypeW(root) != 3: raise ValueError('volume')
filesystem=c.create_unicode_buffer(32)
if not k.GetVolumeInformationW(root,None,0,None,None,None,filesystem,len(filesystem)) or filesystem.value != 'NTFS': raise ValueError('filesystem')
parts = path[3:].split('\\')
chain = [root] + [root + '\\'.join(parts[:i]) for i in range(1,len(parts)+1)]
volume = None
for index, name in enumerate(chain):
directory = index != len(chain)-1
h = k.CreateFileW(name, 0x80000000, 1, None, 3, 0x00200000 | (0x02000000 if directory else 0), None)
if h == c.c_void_p(-1).value: raise ValueError('locked')
handles.append(h)
duplicate=w.HANDLE()
if not k.DuplicateHandle(k.GetCurrentProcess(),h,parent,c.byref(duplicate),0,False,2): raise ValueError('duplicate')
copies.append(str(duplicate.value))
info = Info()
if not k.GetFileInformationByHandle(h,c.byref(info)): raise ValueError('identity')
if info.attrs & 0x400 or bool(info.attrs & 0x10) != directory or k.GetFileType(h) != 1: raise ValueError('type')
if volume is None: volume = info.volume
if info.volume != volume: raise ValueError('volume')
out = c.create_unicode_buffer(32768)
n = k.GetFinalPathNameByHandleW(h,out,len(out),0)
if not n or n >= len(out) or out.value.removeprefix('\\\\?\\').casefold().rstrip('\\') != name.casefold().rstrip('\\'): raise ValueError('identity')
size = (info.sizeHi << 32) | info.sizeLo
total += size
if total > maximum: raise ValueError('size')
data = bytearray()
while len(data) < size:
buf = c.create_string_buffer(min(65536,size-len(data))); n = w.DWORD()
try:
if not k.ReadFile(h,buf,len(buf),c.byref(n),None) or not n.value: raise ValueError('read')
data.extend(buf.raw[:n.value])
finally:
c.memset(c.addressof(buf),0,len(buf))
buf=None
after = Info()
if not k.GetFileInformationByHandle(h,c.byref(after)) or any(getattr(info,n) != getattr(after,n) for n in ('attrs','volume','sizeHi','sizeLo','links','indexHi','indexLo')) or bytes(info.written) != bytes(after.written): raise ValueError('changed')
files.append({'path':path,'size':size,'sha256':hashlib.sha256(data).hexdigest(),'volume':info.volume,'fileId':f'{info.indexHi:08x}{info.indexLo:08x}','bytes':base64.b64encode(data).decode('ascii')})
data[:] = b'\x00' * len(data)
data.clear()
print(json.dumps({'version':VERSION,'ok':True,'files':files,'handles':copies},separators=(',',':')),flush=True)
announced=True
files.clear()
command = sys.stdin.buffer.readline(129)
if command != b'{"release":true}\n': raise ValueError('release')
for value in copies:
duplicate=w.HANDLE()
if not k.DuplicateHandle(parent,w.HANDLE(int(value)),k.GetCurrentProcess(),c.byref(duplicate),0,False,3): raise ValueError('cleanup')
k.CloseHandle(duplicate)
for h in reversed(handles): k.CloseHandle(h)
handles.clear()
print(json.dumps({'version':VERSION,'released':True}),flush=True)
finally:
data[:] = b'\x00' * len(data)
data.clear()
if buf is not None: c.memset(c.addressof(buf),0,len(buf))
if not announced and copies and parent:
for value in copies:
duplicate=w.HANDLE()
if not k.DuplicateHandle(parent,w.HANDLE(int(value)),k.GetCurrentProcess(),c.byref(duplicate),0,False,3):
os._exit(2)
k.CloseHandle(duplicate)
for h in reversed(handles): k.CloseHandle(h)
if __name__ == '__main__':
try: run()
except BaseException:
print(json.dumps({'version':VERSION,'ok':False,'code':'upload-lease-refused'}),flush=True)
sys.exit(1)