first commit

This commit is contained in:
Karl committed 2026-10-13 10:34:48 +02:00
commit ff482ebe23
251 files changed
+19126

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
# Local inspector capabilities
All inspection is local. No scan downloads models or calls cloud inference.
- YARA: genuine pinned yara-python 4.5.4 in private Windows CPython 3.13 runtime. Centrally supplied sources have bounded size and count, includes disabled, imported modules disallowed, compile warnings treated as errors, and five-second match timeout. Rules compile from in-memory strings and scan bytes. Errors, unavailable engine, missing rules and oversize payloads block downloads. Compilation currently runs inside the inspector process; a native compiler hang is bounded by the browser's inspector request deadline but does not restart the inspector automatically.
- Text: UTF-8 extraction and deterministic secret/literal/fingerprint classification.
- PDF/OOXML: bounded extraction of text and embedded images; full rendered visual coverage is incomplete and outbound DLP fails closed.
- Images: bundled RapidOCR 3.8.1 with CPU ONNXRuntime 1.24.4 and packaged PP-OCRv4 detection/recognition plus v2 classification weights. Explicit verified local model paths bypass all model downloads. Real Windows test renders a secret into an image and blocks it with network model downloading forbidden. Models primarily support Chinese/English; other languages and recognition accuracy require validation. OCR completion indicates all frames processed, not guaranteed recognition accuracy. Pillow parsing has dimension/frame limits. Optional `SAFETYNET_OCR_ENGINE=tesseract` uses administrator-installed trusted `TESSERACT_PATH`, optionally `TESSDATA_PREFIX` for local traineddata. Tesseract weights are not bundled. Missing OCR blocks DLP.
- Audio/video: recognition of container types only. Transcription and full video inspection are unconfigured; DLP blocks rather than claiming safe coverage.
- Semantic adapter: real Google EmbeddingGemma 2, Q8_0 GGUF export revision `bfcd298762cc34d0357ece5ebdd31791a3a374d8`, 768 dimensions. A verified native worker runs explicitly on CPU, reads bounded local NDJSON, and returns actual normalized vectors. Both GGUF weights and native runtime modules must pass size/SHA-256 checks before loading. Configure `EMBEDDING_MODEL_DIR` and `EMBEDDING_WORKER_PATH` to staged, protected local resources. No cloud inference, model download or Python ML framework is used. Text uses the same pinned `document-v1` profile for training and inspection; native token windows must cover exact UTF-8 bytes through the final byte, otherwise scanning fails closed. At most 64 vectors are allowed; incomplete or oversized training is refused. Native image embeddings process all bounded frames resized to at most672px with48px alignment. Native audio supports only mono16kHz PCM16 WAV up to30seconds; other media remains uninspectable. Actual model fixtures must run separately from protocol unit tests to establish operational readiness.
- Reference registration: SHA-256 plus normalized eight-word shingle and paragraph hashes, coverage and registrationReady. Every shingle offset is inspected; paragraph normalization uses NFKC, lowercase, sharp-s to ss, final sigma to sigma and collapsed whitespace, shared with JavaScript policy enforcement. Up to 1000 hashes return per registration. Published `fragment-sha256` rules use this same canonicalization and detect excerpts after reformatting. No extracted plaintext returned or logged. Hashes remain sensitive reference metadata and require access control. The whole-file fingerprint is useful even for incomplete extraction, but registrationReady requires complete coverage.
`POST /scan-download` accepts `{data:base64,policy}` and returns malware-only `{decision,ruleId,engine,sha256,bytes,matchedRules,coverage}`. A YARA allow is not a DLP allow. `/inspect` applies enabled YARA first and then DLP. `/reference-document` accepts `{data:base64}` and returns registration features.
Dependencies and packaged model distributions are pinned with official PyPI wheel SHA-256 hashes and installed with `--require-hashes`. OCR model license review and a complete third-party software notice are still required before distribution. The Windows broker uses a Job Object limiting its process tree to four processes and 4 GiB, denying breakaway and killing descendants when the owner exits. Document parsing, YARA, OCR and inference now execute in a separate AppContainer per request. The worker verifies its actual AppContainer token and zero capabilities before importing parsers. Documents cross bounded anonymous pipes; source/runtime/model files receive package-SID read/execute access, never writable grants. Network and private-file probes, native child inheritance, deadlines, 16 MiB pipe integrity and orphan termination are exercised in `test_windows_appcontainer.py` and `artifacts/windows-appcontainer-proof.json`.
The AppContainer can use its Windows-provided private package storage and system resources that Windows exposes to application packages. This is not a promise that no filesystem writes anywhere are possible. Runtime roots and their ancestors reject reparse points; external Python/parser/model overrides are rejected rather than granting access to other projects. An explicit installer/deployment-owner command provisions specific-SID ACLs; normal startup reads native DACLs without changing them. Missing or writable grants fail closed. Packaging provisions the completed copied resources. Protected Program Files operation under a separate standard user remains untested. No capability or loopback exemption is added. Broker startup allows 30 seconds for cold ACL verification; each parser request retains a 29-second worker deadline and 30-second browser request budget. Broker/worker/verifier bytecode writes are disabled so cold startup does not add unapproved files. Independent exploit/bypass assessment remains required.
The isolated worker uses the bundled RapidOCR path. External Tesseract executables and their deployment environment are not forwarded into this boundary; a future separately validated sandbox configuration is required to support them.
Training retains only returned fingerprints, fragment hashes and coverage metadata. The inspector does not retain source documents or extracted plaintext, and its HTTP handlers clear transient request references after processing. Python immutable byte/string allocations and native parser/OCR buffers cannot guarantee forensic memory erasure; OS paging and crash dumps need endpoint controls. Text and bundled RapidOCR image training use memory only. Optional Tesseract writes temporary decoded image frames and removes them on normal completion; secure deletion is not guaranteed, so use the default RapidOCR mode for memory-only training.
Registration reports `extractionComplete` separately from `featuresComplete`, total/retained fragment counts and `fragmentHashesTruncated`. More than 1000 fragment hashes marks coverage incomplete and registrationReady false; a reduced document or separately approved whole-file fingerprint can then be used. A truncated feature set is never presented as complete protection.
Sources: https://yara.readthedocs.io/en/stable/yarapython.html https://pypi.org/project/yara-python/4.5.4/ https://github.com/RapidAI/RapidOCR https://pypi.org/project/rapidocr/3.8.1/
Google model sources: https://ai.google.dev/gemma/docs/embeddinggemma/model_card_2 https://ai.google.dev/gemma/docs/embeddinggemma/multimodal-embeddinggemma-with-sentence-transformers and https://huggingface.co/ggml-org/embeddinggemma-2-GGUF . Multimodal vectors support classification signals; unsupported parsing or incomplete extraction never becomes a DLP allow solely because a vector exists. Model similarity is probabilistic and requires customer-specific thresholds and false-negative testing. Native worker errors/timeouts are sanitized and kill the worker; startup readiness must attest CPU and no Vulkan module. Source strings and native memory are transient but cannot be securely erased by this Python adapter.
Actual Windows CPU validation on 2026-10-12: four real-weight fixtures passed in 25.774 seconds, covering native image embeddings, native mono 16 kHz audio embeddings, multi-window document tail coverage, and a semantic-only policy that blocks a protected paraphrase while allowing unrelated public text. All emitted vectors have 768 finite normalized values. A separate three-document check measured protected-paraphrase cosine 0.861730 versus unrelated-text 0.606880; these are synthetic fixture measurements, not a general accuracy claim. The worker handshake pins the exact hardened build `b11461-no-logits-v1`; policy/runtime manifest field `b11461` identifies its upstream runtime release. Other suffixes are refused, CPU must be attested, and loaded Vulkan is refused.
Repeat actual fixtures with staged resources using `SAFETYNET_TEST_NATIVE_SEMANTIC=1`, `EMBEDDING_MODEL_DIR`, and `EMBEDDING_WORKER_PATH`, then run the bundled Python `-m unittest discover -s apps/inspector -p test_semantic_real.py -v`. Routine protocol tests do not substitute for these opt-in real-model checks.
+33
View File
@@ -0,0 +1,33 @@
"""Current-user Windows capability protection; no browser profile crypto state."""
import base64
import ctypes
import sys
class Blob(ctypes.Structure):
_fields_=[('size',ctypes.c_uint32),('data',ctypes.POINTER(ctypes.c_ubyte))]
def convert(data,protect):
crypt=ctypes.WinDLL('crypt32',use_last_error=True)
kernel=ctypes.WinDLL('kernel32',use_last_error=True)
allocation=(ctypes.c_ubyte*len(data)).from_buffer_copy(data)
source=Blob(len(data),allocation);result=Blob()
function=crypt.CryptProtectData if protect else crypt.CryptUnprotectData
function.argtypes=[ctypes.POINTER(Blob),ctypes.c_void_p,ctypes.c_void_p,ctypes.c_void_p,ctypes.c_void_p,ctypes.c_uint32,ctypes.POINTER(Blob)]
function.restype=ctypes.c_int
# UI_FORBIDDEN; omit LOCAL_MACHINE so only the signed-in Windows user can decrypt.
if not function(ctypes.byref(source),None,None,None,None,1,ctypes.byref(result)):
raise ValueError('DPAPI rejected capability')
kernel.LocalFree.argtypes=[ctypes.c_void_p];kernel.LocalFree.restype=ctypes.c_void_p
try:return ctypes.string_at(result.data,result.size)
finally:kernel.LocalFree(result.data)
if __name__=='__main__':
try:
if sys.platform!='win32' or len(sys.argv)!=2 or sys.argv[1] not in ['protect','unprotect']: raise ValueError()
data=sys.stdin.buffer.read(32769)
if not data or len(data)>32768:raise ValueError()
if sys.argv[1]=='protect':output=base64.b64encode(convert(data,True))
else:output=convert(base64.b64decode(data,validate=True),False)
sys.stdout.buffer.write(output)
except Exception:
sys.stderr.write('Windows capability protection failed.\n');sys.exit(1)
+340
View File
@@ -0,0 +1,340 @@
"""Private content inspection. No external inference, raw-content logs or remote URLs."""
import base64
import hashlib
import io
import math
import os
import re
import shutil
import subprocess
import tempfile
import unicodedata
import zipfile
from pathlib import Path
os.environ['HF_HUB_OFFLINE']='1'
os.environ['TRANSFORMERS_OFFLINE']='1'
os.environ['HF_HUB_DISABLE_TELEMETRY']='1'
MAX_BYTES = 20 * 1024 * 1024
MAX_TEXT = 1_000_000
_MODEL = None
_OCR = None
def local_ocr():
global _OCR
if _OCR is None:
import onnxruntime
onnxruntime.disable_telemetry_events()
import rapidocr
from rapidocr import RapidOCR
directory=Path(rapidocr.__file__).parent/"models"
names={"Det":"ch_PP-OCRv4_det_infer.onnx","Rec":"ch_PP-OCRv4_rec_infer.onnx","Cls":"ch_ppocr_mobile_v2.0_cls_infer.onnx"}
checksums={"Det":"d2a7720d45a54257208b1e13e36a8479894cb74155a5efe29462512d42f49da9",
"Rec":"48fc40f24f6d2a207a2b1091d3437eb3cc3eb6b676dc3ef9c37384005483683b",
"Cls":"e47acedf663230f8863ff1ab0e64dd2d82b838fceb5957146dab185a89d6215c"}
params={"Global.log_level":"critical","EngineConfig.onnxruntime.intra_op_num_threads":2,
"EngineConfig.onnxruntime.inter_op_num_threads":1}
for task,name in names.items():
model=directory/name
if not model.is_file(): raise ValueError("Bundled offline OCR model unavailable")
if hashlib.sha256(model.read_bytes()).hexdigest()!=checksums[task]: raise ValueError("Offline OCR model checksum mismatch")
params[task+".model_path"]=str(model)
# Explicit paths bypass RapidOCR's download path entirely.
_OCR=RapidOCR(params=params)
return _OCR
def scan_download(data, policy):
"""Malware gate only: never implies document/DLP coverage."""
result={"decision":"block","ruleId":"yara-unavailable","engine":"yara",
"sha256":hashlib.sha256(data).hexdigest(),"bytes":len(data),
"matchedRules":[],"coverage":{"complete":False}}
try:
config=policy.get("security",{}).get("yara",{})
if config.get("enabled") is not True:
result["ruleId"]="yara-required";return result
maximum=config.get("maxScanBytes",MAX_BYTES)
if not isinstance(maximum,int) or maximum<=0 or len(data)>min(maximum,MAX_BYTES):
result["ruleId"]="yara-size-limit";return result
entries=config.get("rules",[])
if not isinstance(entries,list) or not 1<=len(entries)<=128: raise ValueError()
sources={};ids={};total=0
for index,entry in enumerate(entries):
source=entry["source"];identifier=entry["id"]
if not isinstance(source,str) or not isinstance(identifier,str) or not identifier: raise ValueError()
total+=len(source.encode("utf-8"))
if total>262144: raise ValueError()
# No imported modules: console can leak scanned content; others expand attack surface.
if re.search(r'\bimport\s+"',source): raise ValueError()
namespace="policy_"+str(index);sources[namespace]=source;ids[namespace]=identifier
import yara
rules=yara.compile(sources=sources,includes=False,error_on_warning=True)
matches=rules.match(data=data,timeout=5)
result["coverage"]["complete"]=True
result["matchedRules"]=[{"id":ids[item.namespace],"rule":item.rule} for item in matches]
if matches: result["ruleId"]=ids[matches[0].namespace]
else: result["decision"]="allow";result["ruleId"]=None
except ImportError: pass
except Exception: result["ruleId"]="yara-inspection-failed"
return result
def reference_document(data):
"""Return privacy-preserving registration features, without extracted plaintext."""
result={"sha256":hashlib.sha256(data).hexdigest(),"bytes":len(data),
"contentType":content_kind(data),"fragmentHashes":[],"coverage":{"complete":False}}
text="";media=[];hashes=set()
try:
if len(data)>MAX_BYTES: raise ValueError()
text,complete,media=extract(data,result["contentType"])
if len(text)>MAX_TEXT: raise ValueError()
hashes=fragment_hashes(text)
result["fragmentHashes"]=sorted(hashes)[:1000]
truncated=len(hashes)>1000
result["coverage"]={"complete":complete and not truncated,"extractionComplete":complete,
"featuresComplete":not truncated,"fragmentHashesTruncated":truncated,
"fragmentHashesTotal":len(hashes),"fragmentHashesRetained":len(result["fragmentHashes"]),
"textCharacters":len(text),"mediaItems":len(media)}
result["registrationReady"]=bool(complete and not truncated and (hashes or text.strip()))
except Exception: result["registrationReady"]=False
finally:
# Release transient content references; immutable Python strings cannot be securely wiped.
text="";media.clear();hashes.clear();data=None
return result
def fragment_hashes(text):
normalized=unicodedata.normalize("NFKC",text).lower().replace("ß","ss").replace("ς","σ")
words=re.findall(r"\w+",normalized)
# Every shingle matches excerpts even when their starting offset changes.
hashes={hashlib.sha256(" ".join(words[index:index+8]).encode()).hexdigest()
for index in range(max(0,len(words)-7))}
for paragraph in re.split(r"\n\s*\n",normalized):
paragraph=" ".join(paragraph.split())
if len(paragraph)>=32: hashes.add(hashlib.sha256(paragraph.encode()).hexdigest())
return hashes
def cosine(left, right):
if len(left) != len(right) or not left:
raise ValueError("Embedding dimensions do not match")
if not all(math.isfinite(float(v)) for v in [*left, *right]):
raise ValueError("Invalid embedding")
denominator = math.sqrt(sum(v*v for v in left)) * math.sqrt(sum(v*v for v in right))
if not denominator:
raise ValueError("Empty embedding")
return sum(a*b for a, b in zip(left, right)) / denominator
def content_kind(data):
if data.startswith(b"%PDF-"): return "pdf"
if data.startswith(b"PK\x03\x04"): return "office"
if data.startswith(b"\x89PNG\r\n\x1a\n") or data.startswith(b"\xff\xd8\xff") or data.startswith((b"GIF87a", b"GIF89a")): return "image"
if data.startswith(b"RIFF"):
return {b"WAVE": "audio", b"WEBP": "image", b"AVI ": "video"}.get(data[8:12], "unknown")
if data.startswith((b"ID3", b"fLaC", b"OggS")): return "audio"
if len(data)>12 and data[4:8] == b"ftyp": return "video"
try:
text=data.decode("utf-8", errors="strict")
if "\x00" in text: return "unknown"
return "text"
except UnicodeError: return "unknown"
def ocr_image(data):
from PIL import Image, ImageSequence
image=Image.open(io.BytesIO(data))
frames=getattr(image,"n_frames",1)
if image.width*image.height>20_000_000 or frames>32 or image.width*image.height*frames>50_000_000:
raise ValueError("Image processing budget exceeded")
if os.environ.get("SAFETYNET_OCR_ENGINE","rapidocr")=="rapidocr":
import numpy as np
engine=local_ocr();parts=[]
for frame in ImageSequence.Iterator(image):
output=engine(np.asarray(frame.convert("RGB")))
parts.extend(output.txts or [])
return "\n".join(parts),True
configured=os.environ.get("TESSERACT_PATH", "")
executable=configured if configured and Path(configured).is_file() else shutil.which("tesseract")
if not executable: return "", False
parts=[]
with tempfile.TemporaryDirectory(prefix="safetynet-ocr-") as temp:
for index, frame in enumerate(ImageSequence.Iterator(image)):
file=Path(temp)/f"frame-{index}.png"
frame.convert("RGB").save(file)
result=subprocess.run([executable,str(file),"stdout","--psm","3"],capture_output=True,timeout=20,check=True)
parts.append(result.stdout.decode("utf-8"))
return "\n".join(parts), True
def extract(data, kind):
if kind=="text": return data.decode("utf-8"), True, []
if kind=="image":
text,complete=ocr_image(data)
return text,complete,[("image",data)]
if kind=="pdf":
from pypdf import PdfReader
reader=PdfReader(io.BytesIO(data),strict=True)
if reader.is_encrypted: raise ValueError("Encrypted PDF cannot be inspected")
if len(reader.pages)>100: raise ValueError("PDF page budget exceeded")
text=[];complete=True;media=[]
for page in reader.pages:
value=page.extract_text() or ""
text.append(value)
images=list(page.images)
if not value.strip() and not images: complete=False
for image in images:
if len(image.data)>MAX_BYTES: raise ValueError("PDF image budget exceeded")
scanned,ok=ocr_image(image.data);text.append(scanned);complete=complete and ok
media.append(("image",image.data))
if len(media)>100: raise ValueError("PDF media budget exceeded")
# Attachments, JavaScript, forms, actions and vector drawings need deeper inspection.
if page.get("/Annots"): complete=False
root=reader.trailer["/Root"]
if root.get("/AcroForm") or root.get("/OpenAction") or root.get("/Names"): complete=False
# Text extraction is useful, but does not prove all visible/vector PDF content was covered.
return "\n".join(text),False,media
if kind=="office":
from defusedxml.ElementTree import fromstring
text=[];media=[];complete=True
with zipfile.ZipFile(io.BytesIO(data)) as archive:
members=archive.infolist()
if len(members)>2000 or sum(x.file_size for x in members)>MAX_BYTES:
raise ValueError("Archive expansion budget exceeded")
if not any(x.filename in ["word/document.xml","ppt/presentation.xml","xl/workbook.xml"] for x in members):
raise ValueError("Only OOXML document archives are supported")
for entry in members:
if entry.flag_bits&1 or entry.file_size>MAX_BYTES or entry.file_size>max(entry.compress_size,1)*200:
raise ValueError("Unsafe document archive")
name=entry.filename
if any(s in name.lower() for s in ["vbaproject","embeddings/","activex/","customxml/"]): complete=False
if name.endswith(".xml"):
root=fromstring(archive.read(entry))
text.extend(element.text for element in root.iter() if element.tag.rsplit("}",1)[-1] in ["t","v"] and element.text)
elif "/media/" in name:
payload=archive.read(entry)
if content_kind(payload)!="image": complete=False; continue
scanned,ok=ocr_image(payload);text.append(scanned);complete=complete and ok;media.append(("image",payload))
if len(media)>100: raise ValueError("Office media budget exceeded")
# Charts, relationships, equations and rendered layout are not yet fully inspected.
return "\n".join(text),False,media
if kind in ["audio","video"]: return "",False,[(kind,data)]
raise ValueError("Unsupported content type")
def get_model():
global _MODEL
directory=os.environ.get("EMBEDDING_MODEL_DIR", "")
worker=os.environ.get("EMBEDDING_WORKER_PATH", "")
if not directory or not Path(directory).is_dir() or not worker: raise ValueError("Verified local EmbeddingGemma 2 runtime is not configured")
if _MODEL is None:
from semantic import NativeSemanticModel
_MODEL=NativeSemanticModel(directory,worker)
return _MODEL
def embed(data, kind):
model=get_model()
if kind=="text": vectors=model.text_vectors(data.decode("utf-8"))
elif kind=="image": vectors=model.image_vectors(data)
elif kind=="audio": vectors=model.audio_vectors(data)
else: raise ValueError("Native semantic modality is not configured")
return aggregate_vectors(vectors)
def aggregate_vectors(vectors):
from semantic import validate_vectors,DIMENSIONS
validate_vectors(vectors)
vector=[sum(item[index] for item in vectors)/len(vectors) for index in range(DIMENSIONS)]
norm=math.sqrt(sum(value*value for value in vector))
if norm<1e-12: raise ValueError("Degenerate semantic aggregate")
return [value/norm for value in vector]
def semantic_vector_features(text,media):
model=get_model();vectors=model.text_vectors(text) if text.strip() else []
text_chunks=len(vectors)
for kind,payload in media:
if kind=="image": vectors.extend(model.image_vectors(payload))
elif kind=="audio": vectors.extend(model.audio_vectors(payload))
else: raise ValueError("Native semantic modality is not configured")
if len(vectors)>64: raise ValueError("Semantic vector budget exceeded")
if not vectors: raise ValueError("No semantic inputs")
return vectors,text_chunks
def semantic_vectors(text,media):
return semantic_vector_features(text,media)[0]
def embedding_features(data):
kind=content_kind(data)
if len(data)>MAX_BYTES: raise ValueError("Embedding input budget exceeded")
text,complete,media=extract(data,kind)
if len(text)>MAX_TEXT: raise ValueError("Extracted text budget exceeded")
vectors,text_chunks=semantic_vector_features(text,media)
return {**get_model().info(),"vector":aggregate_vectors(vectors),"vectors":vectors,
"coverage":{"complete":complete,"extractionComplete":complete,"textChunks":text_chunks,"mediaItems":len(media)}}
def deterministic(text, data, policy):
digest=hashlib.sha256(data).hexdigest()
normalized=unicodedata.normalize("NFKC",text).casefold()
patterns=[("private-key",r"-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----"),
("aws-access-key",r"\b(?:AKIA|ASIA)[A-Z0-9]{16}\b"),
("api-secret",r"\b(?:sk-[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9]{20,})\b")]
for rule,pattern in patterns:
if re.search(pattern,text): return rule
fragments=None
for rule in policy["dlp"]["rules"]:
if rule["kind"]=="sha256" and rule["value"].lower()==digest: return rule["id"]
if rule["kind"]=="literal" and unicodedata.normalize("NFKC",rule["value"]).casefold() in normalized: return rule["id"]
if rule["kind"]=="fragment-sha256" and text:
if fragments is None: fragments=fragment_hashes(text)
if rule["value"].lower() in fragments: return rule["id"]
return None
def inspect_content(data, policy, encoder=embed):
kind=content_kind(data)
result={"decision":"block","ruleId":"uninspectable","contentType":kind,"coverage":{"complete":False},"model":None}
if len(data)>min(MAX_BYTES,policy["dlp"]["maxBytes"]): result["ruleId"]="size-limit";return result
if policy.get("security",{}).get("yara",{}).get("enabled"):
malware=scan_download(data,policy)
if malware["decision"]!="allow": return {**result,"ruleId":malware["ruleId"],"malware":malware}
# Fingerprints can block any bytes before invoking parsers.
rule=deterministic("",data,policy)
if rule: result["ruleId"]=rule;return result
try:
text,complete,media=extract(data,kind)
if len(text)>MAX_TEXT: raise ValueError("Extracted text budget exceeded")
result["coverage"]={"complete":complete,"textCharacters":len(text),"mediaItems":len(media)}
rule=deterministic(text,data,policy)
if rule: result["ruleId"]=rule;return result
semantic=policy["dlp"].get("semantic",{})
if semantic.get("enabled"):
if encoder is embed:
info=get_model().info()
if any(semantic.get(key)!=info[key] for key in ["model","revision","profile","dimensions","artifactFormat","quantization","runtimeRevision"]): raise ValueError("Pinned model profile unavailable")
elif semantic.get("model")!="google/embeddinggemma-2" or semantic.get("revision")!=os.environ.get("EMBEDDING_MODEL_REVISION"): raise ValueError("Pinned test model revision unavailable")
if not semantic.get("categories"): raise ValueError("Semantic policy has no reference categories")
result["model"]=semantic["model"];result["similarity"]=[]
if encoder is embed: vectors=semantic_vectors(text,media)
else:
inputs=[("text",text[i:i+4000].encode()) for i in range(0,len(text),3500)]+media
if not inputs or len(inputs)>64: raise ValueError("Inference budget exceeded")
vectors=[encoder(payload,modality) for modality,payload in inputs]
if sum(len(category.get("prototypes") or [category["prototype"]]) for category in semantic["categories"])>256: raise ValueError("Semantic reference budget exceeded")
for category in semantic["categories"]:
prototypes=category.get("prototypes") or [category["prototype"]]
if not isinstance(category.get("threshold"),(int,float)) or not math.isfinite(category["threshold"]) or not 0<category["threshold"]<=1: raise ValueError("Invalid semantic threshold")
scores=[cosine(vector,prototype) for vector in vectors for prototype in prototypes]
score=max(scores);result["similarity"].append({"categoryId":category["id"],"score":round(score,6)})
if score>=category["threshold"]: result["ruleId"]=category["id"];return result
if complete: result["decision"]="allow";result["ruleId"]=None
return result
except Exception:
# Parser/model errors never release content and never serialize raw exception text.
result["ruleId"]="inspection-incomplete";return result
+37
View File
@@ -0,0 +1,37 @@
"""Installer-only ACL provisioning for this exact SafetyNet installation.
Run with the installed private Python. No caller-controlled grant roots exist.
Normal service startup verifies ACLs without changing them.
"""
import argparse
import json
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent))
sys.dont_write_bytecode=True
from windows_appcontainer import AppContainer, SandboxError
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--provision', action='store_true', help='Installer/owner only: establish read/execute ACLs')
args = parser.parse_args()
source = Path(__file__).absolute().parent
development = source.parent.name == 'apps'
installation = source.parent.parent if development else source.parent
runtime = installation / ('.runtime/windows/python' if development else 'python')
semantic = installation / ('.runtime/windows/semantic' if development else 'semantic')
if Path(sys.executable).resolve().parent != runtime.resolve():
raise SandboxError('Use the private Python belonging to this installation')
roots = [source, runtime]
if semantic.is_dir(): roots.append(semantic)
sandbox = AppContainer(roots, provision=args.provision)
print(json.dumps({'verified': True, 'provisioned': args.provision, 'rootCount': len(roots), 'profile': sandbox.name}))
if __name__ == '__main__':
try: main()
except (SandboxError, OSError):
print('Approved installation ACL verification/provisioning failed.', file=sys.stderr)
raise SystemExit(1)
+3
View File
@@ -0,0 +1,3 @@
# Native offline EmbeddingGemma 2 inference uses the verified GGUF worker.
# No sentence-transformers, PyTorch or model-download client is needed at runtime.
-r requirements.txt
+26
View File
@@ -0,0 +1,26 @@
certifi==2026.7.22 --hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775
charset-normalizer==3.5.2 --hash=sha256:195c26fb65950f8fce54e26349852b7bdd7c5f120aeefbcc440b8a20faaed4a3 --hash=sha256:9373ad13ef0d2c0fb761e04e55bfdee5a08b52cef2c882c8fbe9935b1517152e --hash=sha256:ddf19c062bea7a0cc80f519243d2c01dd091be0cf952a0750d4ad576709559f5 --hash=sha256:3d14b50de6bf4d0edf857a9386836846f982b8f524e188e2e68b96d702bcf4aa --hash=sha256:28a15fdad492a99b6eccfaaed66ef3f74050680545ea61ec8b2f4c538f1f1320 --hash=sha256:8a893cc101149f80a653f82062ebc95b34525a2614382e1da5458fe7c6997249 --hash=sha256:619799369eeef6366ed3e8755a5670f4f2f0fb6b30a0fd7264dc0fdc2357058e --hash=sha256:447441e76ec720b15e64418d32e092297340387053047c7c694f579efb0ee1d9 --hash=sha256:62588a277bfb59def052abd940703fa35107152bf479781a878617d60faf8fb5 --hash=sha256:44bd4fbb29dfbeba60e7d2bd000c59e4b21ddb3cc53912b14048d37092706d7c --hash=sha256:30fcd120b732aa79317f08dee04d7de0847822e4cf7ee0e9f445bb958832252c --hash=sha256:50e3adfb96fc189eb27b1cf62d3b598b89b4bb0420d93a3d3e42e137409011be --hash=sha256:b736353c0a625bbd5fcec108576e2385db3496f4f771f785ff32e108d3c3bc45 --hash=sha256:f5833ad231be5eb6553de524a70f48d71b2c8563101750531e0b80184e175cd4 --hash=sha256:1461ac396c4fdb983a675f20aa555624f0ee18ac83d832b9244ffff3d8055275 --hash=sha256:c6708715abcf3c73b99508253e961a9967f02fe536532834149574eda6de0d1c --hash=sha256:3d21b8b13c7592db2ac5e544a6d83187b995257472b0c9e8351b6d507ae37ed6 --hash=sha256:d760fe2a4d7c3b226cb9026d6a842868d52a7901bd98420e1baf14e80da85cf5 --hash=sha256:c9790464842f85f437dbbb54417eda1e0e6bfc52dd8d22d6fd1c994b73b2dc74 --hash=sha256:4685902cf26edf013ed7a3da0f426ebba7a00ebb9541386d835afbf002c11cab --hash=sha256:4495c5002a7b28557e7e222e77e0b661183e432b7d6d2e788101e3f240e05b8c --hash=sha256:211d5a3eb6af8f513b8d4ca19a8c1b7accab1b5f0d3175f9826b03c1a920dc1f --hash=sha256:ef4fcbf3327382cd4c9f540babd61248208af7b93eec4de397b4d5f58a09e288 --hash=sha256:bd16aabe4a02a297c23417aa17ac6299dbd8c49f673bcd645b4929b11f5a4400 --hash=sha256:fb9e68df06293761f9fe66ade60a9bc6d0f5e42b8acf2939a9158af86ab0e5bd --hash=sha256:59f63901b0031c3136cf64704dcb21de0bbae62ce2c9529bc39d27665463de37 --hash=sha256:304d5463e65a35d7bb0850550e0780395395f6fcf452f04db7d5ca7cecc425ac --hash=sha256:9cf9b1a857e25c4baceeb3624e92a56df3668f398c4acba74e174d81fb4d1d3a --hash=sha256:114e4d0c92d618409ed82a99e22b5c5e768fe995f2973f78265f4524f49d4640 --hash=sha256:2625388c6c754520c37abaf3b41eb34d1cc4a373f457898f08606c8e362b891d --hash=sha256:87e50a3e7cb90af586b6c5faf23e302a970415ac73bd7bd90a515a04b427ef96 --hash=sha256:254eb48b9fa5ee9898a3c445825a1f340fe53712a098904b39b0bddba8ea3cb1 --hash=sha256:ed2a239c0ea213acc1908150a3037257083c7c083128f1a4cec2ec4b97dca491 --hash=sha256:b91363207bd9dc966a691e959bb47f64b30f7ac4b072be9968b366982f7db77c --hash=sha256:38a873987f3be698494da8b2e3085e29da02da7b633dce73e79c699a113d7bf0 --hash=sha256:355ad8011081dec5412240c087a9a0c9d4d5039f3ed11a3f13e18c2b29b56c51 --hash=sha256:ee21e28f0430bd6dc9086c6e525d5e818a44a5ad19720c8a0ef766792f3eb5e5 --hash=sha256:3d31298449090ab8d47b7b1b2a555ff73cac7ed438a08b7ac160980c7ebed649 --hash=sha256:5cde776b7cc66e4f6c99612cea4aa7269aa65863f7a15841b2c264f103822f4e --hash=sha256:ae4f5fea5b8b8ccff88238cc8569303e5ee95efae67fa62922a311397a71f346 --hash=sha256:f7d486c83842422badd511868fd8a9a20e9407ace71564b6af47ce7e60a336c1 --hash=sha256:11a4d68a6ecda3292cb1e50239e111543ba5d709bb62a6b4ea1afcfa729d8875 --hash=sha256:d6734d2ef8a50fbf8445c139477da401f50d62a0606bf00e20ec6d87773fefb1 --hash=sha256:a815775b6c38d4e0ff7bcffbeba67feded90202bb6a226b8dd35f1c855217413 --hash=sha256:23851fb4e1b85ed3f6c2a27b777cdfe2e19fb5b38429a8faf38c7542b7665869 --hash=sha256:db19d07e2e0129e974a0e65d0064fc222a446cd5122c2fd4184d2af9fc734a9e --hash=sha256:780fbe7cab297b81dad9fb8dc5eb003c0468ffb0d9e5f65068c53a34661a96bc --hash=sha256:e2af3aad578aa6bd1384bcf4750fc285e5a9de53f40b7d41e5a0bf748edeb2b3 --hash=sha256:ed905975ab14056a2e5eb1c376cb2e1ebc5396baf84163939c518556fccde9f5 --hash=sha256:a66c3bc5ab1f0ff2164fc9965ddd611ff0802173f4b9d24554c563f6ab7e1d6e --hash=sha256:d2374b62878abb00cd8309b32af6c0b715cd02dec0ca74ef12e5069bdc64144a --hash=sha256:d376bbd28b3a8999db1a103b3b388aee6f1ddeb3e51bc2172993efdcd86e064d --hash=sha256:6045373d5a89a5ec71afde535db987ca28e76dfa276c2d4c818265b375d4b055 --hash=sha256:849df64e889b2e17230d58410a03dba311a65b163508fd33679b2b737d4b7858 --hash=sha256:15c44f7edfd477b06f517a5cc317fc1707edb9de2c865f43d4b6513907473234 --hash=sha256:a89012d6d5476ee112d20d998570ed58df2260a852afb1758809cd6900411d21 --hash=sha256:0c951d5e6dd9c2ff60609476752bee49da4206adde960ebc247766937f72e718 --hash=sha256:7218e8f32b0956cfcd048fd42d9d5779809745ca1d86113ca56f66e7ae1549c4 --hash=sha256:a19a731138fc27d5682277d3b9df22855cea1239bce7fcec5f78f42ef2d1f3c3 --hash=sha256:62603db9a7caa0802eaa28c1c46fecd7b3a263a774069c24c3c28c302448721c --hash=sha256:b6856554c4f44d79fc2307d5768854310a8f0096e501c75637542c82292b0429 --hash=sha256:1bc0baf5ef96b6ede57d47f4b8fe4d9d84019c3bfcbeb20a41edc6a6ee341f1f --hash=sha256:56bc200a365efb37383b7852e4cc5898d3b2da5987289b543956cf8cad710Line truncated
colorama==0.4.6 --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
colorlog==6.12.0 --hash=sha256:30d392604e9110045a2c2aeefc27d7a017abbab63f3a8aee594eac0801df784e
defusedxml==0.7.1 --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61
flatbuffers==25.12.19 --hash=sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4
idna==3.20 --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c
mpmath==1.3.0 --hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c
numpy==2.5.4 --hash=sha256:c6342f54c67093cae5c0227eb0eb772fdb79f2a2c37a6eb278b9909ee06aa356 --hash=sha256:b11e8fda06a7d69f15ebf542660b74466c2e51094800c1fb794f47ad4faeef17 --hash=sha256:9cb18a327b49c5c337f972b03682f6a49855525faaf3c0d3e9c96cd0fd8880a8 --hash=sha256:aec3fc4b32ff82421274f5d205c559c51c840c8df66a78efd7f3612dd005a26a --hash=sha256:fe4d21ab149f15e4e6043dfb0de87e6e5f34ac176cde83060e9802981fca2ac2 --hash=sha256:fbde6962867ee75b48b0ee29b2b9372ec5d617799dbaf38e82dc0596f2f7738a --hash=sha256:381a7a3d2e65e64c0ec302795ab9dc12bb1e73f150904699c153716177eebdaf --hash=sha256:b89d0aaae2fe498c648f4c4795c084db535af5bd98ef942b2a3681fb74ce8645 --hash=sha256:9968ab7e49b93ac6e1c3b2239732183152c9150f16308d30b66a372cffe3483c --hash=sha256:a7b1b6353e36a7e50de2973a38d705c88ee93adcf120673cee7f45a4a3fa223a --hash=sha256:aa1cce2ff3f8d953de38b76bf44602caeb69f101430208f64a10067f7cb4b1d3 --hash=sha256:2377da2dd3ba2c1200956acbab2a358c83b8e1f8531191672d1cd6ad83250d53 --hash=sha256:7415db95818b39ec475a5eea54d9e3b6bc83e3912158e46da3438cdce399804d --hash=sha256:6d6a71b9d9a97c03633aa12565ef2825ffa036cc1d99cfd50dacf0f128af4fe2 --hash=sha256:d8200f16437b289a5bb927c6e184eccc3e8389bc0070fea4cd5b9e13c1757959 --hash=sha256:1c2e71b04c6cad90026e544501bbe0ab9290fa8a4d845e7e8c0d124fb429c988 --hash=sha256:6ffa07666f8da0eef81d149934a626d0d95fbd6838432a33e66245423a9062c0 --hash=sha256:2fa3328f784fc8277fc48026f6cad516f5c561c5d8e2e39b3c9e0c8f23223b34 --hash=sha256:b86966fbe4ad7de710422175572bcdc75fdedadfb54bc6fab7deabccddd7780b --hash=sha256:5258bc06526964be5face2fc6f756857a3f24f21ec3e72ca131337a75b165d6c --hash=sha256:8b4d2fd2d34e5f8c9235ee787de5631a37a28402b15cb80814df973d2be54129 --hash=sha256:bc39ac66a7a9a3fbd6134fda43136b60ffde99c8f4501e64e0d2b24da137babf --hash=sha256:c668b2f0d651605b58892644b0e302c7157f7159544227758c896982ef384b18 --hash=sha256:ffa6ce09a1c6a08e9667dd9c97aa0b14184e8d18f2a14b78b2a2328c9147f076 --hash=sha256:956555e0603a4d38019ae6925711cb9dc43195c076a928accf7ea5d50bddfe53 --hash=sha256:2c2c4afffdeb7920e445028dd71eb932cac3e704792e964bc2a232426d4f1255 --hash=sha256:4054173604cd8658796053f1f3bc0befb68ec1c0762c57fdad61e199256a8617 --hash=sha256:d549420b8858885cea8838a727842249218b9c1da24dd517e25c9c7a948310a3 --hash=sha256:823874a507a84af050493b622affde94b6f7c3a0dc22cb2801381bc03b871c00 --hash=sha256:4e263278bfb5ee6409db8aedbc4cc32973b1b82bc1e8d3c668551d04d83a7e37 --hash=sha256:1ef3aa6d7e29bb13677323114280b05acc57607fa2300e66432d665d5418a162 --hash=sha256:98b053943e5a0474ec0da309d2cb9d3f18ea57f8a2067c2ab7b5f763d1068380 --hash=sha256:b64a85f40e154983960a4167d4c1d57a50c7f109b3d3264a3a984154e90a8454 --hash=sha256:a813ed7719bf45463c51779e6a98d0385fe905e48447526938a4b8337333d551 --hash=sha256:c9b80cdf5cedba0e90d93fa5f9a333c4d65bd545cd669b71bb97ce2b703c9d73 --hash=sha256:2199ed071f460487c8db2c0e5c0b564494190edb4772fe80f9aad88b2604def5 --hash=sha256:64f9c9878c1938476365e11ccfb6b770f3b9e5f045ccddc514235041e6959365 --hash=sha256:64d1c8ac28a4077cf987e0a71a7a0ef7e2df70722f07f0baa42dbb7eb6938647 --hash=sha256:067374eb538c34c745436365cf7b0112595c1d326f21ce4ff340f61230239fbb --hash=sha256:e94aef2c639da4a960ad0db8e06471208d8589974953d78b61d345b4eb99e394 --hash=sha256:cfd73180400042a7c532d30c5e287bdd03c59ff9ee1b4c0316af0539e29dfe23 --hash=sha256:2ca144f15135b6212a5c47b1e2aeca6e412f102f95a2d5d88d8aec77eb255de3 --hash=sha256:468397ba3c64427474706e5c9123fe266395496714dc684294eac75cd4930d1e --hash=sha256:8dddfbee2e68d26d0d7d7d9cb247b1fd4409241cce32d815a11d97ec2cfde179 --hash=sha256:81e3420b27048b65eb14c3acf0c174a8cb0e023277716110347d2dcb26026dad --hash=sha256:0b4724a19de67bea8cfc4970798efa78bcbbe2ac2613cfac16721a42d44de2a5 --hash=sha256:2132418bf8dd124a427ca9e6a1daf9ee1a87185344c95119ceae868b99466da1 --hash=sha256:325518d4245b9e331387702aa58c2ce1dc4cdcbb41dfb4ccd5dcbc7e08db1266 --hash=sha256:56733449d2544178beaa4545cee357370440cf056c197f9c7bfb19dbfdd0e86d --hash=sha256:5ec3753760c1a6d8bb91200666e545c3a9728e6269dfb5d6ce02340996698aa3 --hash=sha256:b1185012870173de7ae33d370bd45b1cf5baee747ea4b97036b65f4e93016877 --hash=sha256:950ea81d57ef070665581b6e1b5f6a029306423cd1739c5b95fe78aa30db6b9d --hash=sha256:c05ede731b03fb1b7591faca9389ade3267d2bddf1ad8882bb3f2cc5e101694f --hash=sha256:5fbf7141bbfd63aea22f435c9062a032b9ea0082fe9845dad7f021d3f1234e71 --hash=sha256:3573cd22564692a5b899ec344e5d5b9cc4576f2985b96f22af3564ed54f2710f --hash=sha256:6c109eac9cd439193678f69d70733c1108487546ca8eafc107b510ae10c1aecd --hash=sha256:80d6ef6e8620eb2c2b4c4caad50b5935d6db3cde2d51581b55dcc79e14016d1d --hash=sha256:77045a4b175bbf5316ec08003880804336c78f92281a1b72222b274ea85ec5ac --hash=sha256:0f02a46e49cfb6c73bdb7aea1c0d3461dbae9aba613542b65f657cd3d17b9fab --hash=sha256:ad62a416ddcf863bf44bba76fbf6b53366ab0692e294f51cae4b5fbe0d246788 --hash=sha256:38f47be9f74ab870d2633b5456ae519c43758a8d1fd05342f0ce4ecc034396ee --hash=sha256:7a14a461d9340f1b46b8648578aed9cdb8b3b018a8fac6c1dde2c9192a01a87f --hash=sha256:298eca75243f2cbbfdb460560b9fb2a1792a33cf2ab4286efd43d92e8d3df508 --hash=shLine truncated
omegaconf==2.4.0 --hash=sha256:f141a664c777582405e3cc6e0545ecc79429e79f844995b1a6e71c35946a025c
onnxruntime==1.24.4 --hash=sha256:0bdfce8e9a6497cec584aab407b71bf697dac5e1b7b7974adc50bf7533bdb3a2 --hash=sha256:046ff290045a387676941a02a8ae5c3ebec6b4f551ae228711968c4a69d8f6b7 --hash=sha256:e54ad52e61d2d4618dcff8fa1480ac66b24ee2eab73331322db1049f11ccf330 --hash=sha256:b43b63eb24a2bc8fc77a09be67587a570967a412cccb837b6245ccb546691153 --hash=sha256:e26478356dba25631fb3f20112e345f8e8bf62c499bb497e8a559f7d69cf7e7b --hash=sha256:cad1c2b3f455c55678ab2a8caa51fb420c25e6e3cf10f4c23653cdabedc8de78 --hash=sha256:1a5c5a544b22f90859c88617ecb30e161ee3349fcc73878854f43d77f00558b5 --hash=sha256:0d640eb9f3782689b55cfa715094474cd5662f2f137be6a6f847a594b6e9705c --hash=sha256:535b29475ca42b593c45fbb2152fbf1cdf3f287315bf650e6a724a0a1d065cdb --hash=sha256:e6214096e14b7b52e3bee1903dc12dc7ca09cb65e26664668a4620cc5e6f9a90 --hash=sha256:e99a48078baaefa2b50fe5836c319499f71f13f76ed32d0211f39109147a49e0 --hash=sha256:dc4aaed1e5e1aaacf2343c838a30a7c3ade78f13eeb16817411f929d04040a13 --hash=sha256:e30c972bc02e072911aabb6891453ec73795386c0af2b761b65444b8a4c4745f --hash=sha256:1700f559c8086d06b2a4d5de51e62cb4ff5e2631822f71a36db8c72383db71ee --hash=sha256:4c74e268dc808e61e63784d43f9ddcdaf50a776c2819e8bd1d1b11ef64bf7e36 --hash=sha256:3b6ba8b0181a3aa88edab00eb01424ffc06f42e71095a91186c2249415fcff93 --hash=sha256:71d6a5c1821d6e8586a024000ece458db8f2fc0ecd050435d45794827ce81e19 --hash=sha256:fbff2a248940e3398ae78374c5a839e49a2f39079b488bc64439fa0ec327a3e4 --hash=sha256:e2b7969e72d8cb53ffc88ab6d49dd5e75c1c663bda7be7eb0ece192f127343d1 --hash=sha256:14ed1f197fab812b695a5eaddb536c635e58a2fbbe50a517c78f082cc6ce9177 --hash=sha256:aa12ddc54c9c4594073abcaa265cd9681e95fb89dae982a6f508a794ca42e661 --hash=sha256:e1cc6a518255f012134bc791975a6294806be9a3b20c4a54cca25194c90cf731 --hash=sha256:311e309f573bf3c12aa5723e23823077f83d5e412a18499d4485c7eb41040858 --hash=sha256:3f0b910e86b759a4732663ec61fd57ac42ee1b0066f68299de164220b660546d
opencv-python==5.0.0.93 --hash=sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898 --hash=sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b --hash=sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac --hash=sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881 --hash=sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2 --hash=sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039 --hash=sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157 --hash=sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2
packaging==26.3 --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c
pillow==12.3.0 --hash=sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a --hash=sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7 --hash=sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f --hash=sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec --hash=sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468 --hash=sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed --hash=sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1 --hash=sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb --hash=sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f --hash=sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756 --hash=sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6 --hash=sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd --hash=sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd --hash=sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c --hash=sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5 --hash=sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b --hash=sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a --hash=sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26 --hash=sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965 --hash=sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7 --hash=sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9 --hash=sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91 --hash=sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c --hash=sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df --hash=sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f --hash=sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09 --hash=sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510 --hash=sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89 --hash=sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace --hash=sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec --hash=sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66 --hash=sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35 --hash=sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65 --hash=sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3 --hash=sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a --hash=sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e --hash=sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f --hash=sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8 --hash=sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b --hash=sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330 --hash=sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217 --hash=sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930 --hash=sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8 --hash=sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0 --hash=sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321 --hash=sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b --hash=sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198 --hash=sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130 --hash=sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e --hash=sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17 --hash=sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385 --hash=sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c --hash=sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d --hash=sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931 --hash=sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7 --hash=sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c --hash=sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45 --hash=sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a --hash=sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d --hash=sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838 --hash=sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139 --hash=sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402 --hash=sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c --hash=Line truncated
protobuf==7.36.2 --hash=sha256:cbc70b17ee27e28894c7fee8bb04be1abead49e936bc70eb60052531eee2079e --hash=sha256:e11e1f0180583a2af89db6a2ecd9e8dc40aa6d2988ca175bfd0e6d12ea72d74e --hash=sha256:f4fee11ec330d238b34a05c9b675f693c20415d1c5bd7d5320cc2f8a798eb9cf --hash=sha256:89f23aa53c24553a2416fd4fd1ec06f74fa42b14b546d8883128813f775bbfd2 --hash=sha256:912c1221170e16c08d1f086762f563dd61ff83c18b5fa6652952dfaded66f728 --hash=sha256:a300819d441e078a5608c0d3c709796bb548136058fda017ae51d425b44fd353 --hash=sha256:bdb3a345d48db958e6ce1f18e508beb0cc981d64f24088427549c866cd039f1e
pyclipper==1.4.0 --hash=sha256:bafad70d2679c187120e8c44e1f9a8b06150bad8c0aecf612ad7dfbfa9510f73 --hash=sha256:0b74a9dd44b22a7fd35d65fb1ceeba57f3817f34a97a28c3255556362e491447 --hash=sha256:0a4d2736fb3c42e8eb1d38bf27a720d1015526c11e476bded55138a977c17d9d --hash=sha256:b3b3630051b53ad2564cb079e088b112dd576e3d91038338ad1cc7915e0f14dc --hash=sha256:8d42b07a2f6cfe2d9b87daf345443583f00a14e856927782fde52f3a255e305a --hash=sha256:6a97b961f182b92d899ca88c1bb3632faea2e00ce18d07c5f789666ebb021ca4 --hash=sha256:adcb7ca33c5bdc33cd775e8b3eadad54873c802a6d909067a57348bcb96e7a2d --hash=sha256:fd24849d2b94ec749ceac7c34c9f01010d23b6e9d9216cf2238b8481160e703d --hash=sha256:1b6c8d75ba20c6433c9ea8f1a0feb7e4d3ac06a09ad1fd6d571afc1ddf89b869 --hash=sha256:58e29d7443d7cc0e83ee9daf43927730386629786d00c63b04fe3b53ac01462c --hash=sha256:a8d2b5fb75ebe57e21ce61e79a9131edec2622ff23cc665e4d1d1f201bc1a801 --hash=sha256:e9b973467d9c5fa9bc30bb6ac95f9f4d7c3d9fc25f6cf2d1cc972088e5955c01 --hash=sha256:222ac96c8b8281b53d695b9c4fedc674f56d6d4320ad23f1bdbd168f4e316140 --hash=sha256:f3672dbafbb458f1b96e1ee3e610d174acb5ace5bd2ed5d1252603bb797f2fc6 --hash=sha256:d1f807e2b4760a8e5c6d6b4e8c1d71ef52b7fe1946ff088f4fa41e16a881a5ca --hash=sha256:ce1f83c9a4e10ea3de1959f0ae79e9a5bd41346dff648fee6228ba9eaf8b3872 --hash=sha256:3ef44b64666ebf1cb521a08a60c3e639d21b8c50bfbe846ba7c52a0415e936f4 --hash=sha256:d1e5498d883b706a4ce636247f0d830c6eb34a25b843a1b78e2c969754ca9037 --hash=sha256:d49df13cbb2627ccb13a1046f3ea6ebf7177b5504ec61bdef87d6a704046fd6e --hash=sha256:37bfec361e174110cdddffd5ecd070a8064015c99383d95eb692c253951eee8a --hash=sha256:14c8bdb5a72004b721c4e6f448d2c2262d74a7f0c9e3076aeff41e564a92389f --hash=sha256:f2a50c22c3a78cb4e48347ecf06930f61ce98cf9252f2e292aa025471e9d75b1 --hash=sha256:c9a3faa416ff536cee93417a72bfb690d9dea136dc39a39dbbe1e5dadf108c9c --hash=sha256:d4b2d7c41086f1927d14947c563dfc7beed2f6c0d9af13c42fe3dcdc20d35832 --hash=sha256:7c87480fc91a5af4c1ba310bdb7de2f089a3eeef5fe351a3cedc37da1fcced1c --hash=sha256:81d8bb2d1fb9d66dc7ea4373b176bb4b02443a7e328b3b603a73faec088b952e --hash=sha256:773c0e06b683214dcfc6711be230c83b03cddebe8a57eae053d4603dd63582f9 --hash=sha256:9bc45f2463d997848450dbed91c950ca37c6cf27f84a49a5cad4affc0b469e39 --hash=sha256:f160a2c6ba036f7eaf09f1f10f4fbfa734234af9112fb5187877efed78df9303 --hash=sha256:a9f11ad133257c52c40d50de7a0ca3370a0cdd8e3d11eec0604ad3c34ba549e9 --hash=sha256:bbc827b77442c99deaeee26e0e7f172355ddb097a5e126aea206d447d3b26286 --hash=sha256:29dae3e0296dff8502eeb7639fcfee794b0eec8590ba3563aee28db269da6b04 --hash=sha256:0b8c2105b3b3c44dbe1a266f64309407fe30bf372cf39a94dc8aaa97df00da5b --hash=sha256:6c317e182590c88ec0194149995e3d71a979cfef3b246383f4e035f9d4a11826 --hash=sha256:98b2a40f98e1fc1b29e8a6094072e7e0c7dfe901e573bf6cfc6eb7ce84a7ae87
pypdf==6.20.1 --hash=sha256:aa5a55ddcffdc5e5ab291d5decb23f6383f4e56f8e3263dc39af41fff03885ad
PyYAML==6.0.3 --hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b --hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 --hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 --hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 --hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b --hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 --hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 --hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e --hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c --hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e --hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 --hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c --hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d --hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a --hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 --hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b --hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf --hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 --hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc --hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e --hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea --hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 --hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b --hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd --hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 --hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c --hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 --hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be --hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 --hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c --hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb --hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac --hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 --hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 --hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 --hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 --hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 --hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 --hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 --hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba --hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c --hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c --hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 --hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 --hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 --hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b --hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac --hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 --hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f --hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 --hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 --hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 --hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 --hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 --hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b --hash=sLine truncated
rapidocr==3.8.1 --hash=sha256:650044b1fbce9e6bae5cae462dcf8be754cde11e2f23fc51f65dcc08deae2c46
requests==2.34.2 --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0
shapely==2.2.0 --hash=sha256:596b7994ceafa526b6e0522ca29fbc41d19f86459161d6efe1f251d0acd49f3f --hash=sha256:7c0b262116bb75b86751440b42e19673911bc0a8f0d5ce723ce294c3d6e4d5c0 --hash=sha256:7765e0e5d51d63eae0a911861cbda87165a01677bc9bce6ed20d06858ccde99f --hash=sha256:5d61088e2ef71dafad0dd4fae8a521cc1f20da4a89d3096bab5b3260b39b3052 --hash=sha256:0edec813c81effaf4e20c18b1aa86827925ce27c0315621f2a1a080e22e0de5e --hash=sha256:8d6ffe94710f37535a47161120cd5f7f0f0d9bb800c2fddebbd089cb7f1b3453 --hash=sha256:ce858295be3947143a3f44f145fa6dbacd5dcc5c4103801d42cd3be4a2034614 --hash=sha256:806d399418b23eee7241736d572ad1e0b784782f9241d7c8e2cfceb00787831d --hash=sha256:5b740c9a197e5feb30bdc6e64a5eb3ca2a7324d11498844136dfc317daac6a99 --hash=sha256:626fe4c0d32860a98e75ecffabf5a62254c6168eac96b633ad313cd62a38bb2b --hash=sha256:c36ccbff5c3374c349c370bfdac22c7676b268b4a707c98e9031f498965aa02d --hash=sha256:a9a380624cdd7a7e661bf15a4d1625082766f07ccd2540cb0a9e0df1ad4f6c11 --hash=sha256:650a5f4d8a8e3c96982079d8c99b6ddbe6602bbd1e34c75c2b95dbc0d28ac997 --hash=sha256:a851e077f0f02a3383923e02eca5447a29ddbf234e39593b91c8b7ac75218133 --hash=sha256:dc5faa593948aa64d9afae48331b80f43f7aacc68425d99064a4d6772f53f1ad --hash=sha256:da47a0cc9e630b4dff0db46e8972b29d2d27f337425ce9d4c77fd046ce48eabd --hash=sha256:90895df6542ae039fc6557dec6194e3509e883fbd6f5788e3c3e7a38fe46b257 --hash=sha256:7cf5b3a801b9b4febf774efde2e31280e647388deae8452693d8e6420b3a1ff2 --hash=sha256:c037369c35510f51100dd6d386ee3203bac32f164d53e27ca12c3cea5bb643b1 --hash=sha256:d75957716368f919c63016dae1977a0d007e15f06861cd178701edb91b08d2b0 --hash=sha256:4ed79beb8d4b6cc7c67780fd381feed25848a5f9b8a2385ac5711eccd115647a --hash=sha256:f340e7f99aaee3df5acd6b247cddf723051a7c93d1e1ef09025b80d84e4c0ded --hash=sha256:17434cb9819c9974c3331333a3b878fa5bf8f85dd69cc3fb7ff5d260f6fbc102 --hash=sha256:b2338ac40e6652c8bfb857936ea9be9a16f43a362c6f67eb3bad741b05fd5683 --hash=sha256:40871d7135cd723f965d200181aa28418e9ec029fd85bdd010488259d1c01906 --hash=sha256:1eaa2cb64cdedaf65d6bc86f2819c9cd7d6d68f969aa3ebfdc93743ab581f437 --hash=sha256:f79b3b34ad2d067207f21f821489c720b14ce40f3bfda931987a193165f80133 --hash=sha256:000c0ce2a3ba49427e6288b7add9de5d8525d4e65d6ebc8840103040d4d57b86 --hash=sha256:0a63e6b68ec785ef3aae3935c4aa9fb8edccced94e23c79d5d85276442c60859 --hash=sha256:770d4db5cf0bfeed931a1c4aaf4f4eadad0f43f5fc72c27c88fe1f07904ae767 --hash=sha256:74f4313af38d6e49ea83532d6cedfb4fe5e6c5485d7c40202bd61b19d6ff09bf --hash=sha256:9ee11aeba1759d15a525ded58e17916d3edfa60d52110fd8df6a7609a871f066 --hash=sha256:24b175c570efc91d1180ac6cd527dc80e863bb7de37f8b2771703d822c65e023 --hash=sha256:c2915ae1b858e73d5832be7fb5e89497cc5140fa505da40a45223029dc6deace --hash=sha256:74028f468e05e461b30a479b08c1fb5094fa45062abeeec8e7905a6711761436 --hash=sha256:6ec5178a39803fa8626322f69d298037f182461dd28e3ae96c2c7a4309a6bf30 --hash=sha256:593e51cd04fe1122f1ab3fae87b306c36b2be0184a5e0d9c26849c55ff4580dc --hash=sha256:3575a323b7665d7a2e391b16a626caa6b6f6348f399183aca3fc656febd7cf04 --hash=sha256:776cc8571d53e42be8fa6d42ad52a599b8e2186dd0c752922831508099af71e2 --hash=sha256:f8cd733a66a2a10f461a70dde9fad7b2b62c6a48c7a66cea57ee6f1cd9f2bd2f --hash=sha256:7f68c1fbacab81c0c066d1c3051eeb0f680b7a7a2c511e741f77741640187896 --hash=sha256:9147ebc3b116a0511dca043937f85caf1a41690815643d5b89c8bc472f51c850 --hash=sha256:4e5830637c080bdc646c5982ad6f7cc296b93038879649f7a6acd8e0f1c4db04 --hash=sha256:48dd1d961391f314ab7fa8812c86ca2a727bee2bdca1478730eacaea007da18e --hash=sha256:c4127c064bc71f8b7f9b3f341d6627ed39977fd0b61a17c68d09179f5e0089ae --hash=sha256:715561ceda03b09ca1c6baf9922179392d8c2bc53a1b877965225f0dfb487a58 --hash=sha256:556f20346a7d96fefbb71b74640d84ca14041703d60f0d2ff47b29d9b3e0093d --hash=sha256:ff9e87b534edf35af65758fafb31ad3b797354cba9323899e263f450c69a2ff2 --hash=sha256:fdb599ec540cea5b635ac47bf24fca4cdfd1c39730ffc0b6cf0d2666b0dd9a33 --hash=sha256:b8cb04906b74db26f848f76744fa995cd6abeae9145d27cc405277de1f949660 --hash=sha256:d9b11d712ac72f1d869f2b6964dea5bd9f20b89901adcd796d6712496144ab22 --hash=sha256:c88b21a0e9599ebb741e08f71a95c8f07a434af909efb088828a9874d234d06d --hash=sha256:cbe184e1946cfe115a9dfeadd2effd88ab4a237ab1a4335d106defa80fbc2d82 --hash=sha256:8bc985ad731da2f2cedde9c3cfb3c3d946fe6fc63d2ca557673dc33dd1e389b9 --hash=sha256:c3caa4c6308e7eaf18f4661134a1575eb290a56df78d0ae1b02f919a4cc7bd9d --hash=sha256:2fd87e55d7a7d310553b527378545cdc6ef8702473ed9294926b892c3cfb2ba0 --hash=sha256:7416db8ff3a1003687d4118e741343b3cf9ac2a4a925a59d44d98a865ac4e9e7 --hash=sha256:778421a19085bef1fb38bc0699db1ee9b08fdd0e30a8768788d601a4371f2de0 --hash=sha256:287ec7602f7a114b862ae0123880e57160cebe059843a4c7028aaee9e74287f6 --hash=sha256:e414c78bc81aadd76a429111a350f4ef3d05fc13019805617b524951258468e5 --hash=sha256:1af6935acde1db0b6a1bcbea30cbad5ae900723dfd398367ae1488470dc53667 --hash=sha256:96e5101ad2d73df869255bae4c55537f372d32066e2328c376e09841f0f66800 --hash=sha256:446b2d5a323bddd1c2a27f41325fdb3a3e8e33c1f8f0f840bdb63e8c1515b29e
six==1.17.0 --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274
sympy==1.14.0 --hash=sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5
tqdm==4.70.1 --hash=sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73
urllib3==2.8.0 --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3
yara-python==4.5.4 --hash=sha256:721d341bd2013fbada4df5aba0eb79a9e4e21c4b86441f7f111ab8c31671f125 --hash=sha256:c785fd16475f2a3191cd4fae0cd608b1ba6271f495ec86fa26a3c5ac53f5f2e1 --hash=sha256:5eefa3b157cd5f4454a317907bab334036f61385324b70cb61dbc656c44168d5 --hash=sha256:bbc0c5a2ee67e6043e4c2622093ebfc7d2c173dc643dd089742aedbdea48d6a4 --hash=sha256:1b091f1bd6c5d2a9b5c0c682ba67ba31b87bb71b27876430775998b71c8e3f97 --hash=sha256:91276c90bb5e148e10050015fec8a1d4009a95eee9eb832d154f80355d0b4080 --hash=sha256:7e20e1f69b6239fe4f4da97e9ff361d9be25d6f1d747589ea44b8a9ec412a12d --hash=sha256:a83773b561727fc360f7a6874f7fac1409bc9c391134dc3e070f1c2515c0db98 --hash=sha256:14f203bd9fb33ad591e046429560133127fa4a6201dac28c525fa7c6c7ca36a7 --hash=sha256:e632daf9f38f8d4b433f08f798b07a45756e6c396e9e0ec54aac10045f6d241d --hash=sha256:a3866830f7f2d071f94cbce7c41d91444ac29e2cbbe279914abf518d57a2d41f --hash=sha256:135c1097ea0445a323038acd509162675ce6d5e21f848aa856779632d48dec42 --hash=sha256:e3e2a5575d61adc2b4ff2007737590783a43d16386b061ac12e6e70a82e5d1de --hash=sha256:f79a27dbdafb79fc2dc03c7c3ba66751551e3e0b350ab69cc499870b78a6cb95 --hash=sha256:9d9acf6f8135bcee03f47b1096ad69f4a2788abe37dd070aab6e9dd816742ecc --hash=sha256:0e762e6c5b47ddf30b0128ba723da46fcc2aa7959a252748497492cb452d1c84 --hash=sha256:adcfac4b225e76ab6dcbeaf10101f0de2731fdbee51610dbc77b96e667e85a3a --hash=sha256:a82c87038f0da2d90051bfd6449cf9a4b977a15ee8372f3512ce0a413ef822fd --hash=sha256:1a1721b61ee4e625a143e8e5bf32fa6774797c06724c45067f3e8919a8e5f8f3 --hash=sha256:57d80c7591bbc6d9e73934e0fa4cbbb35e3e733b2706c5fd6756edf495f42678 --hash=sha256:3872b5f5575d6f5077f86e2b8bcdfe8688f859a50854334a4085399331167abc --hash=sha256:fd84af5b6da3429236b61f3ad8760fdc739d0e1d6a08b8f3d90cd375e71594df --hash=sha256:491c9de854e4a47dfbef7b3a38686c574459779915be19dcf4421b65847a57ce --hash=sha256:2a1bf52cb7b9178cc1ee2acd1697a0c8468af0c76aa1beffe22534bd4f62698b --hash=sha256:ade234700c492bce0efda96c1cdcd763425016e40df4a8d30c4c4e6897be5ace --hash=sha256:e1dedd149be61992781f085b592d169d1d813f9b5ffc7c8c2b74e429b443414c --hash=sha256:92b233aae320ee9e59728ee23f9faf4a423ae407d4768b47c8f0e472a34dbae2 --hash=sha256:1f238f10d26e4701559f73a69b22e1e192a6fa20abdd76f57a7054566780aa89 --hash=sha256:d29d0e137e0d77dd110186369276e88381f784bdc45b5932a2fb3463e2a1b1c7 --hash=sha256:e7d0039d734705b123494acad7a00b67df171dd5b1c16ff7b18ff07578efd4cd --hash=sha256:e5eae935b05a9f8dc71df55a79c38f52abd93f8840310fe4e0d75fbd78284f24 --hash=sha256:30fc7959394532c6e3f48faf59337f5da124f1630668258276b6cfa54e555a6e --hash=sha256:e6d8c2acaf33931338fdb78aba8a68462b0151d833b2eeda712db87713ac2abf --hash=sha256:a3c7bc8cd0db5fb87ab579c755de83723030522f3c0cd5b3374044055a8ce6c6 --hash=sha256:d12e57101683e9270738a1bccf676747f93e86b5bc529e7a7fb7adf94f20bd77 --hash=sha256:bf14a8af06b2b980a889bdc3f9e8ccd6e703d2b3fa1c98da5fd3a1c3b551eb47 --hash=sha256:fe8ad189843c729eae74be3b8447a4753fac2cebe705e5e2a7280badfcc7e3b4 --hash=sha256:94e290d5035be23059d0475bff3eac8228acd51145bf0cabe355b1ddabab742b --hash=sha256:4537f8499d166d22a54739f440fb306f65b0438be2c6c4ecb2352ecb5adb5f1c --hash=sha256:ab5133a16e466db6fe9c1a08d1b171013507896175010fb85fc1b92da32e558c --hash=sha256:93f5f5aba88e2ed2aaebfbb697433a0c8020c6a6c6a711e900a29e9b512d5c3a --hash=sha256:473c52b53c39d5daedc1912bd8a82a1c88702a3e393688879d77f9ff5f396543 --hash=sha256:9d9a58b7dc87411a2443d2e0382a111bd892aef9f6db2a1ebb4a9215eef0db71 --hash=sha256:0b9de86fbe8a646c0644df9e1396d6941dc6ed0f89be2807e6c52ab39161fd9f --hash=sha256:973f0bc24470ac86b6009baf2800ad3eadfa4ab653b6546ba5c65e9239850f47 --hash=sha256:cb0f0e7183165426b09e2b1235e70909e540ac18e2c6be96070dfe17d7db4d78 --hash=sha256:7707b144c8fcdb30c069ea57b94799cd7601f694ba01b696bbd1832721f37fd0 --hash=sha256:5f1288448991d63c1f6351c9f6d112916b0177ceefaa27d1419427a6ff09f829 --hash=sha256:7205c36a6798251925e4c1763eba0737fec7a95360df8aaa4e74e2f2f6b5cdcf --hash=sha256:9c77711a4e469ea97bdd16e85b5ab0720d6a481862a540ea7216897a10e93d19 --hash=sha256:9e6f0ca64cf9b0125be8fe8b821ba7e0f80427bcee136f83914dff0d81b4f27a --hash=sha256:8caad9de64dc4fc9614f331a04ea220d57aea3fbf997f3e23a298ee67cf4a69c --hash=sha256:9d5fcf99187cb6ec0a27c755aec22774a1ea578fdc2a5734f484c601de4ad6c0 --hash=sha256:bdbb62003cced7739d5c98c5af7a08c819baedf12e09276b2bbf0f3cb47828af --hash=sha256:5f901ec61db76a326f77882c8000139b29f218a7c6b8dd997195bab265602345 --hash=sha256:99f8d1145c11f61340fcd19fd6f3f3ef6306910829f4218daece45e831ceb54e --hash=sha256:da826f12c6fa459c6b2f9e7dff3a57416ac3a6536264f7a10d1ff839d4bc943f --hash=sha256:ebacf0b3325b3fb6f15bc3526f39f39c3576bafa4857493cc90afd2651ec8d36 --hash=sha256:e6eba7d4387f8123dd69852ba6776799150c4019fcb3e1212a3b053d42e151ab --hash=sha256:9addd1d6fe9d3b1efe40c7a37fa5d88fe6cd7e22c7e454617beb382c9c74b11b
+6
View File
@@ -0,0 +1,6 @@
pypdf>=6,<8
Pillow>=12,<14
defusedxml>=0.7,<1
yara-python==4.5.4
rapidocr==3.8.1
onnxruntime==1.24.4
+59
View File
@@ -0,0 +1,59 @@
"""Single-request parser entrypoint, launched only inside the AppContainer."""
import base64
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
def attest_appcontainer():
import ctypes
from ctypes import wintypes as W
if sys.platform != 'win32': raise RuntimeError('Windows AppContainer required')
kernel=ctypes.WinDLL('kernel32',use_last_error=True)
advapi=ctypes.WinDLL('advapi32',use_last_error=True)
kernel.GetCurrentProcess.restype=W.HANDLE
kernel.CloseHandle.argtypes=[W.HANDLE]
advapi.OpenProcessToken.argtypes=[W.HANDLE,W.DWORD,ctypes.POINTER(W.HANDLE)]
advapi.GetTokenInformation.argtypes=[W.HANDLE,ctypes.c_int,ctypes.c_void_p,W.DWORD,ctypes.POINTER(W.DWORD)]
token=W.HANDLE();value=W.DWORD();length=W.DWORD()
if not advapi.OpenProcessToken(kernel.GetCurrentProcess(),8,ctypes.byref(token)):
raise RuntimeError('Process token unavailable')
try:
if not advapi.GetTokenInformation(token,29,ctypes.byref(value),4,ctypes.byref(length)) or value.value!=1:
raise RuntimeError('Uncontained parser process rejected')
# TokenCapabilities is TOKEN_GROUPS; no capability SID may be enabled.
advapi.GetTokenInformation(token,30,None,0,ctypes.byref(length))
if not 4<=length.value<=65536: raise RuntimeError('Capability token unavailable')
capabilities=ctypes.create_string_buffer(length.value)
if not advapi.GetTokenInformation(token,30,capabilities,length.value,ctypes.byref(length)) or ctypes.cast(capabilities,ctypes.POINTER(W.DWORD)).contents.value!=0:
raise RuntimeError('Unexpected parser process capability')
finally:kernel.CloseHandle(token)
def main():
attest_appcontainer()
# Preserve only the broker response pipe; parser/native diagnostics cannot
# contaminate the protocol or leak document content through stderr.
response = os.fdopen(os.dup(sys.stdout.fileno()), 'w', encoding='utf-8', buffering=1)
sink = os.open(os.devnull, os.O_WRONLY)
os.dup2(sink,sys.stdout.fileno());os.dup2(sink,sys.stderr.fileno());os.close(sink)
# No socket listener, deployment credential or filesystem document input.
from engine import MAX_BYTES, inspect_content, scan_download, reference_document, embedding_features
request = json.loads(sys.stdin.buffer.read(MAX_BYTES*2+1))
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')
response.write(json.dumps(result, allow_nan=False))
response.flush()
response.close()
if __name__ == '__main__':
try: main()
except Exception: sys.exit(2)
+177
View File
@@ -0,0 +1,177 @@
"""Pinned offline EmbeddingGemma 2 GGUF worker. No downloads or HTTP inference."""
import atexit
import base64
import hashlib
import io
import json
import math
import os
from pathlib import Path
import queue
import re
import subprocess
import threading
import time
import wave
MODEL_ID="google/embeddinggemma-2"
REVISION="bfcd298762cc34d0357ece5ebdd31791a3a374d8"
DIMENSIONS=768
PROFILE="document-v1"
RUNTIME_REVISION="b11461"
RUNTIME_BUILD="b11461-no-logits-v1"
MAX_CHUNKS=64
MAX_LINE=4*1024*1024
MAX_REQUEST=16*1024*1024
MODEL_FILES={
"embeddinggemma-2-Q8_0.gguf":(309855456,"2188ac1deca4b77dffefd603c2776a9d76d9d74ec01841392982ebb840b09135"),
"mmproj-embeddinggemma-2-Q8_0.gguf":(554821024,"c4a8a52691ecef40618438928bdf9e68379b854e24166f292592353db0aab64f")}
def verify_file(directory,name,size,digest):
if not isinstance(name,str) or not name or Path(name).is_absolute() or ".." in Path(name).parts: raise ValueError("Invalid local model path")
file=(directory/name).resolve()
if not file.is_relative_to(directory) or not file.is_file() or file.stat().st_size!=size: raise ValueError("Local semantic artifact unavailable")
if not re.fullmatch(r"[0-9a-f]{64}",digest): raise ValueError("Invalid artifact digest")
calculated=hashlib.sha256()
with file.open("rb") as source:
while part:=source.read(1024*1024): calculated.update(part)
if calculated.hexdigest()!=digest: raise ValueError("Local semantic artifact verification failed")
return file
def validate_vectors(vectors,expected=None):
if not isinstance(vectors,list) or not 1<=len(vectors)<=MAX_CHUNKS or expected is not None and len(vectors)!=expected: raise ValueError("Invalid semantic vector batch")
for vector in vectors:
if not isinstance(vector,list) or len(vector)!=DIMENSIONS or any(not isinstance(value,(int,float)) or isinstance(value,bool) or not math.isfinite(value) for value in vector): raise ValueError("Invalid semantic output")
norm=math.sqrt(sum(value*value for value in vector))
if not 0.99<=norm<=1.01: raise ValueError("Semantic output is not normalized")
return vectors
def validate_readiness(ready):
if not isinstance(ready,dict) or ready.get("ready") is not True or ready.get("model")!=MODEL_ID or ready.get("dimensions")!=DIMENSIONS or ready.get("runtime")!=RUNTIME_BUILD or ready.get("device")!="cpu" or ready.get("vulkan_module_loaded") is not False:
raise ValueError("Local semantic worker identity mismatch")
class NativeSemanticModel:
def __init__(self,directory,worker_path):
self.directory=Path(directory).resolve(); self.worker_path=Path(worker_path).resolve()
manifest_path=self.directory/"model-manifest.json"
if not manifest_path.is_file() or manifest_path.stat().st_size>65536: raise ValueError("Local model manifest unavailable")
manifest=json.loads(manifest_path.read_text(encoding="utf-8"))
if manifest.get("model_id")!=MODEL_ID or manifest.get("revision")!=REVISION or manifest.get("dimensions")!=DIMENSIONS or manifest.get("quantization")!="Q8_0" or manifest.get("runtime_tag")!=RUNTIME_REVISION: raise ValueError("Pinned Google model identity mismatch")
self.model=verify_file(self.directory,*next((name,*value) for name,value in MODEL_FILES.items() if not name.startswith("mmproj")))
self.projector=verify_file(self.directory,*next((name,*value) for name,value in MODEL_FILES.items() if name.startswith("mmproj")))
runtime_path=self.worker_path.parent/"runtime-manifest.json"
if not runtime_path.is_file() or runtime_path.stat().st_size>65536: raise ValueError("Verified native runtime manifest unavailable")
runtime=json.loads(runtime_path.read_text(encoding="utf-8")); files=runtime.get("files")
if not isinstance(files,list) or not 1<=len(files)<=64 or not any(item.get("path")==self.worker_path.name for item in files): raise ValueError("Invalid native runtime manifest")
for item in files: verify_file(self.worker_path.parent,item["path"],item["bytes"],item["sha256"])
# All DLLs load from the private, verified runtime directory.
verified={item["path"].casefold() for item in files}
if any(file.name.casefold() not in verified for file in self.worker_path.parent.glob("*.dll")): raise ValueError("Unverified runtime module")
self.lock=threading.Lock();self.process=None;self.responses=None
atexit.register(self.close)
def info(self):
return {"model":MODEL_ID,"revision":REVISION,"dimensions":DIMENSIONS,"profile":PROFILE,"artifactFormat":"gguf","quantization":"Q8_0","runtimeRevision":RUNTIME_REVISION,"local":True,"backend":"native-gguf","device":"cpu","modalities":["text","image","audio"]}
def close(self):
process=self.process;self.process=None
if process is not None:
try: process.kill();process.wait(timeout=3)
except Exception: pass
for stream in (process.stdin,process.stdout):
try: stream.close()
except Exception: pass
def _read(self,process,responses):
try:
while True:
line=process.stdout.readline(MAX_LINE+1)
if not line or len(line)>MAX_LINE or not line.endswith(b"\n"): raise ValueError()
response=json.loads(line)
responses.put(response,timeout=1)
except Exception:
try: responses.put({"protocolFailure":True},timeout=1)
except Exception: pass
def _receive(self,timeout):
try:
response=self.responses.get(timeout=timeout)
if not isinstance(response,dict) or response.get("protocolFailure"): raise ValueError()
return response
except Exception:
self.close();raise ValueError("Local semantic worker failed or timed out") from None
def _start(self):
if self.process is not None and self.process.poll() is None: return
self.close();self.responses=queue.Queue(maxsize=2)
environment={key:value for key,value in os.environ.items() if key.upper() in {"SYSTEMROOT","WINDIR","TEMP","TMP"}}
environment.update({"SEARCHI_FORCE_CPU":"1","GGML_CPU_ONLY":"1","GGML_DISABLE_VULKAN":"1"})
self.process=subprocess.Popen([str(self.worker_path),"--model",str(self.model),"--threads","2","--device","cpu"],
cwd=str(self.worker_path.parent),stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,
env=environment,creationflags=getattr(subprocess,"CREATE_NO_WINDOW",0))
threading.Thread(target=self._read,args=(self.process,self.responses),daemon=True).start()
ready=self._receive(20)
try: validate_readiness(ready)
except Exception: self.close();raise
def _request(self,request,timeout=30):
payload=json.dumps(request,ensure_ascii=False,allow_nan=False).encode("utf-8")+b"\n"
if len(payload)>=MAX_REQUEST: raise ValueError("Semantic request budget exceeded")
with self.lock:
self._start()
process=self.process
# A blocked pipe write is also bounded and tears down the process.
timer=threading.Timer(timeout,lambda:process.kill() if process.poll() is None else None);timer.daemon=True;timer.start()
try:
process.stdin.write(payload);process.stdin.flush();response=self._receive(timeout)
if response.get("ok") is not True or response.get("device")!="cpu": raise ValueError("Local semantic inference failed")
return response
except Exception:
self.close();raise ValueError("Local semantic inference failed") from None
finally: timer.cancel();payload=None
def text_vectors(self,text):
if not isinstance(text,str) or not text.strip() or len(text.encode("utf-8"))>1024*1024: raise ValueError("Semantic text budget exceeded")
encoded=text.encode("utf-8");windows=self._request({"op":"windows","text":text,"title":"none"}).get("windows")
if not isinstance(windows,list) or not 1<=len(windows)<=MAX_CHUNKS: raise ValueError("Semantic window budget exceeded")
previous_end=0;parts=[]
for item in windows:
start=item.get("start_byte");end=item.get("end_byte");part=item.get("text")
if not isinstance(start,int) or not isinstance(end,int) or not isinstance(part,str) or start<0 or start>previous_end or end<=previous_end or end>len(encoded) or part.encode("utf-8")!=encoded[start:end]: raise ValueError("Semantic source coverage invalid")
previous_end=end;parts.append(part)
if previous_end!=len(encoded): raise ValueError("Semantic source tail missing")
vectors=[];deadline=time.monotonic()+60
for index in range(0,len(parts),8):
remaining=deadline-time.monotonic()
if remaining<=0: raise ValueError("Semantic inference budget exceeded")
batch=parts[index:index+8];response=self._request({"op":"documents","texts":batch,"title":"none"},min(30,remaining))
vectors.extend(validate_vectors(response.get("vectors"),len(batch)))
return vectors
def image_vectors(self,data):
from PIL import Image,ImageSequence,ImageOps
image=Image.open(io.BytesIO(data));frames=getattr(image,"n_frames",1)
if frames>32 or image.width*image.height*frames>50_000_000: raise ValueError("Semantic image budget exceeded")
vectors=[]
for frame in ImageSequence.Iterator(image):
frame=ImageOps.exif_transpose(frame).convert("RGBA")
background=Image.new("RGBA",frame.size,"white");background.alpha_composite(frame);frame=background.convert("RGB")
scale=min(1,672/max(frame.size));width=max(48,round(frame.width*scale/48)*48);height=max(48,round(frame.height*scale/48)*48)
frame=frame.resize((width,height),Image.Resampling.LANCZOS)
request={"op":"image","projector":str(self.projector),"width":width,"height":height,"data":base64.b64encode(frame.tobytes()).decode("ascii")}
vectors.extend(validate_vectors(self._request(request).get("vectors"),1))
return vectors
def audio_vectors(self,data):
import numpy as np
with wave.open(io.BytesIO(data),"rb") as source:
if source.getnchannels()!=1 or source.getframerate()!=16000 or source.getsampwidth()!=2 or not 0<source.getnframes()<=480000: raise ValueError("Only bounded mono16kHz PCM16 WAV is supported")
payload=source.readframes(source.getnframes())
if len(payload)!=source.getnframes()*2: raise ValueError("Truncated audio data")
pcm=np.frombuffer(payload,dtype="<i2").astype("<f4")/32768
request={"op":"audio","projector":str(self.projector),"data":base64.b64encode(pcm.tobytes()).decode("ascii")}
return validate_vectors(self._request(request).get("vectors"),1)
+109
View File
@@ -0,0 +1,109 @@
import base64
import hmac
import json
import os
import sys
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
# Embedded Python uses an isolated ._pth and does not add the script directory.
# Resolve sibling modules from this service's source, not an old runtime copy.
sys.path.insert(0,os.path.dirname(os.path.abspath(__file__)))
sys.dont_write_bytecode=True
from windows_containment import contain_inspector
# Apply limits before loading parsers, OCR libraries or native model workers.
# An unavailable Windows boundary fails startup rather than silently weakening it.
CONTAINMENT=contain_inspector() if __name__=="__main__" else {"active":False,"kind":"not-service-entrypoint"}
# Broker imports no parser, OCR or model library. All untrusted document work
# runs under a capability-free Windows AppContainer, with no network permission.
MAX_BYTES = 20 * 1024 * 1024
SANDBOX = None
SANDBOX_ENV = {}
if __name__ == "__main__":
if sys.platform != 'win32': raise SystemExit('Production parser isolation requires Windows')
from pathlib import Path
from windows_appcontainer import AppContainer
source = Path(__file__).resolve().parent
python_root = Path(sys.executable).resolve().parent
# Development and packaged roots are derived from our own installation;
# externally configured model paths must already be copied into that tree.
installation = source.parent.parent if source.name == 'inspector' and source.parent.name == 'apps' else source.parent
semantic_root = installation / ('.runtime/windows/semantic' if source.parent.name == 'apps' else 'semantic')
expected_python = installation / ('.runtime/windows/python' if source.parent.name == 'apps' else 'python')
if python_root != expected_python.resolve():
raise SystemExit('Parser runtime must reside in the private installed runtime')
roots = [source, python_root]
if semantic_root.is_dir(): roots.append(semantic_root)
for key in ['EMBEDDING_MODEL_DIR', 'EMBEDDING_WORKER_PATH']:
value = os.environ.get(key)
if value:
resolved = Path(value).resolve()
if not resolved.is_relative_to(semantic_root.resolve()):
raise SystemExit('Model artifacts must reside in the private installed runtime')
SANDBOX_ENV[key] = str(resolved)
SANDBOX_ENV.update({'SEARCHI_FORCE_CPU':'1', 'GGML_CPU_ONLY':'1', 'GGML_DISABLE_VULKAN':'1'})
SANDBOX = AppContainer(roots)
CONTAINMENT['parserIsolation'] = {'active':True, 'kind':'windows-appcontainer-v1', 'networkCapabilities':0, 'privatePipes':True, 'readOnlyRuntime':True}
def dispatch(route, request):
if SANDBOX is None: raise RuntimeError('Isolated parser unavailable')
payload = json.dumps({'route':route, 'data':request['data'], 'policy':request.get('policy',{})}, allow_nan=False).encode('utf-8')
code, output = SANDBOX.run(sys.executable, [str(Path(__file__).with_name('sandbox_worker.py'))],
payload, environment=SANDBOX_ENV, timeout=29, max_output=8*1024*1024)
if code: raise RuntimeError('Isolated parser failed')
result = json.loads(output)
if not isinstance(result, dict): raise RuntimeError('Isolated parser response invalid')
if route in ['/inspect','/scan-download']:
if result.get('decision') not in ['allow','block'] or not isinstance(result.get('coverage',{}).get('complete'),bool):
raise RuntimeError('Isolated parser decision invalid')
if result['decision']=='allow' and not result['coverage']['complete']:
raise RuntimeError('Incomplete parser coverage cannot release content')
return result
TOKEN=os.environ.get("INSPECTOR_TOKEN", "")
if len(TOKEN)<32: raise SystemExit("INSPECTOR_TOKEN must contain at least 32 characters")
LOCK=threading.Lock()
def watch_parent():
parent=os.environ.get('INSPECTOR_PARENT_PID')
if not parent or sys.platform!='win32': return
import ctypes
kernel=ctypes.WinDLL('kernel32',use_last_error=True)
kernel.OpenProcess.argtypes=[ctypes.c_uint32,ctypes.c_int,ctypes.c_uint32]
kernel.OpenProcess.restype=ctypes.c_void_p
kernel.WaitForSingleObject.argtypes=[ctypes.c_void_p,ctypes.c_uint32]
handle=kernel.OpenProcess(0x00100000,False,int(parent))
if not handle: os._exit(0)
while kernel.WaitForSingleObject(handle,1000)==258: pass
os._exit(0)
class Handler(BaseHTTPRequestHandler):
def log_message(self, *_args): pass
def do_POST(self):
if not hmac.compare_digest(self.headers.get("Authorization", ""), "Bearer "+TOKEN): return self.reply(401,{"error":"Inspector authentication required"})
request=None;data=None
try:
length=int(self.headers.get("Content-Length", "0"))
if length<=0 or length>MAX_BYTES*2: return self.reply(413,{"error":"Inspection request too large"})
self.connection.settimeout(30)
request=json.loads(self.rfile.read(length))
data=base64.b64decode(request["data"],validate=True)
if len(data)>MAX_BYTES: return self.reply(413,{"error":"Content too large"})
with LOCK:
if self.path not in ['/inspect','/scan-download','/reference-document','/embed']:
return self.reply(404,{"error":"Not found"})
result=dispatch(self.path,request)
return self.reply(200,result)
except Exception: return self.reply(422,{"error":"Content inspection failed"})
finally:
if isinstance(request,dict): request.clear()
data=None
def reply(self,status,data):
body=json.dumps(data,allow_nan=False).encode();self.send_response(status);self.send_header("Content-Type","application/json");self.send_header("Content-Length",str(len(body)));self.send_header("Cache-Control","no-store");self.end_headers();self.wfile.write(body)
if __name__=="__main__":
threading.Thread(target=watch_parent,daemon=True).start()
server=HTTPServer(("127.0.0.1",int(os.environ.get("INSPECTOR_PORT","4311"))),Handler)
server.timeout=30
print(json.dumps({"port":server.server_address[1],"containment":CONTAINMENT}),flush=True)
server.serve_forever()
+41
View File
@@ -0,0 +1,41 @@
import io
import os
import unittest
import zipfile
from engine import cosine, inspect_content, content_kind
def policy(): return {"dlp":{"maxBytes":1048576,"rules":[{"id":"secret","kind":"literal","value":"Secret Roadmap"}],"semantic":{"enabled":False}}}
class InspectionTests(unittest.TestCase):
def test_text_and_binary(self):
self.assertEqual(inspect_content(b"Public information",policy())["decision"],"allow")
self.assertEqual(inspect_content(b"Secret Roadmap",policy())["ruleId"],"secret")
self.assertEqual(inspect_content(b"\xff\x00",policy())["decision"],"block")
def test_office_extraction(self):
output=io.BytesIO()
with zipfile.ZipFile(output,"w") as archive:
archive.writestr("word/document.xml",'<document xmlns="urn:test"><t>Secret Roadmap</t></document>')
result=inspect_content(output.getvalue(),policy())
self.assertEqual(result["contentType"],"office");self.assertEqual(result["ruleId"],"secret")
def test_pdf_extraction(self):
from pypdf import PdfWriter
from pypdf.generic import DecodedStreamObject,NameObject,DictionaryObject
writer=PdfWriter();page=writer.add_blank_page(300,300)
font=DictionaryObject({NameObject('/Type'):NameObject('/Font'),NameObject('/Subtype'):NameObject('/Type1'),NameObject('/BaseFont'):NameObject('/Helvetica')})
page[NameObject('/Resources')]=DictionaryObject({NameObject('/Font'):DictionaryObject({NameObject('/F1'):writer._add_object(font)})})
content=DecodedStreamObject();content.set_data(b'BT /F1 12 Tf 10 100 Td (Secret Roadmap) Tj ET');page[NameObject('/Contents')]=writer._add_object(content)
output=io.BytesIO();writer.write(output)
result=inspect_content(output.getvalue(),policy());self.assertEqual(result['contentType'],'pdf');self.assertEqual(result['ruleId'],'secret')
def test_semantic_threshold_and_missing_model(self):
p=policy();p['dlp']['semantic']={'enabled':True,'model':'google/embeddinggemma-2','revision':'test-revision','categories':[{'id':'semantic-secret','threshold':.8,'prototype':[1.,0.]}]}
os.environ['EMBEDDING_MODEL_REVISION']='test-revision'
result=inspect_content(b'Confidential paraphrase',p,encoder=lambda *_:[1.,0.]);self.assertEqual(result['ruleId'],'semantic-secret')
result=inspect_content(b'Confidential paraphrase',p,encoder=lambda *_: (_ for _ in ()).throw(ValueError()));self.assertEqual(result['decision'],'block')
def test_cosine_validates(self):
self.assertAlmostEqual(cosine([1,0],[1,0]),1)
with self.assertRaises(ValueError):cosine([0,0],[0,0])
def test_video_has_no_false_safe_coverage(self):
video=b'\x00\x00\x00\x18ftypisom'+b'\x00'*20
self.assertEqual(content_kind(video),'video');result=inspect_content(video,policy());self.assertEqual(result['decision'],'block');self.assertFalse(result['coverage']['complete'])
if __name__=='__main__':unittest.main()
+68
View File
@@ -0,0 +1,68 @@
import unittest
from unittest.mock import patch
import io
from engine import scan_download, reference_document, inspect_content
def policy(source='rule malicious { strings: $a = "MALWARE_TEST_PAYLOAD" condition: $a }'):
return {'security':{'yara':{'enabled':True,'rules':[{'id':'central-malware','source':source}], 'maxScanBytes':1048576}},
'dlp':{'maxBytes':1048576,'rules':[],'semantic':{'enabled':False}}}
class SecurityTests(unittest.TestCase):
def test_real_yara_match(self):
import yara
result=scan_download(b'MZ\x00MALWARE_TEST_PAYLOAD',policy())
self.assertEqual(result['decision'],'block')
self.assertEqual(result['ruleId'],'central-malware')
self.assertTrue(result['coverage']['complete'])
def test_binary_malware_coverage_separate_from_dlp(self):
data=b'MZ\x00\xffbenign'
self.assertEqual(scan_download(data,policy())['decision'],'allow')
self.assertEqual(inspect_content(data,policy())['decision'],'block')
def test_invalid_or_empty_rules_fail_closed(self):
self.assertEqual(scan_download(b'safe',policy('invalid'))['decision'],'block')
p=policy();p['security']['yara']['rules']=[]
self.assertEqual(scan_download(b'safe',p)['decision'],'block')
self.assertEqual(scan_download(b'safe',{})['decision'],'block')
def test_include_and_console_import_rejected(self):
self.assertEqual(scan_download(b'safe',policy('include "C:/secret"'))['decision'],'block')
self.assertEqual(scan_download(b'safe',policy('import "console" rule leak { condition: console.log("secret") }'))['decision'],'block')
def test_size_failure(self):
p=policy();p['security']['yara']['maxScanBytes']=2
self.assertEqual(scan_download(b'large',p)['ruleId'],'yara-size-limit')
def test_reference_registration_contains_no_plaintext(self):
text=b'Internal confidential roadmap for new product launch during next quarter'
result=reference_document(text)
self.assertTrue(result['registrationReady']);self.assertTrue(result['fragmentHashes'])
self.assertNotIn('confidential',str(result))
self.assertFalse(reference_document(b'\xff\x00')['registrationReady'])
def test_training_truncation_never_claims_complete(self):
result=reference_document(' '.join('distinctword'+str(index) for index in range(1200)).encode())
self.assertEqual(len(result['fragmentHashes']),1000)
self.assertGreater(result['coverage']['fragmentHashesTotal'],1000)
self.assertTrue(result['coverage']['fragmentHashesTruncated'])
self.assertTrue(result['coverage']['extractionComplete'])
self.assertFalse(result['coverage']['complete'])
self.assertFalse(result['registrationReady'])
def test_text_training_does_not_create_temporary_content_files(self):
with patch('engine.tempfile.TemporaryDirectory',side_effect=AssertionError('Raw content persistence forbidden')):
result=reference_document(b'Confidential strategic roadmap contains enough words for fingerprint training')
self.assertTrue(result['registrationReady'])
def test_trained_fragments_block_reformatted_excerpt(self):
text=b'Internal confidential roadmap for new product launch during next quarter'
reference=reference_document(text)
p=policy();p['dlp']['rules']=[{'id':'trained','kind':'fragment-sha256','value':value} for value in reference['fragmentHashes']]
result=inspect_content(b'Introduction. INTERNAL confidential roadmap for new product launch during next quarter. Ending.',p)
self.assertEqual(result['ruleId'],'trained')
def test_real_bundled_ocr_offline_detects_image_secret(self):
from PIL import Image, ImageDraw, ImageFont
image=Image.new('RGB',(1000,180),'white')
ImageDraw.Draw(image).text((30,45),'Secret Roadmap',font=ImageFont.truetype('C:/Windows/Fonts/arial.ttf',60),fill='black')
output=io.BytesIO();image.save(output,format='PNG')
p=policy();p['dlp']['rules']=[{'id':'image-secret','kind':'literal','value':'Secret Roadmap'}]
# Inference and lazy initialization must not enter a model-download path.
with patch('rapidocr.utils.download_file.DownloadFile.run',side_effect=AssertionError('Network model download forbidden')):
result=inspect_content(output.getvalue(),p)
self.assertEqual(result['ruleId'],'image-secret')
self.assertTrue(result['coverage']['complete'])
if __name__=='__main__':unittest.main()
+87
View File
@@ -0,0 +1,87 @@
import hashlib
import io
import json
import queue
import tempfile
from pathlib import Path
import unittest
from unittest.mock import patch
from semantic import NativeSemanticModel,validate_vectors,validate_readiness,verify_file,DIMENSIONS,PROFILE,RUNTIME_BUILD
from engine import aggregate_vectors,embedding_features,inspect_content
def vector(index=0):
result=[0.0]*DIMENSIONS;result[index]=1.0;return result
class SemanticProtocolTests(unittest.TestCase):
def model(self,windows):
model=NativeSemanticModel.__new__(NativeSemanticModel)
def request(payload,timeout=30):
if payload['op']=='windows':return {'windows':windows}
return {'vectors':[vector() for _ in payload['texts']]}
model._request=request
return model
def test_unicode_windows_cover_exact_source_and_tail(self):
text='Confidential café 中央 secret';encoded=text.encode()
windows=[{'text':text,'start_byte':0,'end_byte':len(encoded)}]
self.assertEqual(self.model(windows).text_vectors(text),[vector()])
windows[0]['end_byte']-=1
with self.assertRaises(ValueError):self.model(windows).text_vectors(text)
def test_missing_tail_gaps_and_substitution_are_refused(self):
for windows in [[{'text':'hello','start_byte':0,'end_byte':5}],
[{'text':'hello','start_byte':0,'end_byte':5},{'text':'world','start_byte':6,'end_byte':11}],
[{'text':'altered text','start_byte':0,'end_byte':11}]]:
with self.assertRaises(ValueError):self.model(windows).text_vectors('hello world')
def test_full_window_batch_is_not_silently_truncated(self):
text='a'*65
windows=[{'text':'a','start_byte':index,'end_byte':index+1} for index in range(65)]
with self.assertRaises(ValueError):self.model(windows).text_vectors(text)
def test_output_dimensions_finite_norm_and_aggregation(self):
self.assertEqual(validate_vectors([vector()]),[vector()])
for invalid in [[0.0]*768,[1.0]*767,[float('nan')]*768,[float('inf')]*768]:
with self.assertRaises(ValueError):validate_vectors([invalid])
aggregate=aggregate_vectors([vector(0),vector(1)])
self.assertAlmostEqual(sum(value*value for value in aggregate),1)
def test_only_exact_hardened_cpu_worker_build_is_accepted(self):
ready={'ready':True,'model':'google/embeddinggemma-2','dimensions':768,'runtime':RUNTIME_BUILD,'device':'cpu','vulkan_module_loaded':False}
validate_readiness(ready)
for changed in [{'runtime':'b11461'},{'runtime':'b11461-no-logits-v2'},{'device':'gpu'},{'vulkan_module_loaded':True},{'dimensions':128}]:
with self.assertRaises(ValueError):validate_readiness({**ready,**changed})
def test_artifact_hash_size_and_escape_checks(self):
with tempfile.TemporaryDirectory() as directory:
root=Path(directory).resolve();file=root/'artifact';file.write_bytes(b'public model fixture')
digest=hashlib.sha256(file.read_bytes()).hexdigest()
self.assertEqual(verify_file(root,'artifact',file.stat().st_size,digest),file)
with self.assertRaises(ValueError):verify_file(root,'artifact',file.stat().st_size,'0'*64)
with self.assertRaises(ValueError):verify_file(root,'artifact',1,digest)
with self.assertRaises(ValueError):verify_file(root,'../escape',1,digest)
def test_training_features_return_only_vectors_and_metadata(self):
class FixtureModel:
def text_vectors(self,text):return [vector(),vector(1)]
def info(self):return {'model':'google/embeddinggemma-2','revision':'a'*40,'dimensions':768,'profile':PROFILE,'local':True}
with patch('engine.get_model',return_value=FixtureModel()):result=embedding_features(b'Internal secret not to retain in reference data')
self.assertTrue(result['coverage']['complete']);self.assertEqual(result['coverage']['textChunks'],2)
self.assertEqual(len(result['vectors']),2);self.assertNotIn('Internal secret',json.dumps(result))
def test_category_matching_compares_all_reference_prototypes(self):
policy={'dlp':{'maxBytes':1024,'rules':[],'semantic':{'enabled':True,'model':'google/embeddinggemma-2','revision':'a'*40,'categories':[{'id':'reference','prototype':vector(0),'prototypes':[vector(0),vector(1)],'threshold':.9}]}}}
with patch.dict('os.environ',{'EMBEDDING_MODEL_REVISION':'a'*40}):
result=inspect_content(b'Paraphrased protected concept',policy,encoder=lambda *_:vector(1))
self.assertEqual(result['ruleId'],'reference')
def test_worker_timeout_kills_and_sanitizes_errors(self):
model=NativeSemanticModel.__new__(NativeSemanticModel);model.responses=queue.Queue()
with patch.object(model,'close') as close:
with self.assertRaisesRegex(ValueError,'timed out'):model._receive(.001)
close.assert_called_once()
def test_worker_private_exception_text_is_never_returned(self):
import threading
class Process:
stdin=io.BytesIO()
def poll(self):return None
def kill(self):pass
model=NativeSemanticModel.__new__(NativeSemanticModel);model.process=Process();model.lock=threading.Lock()
model._start=lambda:None
model._receive=lambda timeout:{'ok':False,'device':'cpu','error':'PRIVATE DOC CONTENT'}
with patch.object(model,'close') as close:
with self.assertRaises(ValueError) as error:model._request({'op':'documents','texts':['public fixture']})
self.assertNotIn('PRIVATE',str(error.exception));close.assert_called_once()
if __name__=='__main__':unittest.main()
+47
View File
@@ -0,0 +1,47 @@
"""Opt-in operational fixtures using staged real Google weights, CPU only."""
import io
import math
import os
import unittest
import wave
from engine import get_model,embedding_features,inspect_content,cosine
from semantic import validate_vectors,DIMENSIONS
@unittest.skipUnless(os.environ.get('SAFETYNET_TEST_NATIVE_SEMANTIC')=='1','Opt in to verified native CPU fixtures')
class NativeCPUFixtures(unittest.TestCase):
@classmethod
def setUpClass(cls):cls.model=get_model()
@classmethod
def tearDownClass(cls):cls.model.close()
def test_real_paraphrase_blocks_and_unrelated_text_allows(self):
reference=embedding_features(b'Our confidential launch plan schedules the new product announcement for next October.')
protected=b'The secret product rollout will be announced in October next year.'
public=b'The public cafeteria menu includes tomato soup and bread.'
related=embedding_features(protected);unrelated=embedding_features(public)
self.assertGreater(cosine(reference['vector'],related['vector']),.8)
self.assertLess(cosine(reference['vector'],unrelated['vector']),.8)
policy={'dlp':{'maxBytes':1048576,'rules':[],'semantic':{**self.model.info(),'enabled':True,'categories':[{'id':'native-reference','prototype':reference['vector'],'prototypes':reference['vectors'],'threshold':.8}]}}}
self.assertEqual(inspect_content(protected,policy)['ruleId'],'native-reference')
self.assertEqual(inspect_content(public,policy)['decision'],'allow')
def test_native_token_windows_cover_long_document_tail(self):
text='Public synthetic office lunch menu and team meeting schedule. '*600+'Final unique coverage marker.'
result=embedding_features(text.encode())
self.assertGreater(result['coverage']['textChunks'],1)
self.assertTrue(result['coverage']['complete']);validate_vectors(result['vectors'])
def test_native_image_encoder_returns_real_normalized_vector(self):
from PIL import Image,ImageDraw
image=Image.new('RGB',(192,192),'white');draw=ImageDraw.Draw(image)
draw.rectangle((20,20,90,170),fill='blue');draw.ellipse((110,50,175,115),fill='red')
output=io.BytesIO();image.save(output,format='PNG')
vectors=self.model.image_vectors(output.getvalue())
self.assertEqual(len(vectors),1);validate_vectors(vectors)
def test_native_audio_encoder_returns_real_normalized_vector(self):
import numpy as np
samples=(np.sin(np.arange(16000)*2*math.pi*440/16000)*12000).astype('<i2')
output=io.BytesIO()
with wave.open(output,'wb') as stream:
stream.setnchannels(1);stream.setsampwidth(2);stream.setframerate(16000);stream.writeframes(samples.tobytes())
vectors=self.model.audio_vectors(output.getvalue())
self.assertEqual(len(vectors),1);validate_vectors(vectors)
if __name__=='__main__':unittest.main()
+48
View File
@@ -0,0 +1,48 @@
import base64
import json
import os
from pathlib import Path
import queue
import secrets
import subprocess
import sys
import threading
import unittest
import urllib.error
import urllib.request
@unittest.skipUnless(sys.platform=='win32','Production inspector isolation requires Windows')
class InspectorHTTPTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.token=secrets.token_urlsafe(32)
env={key:value for key,value in os.environ.items() if key.upper() in {'SYSTEMROOT','WINDIR','USERPROFILE','LOCALAPPDATA','APPDATA','TEMP','TMP'}}
env.update({'INSPECTOR_TOKEN':cls.token,'INSPECTOR_PORT':'0'})
cls.server=subprocess.Popen([sys.executable,str(Path(__file__).with_name('server.py'))],stdout=subprocess.PIPE,stderr=subprocess.PIPE,env=env,creationflags=subprocess.CREATE_NO_WINDOW)
lines=queue.Queue();threading.Thread(target=lambda:lines.put(cls.server.stdout.readline()),daemon=True).start()
try:
cls.ready=json.loads(lines.get(timeout=40));cls.port=cls.ready['port']
if cls.ready['containment']['parserIsolation']['kind']!='windows-appcontainer-v1':raise RuntimeError('Production sandbox attestation missing')
except Exception:
cls.server.kill();cls.server.wait(timeout=5);cls.server.stdout.close();cls.server.stderr.close();raise
@classmethod
def tearDownClass(cls):
cls.server.kill();cls.server.wait(timeout=5);cls.server.stdout.close();cls.server.stderr.close()
def request(self,data,credential):
policy={'dlp':{'maxBytes':1048576,'rules':[{'id':'secret','kind':'literal','value':'Secret Roadmap'}],'semantic':{'enabled':False}}}
body=json.dumps({'data':base64.b64encode(data).decode(),'policy':policy}).encode()
request=urllib.request.Request(f'http://127.0.0.1:{self.port}/inspect',data=body,headers={'Content-Type':'application/json','Authorization':'Bearer '+credential})
return urllib.request.urlopen(request,timeout=30)
def test_authenticated_content_inspection(self):
with self.request(b'Secret Roadmap',self.token) as response:
result=json.load(response);self.assertEqual(result['decision'],'block');self.assertEqual(result['ruleId'],'secret')
with self.request(b'Public information',self.token) as response:self.assertEqual(json.load(response)['decision'],'allow')
def test_rejects_unauthenticated_content(self):
with self.assertRaises(urllib.error.HTTPError) as error:self.request(b'Public information','wrong')
self.assertEqual(error.exception.code,401);error.exception.close()
def test_actual_parser_isolation_attestation(self):
isolation=self.ready['containment']['parserIsolation'];self.assertTrue(isolation['active']);self.assertEqual(isolation['networkCapabilities'],0);self.assertTrue(isolation['privatePipes']);self.assertTrue(isolation['readOnlyRuntime'])
if __name__=='__main__':unittest.main()
+175
View File
@@ -0,0 +1,175 @@
"""Native OS boundary tests; assertions inspect actual Windows tokens/access."""
import base64
import json
import os
from pathlib import Path
import socket
import subprocess
import sys
import tempfile
import unittest
@unittest.skipUnless(sys.platform == 'win32', 'Windows AppContainer native integration')
class WindowsAppContainerTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
from windows_appcontainer import AppContainer
cls.source = Path(__file__).resolve().parent
cls.runtime = Path(sys.executable).resolve().parent
cls.sandbox = AppContainer([cls.source, cls.runtime], provision=True)
def test_read_only_startup_never_mutates_permissions(self):
from unittest.mock import patch
from windows_appcontainer import AppContainer
with patch('windows_appcontainer.subprocess.run', side_effect=AssertionError('Startup must not invoke icacls')):
sandbox = AppContainer([self.source, self.runtime])
code, output = sandbox.run(sys.executable, ['-c', 'print("read-only-startup")'])
self.assertEqual(code, 0)
self.assertEqual(output.strip(), b'read-only-startup')
def test_unprovisioned_runtime_fails_closed(self):
from unittest.mock import patch
from windows_appcontainer import AppContainer, SandboxError
with tempfile.TemporaryDirectory(prefix='safetynet-unprovisioned-') as folder:
with patch('windows_appcontainer.subprocess.run', side_effect=AssertionError('No automatic grants')):
with self.assertRaisesRegex(SandboxError, 'provisioning is required'):
AppContainer([folder])
def test_real_token_filesystem_network_and_secret_boundary(self):
source = '''import ctypes,json,os,socket,sys
from ctypes import wintypes as W
k=ctypes.WinDLL('kernel32');a=ctypes.WinDLL('advapi32')
k.GetCurrentProcess.restype=W.HANDLE
a.OpenProcessToken.argtypes=[W.HANDLE,W.DWORD,ctypes.POINTER(W.HANDLE)]
a.GetTokenInformation.argtypes=[W.HANDLE,ctypes.c_int,ctypes.c_void_p,W.DWORD,ctypes.POINTER(W.DWORD)]
t=W.HANDLE();a.OpenProcessToken(k.GetCurrentProcess(),8,ctypes.byref(t));v=W.DWORD();n=W.DWORD();a.GetTokenInformation(t,29,ctypes.byref(v),4,ctypes.byref(n))
r={'appContainer':v.value==1,'secretAbsent':'INSPECTOR_TOKEN' not in os.environ,'echo':sys.stdin.buffer.read().decode()}
for key,path,mode in [('privateDenied',sys.argv[1],'rb'),('runtimeWriteDenied',sys.argv[2],'wb')]:
try:open(path,mode).close();r[key]=False
except PermissionError:r[key]=True
for key,kind in [('tcpDenied',socket.SOCK_STREAM),('udpDenied',socket.SOCK_DGRAM)]:
try:
s=socket.socket(type=kind);s.settimeout(2)
if kind==socket.SOCK_STREAM:s.connect(('127.0.0.1',int(sys.argv[3])))
else:s.sendto(b'probe',('127.0.0.1',int(sys.argv[4])))
r[key]=False
except OSError:r[key]=True
finally:s.close()
print(json.dumps(r))
'''
listener = socket.socket();listener.bind(('127.0.0.1',0));listener.listen()
udp = socket.socket(type=socket.SOCK_DGRAM);udp.bind(('127.0.0.1',0));udp.settimeout(0.2)
with tempfile.TemporaryDirectory(prefix='safetynet-private-boundary-') as folder:
private = Path(folder)/'private.txt';private.write_text('synthetic private fixture')
target = self.runtime/'appcontainer-unauthorized-write.txt'
try:
code, output = self.sandbox.run(sys.executable,['-c',source,str(private),str(target),str(listener.getsockname()[1]),str(udp.getsockname()[1])], b'pipe-only fixture')
self.assertEqual(code,0,output.decode(errors='replace'))
result=json.loads(output)
for key in ['appContainer','secretAbsent','privateDenied','runtimeWriteDenied','tcpDenied']:self.assertTrue(result[key],key)
self.assertEqual(result['echo'],'pipe-only fixture')
self.assertFalse(target.exists())
# Windows firewall can silently discard UDP, so verify the live
# receiver instead of trusting sendto's immediate return value.
with self.assertRaises(socket.timeout): udp.recvfrom(1024)
finally:listener.close();udp.close()
def test_child_process_inherits_sandbox(self):
child = "import ctypes,json;from ctypes import wintypes as W;k=ctypes.WinDLL('kernel32');a=ctypes.WinDLL('advapi32');k.GetCurrentProcess.restype=W.HANDLE;a.OpenProcessToken.argtypes=[W.HANDLE,W.DWORD,ctypes.POINTER(W.HANDLE)];a.GetTokenInformation.argtypes=[W.HANDLE,ctypes.c_int,ctypes.c_void_p,W.DWORD,ctypes.POINTER(W.DWORD)];t=W.HANDLE();a.OpenProcessToken(k.GetCurrentProcess(),8,ctypes.byref(t));v=W.DWORD();n=W.DWORD();a.GetTokenInformation(t,29,ctypes.byref(v),4,ctypes.byref(n));print(v.value)"
parent='import subprocess,sys; result=subprocess.run([sys.executable,"-c",'+repr(child)+'],capture_output=True,text=True,timeout=5);print(result.stdout.strip());sys.exit(result.returncode)'
code, output=self.sandbox.run(sys.executable,['-c',parent])
self.assertEqual(code,0,output.decode());self.assertEqual(output.strip(),b'1')
def test_worker_cannot_open_broker_memory(self):
# Test access rights without reading any actual parent memory or secrets.
source='''import ctypes,json,sys
from ctypes import wintypes as W
k=ctypes.WinDLL('kernel32',use_last_error=True)
k.OpenProcess.argtypes=[W.DWORD,W.BOOL,W.DWORD];k.OpenProcess.restype=W.HANDLE
k.CloseHandle.argtypes=[W.HANDLE]
handle=k.OpenProcess(0x0010,False,int(sys.argv[1]))
result={'brokerMemoryReadDenied':not bool(handle),'error':ctypes.get_last_error()}
if handle:k.CloseHandle(handle)
print(json.dumps(result))
'''
code, output=self.sandbox.run(sys.executable,['-c',source,str(os.getpid())])
self.assertEqual(code,0,output.decode())
result=json.loads(output)
self.assertTrue(result['brokerMemoryReadDenied'])
self.assertEqual(result['error'],5)
def test_real_parser_crosses_only_pipe(self):
request={'route':'/inspect','data':base64.b64encode(b'Public text document').decode(),'policy':{'security':{},'dlp':{'rules':[], 'maxBytes':20971520}}}
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'],'allow');self.assertTrue(result['coverage']['complete'])
def test_real_yara_upload_and_download_inside_sandbox(self):
rule={'id':'test-marker','source':'rule Block_Test { strings: $a = "SAFETYNET_BLOCK_TEST" condition: $a }'}
policy={'security':{'yara':{'enabled':True,'rules':[rule],'maxScanBytes':1048576}},'dlp':{'maxBytes':1048576,'rules':[]}}
for route,data,decision in [('/inspect',b'Public information','allow'),('/inspect',b'SAFETYNET_BLOCK_TEST','block'),('/scan-download',bytes([0,1,2,3,255]),'allow'),('/scan-download',b'SAFETYNET_BLOCK_TEST','block')]:
request={'route':route,'data':base64.b64encode(data).decode(),'policy':policy}
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'],decision,(route,result))
if route=='/inspect' and decision=='block':self.assertTrue(result['malware']['coverage']['complete'])
else:self.assertTrue(result['coverage']['complete'])
if decision=='block':self.assertEqual(result['ruleId'],'test-marker')
def test_orphan_worker_is_killed_when_request_ends(self):
import ctypes
from ctypes import wintypes as W
source='import subprocess,sys;child=subprocess.Popen([sys.executable,"-c","import time;time.sleep(60)"]);print(child.pid,flush=True)'
code,output=self.sandbox.run(sys.executable,['-c',source])
self.assertEqual(code,0,output.decode());pid=int(output.strip())
kernel=ctypes.WinDLL('kernel32',use_last_error=True)
kernel.OpenProcess.argtypes=[W.DWORD,W.BOOL,W.DWORD];kernel.OpenProcess.restype=W.HANDLE
kernel.WaitForSingleObject.argtypes=[W.HANDLE,W.DWORD];kernel.CloseHandle.argtypes=[W.HANDLE]
handle=kernel.OpenProcess(0x00100000,False,pid)
if handle:
try:self.assertEqual(kernel.WaitForSingleObject(handle,3000),0)
finally:kernel.CloseHandle(handle)
def test_large_binary_pipe_complete(self):
import hashlib
data=bytes(range(256))*65536
code,output=self.sandbox.run(sys.executable,['-c','import hashlib,sys;print(hashlib.sha256(sys.stdin.buffer.read()).hexdigest())'],data)
self.assertEqual(code,0);self.assertEqual(output.strip().decode(),hashlib.sha256(data).hexdigest())
def test_worker_rejects_unsandboxed_execution(self):
result=subprocess.run([sys.executable,str(self.source/'sandbox_worker.py')],input=b'{}',capture_output=True,timeout=5)
self.assertNotEqual(result.returncode,0);self.assertEqual(result.stdout,b'');self.assertEqual(result.stderr,b'')
def test_broker_dispatch_remains_authenticated_and_isolated(self):
import queue
import secrets
import threading
import urllib.error
import urllib.request
token=secrets.token_urlsafe(32)
env={key:value for key,value in os.environ.items() if key.upper() in {'SYSTEMROOT','WINDIR','USERPROFILE','APPDATA','LOCALAPPDATA','TEMP','TMP'}}
env.update({'INSPECTOR_TOKEN':token,'INSPECTOR_PORT':'0'})
process=subprocess.Popen([sys.executable,str(self.source/'server.py')],stdout=subprocess.PIPE,stderr=subprocess.PIPE,env=env,creationflags=subprocess.CREATE_NO_WINDOW)
lines=queue.Queue();threading.Thread(target=lambda:lines.put(process.stdout.readline()),daemon=True).start()
try:
ready=json.loads(lines.get(timeout=40))
self.assertEqual(ready['containment']['parserIsolation']['kind'],'windows-appcontainer-v1')
request={'data':base64.b64encode(b'Public allowed document').decode(),'policy':{'security':{},'dlp':{'maxBytes':20971520,'rules':[]}}}
url='http://127.0.0.1:'+str(ready['port'])+'/inspect'
body=json.dumps(request).encode()
with self.assertRaises(urllib.error.HTTPError) as denied:urllib.request.urlopen(urllib.request.Request(url,data=body),timeout=5)
self.assertEqual(denied.exception.code,401)
with urllib.request.urlopen(urllib.request.Request(url,data=body,headers={'Authorization':'Bearer '+token}),timeout=30) as response:result=json.load(response)
self.assertEqual(result['decision'],'allow');self.assertTrue(result['coverage']['complete'])
finally:
process.kill();process.wait(timeout=5)
process.stdout.close();process.stderr.close()
def test_deadline_and_response_bound_fail_closed(self):
from windows_appcontainer import SandboxError
with self.assertRaises(SandboxError):self.sandbox.run(sys.executable,['-c','import time;time.sleep(30)'],timeout=0.2)
with self.assertRaises(SandboxError):self.sandbox.run(sys.executable,['-c','print("x"*10000)'],max_output=100)
if __name__=='__main__':unittest.main()
@@ -0,0 +1,83 @@
import json
import os
import subprocess
import sys
import unittest
from pathlib import Path
@unittest.skipUnless(sys.platform == "win32", "Windows Job Object integration")
class WindowsContainmentTests(unittest.TestCase):
def run_child(self, source):
source = f"import sys; sys.path.insert(0, {str(Path(__file__).parent)!r});\n" + source
return subprocess.run([sys.executable, "-c", source], cwd=Path(__file__).parent,
capture_output=True, text=True, timeout=20,
creationflags=subprocess.CREATE_NO_WINDOW)
def test_process_limit_and_no_breakaway(self):
result = self.run_child("""
import json, subprocess, sys
from windows_containment import contain_inspector
status=contain_inspector()
children=[]
try:
for i in range(3):
children.append(subprocess.Popen([sys.executable,'-c','import time; time.sleep(15)'],creationflags=subprocess.CREATE_NO_WINDOW))
denied=False
try:
extra=subprocess.Popen([sys.executable,'-c','pass'],creationflags=subprocess.CREATE_NO_WINDOW)
extra.wait()
except OSError:
denied=True
breakaway=False
try:
escaped=subprocess.Popen([sys.executable,'-c','pass'],creationflags=subprocess.CREATE_NO_WINDOW | subprocess.CREATE_BREAKAWAY_FROM_JOB)
escaped.wait()
except OSError:
breakaway=True
print(json.dumps({'status':status,'denied':denied,'breakawayDenied':breakaway}),flush=True)
finally:
for child in children:
child.terminate();child.wait()
""")
self.assertEqual(result.returncode, 0, result.stderr)
proof = json.loads(result.stdout)
self.assertTrue(proof["status"]["active"])
self.assertEqual(proof["status"]["memoryBytes"], 4 * 1024 ** 3)
self.assertTrue(proof["denied"])
self.assertTrue(proof["breakawayDenied"])
def test_descendant_is_killed_when_owner_is_terminated(self):
source = """
import subprocess,sys,time
from windows_containment import contain_inspector
contain_inspector()
child=subprocess.Popen([sys.executable,'-c','import time; time.sleep(60)'],creationflags=subprocess.CREATE_NO_WINDOW)
print(child.pid,flush=True)
time.sleep(60)
"""
source = f"import sys; sys.path.insert(0, {str(Path(__file__).parent)!r});\n" + source
owner = subprocess.Popen([sys.executable, "-c", source], cwd=Path(__file__).parent,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
creationflags=subprocess.CREATE_NO_WINDOW)
handle = None
try:
# Opening the descendant before termination permits a reliable wait,
# avoiding PID reuse or process enumeration heuristics.
child_pid = int(owner.stdout.readline())
import ctypes
kernel = ctypes.WinDLL("kernel32", use_last_error=True)
kernel.OpenProcess.argtypes = [ctypes.c_uint32, ctypes.c_int, ctypes.c_uint32]
kernel.OpenProcess.restype = ctypes.c_void_p
kernel.WaitForSingleObject.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
kernel.CloseHandle.argtypes = [ctypes.c_void_p]
handle = kernel.OpenProcess(0x00100000, False, child_pid)
self.assertTrue(handle)
owner.terminate();owner.wait(timeout=10)
self.assertEqual(kernel.WaitForSingleObject(handle, 10000), 0)
finally:
if owner.poll() is None:
owner.terminate();owner.wait(timeout=10)
if handle:
kernel.CloseHandle(handle)
owner.stdout.close();owner.stderr.close()
@@ -0,0 +1,363 @@
{
"purpose": "Unused abandoned ONNX-tokenizer experiment; do not delete locked dependencies",
"distributions": [
{
"name": "anyio",
"version": "4.15.1",
"requires": [
"exceptiongroup>=1.0.2; python_version < \"3.11\"",
"idna>=2.8",
"typing_extensions>=4.16.0; python_version < \"3.15\"",
"trio>=0.32.0; extra == \"trio\""
],
"topLevelPaths": [
"anyio",
"anyio-4.15.1.dist-info"
]
},
{
"name": "click",
"version": "8.5.0",
"requires": [],
"topLevelPaths": [
"click",
"click-8.5.0.dist-info"
]
},
{
"name": "filelock",
"version": "4.1.1",
"requires": [],
"topLevelPaths": [
"filelock",
"filelock-4.1.1.dist-info"
]
},
{
"name": "fsspec",
"version": "2026.9.0",
"requires": [
"adlfs; extra == 'abfs'",
"adlfs; extra == 'adl'",
"pyarrow>=1; extra == 'arrow'",
"dask; extra == 'dask'",
"distributed; extra == 'dask'",
"pre-commit; extra == 'dev'",
"ruff>=0.5; extra == 'dev'",
"numpydoc; extra == 'doc'",
"sphinx; extra == 'doc'",
"sphinx-design; extra == 'doc'",
"sphinx-rtd-theme; extra == 'doc'",
"yarl; extra == 'doc'",
"dropbox; extra == 'dropbox'",
"dropboxdrivefs; extra == 'dropbox'",
"requests; extra == 'dropbox'",
"adlfs; extra == 'full'",
"aiohttp!=4.0.0a0,!=4.0.0a1; extra == 'full'",
"dask; extra == 'full'",
"distributed; extra == 'full'",
"dropbox; extra == 'full'",
"dropboxdrivefs; extra == 'full'",
"fusepy; extra == 'full'",
"gcsfs>=2026.4.0; extra == 'full'",
"libarchive-c; extra == 'full'",
"ocifs; extra == 'full'",
"panel; extra == 'full'",
"paramiko; extra == 'full'",
"pyarrow>=1; extra == 'full'",
"pygit2; extra == 'full'",
"requests; extra == 'full'",
"s3fs>=2026.6.0; extra == 'full'",
"smbprotocol; extra == 'full'",
"tqdm; extra == 'full'",
"fusepy; extra == 'fuse'",
"gcsfs>=2026.4.0; extra == 'gcs'",
"pygit2; extra == 'git'",
"requests; extra == 'github'",
"gcsfs>=2026.4.0; extra == 'gs'",
"panel; extra == 'gui'",
"pyarrow>=1; extra == 'hdfs'",
"aiohttp!=4.0.0a0,!=4.0.0a1; extra == 'http'",
"libarchive-c; extra == 'libarchive'",
"ocifs; extra == 'oci'",
"s3fs>=2026.6.0; extra == 's3'",
"paramiko; extra == 'sftp'",
"smbprotocol; extra == 'smb'",
"paramiko; extra == 'ssh'",
"aiohttp!=4.0.0a0,!=4.0.0a1; extra == 'test'",
"numpy; extra == 'test'",
"pytest; extra == 'test'",
"pytest-asyncio!=0.22.0; extra == 'test'",
"pytest-benchmark; extra == 'test'",
"pytest-cov; extra == 'test'",
"pytest-mock; extra == 'test'",
"pytest-recording; extra == 'test'",
"pytest-rerunfailures; extra == 'test'",
"requests; extra == 'test'",
"aiobotocore<3.0.0,>=2.5.4; extra == 'test-downstream'",
"dask[dataframe,test]; extra == 'test-downstream'",
"moto[server]<5,>4; extra == 'test-downstream'",
"pytest-timeout; extra == 'test-downstream'",
"xarray; extra == 'test-downstream'",
"zarr; extra == 'test-downstream'",
"adlfs; extra == 'test-full'",
"aiohttp!=4.0.0a0,!=4.0.0a1; extra == 'test-full'",
"backports-zstd; (python_version < '3.14') and extra == 'test-full'",
"cloudpickle; extra == 'test-full'",
"dask; extra == 'test-full'",
"distributed; extra == 'test-full'",
"dropbox; extra == 'test-full'",
"dropboxdrivefs; extra == 'test-full'",
"fastparquet; extra == 'test-full'",
"fusepy; extra == 'test-full'",
"gcsfs>=2026.4.0; extra == 'test-full'",
"jinja2; extra == 'test-full'",
"kerchunk; extra == 'test-full'",
"libarchive-c; extra == 'test-full'",
"lz4; extra == 'test-full'",
"notebook; extra == 'test-full'",
"numpy; extra == 'test-full'",
"ocifs; extra == 'test-full'",
"pandas<3.0.0; extra == 'test-full'",
"panel; extra == 'test-full'",
"paramiko; extra == 'test-full'",
"pyarrow>=1; extra == 'test-full'",
"pyftpdlib; extra == 'test-full'",
"pygit2; extra == 'test-full'",
"pytest; extra == 'test-full'",
"pytest-asyncio!=0.22.0; extra == 'test-full'",
"pytest-benchmark; extra == 'test-full'",
"pytest-cov; extra == 'test-full'",
"pytest-mock; extra == 'test-full'",
"pytest-recording; extra == 'test-full'",
"pytest-rerunfailures; extra == 'test-full'",
"python-snappy; extra == 'test-full'",
"requests; extra == 'test-full'",
"s3fs>=2026.6.0; extra == 'test-full'",
"smbprotocol; extra == 'test-full'",
"tqdm; extra == 'test-full'",
"urllib3; extra == 'test-full'",
"zarr<3.2.0; extra == 'test-full'",
"zstandard; (python_version < '3.14') and extra == 'test-full'",
"tqdm; extra == 'tqdm'"
],
"topLevelPaths": [
"fsspec",
"fsspec-2026.9.0.dist-info"
]
},
{
"name": "h11",
"version": "0.16.0",
"requires": [],
"topLevelPaths": [
"h11",
"h11-0.16.0.dist-info"
]
},
{
"name": "hf-xet",
"version": "1.7.0",
"requires": [
"pytest ; extra == 'tests'"
],
"topLevelPaths": [
"hf_xet",
"hf_xet-1.7.0.dist-info"
]
},
{
"name": "httpcore",
"version": "1.0.9",
"requires": [
"certifi",
"h11>=0.16",
"anyio<5.0,>=4.0; extra == 'asyncio'",
"h2<5,>=3; extra == 'http2'",
"socksio==1.*; extra == 'socks'",
"trio<1.0,>=0.22.0; extra == 'trio'"
],
"topLevelPaths": [
"httpcore",
"httpcore-1.0.9.dist-info"
]
},
{
"name": "httpx",
"version": "0.28.1",
"requires": [
"anyio",
"certifi",
"httpcore==1.*",
"idna",
"brotli; (platform_python_implementation == 'CPython') and extra == 'brotli'",
"brotlicffi; (platform_python_implementation != 'CPython') and extra == 'brotli'",
"click==8.*; extra == 'cli'",
"pygments==2.*; extra == 'cli'",
"rich<14,>=10; extra == 'cli'",
"h2<5,>=3; extra == 'http2'",
"socksio==1.*; extra == 'socks'",
"zstandard>=0.18.0; extra == 'zstd'"
],
"topLevelPaths": [
"httpx",
"httpx-0.28.1.dist-info"
]
},
{
"name": "huggingface_hub",
"version": "1.33.0",
"requires": [
"click<9.0.0,>=8.4.2",
"filelock>=3.10.0",
"fsspec>=2023.5.0",
"hf-xet<2.0.0,>=1.6.0; platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"",
"httpx<1,>=0.23.0",
"packaging>=20.9",
"pyyaml>=5.1",
"tomli>=1.1.0; python_version < \"3.11\"",
"tqdm>=4.42.1",
"typing-extensions>=4.1.0",
"authlib>=1.3.2; extra == \"oauth\"",
"fastapi; extra == \"oauth\"",
"httpx; extra == \"oauth\"",
"itsdangerous; extra == \"oauth\"",
"torch; extra == \"torch\"",
"safetensors[torch]; extra == \"torch\"",
"toml; extra == \"fastai\"",
"fastai>=2.4; extra == \"fastai\"",
"fastcore>=1.3.27; extra == \"fastai\"",
"hf-xet<2.0.0,>=1.6.0; extra == \"hf-xet\"",
"mcp<2.0.0,>=1.9.4; extra == \"mcp\"",
"authlib>=1.3.2; extra == \"testing\"",
"fastapi; extra == \"testing\"",
"httpx; extra == \"testing\"",
"itsdangerous; extra == \"testing\"",
"jedi; extra == \"testing\"",
"Jinja2; extra == \"testing\"",
"pytest>=8.4.2; extra == \"testing\"",
"pytest-cov; extra == \"testing\"",
"pytest-env; extra == \"testing\"",
"pytest-xdist; extra == \"testing\"",
"pytest-vcr; extra == \"testing\"",
"pytest-asyncio; extra == \"testing\"",
"pytest-rerunfailures>=16.2; extra == \"testing\"",
"pytest-mock; extra == \"testing\"",
"urllib3<2.0; extra == \"testing\"",
"soundfile; extra == \"testing\"",
"Pillow; extra == \"testing\"",
"numpy; extra == \"testing\"",
"duckdb; extra == \"testing\"",
"fastapi; extra == \"testing\"",
"gradio>=5.0.0; extra == \"gradio\"",
"requests; extra == \"gradio\"",
"typing-extensions>=4.8.0; extra == \"typing\"",
"types-PyYAML; extra == \"typing\"",
"types-simplejson; extra == \"typing\"",
"types-toml; extra == \"typing\"",
"types-tqdm; extra == \"typing\"",
"types-urllib3; extra == \"typing\"",
"ruff>=0.9.0; extra == \"quality\"",
"mypy==1.15.0; extra == \"quality\"",
"libcst>=1.4.0; extra == \"quality\"",
"ty; extra == \"quality\"",
"authlib>=1.3.2; extra == \"all\"",
"fastapi; extra == \"all\"",
"httpx; extra == \"all\"",
"itsdangerous; extra == \"all\"",
"jedi; extra == \"all\"",
"Jinja2; extra == \"all\"",
"pytest>=8.4.2; extra == \"all\"",
"pytest-cov; extra == \"all\"",
"pytest-env; extra == \"all\"",
"pytest-xdist; extra == \"all\"",
"pytest-vcr; extra == \"all\"",
"pytest-asyncio; extra == \"all\"",
"pytest-rerunfailures>=16.2; extra == \"all\"",
"pytest-mock; extra == \"all\"",
"urllib3<2.0; extra == \"all\"",
"soundfile; extra == \"all\"",
"Pillow; extra == \"all\"",
"numpy; extra == \"all\"",
"duckdb; extra == \"all\"",
"fastapi; extra == \"all\"",
"ruff>=0.9.0; extra == \"all\"",
"mypy==1.15.0; extra == \"all\"",
"libcst>=1.4.0; extra == \"all\"",
"ty; extra == \"all\"",
"typing-extensions>=4.8.0; extra == \"all\"",
"types-PyYAML; extra == \"all\"",
"types-simplejson; extra == \"all\"",
"types-toml; extra == \"all\"",
"types-tqdm; extra == \"all\"",
"types-urllib3; extra == \"all\"",
"authlib>=1.3.2; extra == \"dev\"",
"fastapi; extra == \"dev\"",
"httpx; extra == \"dev\"",
"itsdangerous; extra == \"dev\"",
"jedi; extra == \"dev\"",
"Jinja2; extra == \"dev\"",
"pytest>=8.4.2; extra == \"dev\"",
"pytest-cov; extra == \"dev\"",
"pytest-env; extra == \"dev\"",
"pytest-xdist; extra == \"dev\"",
"pytest-vcr; extra == \"dev\"",
"pytest-asyncio; extra == \"dev\"",
"pytest-rerunfailures>=16.2; extra == \"dev\"",
"pytest-mock; extra == \"dev\"",
"urllib3<2.0; extra == \"dev\"",
"soundfile; extra == \"dev\"",
"Pillow; extra == \"dev\"",
"numpy; extra == \"dev\"",
"duckdb; extra == \"dev\"",
"fastapi; extra == \"dev\"",
"ruff>=0.9.0; extra == \"dev\"",
"mypy==1.15.0; extra == \"dev\"",
"libcst>=1.4.0; extra == \"dev\"",
"ty; extra == \"dev\"",
"typing-extensions>=4.8.0; extra == \"dev\"",
"types-PyYAML; extra == \"dev\"",
"types-simplejson; extra == \"dev\"",
"types-toml; extra == \"dev\"",
"types-tqdm; extra == \"dev\"",
"types-urllib3; extra == \"dev\""
],
"topLevelPaths": [
"huggingface_hub",
"huggingface_hub-1.33.0.dist-info"
]
},
{
"name": "tokenizers",
"version": "0.22.2",
"requires": [
"huggingface-hub>=0.16.4,<2.0",
"pytest ; extra == 'testing'",
"pytest-asyncio ; extra == 'testing'",
"requests ; extra == 'testing'",
"numpy ; extra == 'testing'",
"datasets ; extra == 'testing'",
"ruff ; extra == 'testing'",
"ty ; extra == 'testing'",
"sphinx ; extra == 'docs'",
"sphinx-rtd-theme ; extra == 'docs'",
"setuptools-rust ; extra == 'docs'",
"tokenizers[testing] ; extra == 'dev'"
],
"topLevelPaths": [
"tokenizers",
"tokenizers-0.22.2.dist-info"
]
},
{
"name": "typing_extensions",
"version": "4.16.0",
"requires": [],
"topLevelPaths": [
"__pycache__",
"typing_extensions-4.16.0.dist-info",
"typing_extensions.py"
]
}
]
}
+271
View File
@@ -0,0 +1,271 @@
"""Capability-free Windows AppContainer process launcher.
The HTTP broker must stay outside this boundary. Only approved immutable runtime,
parser source and model directories receive read/execute access. Documents travel
through anonymous pipes, never a temporary plaintext file. No network capability
or loopback exemption is assigned. Windows supplies the package-private storage.
"""
import ctypes
import hashlib
import os
import subprocess
import sys
import threading
from pathlib import Path
from ctypes import wintypes as W
class SandboxError(RuntimeError):
pass
def _verify_read_acl(path, sid):
"""Read the DACL directly; startup never needs WRITE_DAC or icacls."""
a = ctypes.WinDLL('advapi32', use_last_error=True)
k = ctypes.WinDLL('kernel32', use_last_error=True)
a.GetNamedSecurityInfoW.argtypes = [W.LPWSTR, W.DWORD, W.DWORD, ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p), ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p)]
a.GetNamedSecurityInfoW.restype = W.DWORD
a.GetAce.argtypes = [ctypes.c_void_p, W.DWORD, ctypes.POINTER(ctypes.c_void_p)]
a.GetAce.restype = W.BOOL
a.EqualSid.argtypes = [ctypes.c_void_p, ctypes.c_void_p]; a.EqualSid.restype = W.BOOL
k.LocalFree.argtypes = [ctypes.c_void_p]; k.LocalFree.restype = ctypes.c_void_p
dacl = ctypes.c_void_p(); descriptor = ctypes.c_void_p()
if a.GetNamedSecurityInfoW(str(path), 1, 4, None, None, ctypes.byref(dacl), None, ctypes.byref(descriptor)):
raise SandboxError('Approved runtime ACL could not be inspected')
try:
if not dacl: raise SandboxError('Approved runtime has unrestricted access')
count = ctypes.c_ushort.from_address(dacl.value + 4).value
allow = 0
required = 0x1200a9 # FILE_GENERIC_READ | FILE_GENERIC_EXECUTE
for index in range(count):
ace = ctypes.c_void_p()
if not a.GetAce(dacl, index, ctypes.byref(ace)): raise SandboxError('Runtime ACL is invalid')
kind = ctypes.c_ubyte.from_address(ace.value).value
flags = ctypes.c_ubyte.from_address(ace.value + 1).value
if flags & 8: continue # INHERIT_ONLY does not apply to this object.
mask = ctypes.c_uint32.from_address(ace.value + 4).value
# Conservative: a relevant deny ACE requires administrator review.
if kind == 1 and mask & (required | 0x90000000):
raise SandboxError('Approved runtime has conflicting deny permissions')
if kind == 0 and a.EqualSid(ctypes.c_void_p(ace.value + 8), sid):
if mask & 0x10000000: mask |= 0x1f01ff
if mask & 0x80000000: mask |= 0x120089
if mask & 0x20000000: mask |= 0x1200a0
allow |= mask
if allow & required != required:
raise SandboxError('Runtime ACL provisioning is required before inspector startup')
if allow & 0x400d0116: # write/append/EA/attributes/delete/WRITE_DAC/WRITE_OWNER
raise SandboxError('AppContainer runtime access must be read-only')
finally:
k.LocalFree(descriptor)
def _api():
if sys.platform != 'win32':
raise SandboxError('AppContainer requires Windows')
k = ctypes.WinDLL('kernel32', use_last_error=True)
u = ctypes.WinDLL('userenv', use_last_error=True)
a = ctypes.WinDLL('advapi32', use_last_error=True)
for name, args, result in [
('CloseHandle', [W.HANDLE], W.BOOL),
('CreatePipe', [ctypes.POINTER(W.HANDLE), ctypes.POINTER(W.HANDLE), ctypes.c_void_p, W.DWORD], W.BOOL),
('SetHandleInformation', [W.HANDLE, W.DWORD, W.DWORD], W.BOOL),
('InitializeProcThreadAttributeList', [ctypes.c_void_p, W.DWORD, W.DWORD, ctypes.POINTER(ctypes.c_size_t)], W.BOOL),
('UpdateProcThreadAttribute', [ctypes.c_void_p, W.DWORD, ctypes.c_size_t, ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p, ctypes.c_void_p], W.BOOL),
('DeleteProcThreadAttributeList', [ctypes.c_void_p], None),
('CreateProcessW', [W.LPCWSTR, W.LPWSTR, ctypes.c_void_p, ctypes.c_void_p, W.BOOL, W.DWORD, ctypes.c_void_p, W.LPCWSTR, ctypes.c_void_p, ctypes.c_void_p], W.BOOL),
('WaitForSingleObject', [W.HANDLE, W.DWORD], W.DWORD),
('TerminateProcess', [W.HANDLE, W.UINT], W.BOOL),
('GetExitCodeProcess', [W.HANDLE, ctypes.POINTER(W.DWORD)], W.BOOL),
('LocalFree', [ctypes.c_void_p], ctypes.c_void_p),
('CreateJobObjectW', [ctypes.c_void_p, W.LPCWSTR], W.HANDLE),
('SetInformationJobObject', [W.HANDLE, ctypes.c_int, ctypes.c_void_p, W.DWORD], W.BOOL),
('AssignProcessToJobObject', [W.HANDLE, W.HANDLE], W.BOOL),
('ResumeThread', [W.HANDLE], W.DWORD),
]:
fn = getattr(k, name); fn.argtypes = args; fn.restype = result
u.CreateAppContainerProfile.argtypes = [W.LPCWSTR, W.LPCWSTR, W.LPCWSTR, ctypes.c_void_p, W.DWORD, ctypes.POINTER(ctypes.c_void_p)]
u.CreateAppContainerProfile.restype = ctypes.c_long
u.DeriveAppContainerSidFromAppContainerName.argtypes = [W.LPCWSTR, ctypes.POINTER(ctypes.c_void_p)]
u.DeriveAppContainerSidFromAppContainerName.restype = ctypes.c_long
a.ConvertSidToStringSidW.argtypes = [ctypes.c_void_p, ctypes.POINTER(W.LPWSTR)]
a.ConvertSidToStringSidW.restype = W.BOOL
a.FreeSid.argtypes = [ctypes.c_void_p]; a.FreeSid.restype = ctypes.c_void_p
return k, u, a
class AppContainer:
"""Broker-owned launcher. Caller supplies trusted deployment directories."""
def __init__(self, read_directories, *, provision=False):
self.k, u, self.a = _api()
roots_input = [Path(p).absolute() for p in read_directories]
for root in roots_input:
for component in [root, *root.parents]:
if component.lstat().st_file_attributes & 0x400:
raise SandboxError('Reparse point in approved sandbox root')
roots = tuple(sorted({str(p.resolve(strict=True)) for p in roots_input}))
if not roots or any(not Path(p).is_dir() or Path(p).parent == Path(p) for p in roots):
raise SandboxError('Invalid approved sandbox runtime directories')
self.name = 'SafetyNet.Parser.' + hashlib.sha256('\0'.join(roots).encode()).hexdigest()[:24]
self.sid = ctypes.c_void_p()
result = u.CreateAppContainerProfile(self.name, 'SafetyNet parser', 'Offline content inspection', None, 0, ctypes.byref(self.sid))
if result != 0 and u.DeriveAppContainerSidFromAppContainerName(self.name, ctypes.byref(self.sid)) != 0:
raise SandboxError('AppContainer profile could not be established')
sid_text = W.LPWSTR()
if not self.a.ConvertSidToStringSidW(self.sid, ctypes.byref(sid_text)):
raise SandboxError('AppContainer identity unavailable')
try:
self.sid_string = sid_text.value
finally:
self.k.LocalFree(sid_text)
# Package SID grants are specific to this deployment, never Everyone or
# ALL APPLICATION PACKAGES. No writable permission is added to source.
for root in roots:
# Reject junctions/symlinks before recursive grants; /L alone is not
# sufficient evidence that every possible reparse target stays local.
entries = [Path(root)]
for directory, folders, files in os.walk(root, followlinks=False):
for name in folders + files:
entry = Path(directory) / name
if entry.lstat().st_file_attributes & 0x400:
raise SandboxError('Reparse point in approved sandbox runtime')
entries.append(entry)
if provision:
completed = subprocess.run(['icacls.exe', root, '/grant:r', '*' + self.sid_string + ':(RX)', '/T', '/L', '/Q'],
capture_output=True, timeout=60, creationflags=subprocess.CREATE_NO_WINDOW)
if completed.returncode:
raise SandboxError('Approved runtime access could not be established')
for entry in entries:
_verify_read_acl(entry, self.sid)
self.roots = roots
def run(self, executable, arguments, data=b'', environment=None, timeout=30, max_output=8*1024*1024):
"""One bounded pipe exchange. Child descendants remain in broker Job."""
import msvcrt
executable = str(Path(executable).resolve(strict=True))
if not any(Path(executable).is_relative_to(Path(root)) for root in self.roots):
raise SandboxError('Executable is outside approved runtime')
class SA(ctypes.Structure):
_fields_ = [('length', W.DWORD), ('descriptor', ctypes.c_void_p), ('inherit', W.BOOL)]
class SI(ctypes.Structure):
_fields_ = [('cb', W.DWORD), ('reserved', W.LPWSTR), ('desktop', W.LPWSTR), ('title', W.LPWSTR),
('x', W.DWORD), ('y', W.DWORD), ('xs', W.DWORD), ('ys', W.DWORD), ('xc', W.DWORD), ('yc', W.DWORD),
('fill', W.DWORD), ('flags', W.DWORD), ('show', W.WORD), ('reserved2size', W.WORD),
('reserved2', ctypes.c_void_p), ('stdin', W.HANDLE), ('stdout', W.HANDLE), ('stderr', W.HANDLE)]
class SIX(ctypes.Structure):
_fields_ = [('startup', SI), ('attributes', ctypes.c_void_p)]
class PI(ctypes.Structure):
_fields_ = [('process', W.HANDLE), ('thread', W.HANDLE), ('pid', W.DWORD), ('tid', W.DWORD)]
class SC(ctypes.Structure):
_fields_ = [('sid', ctypes.c_void_p), ('capabilities', ctypes.c_void_p), ('count', W.DWORD), ('reserved', W.DWORD)]
security = SA(ctypes.sizeof(SA), None, True)
handles = []
attribute = None
process = PI()
job = None
owned_files = []
try:
ir, iw, or_, ow = W.HANDLE(), W.HANDLE(), W.HANDLE(), W.HANDLE()
for read, write in [(ir, iw), (or_, ow)]:
if not self.k.CreatePipe(ctypes.byref(read), ctypes.byref(write), ctypes.byref(security), 0):
raise SandboxError('Sandbox pipes unavailable')
handles.extend([read.value, write.value])
for parent_handle in [iw, or_]:
if not self.k.SetHandleInformation(parent_handle, 1, 0):
raise SandboxError('Sandbox pipe inheritance unavailable')
size = ctypes.c_size_t()
self.k.InitializeProcThreadAttributeList(None, 2, 0, ctypes.byref(size))
attribute = ctypes.create_string_buffer(size.value)
if not self.k.InitializeProcThreadAttributeList(attribute, 2, 0, ctypes.byref(size)):
raise SandboxError('Sandbox launch attributes unavailable')
caps = SC(self.sid, None, 0, 0)
inheritance = (W.HANDLE * 2)(ir.value, ow.value)
for key, value in [(0x20009, caps), (0x20002, inheritance)]:
if not self.k.UpdateProcThreadAttribute(attribute, 0, key, ctypes.byref(value), ctypes.sizeof(value), None, None):
raise SandboxError('Sandbox launch boundary unavailable')
startup = SIX(); startup.startup.cb = ctypes.sizeof(SIX)
startup.startup.flags = 0x100; startup.startup.stdin = ir; startup.startup.stdout = ow; startup.startup.stderr = ow
startup.attributes = ctypes.cast(attribute, ctypes.c_void_p)
# Explicit environment: no broker credential or inherited deployment secrets.
env = {'SystemRoot': os.environ.get('SystemRoot', 'C:\\Windows'), 'PYTHONDONTWRITEBYTECODE': '1', 'PYTHONIOENCODING':'utf-8'}
for key in ['USERPROFILE','LOCALAPPDATA','APPDATA','TEMP','TMP','WINDIR']:
if key in os.environ: env[key] = os.environ[key]
allowed = {'EMBEDDING_MODEL_DIR','EMBEDDING_WORKER_PATH','SEARCHI_FORCE_CPU','GGML_CPU_ONLY','GGML_DISABLE_VULKAN'}
if any(key not in allowed or not isinstance(value,str) or '\0' in value for key,value in (environment or {}).items()):
raise SandboxError('Unapproved AppContainer environment field')
env.update(environment or {})
environment_block = ctypes.create_unicode_buffer('\0'.join(f'{key}={value}' for key, value in sorted(env.items())) + '\0\0')
command = ctypes.create_unicode_buffer(subprocess.list2cmdline([executable, *arguments]))
if not self.k.CreateProcessW(executable, command, None, None, True, 0x80000|0x400|0x8000000|0x4,
environment_block, self.roots[0], ctypes.byref(startup), ctypes.byref(process)):
raise SandboxError('AppContainer process launch failed: ' + str(ctypes.get_last_error()))
# Suspended launch avoids an execution window before descendant cleanup.
class BasicLimits(ctypes.Structure):
_fields_ = [('processTime', ctypes.c_int64), ('jobTime', ctypes.c_int64), ('flags', W.DWORD),
('minWorking', ctypes.c_size_t), ('maxWorking', ctypes.c_size_t), ('processes', W.DWORD),
('affinity', ctypes.c_size_t), ('priority', W.DWORD), ('scheduling', W.DWORD)]
class Extended(ctypes.Structure):
_fields_ = [('basic', BasicLimits), ('io', ctypes.c_uint64 * 6), ('processMemory', ctypes.c_size_t), ('jobMemory', ctypes.c_size_t), ('peakProcess', ctypes.c_size_t), ('peakJob', ctypes.c_size_t)]
limits = Extended(); limits.basic.flags = 0x2000 | 0x8; limits.basic.processes = 2
job = self.k.CreateJobObjectW(None, None)
if not job or not self.k.SetInformationJobObject(job, 9, ctypes.byref(limits), ctypes.sizeof(limits)) or not self.k.AssignProcessToJobObject(job, process.process):
self.k.TerminateProcess(process.process, 1)
raise SandboxError('AppContainer descendant containment unavailable')
if self.k.ResumeThread(process.thread) == 0xffffffff:
raise SandboxError('AppContainer launch resume failed')
self.k.CloseHandle(process.thread); process.thread = None
for child_handle in [ir.value, ow.value]:
self.k.CloseHandle(child_handle); handles.remove(child_handle)
writer = os.fdopen(msvcrt.open_osfhandle(iw.value, os.O_WRONLY|os.O_BINARY), 'wb', buffering=0)
reader = os.fdopen(msvcrt.open_osfhandle(or_.value, os.O_RDONLY|os.O_BINARY), 'rb', buffering=0)
handles.remove(iw.value); handles.remove(or_.value); owned_files = [writer, reader]
chunks = []; exceeded = threading.Event(); errors = []
def write():
try:
position = 0
while position < len(data):
count = writer.write(memoryview(data)[position:])
if not count: raise OSError()
position += count
except OSError:
pass
finally:
writer.close()
def read():
total = 0
try:
while True:
chunk = reader.read(65536)
if not chunk: break
total += len(chunk)
if total > max_output:
exceeded.set(); self.k.TerminateProcess(process.process, 1); break
chunks.append(chunk)
except OSError:
errors.append(True)
threads = [threading.Thread(target=write, daemon=True), threading.Thread(target=read, daemon=True)]
for thread in threads: thread.start()
if self.k.WaitForSingleObject(process.process, int(timeout*1000)) != 0:
self.k.TerminateProcess(process.process, 1)
self.k.WaitForSingleObject(process.process, 5000)
raise SandboxError('AppContainer inspection deadline exceeded')
self.k.CloseHandle(job);job=None
for thread in threads: thread.join(2)
if exceeded.is_set() or errors or any(thread.is_alive() for thread in threads):
raise SandboxError('AppContainer response failed bounded exchange')
code = W.DWORD()
if not self.k.GetExitCodeProcess(process.process, ctypes.byref(code)):
raise SandboxError('AppContainer process status unavailable')
return code.value, b''.join(chunks)
finally:
if job: self.k.CloseHandle(job);job=None
for file in owned_files:
if not file.closed: file.close()
for handle in handles: self.k.CloseHandle(handle)
if job: self.k.CloseHandle(job)
if process.thread: self.k.CloseHandle(process.thread)
if process.process: self.k.CloseHandle(process.process)
if attribute is not None: self.k.DeleteProcThreadAttributeList(attribute)
+67
View File
@@ -0,0 +1,67 @@
"""Windows resource containment for the inspector and all descendant workers.
This is a Job Object, not a restricted-token or filesystem/network sandbox.
The inspector owns the handle: terminating it also terminates its model workers.
"""
import ctypes
import sys
from ctypes import wintypes
_job_handle = None
MEMORY_LIMIT = 4 * 1024 * 1024 * 1024
PROCESS_LIMIT = 4
def contain_inspector():
global _job_handle
if sys.platform != "win32":
return {"active": False, "kind": "unsupported-platform"}
if _job_handle:
return {"active": True, "kind": "windows-job-v1"}
class BasicLimits(ctypes.Structure):
_fields_ = [("PerProcessUserTimeLimit", ctypes.c_int64),
("PerJobUserTimeLimit", ctypes.c_int64), ("LimitFlags", wintypes.DWORD),
("MinimumWorkingSetSize", ctypes.c_size_t), ("MaximumWorkingSetSize", ctypes.c_size_t),
("ActiveProcessLimit", wintypes.DWORD), ("Affinity", ctypes.c_size_t),
("PriorityClass", wintypes.DWORD), ("SchedulingClass", wintypes.DWORD)]
class IOCounters(ctypes.Structure):
_fields_ = [(name, ctypes.c_uint64) for name in
("ReadOperationCount", "WriteOperationCount", "OtherOperationCount",
"ReadTransferCount", "WriteTransferCount", "OtherTransferCount")]
class ExtendedLimits(ctypes.Structure):
_fields_ = [("BasicLimitInformation", BasicLimits), ("IoInfo", IOCounters),
("ProcessMemoryLimit", ctypes.c_size_t), ("JobMemoryLimit", ctypes.c_size_t),
("PeakProcessMemoryUsed", ctypes.c_size_t), ("PeakJobMemoryUsed", ctypes.c_size_t)]
kernel = ctypes.WinDLL("kernel32", use_last_error=True)
kernel.CreateJobObjectW.argtypes = [ctypes.c_void_p, wintypes.LPCWSTR]
kernel.CreateJobObjectW.restype = wintypes.HANDLE
kernel.SetInformationJobObject.argtypes = [wintypes.HANDLE, ctypes.c_int, ctypes.c_void_p, wintypes.DWORD]
kernel.SetInformationJobObject.restype = wintypes.BOOL
kernel.AssignProcessToJobObject.argtypes = [wintypes.HANDLE, wintypes.HANDLE]
kernel.AssignProcessToJobObject.restype = wintypes.BOOL
kernel.GetCurrentProcess.restype = wintypes.HANDLE
kernel.CloseHandle.argtypes = [wintypes.HANDLE]
handle = kernel.CreateJobObjectW(None, None)
if not handle:
raise RuntimeError("Inspector resource containment could not be created")
limits = ExtendedLimits()
# KILL_ON_JOB_CLOSE | JOB_MEMORY | ACTIVE_PROCESS. No BREAKAWAY flags.
limits.BasicLimitInformation.LimitFlags = 0x2000 | 0x200 | 0x8
limits.BasicLimitInformation.ActiveProcessLimit = PROCESS_LIMIT
limits.JobMemoryLimit = MEMORY_LIMIT
if not kernel.SetInformationJobObject(handle, 9, ctypes.byref(limits), ctypes.sizeof(limits)):
kernel.CloseHandle(handle)
raise RuntimeError("Inspector resource containment limits could not be applied")
if not kernel.AssignProcessToJobObject(handle, kernel.GetCurrentProcess()):
kernel.CloseHandle(handle)
raise RuntimeError("Inspector resource containment could not be attached")
# Retain a non-inheritable owner handle until process exit. Do not close early:
# KILL_ON_JOB_CLOSE also terminates the current inspector process.
_job_handle = handle
return {"active": True, "kind": "windows-job-v1", "memoryBytes": MEMORY_LIMIT,
"maxProcesses": PROCESS_LIMIT, "killsDescendants": True}