Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T00:49:45.731Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

collect-omega-installed-tripwires

843 lines · 32.7 KB · text
1#!/usr/bin/env python3
2"""Search the surfaces an installed Omega writes for a marked secret.
3
4The installed proof requires a tripwire receipt and nothing produced one, so
5that input was permanently blocked. This produces it. The method is a
6marked-secret search: the disposable canary the candidate was given during the
7journey is searched for on every surface a secret could plausibly escape onto,
8and the receipt records which surfaces were read and that none of them carried
9it. The needle's value is never written to the receipt or to any log — recording
10the secret you are proving was not leaked would defeat the exercise — so only
11its length is kept.
12
13Three properties keep this receipt from being a formality.
14
15**The needle is not this script's to invent.** It is read from a descriptor the
16caller opened on the canary file, and a run without `--needle-fd` refuses rather
17than minting a fresh secret no other process has ever seen. An earlier version
18generated its own needle, wrote it into a pipe, closed both ends of that pipe in
19the same function, and then searched the disk for it. Nothing could ever have
20written that value anywhere, so `pass` was guaranteed by construction whatever
21the product did. A receipt that cannot fail is worse than no receipt.
22
23**The scan is proved able to fire on this run.** Before any surface is read the
24needle is planted in a private file and the same scanner is pointed at it. If
25that file does not come back as a match the run blocks, so a scanner that has
26been broken — by an edit, by an unreadable temporary directory, by a needle that
27arrived empty — cannot report that nothing was found.
28
29**Every surface is read where Omega actually writes, through the interface that
30surface actually has.** `crates/paths/src/paths.rs` puts `logs_dir()` and the
31telemetry log under `~/Library/Logs/<storage slug>` and `crashes_dir()` under
32`~/Library/Logs/DiagnosticReports`; none of those is under the application
33support root an earlier version scanned, so four of the six surfaces were being
34searched for in directories the product never creates. The clipboard is a
35pasteboard and the accessibility surface is a tree; neither is ever a file in a
36data directory, so each is captured through its own API into a private file that
37is deleted when the run ends.
38
39A surface that exists and does not carry the needle records `pass`. A surface
40whose location is right and whose file the product has not written records
41`absent`. A surface that could not be observed at all records `blocked`, which
42the receipt validator refuses — "nothing was found there" and "nobody looked"
43must not read the same, and only one of them may support a release.
44
45Usage:
46    collect-omega-installed-tripwires \\
47        --app /Applications/Omega.app \\
48        --candidate-evidence <path> \\
49        --output <path> \\
50        --needle-fd 3 3<"$OMEGA_PRIVATE_PROOF_DIR/disposable-canary"
51"""
52
53from __future__ import annotations
54
55import argparse
56import hashlib
57import json
58import os
59import platform
60import shutil
61import stat
62import subprocess
63import sys
64import tempfile
65from collections.abc import Callable
66from datetime import datetime, timezone
67from pathlib import Path
68from typing import Any
69
70TRIPWIRE_SCHEMA = "openagents.omega.installed-secret-tripwires.v1"
71
72#: Bounds the receipt validator enforces on the needle, mirrored here so a
73#: canary that could not support a receipt is refused before anything is
74#: scanned rather than after.
75MINIMUM_NEEDLE_BYTES = 16
76MAXIMUM_NEEDLE_BYTES = 4096
77
78#: `AppChannel::storage_slug` for the RC channel, and the display name
79#: `app_name()` uses for the data root. The two differ, and using either where
80#: the other belongs is what put four surfaces in directories that do not
81#: exist.
82STORAGE_SLUG = "omega-rc"
83DISPLAY_NAME = "Omega RC"
84
85#: Refuse to read anything enormous; a scan that hangs is a scan nobody runs.
86MAX_FILE_BYTES = 64 * 1024 * 1024
87
88#: Surfaces whose file the product writes only under a condition the journey
89#: may not have met. A missing telemetry log or an empty crash directory is a
90#: fact about the run, not a gap in the search, and records `absent`. Every
91#: other surface must be observed or blocked.
92OPTIONAL_SURFACES = frozenset({"telemetry", "crashes"})
93
94
95class TripwireError(Exception):
96    pass
97
98
99def sha256_hex(data: bytes) -> str:
100    return hashlib.sha256(data).hexdigest()
101
102
103def data_root(override: Path | None = None) -> Path:
104    """`paths::data_dir()` on macOS: the application support root."""
105    return override or Path.home() / "Library" / "Application Support" / DISPLAY_NAME
106
107
108def logs_root(override: Path | None = None) -> Path:
109    """`paths::logs_dir()` on macOS.
110
111    Not the data root. `paths.rs` sends macOS logs to `~/Library/Logs/<slug>`,
112    and the live log file and the telemetry log are both there.
113    """
114    return override or Path.home() / "Library" / "Logs" / STORAGE_SLUG
115
116
117def crashes_root(override: Path | None = None) -> Path:
118    """`paths::crashes_dir()` on macOS."""
119    return override or Path.home() / "Library" / "Logs" / "DiagnosticReports"
120
121
122def telemetry_file(logs: Path) -> Path:
123    """`client::telemetry` writes `telemetry.log` inside the logs directory."""
124    return logs / "telemetry.log"
125
126
127def filesystem_surfaces(
128    data: Path, logs: Path, crashes: Path
129) -> dict[str, dict[str, Any]]:
130    """Where each file-backed surface actually lives.
131
132    `anchor` is the directory that must exist for the search to have happened
133    in the right place at all. An absent anchor is not an empty surface: it
134    means this run looked somewhere the product does not write, which is the
135    defect this table was rewritten to remove, so it blocks.
136    """
137    return {
138        "logs": {
139            "anchor": logs,
140            "roots": [logs],
141            # The telemetry log lives in the same directory and is its own
142            # surface below. Excluding it here keeps the two counts disjoint so
143            # neither borrows the other's coverage.
144            "exclude": frozenset({telemetry_file(logs)}),
145        },
146        "telemetry": {
147            "anchor": logs,
148            "roots": [telemetry_file(logs)],
149            "exclude": frozenset(),
150        },
151        "diagnostics": {
152            "anchor": data,
153            "roots": [data / "db", data / "hang_traces"],
154            "exclude": frozenset(),
155        },
156        "crashes": {
157            "anchor": crashes.parent,
158            "roots": [crashes],
159            "exclude": frozenset(),
160        },
161    }
162
163
164class ScanCounts:
165    """Exactly what a scan inspected, and what it found."""
166
167    def __init__(self) -> None:
168        self.files_scanned = 0
169        self.symlinks_skipped = 0
170        self.bytes_scanned = 0
171        self.errors = 0
172        self.match_detected = False
173        self.present = False
174
175
176def scan_paths(
177    roots: list[Path],
178    needle: bytes,
179    exclude: frozenset[Path] = frozenset(),
180) -> ScanCounts:
181    """Scan a set of files and directories for the needle, counting the reads.
182
183    Symlinks are skipped rather than followed. Following them would let a link
184    out of the surface inflate the byte count with files this candidate never
185    wrote, which would make the receipt claim more coverage than it earned.
186    """
187    counts = ScanCounts()
188
189    def read_file(path: Path) -> None:
190        try:
191            file_stat = path.lstat()
192            if not stat.S_ISREG(file_stat.st_mode):
193                return
194            if file_stat.st_size > MAX_FILE_BYTES:
195                counts.errors += 1
196                return
197            payload = path.read_bytes()
198        except OSError:
199            counts.errors += 1
200            return
201        counts.files_scanned += 1
202        counts.bytes_scanned += len(payload)
203        if needle in payload:
204            counts.match_detected = True
205
206    for root in roots:
207        if root.is_symlink():
208            counts.symlinks_skipped += 1
209            continue
210        if not root.exists():
211            continue
212        counts.present = True
213        if root.is_file():
214            if root not in exclude:
215                read_file(root)
216            continue
217        for current, directories, files in os.walk(root, followlinks=False):
218            current_path = Path(current)
219            for directory in list(directories):
220                if (current_path / directory).is_symlink():
221                    counts.symlinks_skipped += 1
222                    directories.remove(directory)
223            for file_name in files:
224                path = current_path / file_name
225                if path in exclude:
226                    continue
227                if path.is_symlink():
228                    counts.symlinks_skipped += 1
229                    continue
230                read_file(path)
231
232    return counts
233
234
235def surface_record(
236    name: str, path_label: str, status: str, counts: ScanCounts | None = None
237) -> dict[str, Any]:
238    """One surface's line in the receipt, in the exact shape the validator reads."""
239    counts = counts or ScanCounts()
240    body = {
241        "name": name,
242        "status": status,
243        "files_scanned": counts.files_scanned,
244        "symlinks_skipped": counts.symlinks_skipped,
245        "bytes_scanned": counts.bytes_scanned,
246        "errors": counts.errors,
247        "match_detected": counts.match_detected,
248    }
249    return {
250        **body,
251        "path_digest": sha256_hex(path_label.encode("utf-8")),
252        "evidence_digest": sha256_hex(json.dumps(body, sort_keys=True).encode("utf-8")),
253    }
254
255
256def resolve_status(name: str, counts: ScanCounts, anchor_present: bool) -> str:
257    """Turn a scan into one of the four things that can honestly be said.
258
259    `blocked` is the answer whenever the search could not be made where it had
260    to be made. It is deliberately a status the receipt validator refuses.
261    """
262    if not anchor_present:
263        return "blocked"
264    if counts.errors:
265        return "error"
266    if counts.match_detected:
267        return "match"
268    if not counts.present:
269        return "absent" if name in OPTIONAL_SURFACES else "blocked"
270    return "pass"
271
272
273def scan_filesystem_surface(
274    name: str, plan: dict[str, Any], needle: bytes
275) -> tuple[dict[str, Any], str]:
276    anchor: Path = plan["anchor"]
277    roots: list[Path] = plan["roots"]
278    counts = scan_paths(roots, needle, plan["exclude"])
279    anchor_present = anchor.is_dir()
280    status = resolve_status(name, counts, anchor_present)
281    label = "\n".join(sorted(str(root) for root in roots))
282    detail = f"{status} at {label}"
283    if not anchor_present:
284        detail = (
285            f"blocked: {anchor} does not exist, so this run did not look where "
286            "the product writes"
287        )
288    return surface_record(name, label, status, counts), detail
289
290
291def appkit_pasteboard_bytes() -> bytes | None:
292    """Every flavour of every item on the general pasteboard, through PyObjC.
293
294    `None` means this interface was not available or did not answer, which
295    falls through to `pbpaste`. It never falls through to a pass.
296    """
297    try:
298        from AppKit import NSPasteboard  # type: ignore[import-not-found]
299    except ImportError:
300        return None
301    try:
302        pasteboard = NSPasteboard.generalPasteboard()
303        payload = bytearray()
304        for item in pasteboard.pasteboardItems() or []:
305            for kind in item.types() or []:
306                data = item.dataForType_(kind)
307                if data is not None:
308                    payload += bytes(data)
309    except Exception:  # noqa: BLE001 - any failed read is a read that did not happen
310        return None
311    return bytes(payload)
312
313
314def pasteboard_bytes() -> tuple[bytes | None, str]:
315    """Read the general pasteboard through its own interface.
316
317    PyObjC reads every flavour of every item when it is available. Where it is
318    not, `pbpaste` reads the text flavours, which is a real read of the same
319    pasteboard and is recorded as exactly that much coverage. A pasteboard is
320    never a file in a data directory, so there is no third option that reads
321    one.
322    """
323    through_appkit = appkit_pasteboard_bytes()
324    if through_appkit is not None:
325        return through_appkit, "NSPasteboard general pasteboard, every type of every item"
326
327    if shutil.which("pbpaste") is None:
328        return None, ""
329    payload = bytearray()
330    read_any = False
331    for flavour in ("txt", "rtf"):
332        try:
333            result = subprocess.run(
334                ["pbpaste", "-Prefer", flavour],
335                capture_output=True,
336                timeout=30,
337                check=False,
338            )
339        except (OSError, subprocess.SubprocessError):
340            continue
341        if result.returncode == 0:
342            read_any = True
343            payload += result.stdout
344    if not read_any:
345        return None, ""
346    return bytes(payload), "pbpaste general pasteboard, text and rich-text flavours"
347
348
349def pasteboard_inventory() -> int | None:
350    """How many bytes the pasteboard itself says it is holding.
351
352    `clipboard info` answers with a flavour and a size for everything on the
353    general pasteboard. It is the corroboration for an empty read: a read that
354    came back with nothing while the pasteboard says it holds something means
355    the flavours were not all reachable, not that the surface is clean.
356    """
357    try:
358        result = subprocess.run(
359            ["osascript", "-e", "clipboard info"],
360            capture_output=True,
361            text=True,
362            timeout=30,
363            check=False,
364        )
365    except (OSError, subprocess.SubprocessError):
366        return None
367    if result.returncode != 0:
368        return None
369    total = 0
370    for token in result.stdout.split(","):
371        token = token.strip()
372        if token.isdigit():
373            total += int(token)
374    return total
375
376
377def pasteboard_capture_status(
378    payload: bytes | None, declared: int | None
379) -> tuple[bool, str]:
380    """Whether a pasteboard read may be recorded as an observation of the surface.
381
382    An empty read is only an observation when the pasteboard agrees it is
383    empty. Otherwise this run saw less than the surface holds, and a partial
384    look at a surface is not a clean one.
385    """
386    if payload is None:
387        return False, "the general pasteboard could not be read through any interface"
388    if payload:
389        return True, ""
390    if declared is None:
391        return False, (
392            "the pasteboard read returned nothing and the pasteboard's own "
393            "inventory could not be read to confirm it is empty"
394        )
395    if declared > 0:
396        return False, (
397            f"the pasteboard holds {declared} bytes in flavours this read "
398            "cannot see, so it was not fully scanned"
399        )
400    return True, ""
401
402
403def capture_pasteboard(directory: Path) -> tuple[Path | None, str, str]:
404    if platform.system() != "Darwin":
405        return None, "", "the pasteboard is a macOS interface and this host is not macOS"
406    payload, method = pasteboard_bytes()
407    readable, reason = pasteboard_capture_status(
408        payload, pasteboard_inventory() if not payload else None
409    )
410    if not readable or payload is None:
411        return None, "", reason
412    destination = directory / "pasteboard.bin"
413    handle = os.open(destination, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
414    with os.fdopen(handle, "wb") as capture:
415        capture.write(payload)
416    return destination, method, ""
417
418
419def candidate_pid(app: Path | None, explicit: int | None) -> int | None:
420    """The running candidate this run reads the accessibility tree of."""
421    if explicit is not None:
422        return explicit
423    if app is None:
424        return None
425    executables = str(app / "Contents" / "MacOS")
426    try:
427        result = subprocess.run(
428            ["pgrep", "-f", executables],
429            capture_output=True,
430            text=True,
431            timeout=30,
432            check=False,
433        )
434    except (OSError, subprocess.SubprocessError):
435        return None
436    for line in result.stdout.split():
437        try:
438            return int(line)
439        except ValueError:
440            continue
441    return None
442
443
444def accessibility_tree(pid: int) -> tuple[str | None, str]:
445    """Every label and value the process publishes to assistive technology.
446
447    This is the surface a screen reader consumes, read through the same
448    `AXUIElement` interface it uses. It is never a file in a data directory,
449    and a run that cannot reach it says so rather than recording an empty
450    directory as a clean surface.
451    """
452    script = f"""
453    tell application "System Events"
454        if not (exists (first process whose unix id is {pid})) then return "NOPROC"
455        tell (first process whose unix id is {pid})
456            set out to ""
457            try
458                repeat with w in windows
459                    repeat with e in (entire contents of w)
460                        try
461                            set d to description of e
462                            if d is not missing value then set out to out & d & linefeed
463                        end try
464                        try
465                            set v to value of e
466                            if v is not missing value then set out to out & (v as text) & linefeed
467                        end try
468                    end repeat
469                end repeat
470            end try
471            return out
472        end tell
473    end tell
474    """
475    try:
476        result = subprocess.run(
477            ["osascript", "-e", script],
478            capture_output=True,
479            text=True,
480            timeout=120,
481            check=False,
482        )
483    except (OSError, subprocess.SubprocessError):
484        return None, "the accessibility tree could not be read; osascript did not answer"
485    if result.returncode != 0:
486        return None, (
487            "the accessibility tree could not be read. Grant this terminal "
488            "Accessibility in System Settings > Privacy & Security > Accessibility"
489        )
490    if result.stdout.strip() == "NOPROC":
491        return None, "the candidate is not running, so it publishes no accessibility tree"
492    return result.stdout, ""
493
494
495def capture_accessibility(directory: Path, pid: int | None) -> tuple[Path | None, str, str]:
496    if platform.system() != "Darwin":
497        return None, "", "the accessibility tree is a macOS interface and this host is not macOS"
498    if pid is None:
499        return None, "", "no running candidate was found to read an accessibility tree from"
500    tree, reason = accessibility_tree(pid)
501    if tree is None:
502        return None, "", reason
503    destination = directory / "accessibility.txt"
504    handle = os.open(destination, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
505    with os.fdopen(handle, "wb") as capture:
506        capture.write(tree.encode("utf-8", errors="surrogateescape"))
507    return destination, f"AXUIElement tree of pid {pid} through System Events", ""
508
509
510def scan_captured_surface(
511    name: str,
512    label: str,
513    capture: Path | None,
514    reason: str,
515    needle: bytes,
516) -> tuple[dict[str, Any], str]:
517    """A surface that had to be captured through an API before it could be read."""
518    if capture is None:
519        return surface_record(name, label, "blocked"), f"blocked: {reason}"
520    counts = scan_paths([capture], needle)
521    status = resolve_status(name, counts, anchor_present=True)
522    return surface_record(name, label, status, counts), f"{status}: {reason}"
523
524
525def read_needle(file_descriptor: int) -> bytes:
526    """Read the canary from the descriptor the caller opened on it.
527
528    The descriptor is the whole point: the secret never becomes an argument an
529    unrelated process can read out of the process table, and this script never
530    invents one. A needle nothing has ever seen cannot be leaked, so searching
531    for one proves nothing at all.
532    """
533    chunks: list[bytes] = []
534    remaining = MAXIMUM_NEEDLE_BYTES + 2
535    while remaining > 0:
536        try:
537            chunk = os.read(file_descriptor, remaining)
538        except OSError as error:
539            raise TripwireError(f"--needle-fd {file_descriptor} could not be read") from error
540        if not chunk:
541            break
542        chunks.append(chunk)
543        remaining -= len(chunk)
544    needle = b"".join(chunks).rstrip(b"\r\n")
545    if not MINIMUM_NEEDLE_BYTES <= len(needle) <= MAXIMUM_NEEDLE_BYTES:
546        raise TripwireError(
547            f"the canary must carry {MINIMUM_NEEDLE_BYTES}..{MAXIMUM_NEEDLE_BYTES} "
548            f"bytes; this descriptor carried {len(needle)}"
549        )
550    return needle
551
552
553def verify_scanner_can_fire(
554    needle: bytes,
555    directory: Path,
556    scanner: Callable[[list[Path], bytes], ScanCounts] | None = None,
557) -> bool:
558    """Plant this run's needle and require the scanner to find it.
559
560    Without this, every way the search can be silently disabled — a needle that
561    arrived empty, an edit that broke the comparison, a walk that reads nothing
562    — reports the same `pass` as a clean product.
563    """
564    control = directory / "scanner-control"
565    control.mkdir(mode=0o700)
566    planted = control / "planted"
567    handle = os.open(planted, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
568    with os.fdopen(handle, "wb") as target:
569        target.write(b"prefix" + needle + b"suffix")
570    counts = (scanner or scan_paths)([control], needle)
571    shutil.rmtree(control, ignore_errors=True)
572    return counts.match_detected and counts.files_scanned == 1
573
574
575def self_test() -> None:
576    """Every property this receipt rests on has to be able to fail here."""
577    with tempfile.TemporaryDirectory() as directory:
578        workspace = Path(directory)
579        needle = b"canary-" + b"0123456789abcdef" * 2
580
581        # The scanner finds a planted needle, and distinguishes clean from
582        # missing. A scanner that cannot do this makes every run pass.
583        root = workspace / "logs"
584        root.mkdir()
585        (root / "clean.log").write_bytes(b"nothing to see")
586        clean = scan_paths([root], needle)
587        if clean.match_detected or not clean.present or clean.files_scanned != 1:
588            raise TripwireError("scanner reported a clean surface incorrectly")
589        (root / "leaked.log").write_bytes(b"secret=" + needle)
590        dirty = scan_paths([root], needle)
591        if not dirty.match_detected:
592            raise TripwireError("scanner failed to detect a planted needle")
593        missing = scan_paths([workspace / "missing"], needle)
594        if missing.present or missing.files_scanned != 0:
595            raise TripwireError("scanner did not distinguish absent from clean")
596
597        # An excluded path is not counted by the surface that excludes it.
598        excluded = scan_paths([root], needle, frozenset({root / "leaked.log"}))
599        if excluded.match_detected or excluded.files_scanned != 1:
600            raise TripwireError("scanner did not honour the exclusion")
601
602        # The live-fire control has to catch a scanner that cannot match.
603        if not verify_scanner_can_fire(needle, workspace):
604            raise TripwireError("the scanner control failed on a working scanner")
605        blind = verify_scanner_can_fire(
606            needle, workspace, scanner=lambda roots, needle: ScanCounts()
607        )
608        if blind:
609            raise TripwireError("the scanner control passed a scanner that never matches")
610
611        # An empty pasteboard read is an observation only when the pasteboard
612        # agrees it is empty.
613        for payload, declared, expected in (
614            (b"something", None, True),
615            (None, None, False),
616            (b"", None, False),
617            (b"", 0, True),
618            (b"", 12, False),
619        ):
620            readable, _ = pasteboard_capture_status(payload, declared)
621            if readable is not expected:
622                raise TripwireError(
623                    f"a pasteboard read of {payload!r} against {declared} was "
624                    f"judged {readable}"
625                )
626
627        # A needle outside the receipt's bounds is refused before anything is
628        # scanned, and a short one is refused too.
629        short_path = workspace / "short-canary"
630        short_path.write_bytes(b"tooshort")
631        with open(short_path, "rb") as short:
632            try:
633                read_needle(short.fileno())
634            except TripwireError:
635                pass
636            else:
637                raise TripwireError("a canary below the minimum length was accepted")
638        long_path = workspace / "canary"
639        long_path.write_bytes(needle + b"\n")
640        with open(long_path, "rb") as canary:
641            if read_needle(canary.fileno()) != needle:
642                raise TripwireError("the canary read back differently than it was written")
643
644        # The surfaces resolve where `paths.rs` writes, not under the
645        # application support root. This is the regression that let four of six
646        # surfaces record `absent` while the product logged 191 KB elsewhere.
647        logs = logs_root()
648        if logs != Path.home() / "Library" / "Logs" / STORAGE_SLUG:
649            raise TripwireError("the logs surface does not resolve to paths::logs_dir()")
650        if "Application Support" in str(logs):
651            raise TripwireError("the logs surface resolves under the data root again")
652        if crashes_root() != Path.home() / "Library" / "Logs" / "DiagnosticReports":
653            raise TripwireError("the crash surface does not resolve to paths::crashes_dir()")
654        plan = filesystem_surfaces(data_root(), logs, crashes_root())
655        if plan["telemetry"]["roots"] != [logs / "telemetry.log"]:
656            raise TripwireError("the telemetry surface does not resolve to the telemetry log")
657        if set(plan) | {"clipboard", "accessibility"} != {
658            "logs",
659            "telemetry",
660            "diagnostics",
661            "crashes",
662            "clipboard",
663            "accessibility",
664        }:
665            raise TripwireError("the surface inventory is not the six the validator requires")
666
667        # A required surface whose anchor does not exist blocks; it never
668        # reports the absence as a clean scan.
669        empty = ScanCounts()
670        if resolve_status("logs", empty, anchor_present=False) != "blocked":
671            raise TripwireError("a missing logs anchor did not block")
672        if resolve_status("logs", empty, anchor_present=True) != "blocked":
673            raise TripwireError("a missing required surface did not block")
674        if resolve_status("crashes", empty, anchor_present=True) != "absent":
675            raise TripwireError("an optional absent surface did not record absent")
676        scanned = ScanCounts()
677        scanned.present = True
678        if resolve_status("logs", scanned, anchor_present=True) != "pass":
679            raise TripwireError("a scanned clean surface did not pass")
680        scanned.match_detected = True
681        if resolve_status("logs", scanned, anchor_present=True) != "match":
682            raise TripwireError("a matched surface did not report the match")
683
684    print(json.dumps({"schema": TRIPWIRE_SCHEMA, "status": "self-test-passed"}))
685
686
687def main() -> int:
688    parser = argparse.ArgumentParser()
689    parser.add_argument(
690        "--app",
691        type=Path,
692        default=Path("/Applications/Omega.app"),
693        help="the installed candidate whose accessibility tree is read",
694    )
695    parser.add_argument("--candidate-evidence", type=Path)
696    parser.add_argument(
697        "--needle-fd",
698        type=int,
699        default=None,
700        help=(
701            "descriptor already open on the disposable canary the candidate was "
702            "given. Required: this script does not invent a secret nothing has seen."
703        ),
704    )
705    parser.add_argument("--candidate-pid", type=int, default=None)
706    parser.add_argument("--data-root", type=Path, default=None)
707    parser.add_argument("--logs-root", type=Path, default=None)
708    parser.add_argument("--crashes-root", type=Path, default=None)
709    parser.add_argument("--output", type=Path)
710    parser.add_argument("--self-test", action="store_true")
711    args = parser.parse_args()
712
713    if args.self_test:
714        self_test()
715        return 0
716
717    if args.candidate_evidence is None or args.output is None:
718        raise TripwireError("--candidate-evidence and --output are required")
719    if args.needle_fd is None:
720        raise TripwireError(
721            "--needle-fd is required. Pass the descriptor of the disposable "
722            "canary the candidate was actually given, for example "
723            "`--needle-fd 3 3<\"$OMEGA_PRIVATE_PROOF_DIR/disposable-canary\"`. "
724            "A needle this script generates itself has never been seen by any "
725            "other process, so searching for it can only ever pass."
726        )
727
728    evidence = json.loads(args.candidate_evidence.read_text(encoding="utf-8"))
729    # The candidate evidence records a structured digest, not a bare string;
730    # the validator compares against its `value`.
731    candidate_digest = evidence.get("candidate_digest", {}).get("value")
732    if not isinstance(candidate_digest, str) or len(candidate_digest) != 64:
733        raise TripwireError(
734            "candidate evidence has no usable candidate_digest to bind to"
735        )
736
737    needle = read_needle(args.needle_fd)
738
739    surfaces: list[dict[str, Any]] = []
740    details: dict[str, str] = {}
741    workspace = Path(tempfile.mkdtemp(prefix="omega-tripwires-"))
742    os.chmod(workspace, 0o700)
743    try:
744        if not verify_scanner_can_fire(needle, workspace):
745            raise TripwireError(
746                "the scanner did not detect a needle planted in front of it, so "
747                "nothing this run could report about the product would mean "
748                "anything"
749            )
750
751        plan = filesystem_surfaces(
752            data_root(args.data_root),
753            logs_root(args.logs_root),
754            crashes_root(args.crashes_root),
755        )
756        for name in sorted(plan):
757            record, detail = scan_filesystem_surface(name, plan[name], needle)
758            surfaces.append(record)
759            details[name] = detail
760
761        capture, method, reason = capture_pasteboard(workspace)
762        record, detail = scan_captured_surface(
763            "clipboard", "NSPasteboard:general", capture, method or reason, needle
764        )
765        surfaces.append(record)
766        details["clipboard"] = detail
767
768        capture, method, reason = capture_accessibility(
769            workspace, candidate_pid(args.app, args.candidate_pid)
770        )
771        record, detail = scan_captured_surface(
772            "accessibility",
773            f"AXUIElement:{args.app}",
774            capture,
775            method or reason,
776            needle,
777        )
778        surfaces.append(record)
779        details["accessibility"] = detail
780    finally:
781        shutil.rmtree(workspace, ignore_errors=True)
782
783    leaked = [surface["name"] for surface in surfaces if surface["match_detected"]]
784    blocked = [
785        surface["name"] for surface in surfaces if surface["status"] in ("blocked", "error")
786    ]
787    if leaked:
788        status = "fail"
789    elif blocked:
790        status = "blocked"
791    else:
792        status = "pass"
793
794    receipt = {
795        "schema": TRIPWIRE_SCHEMA,
796        "candidate_digest": candidate_digest,
797        "generated_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
798        "status": status,
799        # The value is deliberately absent. Recording the secret you are proving
800        # was not leaked would defeat the exercise.
801        "needle": {
802            "source": "protected_file_descriptor",
803            "length": len(needle),
804            "value_recorded": False,
805        },
806        "surfaces": surfaces,
807    }
808
809    args.output.parent.mkdir(parents=True, exist_ok=True)
810    args.output.write_text(
811        json.dumps(receipt, indent=2, sort_keys=True) + "\n", encoding="utf-8"
812    )
813
814    print(
815        json.dumps(
816            {
817                "schema": TRIPWIRE_SCHEMA,
818                "status": status,
819                "surfaces_scanned": len(surfaces),
820                "leaked_surfaces": leaked,
821                "blocked_surfaces": blocked,
822                # How each surface was read, so a reader can see the coverage
823                # the receipt's counts came from. The receipt itself carries no
824                # free-text field the validator would have to trust.
825                "surface_detail": details,
826                "output": str(args.output),
827            },
828            indent=2,
829            sort_keys=True,
830        )
831    )
832    if leaked:
833        return 1
834    return 3 if blocked else 0
835
836
837if __name__ == "__main__":
838    try:
839        sys.exit(main())
840    except TripwireError as error:
841        print(f"error: {error}", file=sys.stderr)
842        sys.exit(2)
843
Served at tenant.openagents/omega Member data and write actions are omitted.