engine: worker Deployment from the image (S3 queue with leases, dispatcher --executor queue, engine-image workflow, scoped RustFS policy); merge main

Implemented with Codex. Cluster deploy pending the k3s kubeconfig and the scoped account.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Marius Ciepluch
2026-10-04 14:23:04 +02:00
co-authored by Claude Opus 5.5
35 changed files with 39473 additions and 29 deletions
+47
View File
@@ -0,0 +1,47 @@
name: Engine image
on:
workflow_dispatch:
push:
branches: [main]
paths:
- 'src/**'
- 'benchmarks/learn/engine_trace*.py'
- 'benchmarks/learn/engine_worker_service.py'
- 'Dockerfile'
- '.github/workflows/engine-image.yml'
permissions:
contents: read
packages: write
concurrency:
group: engine-image-${{ github.ref }}
cancel-in-progress: false
jobs:
image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: names
env:
OWNER: ${{ github.repository_owner }}
run: |
echo "image=ghcr.io/${OWNER,,}/openultrasast" >> "$GITHUB_OUTPUT"
echo "sha=sha-${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"
- id: build
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: true
tags: |
${{ steps.names.outputs.image }}:${{ steps.names.outputs.sha }}
${{ steps.names.outputs.image }}:main
- env:
IMAGE: ${{ steps.names.outputs.image }}
DIGEST: ${{ steps.build.outputs.digest }}
run: echo "$IMAGE@$DIGEST" >> "$GITHUB_STEP_SUMMARY"
+1 -1
View File
@@ -52,7 +52,7 @@ COPY benchmarks ./benchmarks
# The core install is zero-dependency by design; `semantic` adds the tree-sitter grammars the candidate # The core install is zero-dependency by design; `semantic` adds the tree-sitter grammars the candidate
# enumerator uses. The LLM endpoint stays optional and is configured by environment, never baked in. # enumerator uses. The LLM endpoint stays optional and is configured by environment, never baked in.
RUN python3 -m venv /venv \ RUN python3 -m venv /venv \
&& /venv/bin/pip install --no-cache-dir -e ".[semantic]" && /venv/bin/pip install --no-cache-dir -e ".[semantic,s3]"
ENV PATH="/venv/bin:${PATH}" ENV PATH="/venv/bin:${PATH}"
# Analysis never needs root. # Analysis never needs root.
+37 -8
View File
@@ -26,6 +26,7 @@ from concurrent.futures import FIRST_COMPLETED, ThreadPoolExecutor, wait
from pathlib import Path from pathlib import Path
from engine_trace_k8s import IMAGE, Kubernetes from engine_trace_k8s import IMAGE, Kubernetes
from engine_trace_queue import Dispatcher
from engine_trace_worker import EXCLUDED_DIRS, safe_path, unit_record, write_json from engine_trace_worker import EXCLUDED_DIRS, safe_path, unit_record, write_json
from openultrasast.learn.examples import read_jsonl from openultrasast.learn.examples import read_jsonl
@@ -44,7 +45,9 @@ SNAPSHOT_FILES = (
"benchmarks/learn/engine_trace.py", "benchmarks/learn/engine_trace.py",
"benchmarks/learn/engine_trace_worker.py", "benchmarks/learn/engine_trace_worker.py",
"benchmarks/learn/engine_trace_k8s.py", "benchmarks/learn/engine_trace_k8s.py",
"src/openultrasast/model/trace.py", "benchmarks/learn/engine_trace_queue.py",
"benchmarks/learn/engine_worker_service.py",
"benchmarks/learn/engine_trace_parse.py",
"src/openultrasast/model/taint.py", "src/openultrasast/model/taint.py",
"src/openultrasast/cpg/queries/taint.sc", "src/openultrasast/cpg/queries/taint.sc",
) )
@@ -628,11 +631,14 @@ def run_pin(pin, source, provenance, inputs, out, args):
write_json(output / "pin.json", {**pin, "questions": prepare_questions(pin, checkout, args.question_deadline)}) write_json(output / "pin.json", {**pin, "questions": prepare_questions(pin, checkout, args.question_deadline)})
name = "ousast-trace-" + pin_name(pin)[:16] name = "ousast-trace-" + pin_name(pin)[:16]
started = time.monotonic() started = time.monotonic()
if executor == "k8s": if executor in {"k8s", "k8s-jobs"}:
done, node = args.kubernetes.execute(source, checkout, output, json.loads((output / "pin.json").read_text())) done, node = args.kubernetes.execute(source, checkout, output, json.loads((output / "pin.json").read_text()))
status, reason = "failed", done.stderr status, reason = "failed", done.stderr
if done.returncode: if done.returncode:
(output / "container.log").write_text(reason) (output / "container.log").write_text(reason)
elif executor == "queue":
done, node = args.dispatcher.execute(checkout, output, json.loads((output / "pin.json").read_text()), pin_name(pin)[:-5])
status, reason = "timeout", f"queue wait deadline {args.queue_timeout}s"
else: else:
command = docker_command(source, checkout, output, name, args.image, args.deadline, args.question_deadline) command = docker_command(source, checkout, output, name, args.image, args.deadline, args.question_deadline)
started = time.monotonic() started = time.monotonic()
@@ -666,12 +672,20 @@ def run_pin(pin, source, provenance, inputs, out, args):
status=status if unit["supported"] else "unsupported", reason=reason if unit["supported"] else unit["reason"] status=status if unit["supported"] else "unsupported", reason=reason if unit["supported"] else unit["reason"]
) )
record["units"].append(row) record["units"].append(row)
if executor != "queue":
record["container_exit"] = done.returncode if done else None record["container_exit"] = done.returncode if done else None
else:
record.setdefault("container_exit", None)
if done is not None and done.returncode != 0: if done is not None and done.returncode != 0:
for row in record["units"]: for row in record["units"]:
if row["supported"]: if row["supported"]:
row.update(status="failed", reason=reason) row.update(status="failed", reason=reason)
record.update(done=True, seconds=time.monotonic() - started, analyzer=provenance, image=args.image) record.update(
done=True,
seconds=time.monotonic() - started,
analyzer=provenance,
image=record.get("image", args.image) if executor == "queue" else args.image,
)
if (output / "container.log").exists(): if (output / "container.log").exists():
shutil.copyfile(output / "container.log", out / pin_name(pin).replace(".json", ".log")) shutil.copyfile(output / "container.log", out / pin_name(pin).replace(".json", ".log"))
except (OSError, ValueError, subprocess.SubprocessError) as exc: except (OSError, ValueError, subprocess.SubprocessError) as exc:
@@ -681,8 +695,10 @@ def run_pin(pin, source, provenance, inputs, out, args):
"analyzer": provenance, "analyzer": provenance,
"units": [unit_record(u, "failed" if u["supported"] else "unsupported", str(exc)) for u in pin["units"]], "units": [unit_record(u, "failed" if u["supported"] else "unsupported", str(exc)) for u in pin["units"]],
} }
if executor == "k8s": if executor in {"k8s", "k8s-jobs"}:
record.update(executor="k8s", node=node, image=args.image) record.update(executor="k8s", node=node, image=args.image)
if executor == "queue":
record.update(executor="queue", worker=record.get("worker", node), pod=record.get("pod"))
save_pin(out / pin_name(pin), record) save_pin(out / pin_name(pin), record)
print( print(
json.dumps({"repo": pin["repo"], "pin": pin["pin"], "statuses": dict(Counter(u["status"] for u in record["units"]))}), json.dumps({"repo": pin["repo"], "pin": pin["pin"], "statuses": dict(Counter(u["status"] for u in record["units"]))}),
@@ -810,7 +826,9 @@ def main():
parser.add_argument("--cache", type=Path, default=Path.home() / ".cache/openultrasast") parser.add_argument("--cache", type=Path, default=Path.home() / ".cache/openultrasast")
parser.add_argument("--out", type=Path, default=DEFAULT_OUT) parser.add_argument("--out", type=Path, default=DEFAULT_OUT)
parser.add_argument("--image", help="default: openultrasast:dev for Docker; public 2.0.1 image for k8s/mixed") parser.add_argument("--image", help="default: openultrasast:dev for Docker; public 2.0.1 image for k8s/mixed")
parser.add_argument("--executor", choices=("docker", "k8s", "mixed"), default="docker") parser.add_argument("--executor", choices=("docker", "k8s", "k8s-jobs", "mixed", "queue"), default="docker")
parser.add_argument("--queue-status", action="store_true")
parser.add_argument("--queue-timeout", type=int, default=3600, help="total queue wait including execution, seconds")
parser.add_argument("--parallel", type=int, default=1, help="total pins in flight; mixed reserves one lane for Docker") parser.add_argument("--parallel", type=int, default=1, help="total pins in flight; mixed reserves one lane for Docker")
parser.add_argument("--kube-context") parser.add_argument("--kube-context")
parser.add_argument("--kube-namespace", default="ousast-engine") parser.add_argument("--kube-namespace", default="ousast-engine")
@@ -829,11 +847,20 @@ def main():
parser.add_argument("--dry-run", action="store_true") parser.add_argument("--dry-run", action="store_true")
parser.add_argument("--summary", action="store_true") parser.add_argument("--summary", action="store_true")
args = parser.parse_args() args = parser.parse_args()
if args.queue_status:
print(json.dumps(Dispatcher(args).status(), indent=2))
return 0
if args.queue_timeout <= 0:
parser.error("--queue-timeout must be positive")
if args.executor == "queue" and (not args.image or "@sha256:" not in args.image):
parser.error("queue requires --image with the deployed image digest")
if args.executor == "queue" and args.deadline > 900:
parser.error("queue deadline must be <= Deployment termination grace (900s)")
if min(args.deadline, args.question_deadline) <= 0 or args.limit < 0: if min(args.deadline, args.question_deadline) <= 0 or args.limit < 0:
parser.error("deadlines must be positive; limit must be nonnegative") parser.error("deadlines must be positive; limit must be nonnegative")
if args.parallel < 1 or (args.executor == "mixed" and args.parallel < 2): if args.parallel < 1 or (args.executor == "mixed" and args.parallel < 2):
parser.error("--parallel must be positive; mixed needs at least 2 total lanes") parser.error("--parallel must be positive; mixed needs at least 2 total lanes")
if (args.executor != "docker" or args.k8s_check) and not args.kube_context: if (args.executor in {"k8s", "k8s-jobs", "mixed"} or args.k8s_check) and not args.kube_context:
parser.error("--kube-context is required for Kubernetes") parser.error("--kube-context is required for Kubernetes")
args.image = args.image or (IMAGE if args.executor != "docker" or args.k8s_check else "openultrasast:dev") args.image = args.image or (IMAGE if args.executor != "docker" or args.k8s_check else "openultrasast:dev")
if args.k8s_check: if args.k8s_check:
@@ -905,12 +932,14 @@ def main():
args.docker_image = image.stdout.strip() args.docker_image = image.stdout.strip()
if not args.docker_image: if not args.docker_image:
raise ValueError("Docker returned no local image identity") raise ValueError("Docker returned no local image identity")
if args.executor != "docker" and any(u["supported"] for pin in plan for u in pin["units"]): if args.executor in {"k8s", "k8s-jobs", "mixed"} and any(u["supported"] for pin in plan for u in pin["units"]):
args.kubernetes = Kubernetes(args) args.kubernetes = Kubernetes(args)
args.kubernetes.namespace() args.kubernetes.namespace()
if args.executor == "queue":
args.dispatcher = Dispatcher(args)
with tempfile.TemporaryDirectory(prefix="trace-source-", dir=args.out) as scratch: with tempfile.TemporaryDirectory(prefix="trace-source-", dir=args.out) as scratch:
source = Path(scratch) / "frozen" source = Path(scratch) / "frozen"
provenance = freeze_source(source) provenance = {"image": args.image, "executor": "queue"} if args.executor == "queue" else freeze_source(source)
failures = run_plan(plan, source, provenance, inputs, args) failures = run_plan(plan, source, provenance, inputs, args)
return 2 if failures else 0 return 2 if failures else 0
+50
View File
@@ -0,0 +1,50 @@
"""Host queue transport: source and task metadata only, never analyzer code."""
from __future__ import annotations
import json
import subprocess
import time
import uuid
from engine_worker_service import PENDING, client_from_env, pack, put, read, result_key, status
class Dispatcher:
def __init__(self, args, client=None, *, pause=time.sleep, clock=time.monotonic):
self.client = client if client is not None else client_from_env()
self.run = uuid.uuid4().hex
self.args, self.pause, self.clock = args, pause, clock
def execute(self, checkout, output, pin, pin_id):
task_id = f"{self.run}-{pin_id}"
task = dict(
run=self.run,
id=pin_id,
task_id=task_id,
pin=pin,
input=f"engine-queue/inputs/{self.run}/{pin_id}/source.tar",
image=self.args.image,
deadline=self.args.deadline,
question_deadline=self.args.question_deadline,
)
try:
self.client.put(task["input"], pack(checkout), {})
put(self.client, PENDING + task_id, task)
end = self.clock() + self.args.queue_timeout
while self.clock() < end:
result = read(self.client, result_key(task))
if result is not None:
(output / "result.json").write_text(json.dumps(result))
self.client.delete(task["input"])
return subprocess.CompletedProcess([], 0, "", ""), result.get("worker")
self.pause(2)
# Removing pending cancels work that has not been claimed; retain source for a live lease.
self.client.delete(PENDING + task_id)
return None, None
except Exception as exc:
# SDK errors can contain endpoint URLs or credentials. Do not persist their text.
raise OSError("queue transport failed") from exc
def status(self):
return status(self.client)
+2 -1
View File
@@ -19,8 +19,9 @@ import time
from collections import Counter from collections import Counter
from pathlib import Path from pathlib import Path
from engine_trace_parse import parse_trace
from openultrasast.cpg.backend import DATAFLOW_OVERLAY, JoernBackend, _render from openultrasast.cpg.backend import DATAFLOW_OVERLAY, JoernBackend, _render
from openultrasast.model.trace import parse_trace
def write_json(path, value): def write_json(path, value):
+315
View File
@@ -0,0 +1,315 @@
"""Image-resident queue workers. Requires strongly consistent S3 GET/PUT/LIST.
Per-task Lamport bakery tickets use only ordinary object operations. A choosing
marker precedes ticket selection; contenders wait for choosing peers and lower
(ticket, worker) pairs. Late arrivals cannot displace a running owner. Leases
assume bounded clock skew (less than MARGIN / 2) and a worker killed by its deadline.
"""
from __future__ import annotations
import argparse
import contextlib
import ctypes
import io
import json
import logging
import multiprocessing
import os
import signal
import socket
import tarfile
import tempfile
import threading
import time
import uuid
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from engine_trace_worker import unit_record, worker
from openultrasast.plane.memory import S3Client, s3_settings
PENDING = "engine-queue/pending/"
CLAIMED = "engine-queue/claimed/"
RESULTS = "engine-results/"
MARGIN = 60
LOG = logging.getLogger(__name__)
def client_from_env():
settings = s3_settings(os.environ)
bucket = os.environ.get("S3_BUCKET")
if not bucket:
raise ValueError("S3_BUCKET is required")
return S3Client(bucket=bucket, **settings)
def read(client, key):
value = client.get(key)
return json.loads(value[0]) if value else None
def put(client, key, value):
client.put(key, json.dumps(value).encode(), {})
def result_key(task):
return f"{RESULTS}{task['run']}/{task['id']}.json"
def pack(root):
stream = io.BytesIO()
with tarfile.open(fileobj=stream, mode="w") as archive:
archive.add(root, arcname=".")
return stream.getvalue()
def unpack(data, root):
with tarfile.open(fileobj=io.BytesIO(data)) as archive:
# Source snapshots need only regular files and directories, never links/devices.
for member in archive.getmembers():
target = (root / member.name).resolve()
if not target.is_relative_to(root.resolve()) or not (member.isfile() or member.isdir()):
raise ValueError("unsafe source archive")
archive.extractall(root, filter="data")
class Queue:
def __init__(self, client, identity=None, *, now=time.time, pause=time.sleep, stop=None):
self.client = client
self.identity = identity or f"{socket.gethostname()}-{uuid.uuid4().hex}"
self.now, self.pause = now, pause
self.stop = stop or threading.Event()
def markers(self, task_id=None):
result = []
for key in self.client.keys(CLAIMED):
if task_id is None or key.rsplit("/", 1)[-1] == task_id:
marker = read(self.client, key)
if marker:
result.append((key, marker))
return result
def tasks(self):
tasks = {}
for key in self.client.keys(PENDING):
task = read(self.client, key)
if task:
tasks[task["task_id"]] = task
# A dead worker may have removed pending already. Its task remains in the lease.
for _, marker in self.markers():
if marker["expires"] < self.now():
task = marker["task"]
tasks.setdefault(task["task_id"], task)
return list(tasks.values())
def claim(self, task):
if read(self.client, result_key(task)) is not None or self.stop.is_set():
return None
key = f"{CLAIMED}{self.identity}/{task['task_id']}"
marker = dict(task=task, ticket=0, expires=self.now() + task["deadline"] + MARGIN)
put(self.client, key, marker)
try:
live = [m for _, m in self.markers(task["task_id"]) if m["expires"] > self.now()]
marker["ticket"] = 1 + max((m["ticket"] for m in live), default=0)
put(self.client, key, marker)
while not self.stop.is_set():
# Do not start once the execution budget no longer fits within our lease.
if self.now() >= marker["expires"] - task["deadline"] - MARGIN / 2:
break
peers = [(k, m) for k, m in self.markers(task["task_id"]) if k != key and m["expires"] > self.now()]
if any(m["ticket"] == 0 for _, m in peers):
self.pause(0.1)
continue
if any((m["ticket"], k) < (marker["ticket"], key) for k, m in peers):
break # loser backs off and can try another pending task
if read(self.client, key) != marker or read(self.client, result_key(task)) is not None:
break
self.client.delete(PENDING + task["task_id"])
return key
except Exception:
self.client.delete(key)
raise
self.client.delete(key)
return None
def process_one(self, execute=None):
for task in self.tasks():
key = self.claim(task)
if key is None:
continue
try:
lease = read(self.client, key)
if lease is None:
continue
budget = min(task["deadline"], lease["expires"] - self.now() - MARGIN / 2)
if budget <= 0:
continue # Leave the expired lease's task available to recovery.
result = (execute or execute_task)(self.client, {**task, "runtime_budget": budget})
result.update(executor="queue", worker=self.identity, pod=socket.gethostname(), image=os.environ.get("ENGINE_IMAGE", ""))
put(self.client, result_key(task), result)
# Publish the result before removing either the input or the claim.
self.client.delete(task["input"])
for old_key, old in self.markers(task["task_id"]):
if old_key == key or old["expires"] < self.now():
self.client.delete(old_key)
return True
except Exception:
# Keep the lease and task for recovery, without logging credential-bearing errors.
LOG.error("task failed; lease retained for recovery")
return False
return False
def _child(pin, root, out, deadline, question_deadline):
os.setsid()
signal.signal(signal.SIGTERM, signal.SIG_DFL)
worker(pin, root, out, deadline, question_deadline)
def adopt_orphans():
# Linux PR_SET_CHILD_SUBREAPER: escaped Joern sessions reparent here, not
# outside the service. There is exactly one task process tree per worker.
if ctypes.CDLL(None, use_errno=True).prctl(36, 1, 0, 0, 0) != 0:
raise OSError("cannot enable task descendant cleanup")
def kill_children():
"""Freeze parents before walking children, including new-session JVMs."""
seen = set()
def freeze(parent):
for entry in Path("/proc").iterdir():
if not entry.name.isdigit() or int(entry.name) in seen:
continue
try:
# comm can contain spaces or parentheses. Fields after it begin at state.
fields = (entry / "stat").read_text().rsplit(")", 1)[1].split()
if int(fields[1]) != parent:
continue
pid = int(entry.name)
os.kill(pid, signal.SIGSTOP)
seen.add(pid)
freeze(pid)
except (FileNotFoundError, ProcessLookupError, PermissionError):
continue
# A child that exited during discovery can reparent descendants to this
# subreaper. Repeat until every remaining descendant is frozen.
while True:
count = len(seen)
freeze(os.getpid())
if len(seen) == count:
break
for pid in seen:
with contextlib.suppress(ProcessLookupError):
os.kill(pid, signal.SIGKILL)
def execute_task(client, task):
adopt_orphans()
pin = task["pin"]
started = time.monotonic()
budget = task.get("runtime_budget", task["deadline"])
status, reason, code = "failed", "worker produced no result", None
with tempfile.TemporaryDirectory(prefix="engine-task-") as scratch:
root, out = Path(scratch) / "case", Path(scratch) / "result.json"
root.mkdir()
try:
if task["deadline"] > 900 or task["deadline"] <= 0:
raise ValueError("deadline exceeds deployment grace")
if task.get("image") != os.environ.get("ENGINE_IMAGE"):
raise ValueError("task image differs from deployed image")
source = client.get(task["input"])
if source is None:
raise ValueError("missing source")
unpack(source[0], root)
# The shipped worker also checks frontend input and graph output for every language.
files = [p for p in root.rglob("*") if p.is_file()]
if not files:
raise ValueError("empty source")
LOG.info("input proof: first file bytes=%d", len(files[0].read_bytes()))
if time.monotonic() - started >= budget:
status, reason = "timeout", "task input deadline"
raise TimeoutError(reason)
process = multiprocessing.get_context("fork").Process(target=_child, args=(pin, root, out, budget, task["question_deadline"]))
process.start()
try:
process.join(max(0, budget - (time.monotonic() - started)))
if process.is_alive():
status, reason = "timeout", "task deadline"
else:
reason = f"worker exit {process.exitcode}"
code = process.exitcode
finally:
# Joern creates separate sessions. Kill the whole descendant tree,
# not merely the Python wrapper's process group.
kill_children()
process.join()
# Reap adopted JVMs and their launchers after the direct child.
while True:
try:
os.waitpid(-1, 0)
except ChildProcessError:
break
record = json.loads(out.read_text()) if out.exists() else {**pin, "units": []}
except Exception:
record = {**pin, "units": []}
reason = "worker input or execution failed"
if not record.get("done") or code != 0:
previous = {u["unit"]: u for u in record["units"]}
record["units"] = [
previous[u["unit"]]
if code is None and previous.get(u["unit"], {}).get("status") in {"path", "asked-nothing"}
else unit_record(u, status if u["supported"] else "unsupported", reason if u["supported"] else u["reason"])
for u in pin["units"]
]
record.update(done=True, container_exit=code, seconds=time.monotonic() - started)
return record
def status(client):
return {
"pending": len(client.keys(PENDING)),
"claimed": len(client.keys(CLAIMED)),
"done": len([k for k in client.keys(RESULTS) if k.endswith(".json")]),
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--port", type=int, default=8080)
args = parser.parse_args()
logging.basicConfig(level=logging.INFO)
stop = threading.Event()
signal.signal(signal.SIGTERM, lambda *_: stop.set())
signal.signal(signal.SIGINT, lambda *_: stop.set())
client = client_from_env()
queue = Queue(client, stop=stop)
class Health(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200 if self.path == "/healthz" and not stop.is_set() else 503)
self.end_headers()
def log_message(self, *_):
pass
server = ThreadingHTTPServer(("0.0.0.0", args.port), Health)
threading.Thread(target=server.serve_forever, daemon=True).start()
try:
while not stop.is_set():
try:
if not queue.process_one():
stop.wait(2)
except Exception:
LOG.error("queue unavailable; retrying")
stop.wait(5)
finally:
server.shutdown()
server.server_close()
if __name__ == "__main__":
main()
+242
View File
@@ -0,0 +1,242 @@
"""Offline audit of completed Joern results; never launches an analyzer or reads corpora.
Missing results stay in coverage, not signal denominators. Observed non-path rows
score zero for path features. Sink kinds are one-hot indicators, never ordinal
category IDs. Both signed and best-direction metrics expose reversed shortcuts.
"""
from __future__ import annotations
import argparse
import ast
import hashlib
import json
import re
from collections import Counter
from pathlib import Path
from slice_leak_audit import THRESHOLD, complete_pairs, metrics
ROOT = Path(__file__).resolve().parents[2]
UNITS = ROOT / "plane/experiments/exp-005-graph-slice.units.jsonl"
RESULTS = Path.home() / "ousast-results/plane/engine-trace"
OUTPUT = ROOT / "benchmarks/measurements/2026-10-04-joern-slice-leak-audit/record.json"
STATUSES = ("path", "asked-nothing", "failed", "timeout", "unsupported", "missing")
ASKED = {"path", "asked-nothing"}
SIGNALS = ("has_path", "shortest_path_steps", "sanitized", "guard_present", "asked")
ASYMMETRY = ("vulnerable_only", "fixed_only", "both", "neither", "not_both_asked")
def materialization_version():
# Read the producer's literal without importing its execution dependencies.
tree = ast.parse(Path(__file__).with_name("engine_trace.py").read_bytes())
for node in tree.body:
if isinstance(node, ast.Assign) and any(isinstance(t, ast.Name) and t.id == "MATERIALIZATION_VERSION" for t in node.targets):
return ast.literal_eval(node.value)
raise ValueError("Producer has no materialization version")
def features(row):
path = row["status"] == "path"
traces = row.get("traces", []) if path else []
candidates = [(i, t) for i, t in enumerate(traces) if t.get("steps")]
index, trace = min(candidates, key=lambda it: len(it[1]["steps"])) if candidates else (None, {})
witnesses = row.get("witness_rows", [])
witness = witnesses[index] if index is not None and index < len(witnesses) else {}
guard = trace.get("guard", {})
sink = witness.get("sinkKind")
if not sink:
# Retain only the call target: arguments, literals and code are not kinds.
match = re.search(r"([\w:$>.\\-]+)\s*\(", str(witness.get("sink", "")))
sink = match[1] if match else "unknown"
return {
"has_path": int(path),
"shortest_path_steps": len(trace["steps"]) if trace else None if path else 0,
"sanitized": int(bool(trace.get("sanitized", witness.get("sanitized", False)))) if trace else None if path else 0,
"guard_present": int(
bool(guard.get("text") or guard.get("bounded") or witness.get("bound") or witness.get("bounded"))
or any("guard" in s.get("roles", []) for s in trace.get("steps", []))
)
if trace
else None
if path
else 0,
"sink_kind": str(sink) if path else "no-path",
"asked": int(row["status"] in ASKED),
}
def scored(rows, signal):
eligible = [r for r in rows if r[signal] is not None]
result = metrics(eligible, signal)
result["excluded_rows"] = len(rows) - len(eligible)
result["flagged"] = (result["pairs"] >= 10 and (result["best_direction_within_pair_accuracy"] or 0) >= THRESHOLD) or (
result["rows"] >= 10 and (result["best_direction_pooled_auc"] or 0) >= THRESHOLD
)
return result
def signal_metrics(rows, kinds):
return {
**{s: scored(rows, s) for s in SIGNALS},
"sink_kind": {kind: scored([{**r, "is_kind": int(r["sink_kind"] == kind)} for r in rows], "is_kind") for kind in kinds},
}
def scope_report(rows, kinds):
pairs = complete_pairs(rows)
asymmetry = Counter()
honest = []
for v, f in pairs:
if not (v["asked"] and f["asked"]):
asymmetry["not_both_asked"] += 1
else:
honest.extend((v, f))
asymmetry[
"both"
if v["has_path"] and f["has_path"]
else "vulnerable_only"
if v["has_path"]
else "fixed_only"
if f["has_path"]
else "neither"
] += 1
observed = [r for r in rows if r["status"] != "missing"]
return {
"rows": len(rows),
"coverage": {s: sum(r["status"] == s for r in rows) for s in STATUSES},
"complete_pairs": len(pairs),
"pair_asymmetry": {s: asymmetry[s] for s in ASYMMETRY},
"both_asked_pairs": len(honest) // 2,
"signals": signal_metrics(observed, kinds),
"both_asked_signals": signal_metrics(honest, kinds),
}
def audit(units, results, *, expected_rows=501, expected_pairs=195):
if len(units) != expected_rows or len({u["unit"] for u in units}) != expected_rows:
raise ValueError(f"Expected {expected_rows} unique units")
if len(complete_pairs(units)) != expected_pairs:
raise ValueError(f"Expected {expected_pairs} complete pairs")
if not results.is_dir():
raise ValueError(f"Results directory is unreadable: {results}")
version = materialization_version()
unit_index = {u["unit"]: u for u in units}
matched = {}
files = []
counts = Counter()
for path in sorted(results.glob("*.json")):
raw = path.read_bytes()
record = json.loads(raw) # Corruption fails loudly; producer uses atomic replace.
if not isinstance(record, dict) or "repo" not in record or "pin" not in record:
continue
stale = record.get("materialization_version") != version
unfinished = record.get("done") is not True
counts["stale"] += stale
counts["unfinished"] += unfinished
counts["skipped"] += stale or unfinished
files.append(
{"path": str(path), "bytes": len(raw), "sha256": hashlib.sha256(raw).hexdigest(), "stale": stale, "unfinished": unfinished}
)
if stale or unfinished:
continue
counts["accepted_files"] += 1
counts["result_rows_read"] += len(record["units"])
for row in record["units"]:
if row["unit"] not in unit_index:
continue
unit = unit_index[row["unit"]]
if any(row.get(k) != unit.get(k) for k in ("family", "label", "pair")):
raise ValueError(f"Result metadata mismatch: {row['unit']}")
if row["status"] not in STATUSES[:-1] or not row.get("source"):
raise ValueError(f"Invalid result status/source: {row['unit']}")
if row["unit"] in matched:
raise ValueError(f"Duplicate current result: {row['unit']}")
matched[row["unit"]] = {
**unit,
"source": row["source"],
"status": row["status"],
"repo": record["repo"],
"pin": record["pin"],
"pin_role": row.get("pin_role"),
**features(row),
}
rows = [
matched.get(u["unit"], {**u, "source": u.get("source", "unknown"), "status": "missing", **features({"status": "missing"})})
for u in units
]
kinds = sorted({r["sink_kind"] for r in rows if r["status"] != "missing"})
scopes = {"overall": scope_report(rows, kinds)}
for field in ("family", "source"):
scopes[f"by_{field}"] = {
value: scope_report([r for r in rows if r[field] == value], kinds) for value in sorted({r[field] for r in rows})
}
return {
"cost_usd": 0,
"materialization_version": version,
"threshold": THRESHOLD,
"minimum_samples": 10,
"unit_rows_read": len(units),
"matched_rows": len(matched),
"inputs": {
**{k: counts[k] for k in ("accepted_files", "result_rows_read", "stale", "unfinished", "skipped")},
"files_read": len(files),
"bytes_read": sum(f["bytes"] for f in files),
"files": files,
},
"semantics": {
"signals": "Shortest nonempty trace (first on ties); its sanitizer, guard and corresponding witness sink call target.",
"missing": "Coverage only; absent source metadata is unknown. Skipped result units are never consumed.",
"non_path": "Observed non-path rows score zero; path rows lacking trace evidence have null trace features.",
"asked": "path or asked-nothing=1; failed, timeout, unsupported=0; missing excluded from metrics.",
"asymmetry": "Disjoint categories; not_both_asked takes precedence over path asymmetry.",
"flags": "Best of both directions, >=0.70 with >=10 pairs for pair accuracy or >=10 rows for AUC.",
"sink_kind": "One binary indicator per observed call target, including no-path/unknown; no ordinal encoding.",
"limits": "Exploratory associations, not proof of leakage; running input snapshot, no multiple-testing correction.",
},
**scopes,
"units": rows,
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--results", type=Path, default=RESULTS)
parser.add_argument("--units", type=Path, default=UNITS)
parser.add_argument("--output", type=Path, default=OUTPUT)
args = parser.parse_args()
raw = args.units.read_bytes()
units = [json.loads(line) for line in raw.splitlines() if line.strip()]
report = audit(units, args.results)
report["units_input"] = {"path": str(args.units), "bytes": len(raw), "sha256": hashlib.sha256(raw).hexdigest()}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n")
overall, inputs = report["overall"], report["inputs"]
lines = [
f"Joern leak audit: $0; materialization_version={report['materialization_version']}",
f"Units read: {len(units)}; bytes={len(raw)}; file={args.units}",
f"Result files read: {inputs['files_read']}; bytes={inputs['bytes_read']}; accepted={inputs['accepted_files']}",
f"Skipped: {inputs['skipped']} (stale={inputs['stale']}, unfinished={inputs['unfinished']}; counts overlap)",
f"Result rows read: {inputs['result_rows_read']}; matched={report['matched_rows']}",
f"Coverage: {overall['coverage']}",
f"Pairs: {overall['complete_pairs']}; both asked={overall['both_asked_pairs']}",
f"Asymmetry: {overall['pair_asymmetry']}",
]
for signal in (*SIGNALS, "sink_kind"):
def brief(value, signal=signal):
if signal == "sink_kind":
return {k: brief_metric(m) for k, m in value.items()}
return brief_metric(value)
lines.append(f"{signal}: all={brief(overall['signals'][signal])}; both asked={brief(overall['both_asked_signals'][signal])}")
lines.append(f"Family/source metrics and per-file read evidence: {args.output}")
print("\n".join(lines))
def brief_metric(metric):
return {k: metric[k] for k in ("rows", "pairs", "within_pair_accuracy", "pooled_auc", "flagged")}
if __name__ == "__main__":
main()
@@ -0,0 +1,326 @@
{
"created": "2026-10-04",
"note": "COUNTS ONLY. Batch 3 of real (vulnerable function, fixed function) pairs whose function survives the fix, mined at zero model cost from reviewed GitHub advisories (pip, npm, maven, composer) and added as pointer-pair corpus benchmarks/pairs/advisory-fixes-3 (source `advisory-fixes-3` in learn/sources.toml); labelled, given static feature records, built into the memory store and embedded. No chat-model call, no Docker, no Joern. Candidate, path, function and repository names stay in the catalog; the pairs the reserved-population guard flagged are withheld (found by bisection with the test output discarded; names never printed).",
"method": {
"enumeration": "gh api /advisories?type=reviewed&ecosystem=<eco>&cwes=<family CWEs>&published=>2023-01-01 per ecosystem (pip, npm, maven, composer) x six compiled families; Go, Ruby and C# not enumerated; newest first",
"advisories_enumerated": 8531,
"advisories_published_after_2026_06_01": 1855,
"automated_screen": "as batch 2 (single family by CWE; linked fix commit, at most 3, one repository; repository not named anywhere under benchmarks/ incl. batches 1-2; excluded v3 file names never opened; no WordPress plugin; one pair per repository; SPDX licence permissive; one-parent commit; the function holding most changed code lines inside its own matcher span, declared once on both sides, body changed, >= 25% of the file's changed code lines) plus three widenings: (a) an advisory linking only a merged pull request uses its single commit or its squash/rebase merge commit; (b) commits of up to 6 non-test source files / 240 changed lines (batch 2: 4 / 120); (c) a second pass over advisories whose CWEs span two families, the family then chosen from the function's diff on review",
"file_reads": "commit metadata and the two versions of each changed source file through the contents API into scratch (no clone, no scanner, engine, plane Run or model on these repositories); scratch deleted after",
"manual_review": "every automated candidate's in-function diff read (agent, no model call); kept only when the named function itself holds the fix; a weak inline check replaced by a stronger helper call counts as the fix, a function that only forwards a flag or calls a fix made elsewhere does not",
"reserved_population_guard": "pytest -q tests/ -k 'reserved or v3' with output discarded, bisected over catalog subsets (18 runs; baseline with an empty batch green); final catalog green",
"rename_check": "canonical full_name of every kept repository from the API: no two kept pairs share a repository, none renamed into a name the benchmarks use",
"mechanism": "family default as batch 2 (injection/output_encoding/deserialization source_reaches_sink, path path_join_user_input, access_control missing_auth_guard, untrusted_destination validation_strength for CWE-601 else source_reaches_sink); provenance = agent"
},
"screened": {
"advisories_examined": 8805,
"outcomes": {
"no fix commit linked": 3480,
"repository already in benchmarks/ (populations v1/v2 or a pair corpus)": 1993,
"licence not permissive or unrecorded": 1249,
"repository already kept (one pair per repository)": 657,
"kept": 398,
"CWEs span several families": 274,
"no function declared on both sides with a changed body": 191,
"large commit (refactor or release, not the fix)": 173,
"fix spread over several commits or repositories": 167,
"merge or root commit": 115,
"no source file changed": 42,
"language outside the feature vocabulary (go/ruby/c#/...)": 32,
"manual review: the chosen function does not hold the fix (comment-only, misattributed, partial or family mismatch)": 19,
"repository unavailable": 9,
"WordPress plugin (population v3 safety)": 4,
"fix commit unavailable": 2
},
"outcomes_note": "single-family pass (screen 2, every advisory examined, no quota met) plus the two-family pass; `kept` = automated candidates; `manual review: the chosen function does not hold the fix (comment-only, misattributed, partial or family mismatch)` = the screen re-meeting a function the 2026-10-03 review had dropped",
"candidates_manually_reviewed": 412,
"kept_after_review": 255,
"review_drops": {
"total": 157,
"change in the function unrelated to the advisory (refactor, rename, formatting, another advisory)": 33,
"function misattributed (matcher span overrun or an undetected enclosing declaration)": 33,
"partial: the function only calls or configures the real fix": 29,
"function diff not small (rewrite or refactor around the fix)": 28,
"unsure": 17,
"family mismatch": 11,
"same fix as a kept pair in a fork or sibling repository (would leak across repository folds)": 3,
"WordPress plugin (population v3 safety; missed by the automated screen)": 2,
"reserved-population guard (flagged by the 2026-10-03 guard run; name not printed)": 1
},
"one_pair_per_repository_drops": 3,
"reserved_population_drops": 2,
"kept": 251
},
"kept": {
"total": 251,
"by_family": {
"access_control": 20,
"deserialization": 9,
"injection": 56,
"output_encoding": 69,
"path": 71,
"untrusted_destination": 26
},
"post_cutoff_by_family": {
"injection": 5,
"output_encoding": 5,
"path": 2
},
"post_cutoff_total": 12,
"by_language": {
"java": 37,
"javascript": 37,
"php": 46,
"python": 83,
"typescript": 48
},
"post_cutoff_by_language": {
"python": 6,
"typescript": 6
},
"by_family_language": {
"access_control:java": 5,
"access_control:javascript": 3,
"access_control:php": 7,
"access_control:python": 2,
"access_control:typescript": 3,
"deserialization:java": 2,
"deserialization:php": 1,
"deserialization:python": 6,
"injection:java": 5,
"injection:javascript": 7,
"injection:php": 9,
"injection:python": 24,
"injection:typescript": 11,
"output_encoding:java": 7,
"output_encoding:javascript": 20,
"output_encoding:php": 12,
"output_encoding:python": 14,
"output_encoding:typescript": 16,
"path:java": 10,
"path:javascript": 4,
"path:php": 10,
"path:python": 34,
"path:typescript": 13,
"untrusted_destination:java": 8,
"untrusted_destination:javascript": 3,
"untrusted_destination:php": 7,
"untrusted_destination:python": 3,
"untrusted_destination:typescript": 5
},
"by_ecosystem": {
"composer": 46,
"maven": 37,
"npm": 81,
"pip": 87
},
"licences": {
"Apache-2.0": 80,
"BSD-2-Clause": 3,
"BSD-3-Clause": 18,
"ISC": 1,
"MIT": 147,
"MPL-2.0": 1,
"Unlicense": 1
},
"split": {
"holdout": 124,
"train": 127
},
"published_by_year": {
"2023": 50,
"2024": 52,
"2025": 66,
"2026": 83
},
"published_range": [
"2023-01-04",
"2026-10-01"
],
"via_pull_request": 13,
"commit_over_120_source_lines": 10,
"two_family_advisory_family_chosen_on_review": 3,
"verification": {
"verified": 251,
"excerpt_bytes_read": 1297516
},
"targets": {
"asked": "injection and untrusted_destination >= 60 each; path, access_control, output_encoding, deserialization >= 30 where advisories allow; >= 250 total",
"met": {
"access_control": false,
"deserialization": false,
"injection": false,
"output_encoding": true,
"path": true,
"untrusted_destination": false
},
"limit": "the reviewed-advisory pool, not the review: every advisory since 2023-01-01 in the four ecosystems was examined; the post-cutoff pool was largely spent by batch 2 (its repositories are now excluded), and untrusted_destination/access_control/deserialization advisories mostly lack a linked single fix commit or sit in repositories the benchmarks already use"
}
},
"labels": {
"command": "ousast learn labels --root . --out <scratch> --snapshot <scratch>",
"rows": [
6981,
7985
],
"pairs_rows": [
1936,
2940
],
"new_pair_rows": 1004
},
"features": {
"command": "benchmarks/learn/build_features.py --labels <the 1004 new rows> --units <merged units.json of `ousast plane harvest --catalog benchmarks/pairs/advisory-fixes-3/catalog.toml` (manifests written, no Run started)> --catalog benchmarks/pairs/advisory-fixes-3/catalog.toml --kinds pairs",
"records": {
"plane:access_control": 40,
"plane:deserialization": 18,
"plane:injection": 112,
"plane:output_encoding": 138,
"plane:path": 142,
"plane:untrusted_destination": 52,
"static:access_control": 40,
"static:deserialization": 18,
"static:injection": 112,
"static:output_encoding": 138,
"static:path": 142,
"static:untrusted_destination": 52
},
"instrument_states_plane_all_families": {
"agree:missing": 164,
"agree:none": 338,
"delta:not_applicable": 502,
"engine:missing": 406,
"engine:none": 96,
"entry_points:not_applicable": 502,
"facts:not_applicable": 502,
"language:ran": 502,
"model_sinks:missing": 502,
"quick:ran": 502,
"roles:ran": 502,
"source:ran": 502,
"verify:missing": 164,
"verify:none": 338
},
"note": "quick rules and deterministic roles ran on each side; engine, verify, agree and model sinks never ran for these pairs (missing/none: null features, never zeros)"
},
"memory_build": {
"command": "ousast learn memory build --labels <labels> --units <merged units.json> --profile static --root . (exit 2: adjudications 9 of 43 anonymous functions, unchanged since 2026-10-01; rows kept)",
"examples": [
1133,
1635
],
"pairs_examples": [
968,
1470
],
"pairs_counts_after": {
"absent_fixed_side": 0,
"examples": 1470,
"labels": 2940,
"no_excerpt": 0,
"no_record": 1470,
"recorded": 1470,
"unread": 0
}
},
"complete_pairs": {
"how": "store example rows (profile static) keyed as learn.experiments.freeze_units pairs them: (repo, candidate, family) holding both labels; whole memory, every family",
"before": 459,
"after": 710,
"by_family": {
"access_control": [
37,
57
],
"config_secrets": [
12,
12
],
"deserialization": [
32,
41
],
"injection": [
68,
124
],
"memory": [
149,
149
],
"output_encoding": [
77,
146
],
"path": [
38,
109
],
"unknown": [
2,
2
],
"untrusted_destination": [
44,
70
]
},
"exp_004_005_units": "195 complete pairs inside exp-004/005's frozen 501 units (six families, evaluation folds); those units are frozen and were not regenerated here"
},
"positives_only_groups": {
"before": 99,
"after": 99,
"groups_both_by_family": {
"access_control": [
37,
57
],
"config_secrets": [
2,
2
],
"deserialization": [
32,
41
],
"injection": [
53,
109
],
"memory": [
5,
5
],
"output_encoding": [
38,
107
],
"path": [
38,
109
],
"unknown": [
2,
2
],
"untrusted_destination": [
42,
68
]
}
},
"embeddings": {
"command": "ousast learn memory embed --budget-usd 0.20 (dry run first: 144,806 estimated tokens, $0.0029)",
"requested": 1479,
"cached": 1004,
"embedded": 475,
"calls": 15,
"tokens": 150339,
"usd": 0.003007
},
"spend": {
"chat_model_calls": 0,
"embedding_usd": 0.003007,
"github_api": "metadata and raw contents only"
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+18 -3
View File
@@ -17,6 +17,21 @@ This page uses two words strictly:
been made.** Section B is guidance, not a record. (kind runs a Kubernetes cluster inside Docker been made.** Section B is guidance, not a record. (kind runs a Kubernetes cluster inside Docker
containers on one machine.) containers on one machine.)
The kind profile runs on a 7.7 GB server VM. Observability is off by default
(`kind_observability = false` in `ops/k8s/profiles/kind.toml`). This saves the measured
930 MiB used by `otel-system`, about 40% of the cluster's 2.3 GB. The smoke Run still
completes with those deployments stopped. To enable the stack, run
`OUSAST_KIND_OBSERVABILITY=1 ops/ax/up.sh` or set `kind_observability = true` in the profile.
`up.sh` stages copied Substrate manifests in a temporary checkout view and installs through
`hack/install-ate-kind.sh`. The local `ops/ax/substrate-kind-lean/` overlay removes the collector,
Prometheus, Jaeger and their supporting resources before installation. This preserves the
installer's secret, CRD and image setup. The upstream checkout is unchanged. The shared
`ate-otel-config` disables trace, metric and log export and the OTEL SDK; it removes the old
collector endpoint and metric timing keys. On an existing cluster, `up.sh` scales the three
observability deployments to zero, replaces the ConfigMap data and rolls its consumers when
the configuration changes. Opting in restores the upstream ConfigMap and observability manifests.
## What runs where today (implemented) ## What runs where today (implemented)
```mermaid ```mermaid
@@ -357,7 +372,7 @@ This section answers the maintainer's questions from the code and the upstream c
The decisions of 2026-10-02 set the target: **two profiles of one code base**. The decisions of 2026-10-02 set the target: **two profiles of one code base**.
- `kind` is this laptop. It stays a first-class local execution mode. Every change is proven here first. - `kind` is this server VM. It stays a first-class local execution mode. Every change is proven here first.
- `k3s` is production. (k3s is a small Kubernetes distribution.) - `k3s` is production. (k3s is a small Kubernetes distribution.)
Everything marked **planned** is not built. The rest is what runs on this host today. Everything marked **planned** is not built. The rest is what runs on this host today.
@@ -368,7 +383,7 @@ flowchart LR
hook["ousast pre-push: local, 30 s deadline"] hook["ousast pre-push: local, 30 s deadline"]
cli["ousast plane run, remote mode (planned)"] cli["ousast plane run, remote mode (planned)"]
end end
subgraph k3s ["k3s cluster (the kind profile is the same picture on one laptop)"] subgraph k3s ["k3s cluster (the kind profile is the same picture on one server VM)"]
subgraph axns ["Namespace ax-system"] subgraph axns ["Namespace ax-system"]
axs["ax-server Deployment + Redis"] axs["ax-server Deployment + Redis"]
end end
@@ -403,7 +418,7 @@ This picture has no inbound path:
- the tasks push their artifacts to the store through the egress gateway; - the tasks push their artifacts to the store through the egress gateway;
- the CLI reads the store. - the CLI reads the store.
Today's EndpointSlice back to a laptop (`ops/ax/receiver-service.yaml.tmpl`) belongs to the kind Today's EndpointSlice back to the server VM (`ops/ax/receiver-service.yaml.tmpl`) belongs to the kind
profile only. It does not appear in production. profile only. It does not appear in production.
### ax, Kubernetes and Agent Substrate in three sentences ### ax, Kubernetes and Agent Substrate in three sentences
+1 -1
View File
@@ -159,7 +159,7 @@ flowchart LR
ax is a Kubernetes application over Agent Substrate. Every Task runs the one runner image as an actor. Each actor ax is a Kubernetes application over Agent Substrate. Every Task runs the one runner image as an actor. Each actor
occupies a whole worker. This code base targets two profiles: occupies a whole worker. This code base targets two profiles:
- `kind` on a laptop (today). - `kind` on a server VM (today).
- `k3s` in production (planned). There the same CLI submits Runs remotely, and the tasks deliver to the S3 store. - `k3s` in production (planned). There the same CLI submits Runs remotely, and the tasks deliver to the S3 store.
[Production topology: ax on a Kubernetes cluster](deployment.md#production-topology-ax-on-a-kubernetes-cluster) [Production topology: ax on a Kubernetes cluster](deployment.md#production-topology-ax-on-a-kubernetes-cluster)
+39
View File
@@ -214,3 +214,42 @@ bucket `OUSAST_MEMORY_TEST_BUCKET`, else `S3_BUCKET`. The tests:
- never configure the bucket. - never configure the bucket.
More on the host setup: [ax on this host](ops/ax/README.md). More on the host setup: [ax on this host](ops/ax/README.md).
## Scoped account for engine workers
Create a **new account** for the k3s engine Deployment. Attach only this policy.
Do not give it the existing memory-store account's policies.
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListEnginePrefixes",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::sast-memory",
"Condition": {
"StringLike": {
"s3:prefix": ["engine-queue/", "engine-queue/*", "engine-results/", "engine-results/*"]
}
}
},
{
"Sid": "EngineObjectsOnly",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": [
"arn:aws:s3:::sast-memory/engine-queue/*",
"arn:aws:s3:::sast-memory/engine-results/*"
]
}
]
}
```
The cluster can read and write only queued engine inputs, claims, and results.
It cannot list or read the rest of the memory store. It cannot change bucket
settings. The host queue dispatcher can use the same scoped account. Store its
keys in the `ousast-engine-s3` Secret; no key values belong in manifests.
Deployment and coordinator commands are in `ops/k8s/engine/README.md`.
+16 -1
View File
@@ -17,6 +17,21 @@ in `~/.cache/ousast/ax-src/`. The cluster's context, registry and pool sizes com
(`ops/k8s/profiles/kind-images.json`), which the templates are re-pinned from and which is committed with them. (`ops/k8s/profiles/kind-images.json`), which the templates are re-pinned from and which is committed with them.
`up.sh` and `smoke-run.sh` run the checkout's code with `OUSAST_PYTHON` (default `.venv/bin/python`). `up.sh` and `smoke-run.sh` run the checkout's code with `OUSAST_PYTHON` (default `.venv/bin/python`).
The kind profile runs on a 7.7 GB server VM. Observability is off by default
(`kind_observability = false` in `ops/k8s/profiles/kind.toml`). This saves the measured
930 MiB used by `otel-system`, about 40% of the cluster's 2.3 GB. The smoke Run still
completes with those deployments stopped. To enable the stack, run
`OUSAST_KIND_OBSERVABILITY=1 ops/ax/up.sh` or set `kind_observability = true` in the profile.
`up.sh` stages copied Substrate manifests in a temporary checkout view and installs through
`hack/install-ate-kind.sh`. The local `ops/ax/substrate-kind-lean/` overlay removes the collector,
Prometheus, Jaeger and their supporting resources before installation. This preserves the
installer's secret, CRD and image setup. The upstream checkout is unchanged. The shared
`ate-otel-config` disables trace, metric and log export and the OTEL SDK; it removes the old
collector endpoint and metric timing keys. On an existing cluster, `up.sh` scales the three
observability deployments to zero, replaces the ConfigMap data and rolls its consumers when
the configuration changes. Opting in restores the upstream ConfigMap and observability manifests.
## How a Run executes ## How a Run executes
A Run (`plane/runs/*.yaml`, kind `openultrasast.io/v1alpha1 Run`) is a DAG of steps; each step names an ax Task A Run (`plane/runs/*.yaml`, kind `openultrasast.io/v1alpha1 Run`) is a DAG of steps; each step names an ax Task
@@ -191,7 +206,7 @@ configure the bucket.
No deployment outside this host's kind cluster has been made. The step-by-step guidance, with what is implemented No deployment outside this host's kind cluster has been made. The step-by-step guidance, with what is implemented
and what is planned, is `docs/deployment.md` (the site page "Deployment"). The Model and Workspace manifests under and what is planned, is `docs/deployment.md` (the site page "Deployment"). The Model and Workspace manifests under
`plane/` move as they are and the Task templates need their image reference re-pinned to your registry; these parts `plane/` move as they are and the Task templates need their image reference re-pinned to your registry; these parts
assume this laptop and must change first (checked 2026-10-02): assume this server VM and must change first (checked 2026-10-02):
| Assumption | Where | Needed in a real cluster | | Assumption | Where | Needed in a real cluster |
|---|---|---| |---|---|---|
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Stage an installer checkout with lean kind manifests; never modify the source checkout.
Usage: python ops/ax/prepare-substrate.py SOURCE EMPTY_DESTINATION
The destination lives only for an install/render. Source/build inputs are symlinked;
manifests are copied because the installer uses fixed paths (including the OTEL ConfigMap).
"""
from __future__ import annotations
import shutil
import sys
from pathlib import Path
import yaml
def prepare(source: Path, destination: Path) -> None:
source = source.resolve()
destination.mkdir(parents=True, exist_ok=True)
if any(destination.iterdir()):
raise ValueError(f"staging directory must be empty: {destination}")
for entry in source.iterdir():
if entry.name != "manifests":
(destination / entry.name).symlink_to(entry, target_is_directory=entry.is_dir())
shutil.copytree(source / "manifests", destination / "manifests")
install = destination / "manifests/ate-install"
shutil.copytree(install / "kind", install / "kind-upstream")
overlay = Path(__file__).resolve().parent / "substrate-kind-lean"
shutil.copytree(overlay, install / "kind", dirs_exist_ok=True)
# The installer applies this file directly before it renders the bundle.
# Keep the strategic-merge directive only in the overlay's patch file.
config = yaml.safe_load((overlay / "ate-otel-config.yaml").read_text())
config["data"].pop("$patch")
(install / "kind/disabled-otel-config.yaml").write_text(yaml.safe_dump(config))
(install / "kind/ate-otel-config.yaml").rename(install / "kind/otel-config-patch.yaml")
(install / "kind/disabled-otel-config.yaml").rename(install / "kind/ate-otel-config.yaml")
kustomization = install / "kind/kustomization.yaml"
kustomization.write_text(kustomization.read_text().replace("path: ate-otel-config.yaml", "path: otel-config-patch.yaml"))
if __name__ == "__main__":
prepare(Path(sys.argv[1]), Path(sys.argv[2]))
@@ -0,0 +1,11 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: ate-otel-config
namespace: ate-system
data:
$patch: replace
OTEL_TRACES_EXPORTER: none
OTEL_METRICS_EXPORTER: none
OTEL_LOGS_EXPORTER: none
OTEL_SDK_DISABLED: "true"
@@ -0,0 +1,44 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# prepare-substrate.py stages the upstream kind overlay beside this directory.
resources:
- ../kind-upstream
patches:
- target:
namespace: otel-system
patch: |-
$patch: delete
apiVersion: v1
kind: ConfigMap
metadata:
name: unused
- target:
kind: Namespace
name: otel-system
patch: |-
$patch: delete
apiVersion: v1
kind: Namespace
metadata:
name: otel-system
- target:
kind: ClusterRole
name: ate-prometheus
patch: |-
$patch: delete
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ate-prometheus
- target:
kind: RoleBinding
name: ate-prometheus
namespace: ate-system
patch: |-
$patch: delete
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ate-prometheus
namespace: ate-system
- path: ate-otel-config.yaml
+62 -2
View File
@@ -15,6 +15,7 @@ profile() { PYTHONPATH="$REPO_ROOT/src" "$PY" -m openultrasast.plane.profile --p
CTX="$(profile kube_context)" # kind names its context kind-<cluster> CTX="$(profile kube_context)" # kind names its context kind-<cluster>
export KIND_CLUSTER_NAME="${CTX#kind-}" export KIND_CLUSTER_NAME="${CTX#kind-}"
export KO_DOCKER_REPO="${KO_DOCKER_REPO:-$(profile registry)}" export KO_DOCKER_REPO="${KO_DOCKER_REPO:-$(profile registry)}"
OBSERVABILITY="$(profile kind_observability)"
IMAGES_FILE="$(profile images)" # the digest pins the reconciler and the templates read IMAGES_FILE="$(profile images)" # the digest pins the reconciler and the templates read
SRC="${OUSAST_AX_SRC:-$HOME/.cache/ousast/ax-src}" SRC="${OUSAST_AX_SRC:-$HOME/.cache/ousast/ax-src}"
@@ -37,9 +38,68 @@ else
fi fi
step "agent substrate (ate-system)" step "agent substrate (ate-system)"
if ready ate-system; then echo "substrate ready; kept"; else # The upstream installer has fixed manifest paths and also creates secrets/CRDs.
(cd "$SRC/substrate" && hack/install-ate-kind.sh --deploy-ate-system) # Give it a temporary checkout view so it never installs the observability resources.
install_substrate() (
if [ "$OBSERVABILITY" = true ]; then
cd "$SRC/substrate"
else
# Symlinked build inputs must not make the staged view a different binary version.
export VERSION="${VERSION:-$(git -C "$SRC/substrate" describe --tags --always --dirty)}"
staged="$(mktemp -d)"
trap 'rm -rf "$staged"' EXIT
"$PY" "$REPO_ROOT/ops/ax/prepare-substrate.py" "$SRC/substrate" "$staged"
cd "$staged"
fi fi
KUBECTL_CONTEXT="$CTX" hack/install-ate-kind.sh --deploy-ate-system
)
if ready ate-system; then echo "substrate ready; kept"; else
install_substrate
fi
# Reconcile the choice even when the existing control plane is already ready.
if [ "$OBSERVABILITY" = true ]; then
for manifest in otel-collector prometheus; do
kubectl --context "$CTX" apply -f "$SRC/substrate/manifests/ate-install/kind/$manifest.yaml"
done
otel_config="$SRC/substrate/manifests/ate-install/kind/ate-otel-config.yaml"
else
otel_config="$REPO_ROOT/ops/ax/substrate-kind-lean/ate-otel-config.yaml"
for deployment in opentelemetry-collector prometheus jaeger; do
existing="$(kubectl --context "$CTX" -n otel-system get deployment "$deployment" --ignore-not-found -o name)"
if [ -n "$existing" ]; then
kubectl --context "$CTX" -n otel-system scale "$existing" --replicas=0
fi
done
echo "kind observability off: existing otel-system deployments scaled to 0; opt in with OUSAST_KIND_OBSERVABILITY=1 ops/ax/up.sh"
fi
# Replace data to remove stale exporter/endpoint keys in either direction. ConfigMap
# envFrom is read at pod start; a content annotation triggers only changed rollouts.
otel_data="$("$PY" - "$otel_config" <<'PYCONFIG'
import json
import sys
import yaml
config = yaml.safe_load(open(sys.argv[1]))
config["data"].pop("$patch", None)
print(json.dumps(config["data"], sort_keys=True))
PYCONFIG
)"
kubectl --context "$CTX" -n ate-system patch configmap ate-otel-config --type=json \
-p "[{\"op\":\"replace\",\"path\":\"/data\",\"value\":$otel_data}]"
otel_hash="$(printf '%s' "$otel_data" | sha256sum | cut -d ' ' -f1)"
otel_workloads="$(kubectl --context "$CTX" -n ate-system get deployments,daemonsets -o json | "$PY" -c '
import json, sys
for item in json.load(sys.stdin)["items"]:
spec = item["spec"]["template"]["spec"]
containers = spec.get("containers", []) + spec.get("initContainers", [])
if any(env.get("configMapRef", {}).get("name") == "ate-otel-config"
for container in containers for env in container.get("envFrom", [])):
print(item["kind"].lower() + "/" + item["metadata"]["name"])
')"
for workload in $otel_workloads; do
kubectl --context "$CTX" -n ate-system patch "$workload" --type=merge \
-p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"ousast.io/otel-config\":\"$otel_hash\"}}}}}"
kubectl --context "$CTX" -n ate-system rollout status "$workload" --timeout=300s
done
kubectl --context "$CTX" -n ate-system get pods kubectl --context "$CTX" -n ate-system get pods
step "egress gateway (atenet-egress, agentgateway variant: one prebuilt image, nothing built)" step "egress gateway (atenet-egress, agentgateway variant: one prebuilt image, nothing built)"
+85
View File
@@ -0,0 +1,85 @@
# Engine workers on k3s
Build the new **Engine image** workflow on main (or dispatch it), then copy its
`ghcr.io/<owner>/openultrasast@sha256:...` job-summary value. The old September
image does not include boto3 or this service. The release workflow still builds
version tags. The Dockerfile copies the worker and parser under `/app/benchmarks/learn`
and installs `.[semantic,s3]`; no analyzer tarball is sent by the dispatcher.
Use two schedulable nodes with at least 3 GiB available each. Required hostname
anti-affinity places one worker on each node. Updates use Recreate so a third
worker does not wait forever for a third node. Local scratch uses the container's
filesystem, with no host mount or Kubernetes API access.
Create a **new scoped account** using the policy in `docs/rustfs.md`. Export its
`S3_ENDPOINT`, `S3_BUCKET=sast-memory`, `AWS_ACCESS_KEY_ID`, and
`AWS_SECRET_ACCESS_KEY` in the coordinator shell. Do not use the memory-store
account. The following commands are for the coordinator; they were not run by the
implementation agent.
```sh
export ENGINE_IMAGE='ghcr.io/<owner>/openultrasast@sha256:<digest-from-workflow>'
# From the repository root, edit the digest parameter without changing the template:
export ENGINE_OVERLAY="$(mktemp -d /tmp/ousast-engine.XXXXXX)"
cp ops/k8s/engine/{namespace,deployment,kustomization}.yaml "$ENGINE_OVERLAY/"
python - <<'PY'
import os
from pathlib import Path
image, digest = os.environ['ENGINE_IMAGE'].split('@', 1)
p = Path(os.environ['ENGINE_OVERLAY']) / 'kustomization.yaml'
p.write_text(p.read_text().replace('ghcr.io/OWNER/openultrasast', image)
.replace('sha256:REPLACE_WITH_ENGINE_IMAGE_DIGEST', digest))
PY
kubectl apply -f ops/k8s/engine/namespace.yaml
kubectl -n ousast-engine create secret generic ousast-engine-s3 \
--from-literal=S3_ENDPOINT="$S3_ENDPOINT" \
--from-literal=S3_BUCKET="$S3_BUCKET" \
--from-literal=AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" \
--from-literal=AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY"
kubectl apply -k "$ENGINE_OVERLAY"
kubectl -n ousast-engine rollout status deployment/ousast-engine-worker
kubectl -n ousast-engine get pods -o wide
export PATH=/home/mc/Source/OpenUltraSAST/.venv/bin:$PATH
export PYTHONPATH="$PWD/src"
python benchmarks/learn/engine_trace.py --queue-status
python benchmarks/learn/engine_trace.py --executor queue \
--image "$ENGINE_IMAGE" --parallel 2 --deadline 900 --queue-timeout 3600 \
--units plane/experiments/exp-005-graph-slice.units.jsonl \
--out "$HOME/ousast-results/plane/engine-trace-queue"
```
The dispatcher uses the existing local memory examples by default. Add
`--examples /path/to/examples.jsonl` for a frozen example snapshot. Materialization
uses locally cached source pins, just as the Docker executor does. `--executor
k8s-jobs` preserves the old Job lane (`k8s` is its legacy alias); `queue` requires
no kubectl access. Resume skips completed local units and retries failed or timed
out units. `progress.json`, `STOP`, filtering, and `--summary` retain their existing
meaning. Each invocation uses a new run ID, so an old remote result cannot satisfy
a new retry. The dispatcher records worker and pod identity in each pin JSON.
Tasks contain the pin, units, prepared questions, source object key, and deadlines.
Results live at `engine-results/<run>/<pin-id>.json`; inputs are removed after
publication. The Docker lane retains JSON and a log, not a result tar, so the queue
also returns JSON. Queue timeout records timeout locally and removes pending;
an already claimed task finishes remotely and cleans its input. Orphaned inputs
from a failed submission can be removed under `engine-queue/inputs/` after checking
that no pending task or claim references them.
Claims use per-worker choosing markers followed by ordered tickets. A worker waits
for choosing peers and every lower ticket before removing pending and starting.
This requires strongly consistent GET/PUT/LIST from RustFS, including paginated
LIST, and synchronized node clocks with skew below 30 seconds. Half of the 60-second lease margin is reserved for clock
skew; the remaining execution budget is checked again after claiming.
It does not require conditional writes, object tags, S3 Select, or bucket-admin
permissions. A dead worker's task is recovered from its expired claim. Execution
runs in a child process. Linux subreaping and a frozen descendant walk kill its
entire tree, including Joern's separate sessions, at the task deadline before the
lease expires. As with any unfenced lease, a node suspended beyond its lease must be
terminated before it rejoins; the protocol cannot guarantee exclusivity against
arbitrary process suspension or an inconsistent object store.
SIGTERM stops new claims and lets the current task finish. The 900-second
termination grace matches the maximum task deadline. Keep these values aligned
if changing the deployment. `/healthz` serves both probes and becomes unready on
shutdown. Errors omit SDK exception text to avoid exposing credentials.
+63
View File
@@ -0,0 +1,63 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ousast-engine-worker
spec:
replicas: 2
strategy:
type: Recreate
selector:
matchLabels:
app: ousast-engine-worker
template:
metadata:
labels:
app: ousast-engine-worker
spec:
automountServiceAccountToken: false
terminationGracePeriodSeconds: 900
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: ousast-engine-worker
topologyKey: kubernetes.io/hostname
containers:
- name: worker
image: engine-image
command: [python, /app/benchmarks/learn/engine_worker_service.py]
envFrom:
- secretRef:
name: ousast-engine-s3
env:
- name: ENGINE_IMAGE
value: engine-image
resources:
requests:
memory: 3Gi
cpu: '1'
limits:
memory: 3Gi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
ports:
- name: health
containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: health
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: health
initialDelaySeconds: 15
periodSeconds: 10
+22
View File
@@ -0,0 +1,22 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ousast-engine
resources:
- namespace.yaml
- deployment.yaml
# Deliberately non-pullable until the coordinator supplies the workflow's digest.
images:
- name: engine-image
newName: ghcr.io/OWNER/openultrasast
digest: sha256:REPLACE_WITH_ENGINE_IMAGE_DIGEST
replacements:
- source:
kind: Deployment
name: ousast-engine-worker
fieldPath: spec.template.spec.containers.0.image
targets:
- select:
kind: Deployment
name: ousast-engine-worker
fieldPaths:
- spec.template.spec.containers.0.env.0.value
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: ousast-engine
+9
View File
@@ -0,0 +1,9 @@
# Reference only; excluded from kustomization. Create using the README command.
apiVersion: v1
kind: Secret
metadata:
name: ousast-engine-s3
namespace: ousast-engine
type: Opaque
stringData: {}
# Required keys: S3_ENDPOINT, S3_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"runner": "localhost:5001/ousast-runner@sha256:d2a55293999cc34581ef1f02a1ffb0c2dd8fc0cbe001ac952df81600e868f019" "runner": "localhost:5001/ousast-runner@sha256:784bc6afc869c679c4966d04d3f9653a8d379726b899809590cc83327f4038e3"
} }
+2 -1
View File
@@ -1,8 +1,9 @@
# The kind profile: the plane on this laptop's single-node kind cluster, which ops/ax/up.sh creates and which is # The kind profile: the plane on this server VM's single-node kind cluster, which ops/ax/up.sh creates and which is
# the development default (plane-on-kubernetes design section 1). Values only; a credential never goes here # the development default (plane-on-kubernetes design section 1). Values only; a credential never goes here
# (keys stay in the environment or .env). Every field is overridable by OUSAST_<FIELD> in the environment, a pool # (keys stay in the environment or .env). Every field is overridable by OUSAST_<FIELD> in the environment, a pool
# by OUSAST_POOL_<NAME>_<FIELD>. This file may name the kind cluster; the code must not (tests/test_plane_literals.py). # by OUSAST_POOL_<NAME>_<FIELD>. This file may name the kind cluster; the code must not (tests/test_plane_literals.py).
kind_observability = false # opt in with OUSAST_KIND_OBSERVABILITY=1
exec = "local" # the reconciler runs on this host against the cluster below exec = "local" # the reconciler runs on this host against the cluster below
kube_context = "kind-ousast" # kind's context for KIND_CLUSTER_NAME=ousast kube_context = "kind-ousast" # kind's context for KIND_CLUSTER_NAME=ousast
registry = "localhost:5001" # the kind-local registry up.sh creates (Substrate rewrites it for the node) registry = "localhost:5001" # the kind-local registry up.sh creates (Substrate rewrites it for the node)
+12
View File
@@ -82,6 +82,18 @@ recorded = "2026-10-03"
status = "development" status = "development"
exclude_tiers = ["title"] exclude_tiers = ["title"]
# Batch 3: more advisory fixes whose function survives the fix, weighted toward injection and untrusted_destination.
# Same slice, cache layout and checks; screening record
# benchmarks/measurements/2026-10-04-decision-engine-harvest-3/record.json.
[[source]]
id = "advisory-fixes-3"
kind = "pairs"
file = "benchmarks/pairs/advisory-fixes-3/catalog.toml"
evaluation = ["benchmarks/pairs/advisory-fixes-3/verification.json"]
recorded = "2026-10-04"
status = "development"
exclude_tiers = ["title"]
[[source]] [[source]]
id = "adjudications" id = "adjudications"
kind = "adjudications" kind = "adjudications"
+12 -2
View File
@@ -1,6 +1,6 @@
"""The plane's configuration surface: one ``PlaneProfile`` per cluster (plane-on-kubernetes design section 1). """The plane's configuration surface: one ``PlaneProfile`` per cluster (plane-on-kubernetes design section 1).
A profile is a TOML file under ``ops/k8s/profiles/`` (``kind.toml`` for the laptop's kind cluster, ``k3s.toml`` for A profile is a TOML file under ``ops/k8s/profiles/`` (``kind.toml`` for the server VM's kind cluster, ``k3s.toml`` for
the production cluster) holding every value that differs between clusters: the execution mode, the kube context, the production cluster) holding every value that differs between clusters: the execution mode, the kube context,
the registry and the digest-pinned images file, the router and ax-server addresses (empty: tunnel through the the registry and the digest-pinned images file, the router and ax-server addresses (empty: tunnel through the
Kubernetes API), the memory store, the atespace, ax's snapshot bucket, the worker pools and the image pull secret. Kubernetes API), the memory store, the atespace, ax's snapshot bucket, the worker pools and the image pull secret.
@@ -59,6 +59,7 @@ class PlaneProfile:
name: str name: str
path: Path path: Path
exec: str = "local" exec: str = "local"
kind_observability: bool = False
kube_context: str = "" kube_context: str = ""
registry: str = "" registry: str = ""
images: Path = Path("images.json") images: Path = Path("images.json")
@@ -135,6 +136,10 @@ def _reject_secret(where: str, key: str, value: object, declared: bool = False)
def _coerce(where: str, key: str, value: object, kind: str) -> Any: def _coerce(where: str, key: str, value: object, kind: str) -> Any:
if kind == "bool":
if not isinstance(value, bool):
raise ProfileError(f"{where}: {key} must be a boolean, got {value!r}")
return value
if kind == "int": if kind == "int":
if isinstance(value, bool) or not isinstance(value, int): if isinstance(value, bool) or not isinstance(value, int):
raise ProfileError(f"{where}: {key} must be an integer, got {value!r}") raise ProfileError(f"{where}: {key} must be an integer, got {value!r}")
@@ -172,6 +177,10 @@ def _env_overrides(environ: Mapping[str, str], where: str, values: dict[str, Any
raw = environ.get(_ENV_PREFIX + name.upper()) raw = environ.get(_ENV_PREFIX + name.upper())
if raw is not None and raw != "": if raw is not None and raw != "":
value: Any = raw value: Any = raw
if kind == "bool":
if raw.lower() not in ("true", "false", "1", "0"):
raise ProfileError(f"{where}: {_ENV_PREFIX}{name.upper()} must be true/false or 1/0, got {raw!r}")
value = raw.lower() in ("true", "1")
if kind == "int": if kind == "int":
try: try:
value = int(raw) value = int(raw)
@@ -241,7 +250,8 @@ def _lookup(profile: PlaneProfile, dotted: str) -> str:
return str(getattr(profile.pools[pool_name], key)) return str(getattr(profile.pools[pool_name], key))
if head not in _SCALARS or rest: if head not in _SCALARS or rest:
raise ProfileError(f"{profile.path}: no such field {dotted!r}") raise ProfileError(f"{profile.path}: no such field {dotted!r}")
return str(getattr(profile, head)) value = getattr(profile, head)
return str(value).lower() if isinstance(value, bool) else str(value)
def main(argv: list[str] | None = None) -> int: def main(argv: list[str] | None = None) -> int:
+15 -6
View File
@@ -21,8 +21,9 @@ from openultrasast.pairs import load_pair_catalog
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
PAIRS = ROOT / "benchmarks" / "pairs" PAIRS = ROOT / "benchmarks" / "pairs"
# batch 1 (2026-10-01) and batch 2 (2026-10-03, functions that survive the fix): one source each, same rules # batch 1 (2026-10-01), batches 2 (2026-10-03) and 3 (2026-10-04, functions that survive the fix): one source each,
BATCHES = {"advisory-fixes": PAIRS / "advisory-fixes", "advisory-fixes-2": PAIRS / "advisory-fixes-2"} # same rules
BATCHES = {name: PAIRS / name for name in ("advisory-fixes", "advisory-fixes-2", "advisory-fixes-3")}
SHA = re.compile(r"[0-9a-f]{40}") SHA = re.compile(r"[0-9a-f]{40}")
PERMISSIVE_OR_COPYLEFT = re.compile( PERMISSIVE_OR_COPYLEFT = re.compile(
r"^(MIT|BSD-[23]-Clause|Apache-2\.0|ISC|Zlib|0BSD|Unlicense|MPL-[12]\.[01]" r"^(MIT|BSD-[23]-Clause|Apache-2\.0|ISC|Zlib|0BSD|Unlicense|MPL-[12]\.[01]"
@@ -106,10 +107,18 @@ def test_no_pair_repeats_a_repository_the_benchmarks_already_name(source_id: str
PERMISSIVE = {"MIT", "BSD-2-Clause", "BSD-3-Clause", "Apache-2.0", "ISC", "MPL-2.0", "Unlicense"} PERMISSIVE = {"MIT", "BSD-2-Clause", "BSD-3-Clause", "Apache-2.0", "ISC", "MPL-2.0", "Unlicense"}
def test_batch_two_is_permissive_and_dated_against_the_cutoff() -> None: @pytest.mark.parametrize(("source_id", "batch"), [("advisory-fixes-2", "2026-10-03"), ("advisory-fixes-3", "2026-10-04")])
"""Batch 2 publishes permissive licences only and records each advisory's publication against the 2026-06-01 def test_later_batches_are_permissive_and_dated_against_the_cutoff(source_id: str, batch: str) -> None:
"""Batches 2 and 3 publish permissive licences only and record each advisory's publication against the 2026-06-01
cutoff, so memorisation can be measured on the post-cutoff pairs.""" cutoff, so memorisation can be measured on the post-cutoff pairs."""
for item in _raw(BATCHES["advisory-fixes-2"]): for item in _raw(BATCHES[source_id]):
assert item["license"] in PERMISSIVE, item["name"] assert item["license"] in PERMISSIVE, item["name"]
assert item["batch"] == "2026-10-03", item["name"] assert item["batch"] == batch, item["name"]
assert item["post_cutoff"] is (item["published_at"][:10] >= "2026-06-01"), item["name"] assert item["post_cutoff"] is (item["published_at"][:10] >= "2026-06-01"), item["name"]
def test_no_repository_repeats_across_batches() -> None:
seen: Counter[str] = Counter()
for folder in BATCHES.values():
seen.update({repo_name(item["repo"]) for item in _raw(folder)})
assert [repo for repo, n in seen.items() if n > 1] == []
+130
View File
@@ -0,0 +1,130 @@
"""Offline overlay contract and installer staging; no Kubernetes executable needed."""
from __future__ import annotations
import runpy
from pathlib import Path
import pytest
import yaml
ROOT = Path(__file__).resolve().parents[1]
OVERLAY = ROOT / "ops/ax/substrate-kind-lean"
DISABLED = {
"OTEL_TRACES_EXPORTER": "none",
"OTEL_METRICS_EXPORTER": "none",
"OTEL_LOGS_EXPORTER": "none",
"OTEL_SDK_DISABLED": "true",
}
def test_ax_lean_overlay_removes_observability_and_replaces_export_config() -> None:
overlay = yaml.safe_load((OVERLAY / "kustomization.yaml").read_text())
assert overlay["resources"] == ["../kind-upstream"]
deletes = [p for p in overlay["patches"] if "target" in p]
assert [p["target"] for p in deletes] == [
{"namespace": "otel-system"},
{"kind": "Namespace", "name": "otel-system"},
{"kind": "ClusterRole", "name": "ate-prometheus"},
{"kind": "RoleBinding", "name": "ate-prometheus", "namespace": "ate-system"},
]
assert all(yaml.safe_load(p["patch"])["$patch"] == "delete" for p in deletes)
config = yaml.safe_load((OVERLAY / overlay["patches"][-1]["path"]).read_text())
assert config["metadata"] == {"name": "ate-otel-config", "namespace": "ate-system"}
# Whole-data replacement drops the upstream endpoint, interval and timeout,
# including any future keys that would otherwise survive a merge.
assert config["data"] == {"$patch": "replace", **DISABLED}
def test_ax_staging_preserves_upstream_and_installer_prerequisites(tmp_path: Path) -> None:
prepare = runpy.run_path(str(ROOT / "ops/ax/prepare-substrate.py"))["prepare"]
source = tmp_path / "source"
kind = source / "manifests/ate-install/kind"
kind.mkdir(parents=True)
original = "resources: [otel-collector.yaml, prometheus.yaml, ate-otel-config.yaml]\n"
(kind / "kustomization.yaml").write_text(original)
(kind / "ate-otel-config.yaml").write_text("upstream config")
(kind / "postgres").mkdir()
(kind / "postgres/kustomization.yaml").write_text("resources: [../../postgres]\n")
(source / "go.mod").write_text("module test\n")
(source / "hack").mkdir()
(source / "hack/install-ate-kind.sh").write_text("upstream installer")
stage = tmp_path / "stage"
prepare(source, stage)
install = stage / "manifests/ate-install"
assert (install / "kind-upstream/kustomization.yaml").read_text() == original
assert (install / "kind/postgres/kustomization.yaml").read_text() == "resources: [../../postgres]\n"
assert (kind / "kustomization.yaml").read_text() == original
assert (kind / "ate-otel-config.yaml").read_text() == "upstream config"
assert (stage / "hack/install-ate-kind.sh").read_text() == "upstream installer"
assert (stage / "go.mod").is_symlink()
config = yaml.safe_load((install / "kind/ate-otel-config.yaml").read_text())
assert config["data"] == DISABLED # direct installer apply must not contain $patch
overlay = yaml.safe_load((install / "kind/kustomization.yaml").read_text())
patch = yaml.safe_load((install / "kind" / overlay["patches"][-1]["path"]).read_text())
assert patch["data"] == {"$patch": "replace", **DISABLED}
with pytest.raises(ValueError, match="must be empty"):
prepare(source, stage)
@pytest.mark.parametrize("enabled", [False, True])
@pytest.mark.parametrize("existing", [False, True])
def test_ax_reconciles_observability_on_ready_clusters(tmp_path: Path, enabled: bool, existing: bool) -> None:
"""Execute the reconciliation shell with a function double, never real kubectl."""
import json
import os
import subprocess
import sys
script = (ROOT / "ops/ax/up.sh").read_text()
section = script.split("# Reconcile the choice", 1)[1].split('kubectl --context "$CTX" -n ate-system get pods', 1)[0]
section = "# Reconcile the choice" + section
source = tmp_path / "substrate/manifests/ate-install/kind"
source.mkdir(parents=True)
upstream_data = {"OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector:4317", "OTEL_LOGS_EXPORTER": "otlp"}
(source / "ate-otel-config.yaml").write_text(yaml.safe_dump({"data": upstream_data}))
log = tmp_path / "calls"
consumer = {
"kind": "Deployment",
"metadata": {"name": "ate-controller"},
"spec": {"template": {"spec": {"containers": [{"envFrom": [{"configMapRef": {"name": "ate-otel-config"}}]}]}}},
}
nonconsumer = {"kind": "Deployment", "metadata": {"name": "unrelated"}, "spec": {"template": {"spec": {"containers": []}}}}
mock = """
set -euo pipefail
kubectl() {
printf '%s\\t' "$@" >> "$CALL_LOG"
printf '\\n' >> "$CALL_LOG"
case " $* " in
*" get deployment "*)
if [ "$EXISTING" = 1 ]; then printf 'deployment.apps/%s\\n' "$7"; fi ;;
*" get deployments,daemonsets "*) printf '%s' "$WORKLOADS" ;;
esac
return 0
}
"""
env = {
**os.environ,
"OBSERVABILITY": str(enabled).lower(),
"EXISTING": str(int(existing)),
"PY": sys.executable,
"CTX": "test-context",
"SRC": str(tmp_path),
"REPO_ROOT": str(ROOT),
"CALL_LOG": str(log),
"WORKLOADS": json.dumps({"items": [consumer, nonconsumer]}),
}
result = subprocess.run(["bash", "-c", mock + section], env=env, text=True, capture_output=True)
assert result.returncode == 0, result.stderr
calls = [line.rstrip("\t").split("\t") for line in log.read_text().splitlines()]
scales = [call for call in calls if "scale" in call]
assert len(scales) == (3 if existing and not enabled else 0)
assert all(call[-1] == "--replicas=0" for call in scales)
applies = [call for call in calls if "apply" in call]
assert len(applies) == (2 if enabled else 0)
config_patch = next(call[-1] for call in calls if "configmap" in call)
assert json.loads(config_patch) == [{"op": "replace", "path": "/data", "value": upstream_data if enabled else DISABLED}]
assert not any("deployment/unrelated" in call for call in calls)
assert any("deployment/ate-controller" in call for call in calls)
if not enabled:
assert "OUSAST_KIND_OBSERVABILITY=1" in result.stdout
+290
View File
@@ -0,0 +1,290 @@
"""Offline queue protocol, image imports and deployment contract controls."""
from __future__ import annotations
import importlib
import json
import os
import subprocess
import sys
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from types import SimpleNamespace
import pytest
import yaml
ROOT = Path(__file__).resolve().parents[1]
with pytest.MonkeyPatch.context() as patch:
patch.syspath_prepend(str(ROOT / "benchmarks/learn"))
service = importlib.import_module("engine_worker_service")
transport = importlib.import_module("engine_trace_queue")
class FakeS3:
def __init__(self):
self.objects = {}
self.lock = threading.Lock()
self.choosing = None
def get(self, key):
with self.lock:
return (self.objects[key], None) if key in self.objects else None
def put(self, key, data, labels):
with self.lock:
self.objects[key] = data
if self.choosing and key.startswith(service.CLAIMED) and json.loads(data)["ticket"] == 0:
self.choosing.wait(timeout=5)
def delete(self, key):
with self.lock:
self.objects.pop(key, None)
def keys(self, prefix):
with self.lock:
return sorted(k for k in self.objects if k.startswith(prefix))
def task():
return dict(
run="run",
id="pin",
task_id="run-pin",
input="engine-queue/inputs/run/pin/source.tar",
deadline=10,
question_deadline=2,
pin={"repo": "owner/repo", "pin": "commit", "units": []},
)
def test_engine_worker_racing_claimants_run_exactly_once():
store = FakeS3()
item = task()
service.put(store, service.PENDING + item["task_id"], item)
store.choosing = threading.Barrier(2)
called = []
def execute(client, entry):
called.append(entry["task_id"])
time.sleep(0.05)
return {**entry["pin"], "done": True, "instrument": {"bytes": 11}}
with ThreadPoolExecutor(2) as pool:
futures = [pool.submit(service.Queue(store, identity).process_one, execute) for identity in ["worker-b", "worker-a"]]
results = [f.result(timeout=10) for f in futures]
assert sum(results) == 1
assert called == ["run-pin"]
result = service.read(store, service.result_key(item))
assert result["worker"] in {"worker-a", "worker-b"}
assert not store.keys(service.CLAIMED)
assert not store.keys(service.PENDING)
def test_engine_worker_expired_lease_recovered_without_pending():
store = FakeS3()
item = task()
service.put(store, "engine-queue/claimed/dead/run-pin", dict(task=item, ticket=1, expires=99))
queue = service.Queue(store, "new", now=lambda: 100)
assert queue.process_one(lambda *_: {**item["pin"], "done": True})
assert service.read(store, service.result_key(item))["worker"] == "new"
assert not store.keys(service.CLAIMED)
def test_engine_worker_late_arrival_cannot_displace_running_owner():
store = FakeS3()
item = task()
first = service.Queue(store, "z-first")
key = first.claim(item)
assert key
stop = threading.Event()
later = service.Queue(store, "a-later", stop=stop)
with ThreadPoolExecutor(1) as pool:
future = pool.submit(later.claim, item)
assert future.result(timeout=3) is None
assert service.read(store, key) is not None
service.put(store, service.result_key(item), {"done": True})
store.delete(key)
def test_engine_queue_dispatch_roundtrip_and_timeout(tmp_path):
store = FakeS3()
checkout, output = tmp_path / "case", tmp_path / "out"
checkout.mkdir()
output.mkdir()
(checkout / "app.py").write_text("print(1)\n")
args = SimpleNamespace(deadline=10, question_deadline=2, queue_timeout=2, image="image@sha256:abc")
unit = {"unit": "u", "supported": True, "reason": "", "family": "injection"}
pin = {"repo": "owner/repo", "pin": "sha", "units": [unit], "questions": []}
def execute(client, item):
unpacked = tmp_path / "unpacked"
unpacked.mkdir()
service.unpack(client.get(item["input"])[0], unpacked)
assert (unpacked / "app.py").read_bytes() == b"print(1)\n"
return {
**item["pin"],
"done": True,
"container_exit": 0,
"units": [service.unit_record(unit, "asked-nothing")],
"instrument": {"bytes": 9, "heap_mb": 2560},
}
queue = service.Queue(store, "pod-1")
dispatcher = transport.Dispatcher(args, store, pause=lambda _: queue.process_one(execute))
completed, worker = dispatcher.execute(checkout, output, pin, "pin")
assert completed.returncode == 0
assert worker == "pod-1"
result = json.loads((output / "result.json").read_text())
assert result["executor"] == "queue"
assert result["units"][0]["status"] == "asked-nothing"
assert result["instrument"]["heap_mb"] == 2560
assert dispatcher.status() == {"pending": 0, "claimed": 0, "done": 1}
assert not store.keys("engine-queue/inputs/")
ticks = iter([0, 3])
dispatcher = transport.Dispatcher(args, store, clock=lambda: next(ticks))
assert dispatcher.execute(checkout, output, pin, "other") == (None, None)
assert not store.keys(service.PENDING)
def test_engine_worker_imports_without_yaml():
code = "import sys; sys.modules['yaml'] = None; import engine_worker_service; import engine_trace_worker"
done = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True,
env={**os.environ, "PYTHONPATH": f"{ROOT / 'src'}:{ROOT / 'benchmarks/learn'}"},
)
assert done.returncode == 0, done.stderr
def test_engine_k8s_render_contract():
base = ROOT / "ops/k8s/engine"
config = yaml.safe_load((base / "kustomization.yaml").read_text())
# Render this base's resource inclusion, namespace, image and replacement transforms offline.
resources = [yaml.safe_load((base / name).read_text()) for name in config["resources"]]
deployment = next(r for r in resources if r["kind"] == "Deployment")
deployment["metadata"]["namespace"] = config["namespace"]
image = config["images"][0]
pod = deployment["spec"]["template"]["spec"]
container = pod["containers"][0]
assert container["image"] == image["name"]
container["image"] = image["newName"] + "@" + image["digest"]
replacement = config["replacements"][0]
assert replacement["source"]["fieldPath"] == "spec.template.spec.containers.0.image"
assert replacement["targets"][0]["fieldPaths"] == ["spec.template.spec.containers.0.env.0.value"]
container["env"][0]["value"] = container["image"]
assert "@sha256:" in container["image"]
assert deployment["spec"]["replicas"] == 2
assert container["resources"] == {"requests": {"memory": "3Gi", "cpu": "1"}, "limits": {"memory": "3Gi"}}
affinity = pod["affinity"]["podAntiAffinity"]["requiredDuringSchedulingIgnoredDuringExecution"][0]
assert affinity["topologyKey"] == "kubernetes.io/hostname"
assert affinity["labelSelector"]["matchLabels"] == deployment["spec"]["template"]["metadata"]["labels"]
assert pod["terminationGracePeriodSeconds"] == 900
assert container["envFrom"] == [{"secretRef": {"name": "ousast-engine-s3"}}]
for probe in ["readinessProbe", "livenessProbe"]:
assert container[probe]["httpGet"] == {"path": "/healthz", "port": "health"}
assert not pod["automountServiceAccountToken"]
assert pod["securityContext"]["runAsNonRoot"]
assert "hostPath" not in json.dumps(resources)
assert "privileged" not in json.dumps(resources)
assert all(r["kind"] != "Secret" for r in resources)
secret = yaml.safe_load((base / "secret.example.yaml").read_text())
assert secret["stringData"] == {}
assert "AWS_SECRET_ACCESS_KEY" not in json.dumps(resources)
def test_engine_image_and_rustfs_policy():
dockerfile = (ROOT / "Dockerfile").read_text()
assert "COPY benchmarks ./benchmarks" in dockerfile
assert ".[semantic,s3]" in dockerfile
for name in ["engine_trace_worker.py", "engine_trace_parse.py", "engine_worker_service.py"]:
assert (ROOT / "benchmarks/learn" / name).stat().st_size > 0
doc = (ROOT / "docs/rustfs.md").read_text().split("## Scoped account for engine workers")[1]
policy = json.loads(doc.split("```json\n")[1].split("```")[0])
listing, objects = policy["Statement"]
assert listing["Action"] == "s3:ListBucket"
assert set(listing["Condition"]["StringLike"]["s3:prefix"]) == {
"engine-queue/",
"engine-queue/*",
"engine-results/",
"engine-results/*",
}
assert set(objects["Action"]) == {"s3:GetObject", "s3:PutObject", "s3:DeleteObject"}
assert set(objects["Resource"]) == {"arn:aws:s3:::sast-memory/engine-queue/*", "arn:aws:s3:::sast-memory/engine-results/*"}
@pytest.mark.parametrize("hang", [False, True])
def test_engine_worker_process_deadline_and_shipped_schema(tmp_path, monkeypatch, hang):
store = FakeS3()
item = task()
item["image"] = "image@sha256:abc"
item["deadline"] = 0.1 if hang else 5
item["pin"]["units"] = [{"unit": "u", "supported": True, "reason": ""}]
(tmp_path / "app.py").write_text("print(1)\n")
store.put(item["input"], service.pack(tmp_path), {})
monkeypatch.setenv("ENGINE_IMAGE", item["image"])
def fake_worker(pin, root, out, deadline, question_deadline):
assert (root / "app.py").read_bytes() == b"print(1)\n"
if hang:
time.sleep(10)
out.write_text(json.dumps({**pin, "done": True, "units": [service.unit_record(pin["units"][0], "asked-nothing")]}))
monkeypatch.setattr(service, "worker", fake_worker)
result = service.execute_task(store, item)
assert result["done"]
assert result["units"][0]["status"] == ("timeout" if hang else "asked-nothing")
assert result["container_exit"] == (None if hang else 0)
assert result["seconds"] < 5
def test_engine_worker_stopping_does_not_claim():
store = FakeS3()
item = task()
service.put(store, service.PENDING + item["task_id"], item)
stop = threading.Event()
stop.set()
assert not service.Queue(store, stop=stop).process_one(lambda *_: pytest.fail("claimed after SIGTERM"))
assert store.keys(service.PENDING)
assert not store.keys(service.CLAIMED)
def test_engine_worker_rejects_archive_links_and_escape(tmp_path):
import io
import tarfile
for name, kind in [("../escape", tarfile.REGTYPE), ("link", tarfile.SYMTYPE)]:
stream = io.BytesIO()
with tarfile.open(fileobj=stream, mode="w") as archive:
member = tarfile.TarInfo(name)
member.type = kind
member.linkname = "/etc/passwd"
archive.addfile(member)
with pytest.raises(ValueError, match="unsafe source archive"):
service.unpack(stream.getvalue(), tmp_path)
def test_engine_worker_timeout_kills_separate_session_descendant(tmp_path, monkeypatch):
store = FakeS3()
item = task()
item.update(image="image@sha256:abc", deadline=0.5)
item["pin"]["units"] = [{"unit": "u", "supported": True, "reason": ""}]
(tmp_path / "app.py").write_text("print(1)\n")
store.put(item["input"], service.pack(tmp_path), {})
monkeypatch.setenv("ENGINE_IMAGE", item["image"])
pid_file = tmp_path / "escaped-pid"
def fake_worker(*_):
child = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(30)"], start_new_session=True)
pid_file.write_text(str(child.pid))
time.sleep(30)
monkeypatch.setattr(service, "worker", fake_worker)
result = service.execute_task(store, item)
assert result["units"][0]["status"] == "timeout"
assert pid_file.exists(), "instrument: fake worker never launched its descendant"
pid = int(pid_file.read_text())
assert not Path(f"/proc/{pid}").exists(), "escaped session survived the task deadline"
+57 -2
View File
@@ -14,7 +14,6 @@ import pytest
from openultrasast.cpg.backend import BEGIN, END, CpgResult, JoernBackend from openultrasast.cpg.backend import BEGIN, END, CpgResult, JoernBackend
from openultrasast.model.specs import taint_specs from openultrasast.model.specs import taint_specs
from openultrasast.model.taint import request_params from openultrasast.model.taint import request_params
from openultrasast.model.trace import parse_trace
SCRIPT = Path(__file__).resolve().parents[1] / "benchmarks/learn/engine_trace.py" SCRIPT = Path(__file__).resolve().parents[1] / "benchmarks/learn/engine_trace.py"
_spec = importlib.util.spec_from_file_location("engine_trace", SCRIPT) _spec = importlib.util.spec_from_file_location("engine_trace", SCRIPT)
@@ -22,6 +21,8 @@ assert _spec and _spec.loader
trace = importlib.util.module_from_spec(_spec) trace = importlib.util.module_from_spec(_spec)
with pytest.MonkeyPatch.context() as patch: with pytest.MonkeyPatch.context() as patch:
patch.syspath_prepend(str(SCRIPT.parent)) patch.syspath_prepend(str(SCRIPT.parent))
from engine_trace_parse import parse_trace
_spec.loader.exec_module(trace) _spec.loader.exec_module(trace)
worker = sys.modules["engine_trace_worker"] worker = sys.modules["engine_trace_worker"]
@@ -304,6 +305,7 @@ def test_docker_command_is_serial_offline_frozen():
assert cmd[-1] == "60" assert cmd[-1] == "60"
assert "/frozen/benchmarks/learn/engine_trace_worker.py" in cmd assert "/frozen/benchmarks/learn/engine_trace_worker.py" in cmd
assert "benchmarks/learn/engine_trace_worker.py" in trace.SNAPSHOT_FILES assert "benchmarks/learn/engine_trace_worker.py" in trace.SNAPSHOT_FILES
assert "benchmarks/learn/engine_trace_parse.py" in trace.SNAPSHOT_FILES
def test_summary_separates_unanswered_pairs(tmp_path): def test_summary_separates_unanswered_pairs(tmp_path):
@@ -461,6 +463,8 @@ sys.modules['yaml'] = None
sys.modules['boto3'] = None sys.modules['boto3'] = None
script = pathlib.Path(sys.argv[1]) script = pathlib.Path(sys.argv[1])
size = len(script.read_bytes()) size = len(script.read_bytes())
# Direct script execution in the image puts the sibling modules on sys.path.
sys.path.insert(0, str(script.parent))
runpy.run_path(str(script), run_name='trace_import_check') runpy.run_path(str(script), run_name='trace_import_check')
print(json.dumps({'bytes': size, 'modules': sorted( print(json.dumps({'bytes': size, 'modules': sorted(
name for name in sys.modules if name == 'openultrasast' or name.startswith('openultrasast.') name for name in sys.modules if name == 'openultrasast' or name.startswith('openultrasast.')
@@ -486,7 +490,6 @@ def test_trace_container_transitive_import_boundary():
"openultrasast.cpg.session", "openultrasast.cpg.session",
"openultrasast.model", "openultrasast.model",
"openultrasast.model.contracts", "openultrasast.model.contracts",
"openultrasast.model.trace",
] ]
@@ -1397,3 +1400,55 @@ def test_k8s_check_roundtrip_and_retention(tmp_path, monkeypatch, capsys, keep):
assert {"name": "CHECK", "value": "1"} in job["spec"]["template"]["spec"]["containers"][0]["env"] assert {"name": "CHECK", "value": "1"} in job["spec"]["template"]["spec"]["containers"][0]["env"]
assert bool(data) is keep assert bool(data) is keep
assert any("delete" in c for c in commands) is not keep assert any("delete" in c for c in commands) is not keep
@pytest.mark.parametrize("outcome", ["success", "timeout", "failure"])
def test_engine_queue_pin_persistence_resume_and_progress(tmp_path, monkeypatch, outcome):
from engine_trace_queue import Dispatcher
from engine_worker_service import Queue
from test_engine_queue import FakeS3
store = FakeS3()
args = k8s_args(tmp_path, executor="queue", queue_timeout=2, image="image@sha256:abc")
plan = trace.make_plan([unit()], tmp_path)
class Inputs:
def materialize(self, pin, target):
target.mkdir()
(target / "app.py").write_text("def run(): pass")
return {"files": 1, "bytes": 15}
def execute(client, task):
return {
**task["pin"],
"done": True,
"container_exit": 0,
"units": [worker.unit_record(u, "asked-nothing") for u in task["pin"]["units"]],
}
queue = Queue(store, "pod-worker")
if outcome == "timeout":
ticks = iter([0, 3])
args.dispatcher = Dispatcher(args, store, clock=lambda: next(ticks))
else:
args.dispatcher = Dispatcher(args, store, pause=lambda _: queue.process_one(execute))
if outcome == "failure":
def fail(*_):
raise RuntimeError("credential-must-not-appear")
monkeypatch.setattr(store, "put", fail)
monkeypatch.setattr(trace, "prepare_questions", lambda *_: {"a": {}})
monkeypatch.setattr(subprocess, "run", lambda *_a, **_k: pytest.fail("queue must not launch Docker/kubectl"))
assert trace.run_plan(plan, None, {"image": args.image}, Inputs(), args) is (outcome != "success")
result = json.loads((tmp_path / trace.pin_name(plan[0])).read_text())
assert result["executor"] == "queue"
assert result["units"][0]["status"] == {"success": "asked-nothing", "timeout": "timeout", "failure": "failed"}[outcome]
assert "credential-must-not-appear" not in json.dumps(result)
if outcome == "success":
assert result["worker"] == "pod-worker"
assert trace.make_plan([unit()], tmp_path) == []
else:
assert trace.make_plan([unit()], tmp_path, rerun_status=("failed", "timeout"))
progress = json.loads((tmp_path / "progress.json").read_text())
assert progress["pins_done"] == 1 and progress["pins_in_flight"] == 0
+161
View File
@@ -0,0 +1,161 @@
"""Synthetic, offline result snapshots; no analyzer, model or corpus access."""
import importlib
import json
from pathlib import Path
import pytest
@pytest.fixture
def audit_module(monkeypatch):
monkeypatch.syspath_prepend(str(Path(__file__).resolve().parents[1] / "benchmarks/learn"))
return importlib.import_module("joern_leak_audit")
def units(count=12):
return [
{"unit": f"{pair}-{label}", "pair": str(pair), "label": label, "family": "injection", "group": "g", "fold": "f"}
for pair in range(count)
for label in (1, 0)
]
def write_result(root, module, rows, name="pin", *, done=True, version=None):
path = root / f"{name}.json"
path.write_text(
json.dumps(
{
"repo": "repo",
"pin": name,
"done": done,
"materialization_version": module.materialization_version() if version is None else version,
"units": rows,
}
)
)
return path
def result(unit, status):
return {
**unit,
"source": "authored",
"status": status,
"pin_role": "vulnerable" if unit["label"] else "fixed",
"traces": [{"steps": [{"roles": ["source"]}, {"roles": ["sink"]}], "sanitized": False, "guard": {"text": "", "bounded": False}}]
if status == "path"
else [],
"witness_rows": [{"sink": "execute(x)"}] if status == "path" else [],
}
def run(module, root, population):
return module.audit(population, root, expected_rows=len(population), expected_pairs=len(population) // 2)
def test_skips_stale_unfinished_and_proves_read(audit_module, tmp_path):
population = units(2)
paths = [
write_result(tmp_path, audit_module, [result(population[0], "path")], "valid"),
write_result(tmp_path, audit_module, [result(population[1], "path")], "stale", version=-1),
write_result(tmp_path, audit_module, [result(population[2], "path")], "unfinished", done=False),
write_result(tmp_path, audit_module, [result(population[3], "path")], "both", version=-1, done=False),
]
report = run(audit_module, tmp_path, population)
evidence = report["inputs"]
assert (evidence["stale"], evidence["unfinished"], evidence["skipped"]) == (2, 2, 3)
assert evidence["files_read"] == 4 and evidence["accepted_files"] == evidence["result_rows_read"] == 1
assert evidence["bytes_read"] == sum(p.stat().st_size for p in paths)
assert report["matched_rows"] == 1
assert report["overall"]["coverage"]["missing"] == 3
assert report["by_source"]["unknown"]["rows"] == 3
assert report["overall"]["signals"]["has_path"]["rows"] == 1
assert report["overall"]["both_asked_signals"]["has_path"]["pooled_auc"] is None
def test_asymmetry_and_honest_denominator(audit_module, tmp_path):
population = units(6)
statuses = [
"path",
"asked-nothing",
"asked-nothing",
"path",
"path",
"path",
"asked-nothing",
"asked-nothing",
"path",
"failed",
"unsupported",
"timeout",
]
write_result(tmp_path, audit_module, [result(u, s) for u, s in zip(population, statuses, strict=True)])
report = run(audit_module, tmp_path, population)
overall = report["overall"]
assert overall["pair_asymmetry"] == {"vulnerable_only": 1, "fixed_only": 1, "both": 1, "neither": 1, "not_both_asked": 2}
assert overall["both_asked_pairs"] == 4
assert overall["both_asked_signals"]["asked"]["within_pair_accuracy"] == 0.5
assert overall["coverage"] == {"path": 5, "asked-nothing": 4, "failed": 1, "timeout": 1, "unsupported": 1, "missing": 0}
assert report["by_family"]["injection"] == overall == report["by_source"]["authored"]
@pytest.mark.parametrize("balanced", [False, True])
def test_constructed_leak_and_balanced_set(audit_module, tmp_path, balanced):
population = units()
rows = [result(u, "path" if u["label"] == (int(u["pair"]) % 2 if balanced else 1) else "asked-nothing") for u in population]
write_result(tmp_path, audit_module, rows)
overall = run(audit_module, tmp_path, population)["overall"]
for denominator in ("signals", "both_asked_signals"):
signals = overall[denominator]
for name in ("has_path", "shortest_path_steps"):
assert signals[name]["flagged"] is not balanced
assert signals[name]["within_pair_accuracy"] == signals[name]["pooled_auc"] == (0.5 if balanced else 1)
assert signals["sink_kind"]["execute"]["flagged"] is not balanced
assert not signals["asked"]["flagged"]
assert not signals["sanitized"]["flagged"]
assert not signals["guard_present"]["flagged"]
def test_minimum_samples_reverse_and_source_proxy(audit_module, tmp_path):
population = units(2)
write_result(tmp_path, audit_module, [result(u, "path" if not u["label"] else "asked-nothing") for u in population])
assert not run(audit_module, tmp_path, population)["overall"]["signals"]["has_path"]["flagged"]
population = units(12)
rows = []
for u in population:
row = result(u, "path" if not u["label"] else "unsupported")
row["source"] = "positive" if u["label"] else "negative"
rows.append(row)
write_result(tmp_path, audit_module, rows)
report = run(audit_module, tmp_path, population)
assert report["overall"]["signals"]["asked"]["flagged"]
assert report["overall"]["signals"]["asked"]["pooled_auc"] == 0
assert report["overall"]["both_asked_pairs"] == 0
assert report["by_source"]["positive"]["signals"]["asked"]["pooled_auc"] is None
def test_shortest_trace_evidence_and_missing_trace(audit_module):
row = result(units(1)[0], "path")
row["traces"].insert(0, {"steps": [{}] * 5, "sanitized": False})
row["witness_rows"].insert(0, {"sink": "longer(x)"})
row["traces"][1].update(sanitized=True, guard={"bounded": True, "text": "off-path bound"})
features = audit_module.features(row)
assert (features["shortest_path_steps"], features["sanitized"], features["guard_present"], features["sink_kind"]) == (
2,
1,
1,
"execute",
)
row["traces"] = []
assert audit_module.features(row)["shortest_path_steps"] is None
def test_bad_inputs_fail_loudly(audit_module, tmp_path):
population = units(1)
write_result(tmp_path, audit_module, [result(population[0], "path")] * 2)
with pytest.raises(ValueError, match="Duplicate"):
run(audit_module, tmp_path, population)
write_result(tmp_path, audit_module, [{**result(population[0], "path"), "label": 0}])
with pytest.raises(ValueError, match="metadata mismatch"):
run(audit_module, tmp_path, population)
+24
View File
@@ -156,3 +156,27 @@ def test_profile_is_frozen_and_typed() -> None:
assert isinstance(loaded, PlaneProfile) assert isinstance(loaded, PlaneProfile)
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
loaded.exec = "remote" # type: ignore[misc] loaded.exec = "remote" # type: ignore[misc]
def test_kind_observability_defaults_off() -> None:
assert load_profile("kind", environ={}).kind_observability is False
@pytest.mark.parametrize(("raw", "expected"), [("1", True), ("true", True), ("0", False), ("FALSE", False)])
def test_kind_observability_environment_override(tmp_path: Path, raw: str, expected: bool) -> None:
path = write_profile(tmp_path, MINIMAL + f"kind_observability = {str(not expected).lower()}\n")
assert load_profile(str(path), environ={"OUSAST_KIND_OBSERVABILITY": raw}).kind_observability is expected
def test_kind_observability_validation_and_print(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
path = write_profile(tmp_path, MINIMAL + 'kind_observability = "false"\n')
with pytest.raises(ProfileError, match="kind_observability must be a boolean"):
load_profile(str(path), environ={})
path = write_profile(tmp_path)
with pytest.raises(ProfileError, match="OUSAST_KIND_OBSERVABILITY must be"):
load_profile(str(path), environ={"OUSAST_KIND_OBSERVABILITY": "maybe"})
monkeypatch.setenv("OUSAST_KIND_OBSERVABILITY", "1")
assert profile_module.main(["--profile", str(path), "--print", "kind_observability"]) == 0
assert capsys.readouterr().out == "true\n"