#!/usr/bin/env python3 """Founder-admitted node wrapper: private local child-ready pipe -> SSH control stdout. No token read/copy, login, tunnel or approval authentication. Parent must capture stdout over its admitted authenticated SSH peer; never use the silent warden lane. """ import argparse import contextlib import hashlib import json import math import os from pathlib import Path import signal import stat import subprocess import sys import threading import time SCHEMA='key39-private-input-ready/v1' PROPOSAL='KEY-WP-0039-T02/attended-lifecycle-v9-ready-input' TARGET='sso/keycape' MAX_READY=4096 MAX_ENVELOPE=65536 READY_TOTAL_SECONDS=5 class Refused(Exception):pass def need(ok,code): if not ok:raise Refused(code) def finite(value): try:return type(value) in (int,float) and math.isfinite(value) except OverflowError:return False def hash_bytes(raw):return hashlib.sha256(raw).hexdigest() def hex_pin(value):return isinstance(value,str) and len(value)==64 and all(c in '0123456789abcdef' for c in value) @contextlib.contextmanager def deadline(seconds): need(finite(seconds) and 00,'wrapper_control_write_failed') offset+=written def run(args): proc=None;read_fd=None;write_fd=None try: need(hex_pin(args.session_nonce),'wrapper_nonce_required') with deadline(READY_TOTAL_SECONDS): child=pinned_file(args.child,args.child_sha256,256*1024) envelope_raw=pinned_file(args.envelope,args.envelope_sha256,MAX_ENVELOPE,private=True) envelope=decode(envelope_raw) need(envelope.get('proposal_id')==PROPOSAL and envelope.get('target')==TARGET and envelope.get('disposition')=='approved' and envelope.get('founder_decision_id'), 'wrapper_exact_owner_envelope_required') need(envelope.get('source_hashes',{}).get(str(Path(args.child)))==args.child_sha256 and envelope.get('source_hashes',{}).get(str(Path(__file__).resolve()))==hash_bytes(Path(__file__).read_bytes()), 'wrapper_child_and_wrapper_pins_required') expiry=envelope['valid_until_epoch'];maximum=envelope['maximum_session_seconds'] need(finite(expiry) and type(maximum) is int and 420<=maximum<=900 and 00,'wrapper_session_window_expired') with deadline(remaining): rc=proc.wait(timeout=remaining) # Bounded captures: even accidental child output is never forwarded. unexpected=proc.stdout.read(MAX_READY+1) or proc.stderr.read(MAX_READY+1) need(not unexpected,'wrapper_child_output_refused') return 0 if rc==0 else 1 except BaseException: return 1 finally: for fd in (read_fd,write_fd): if fd is not None: try:os.close(fd) except OSError:pass if proc is not None: if proc.poll() is None: # Unknown/partial native outcomes remain owner recovery, never retry. try: with deadline(5): proc.terminate();proc.wait(timeout=5) except Exception: try: with deadline(2): proc.kill();proc.wait(timeout=2) except Exception:pass for stream in (proc.stdout,proc.stderr): if stream is not None: try: with deadline(0.02):stream.close() except Exception:pass def main(): parser=argparse.ArgumentParser(description=__doc__) for name in ('child','child-sha256','envelope','envelope-sha256','receipt','session-nonce'): parser.add_argument('--'+name,required=True) return run(parser.parse_args()) if __name__=='__main__':raise SystemExit(main())