Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
183 lines
8.4 KiB
Python
183 lines
8.4 KiB
Python
#!/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 0<seconds<=1200,'wrapper_budget_invalid')
|
|
need(threading.current_thread() is threading.main_thread()
|
|
and signal.getitimer(signal.ITIMER_REAL)==(0.0,0.0),'wrapper_timer_required')
|
|
handler=signal.getsignal(signal.SIGALRM)
|
|
def expired(*_):raise Refused('wrapper_timeout')
|
|
signal.signal(signal.SIGALRM,expired);signal.setitimer(signal.ITIMER_REAL,seconds)
|
|
try:yield
|
|
finally:signal.setitimer(signal.ITIMER_REAL,0);signal.signal(signal.SIGALRM,handler)
|
|
|
|
def decode(raw):
|
|
def unique(pairs):
|
|
result={}
|
|
for key,value in pairs:
|
|
need(key not in result,'wrapper_duplicate_key');result[key]=value
|
|
return result
|
|
try:
|
|
return json.loads(raw,object_pairs_hook=unique,
|
|
parse_constant=lambda _:(_ for _ in ()).throw(Refused('wrapper_nonfinite_json')))
|
|
except Refused:raise
|
|
except Exception:raise Refused('wrapper_json_invalid') from None
|
|
|
|
def pinned_file(path,pin,limit,private=False):
|
|
p=Path(path);need(p.is_absolute() and p.resolve()==p,'wrapper_absolute_pin_required')
|
|
need(hex_pin(pin),'wrapper_pin_required')
|
|
fd=os.open(p,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK)
|
|
try:
|
|
before=os.fstat(fd)
|
|
need(stat.S_ISREG(before.st_mode) and 0<before.st_size<=limit,'wrapper_file_invalid')
|
|
if private:
|
|
need(before.st_uid==os.getuid() and stat.S_IMODE(before.st_mode)==0o600
|
|
and before.st_nlink==1,'wrapper_private_envelope_required')
|
|
raw=os.read(fd,limit+1);after=os.fstat(fd)
|
|
need(len(raw)<=limit and hash_bytes(raw)==pin,'wrapper_source_changed')
|
|
need((before.st_dev,before.st_ino,before.st_size,before.st_mtime_ns,before.st_ctime_ns)==
|
|
(after.st_dev,after.st_ino,after.st_size,after.st_mtime_ns,after.st_ctime_ns),
|
|
'wrapper_file_changed')
|
|
return raw
|
|
finally:os.close(fd)
|
|
|
|
def validate_marker(raw,*,nonce,executor_pin,envelope_pin,envelope):
|
|
need(isinstance(raw,bytes) and 0<len(raw)<=MAX_READY and raw.endswith(b'\n')
|
|
and raw.count(b'\n')==1,'wrapper_ready_invalid')
|
|
marker=decode(raw)
|
|
expected={'schema','session_nonce','proposal_id','target','executor_sha256','envelope_sha256',
|
|
'bao_health_identity_sha256','valid_until_epoch','receive_budget_seconds'}
|
|
need(isinstance(marker,dict) and set(marker)==expected,'wrapper_ready_invalid')
|
|
need(marker['schema']==SCHEMA and marker['session_nonce']==nonce
|
|
and marker['proposal_id']==PROPOSAL and marker['target']==TARGET
|
|
and marker['executor_sha256']==executor_pin and marker['envelope_sha256']==envelope_pin
|
|
and marker['bao_health_identity_sha256']==envelope['bao_health_identity_sha256']
|
|
and marker['valid_until_epoch']==envelope['valid_until_epoch'], 'wrapper_ready_binding_changed')
|
|
need(finite(marker['valid_until_epoch']) and time.time()<marker['valid_until_epoch']
|
|
and finite(marker['receive_budget_seconds']) and 0<marker['receive_budget_seconds']<=15,
|
|
'wrapper_ready_window_expired')
|
|
return marker
|
|
|
|
def read_ready(fd):
|
|
raw=b''
|
|
while len(raw)<=MAX_READY:
|
|
chunk=os.read(fd,MAX_READY+1-len(raw))
|
|
if not chunk:break
|
|
raw+=chunk
|
|
need(len(raw)<=MAX_READY,'wrapper_ready_oversized')
|
|
return raw
|
|
|
|
def forward_ready(raw):
|
|
# Wrapper stdout is a captured control pipe. Never child stdout or token data.
|
|
current=os.fstat(1)
|
|
need(stat.S_ISFIFO(current.st_mode),'wrapper_private_control_pipe_required')
|
|
need((current.st_dev,current.st_ino)!=(os.fstat(0).st_dev,os.fstat(0).st_ino),
|
|
'wrapper_control_input_alias_refused')
|
|
offset=0
|
|
while offset<len(raw):
|
|
written=os.write(1,raw[offset:]);need(type(written) is int and written>0,'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 0<expiry-time.time()<=maximum,'wrapper_action_window_invalid')
|
|
absolute_end=time.monotonic()+expiry-time.time()+300
|
|
read_fd,write_fd=os.pipe()
|
|
proc=subprocess.Popen([sys.executable,args.child,'execute','--envelope',args.envelope,
|
|
'--receipt',args.receipt,'--ready-fd',str(write_fd),'--session-nonce',args.session_nonce,
|
|
'--ready-envelope-sha256',args.envelope_sha256],stdin=sys.stdin.buffer,
|
|
stdout=subprocess.PIPE,stderr=subprocess.PIPE,pass_fds=(write_fd,),close_fds=True)
|
|
os.close(write_fd);write_fd=None
|
|
raw=read_ready(read_fd);os.close(read_fd);read_fd=None
|
|
validate_marker(raw,nonce=args.session_nonce,executor_pin=args.child_sha256,
|
|
envelope_pin=args.envelope_sha256,envelope=envelope)
|
|
forward_ready(raw)
|
|
remaining=absolute_end-time.monotonic();need(remaining>0,'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())
|