key-cape/scripts/openbao_negative_private_handoff.py
tegwick 0f47e0c666 Publish reviewed bounded JWT ready handoff and retained owner history
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 18:31:04 +02:00

266 lines
12 KiB
Python

#!/usr/bin/env python3
"""Owner-held verified KeyCape CLI files -> inherited pipe; never normal output.
No login, secret retrieval, network, executor launch or authenticated admission.
Decoded claims below are shape/lifetime checks, not signature verification.
"""
import argparse
import base64
import contextlib
import fcntl
import json
import math
import os
from pathlib import Path
import signal
import stat
import threading
import time
MAX_FILE = 32768
MAX_TOKEN = 16384
class Refused(Exception):
pass
def need(condition, code):
if not condition:
raise Refused(code)
def unique(pairs):
out = {}
for key, value in pairs:
need(key not in out, 'duplicate_json_key')
out[key] = value
return out
def decode(raw):
try:
return json.loads(raw, object_pairs_hook=unique,
parse_constant=lambda _: (_ for _ in ()).throw(Refused('nonfinite_json')))
except Refused:
raise
except Exception:
raise Refused('invalid_json') from None
@contextlib.contextmanager
def deadline(seconds):
need(type(seconds) in (int, float) and math.isfinite(seconds) and 0 < seconds <= 5,
'invalid_delivery_budget')
need(threading.current_thread() is threading.main_thread(), 'main_thread_required')
need(signal.getitimer(signal.ITIMER_REAL) == (0.0, 0.0), 'existing_timer_refused')
previous = signal.getsignal(signal.SIGALRM)
def expired(*_):
raise Refused('delivery_timeout')
try:
signal.signal(signal.SIGALRM, expired)
signal.setitimer(signal.ITIMER_REAL, seconds)
yield
finally:
signal.setitimer(signal.ITIMER_REAL, 0)
signal.signal(signal.SIGALRM, previous)
READY_SCHEMA = 'key39-private-input-ready/v1'
READY_KEYS = {'schema','session_nonce','proposal_id','target','executor_sha256',
'envelope_sha256','bao_health_identity_sha256','valid_until_epoch',
'receive_budget_seconds'}
def finite(value):
try:
return type(value) in (int,float) and math.isfinite(value)
except (OverflowError,ValueError):
return False
def endpoint(fd, writable):
need(type(fd) is int and fd >= 3, 'private_pipe_fd_required')
try:
identity = os.fstat(fd)
mode = fcntl.fcntl(fd,fcntl.F_GETFL) & os.O_ACCMODE
need(stat.S_ISFIFO(identity.st_mode) and mode ==
(os.O_WRONLY if writable else os.O_RDONLY), 'private_pipe_fd_required')
for standard in (0,1,2):
try:
current = os.fstat(standard)
except OSError:
continue
need((identity.st_dev,identity.st_ino) != (current.st_dev,current.st_ino),
'stdio_pipe_alias_refused')
return identity.st_dev,identity.st_ino
except OSError:
raise Refused('private_pipe_fd_required') from None
def wait_ready(fd, expected):
need(isinstance(expected,dict) and set(expected) == READY_KEYS - {'schema','receive_budget_seconds'},
'expected_child_pins_required')
need(finite(expected['valid_until_epoch']) and expected['valid_until_epoch'] > time.time(),
'ready_window_expired')
raw = b''
while len(raw) < 4096:
chunk = os.read(fd,1)
need(chunk, 'child_ready_missing')
if chunk == b'\n':
break
raw += chunk
else:
raise Refused('child_ready_oversized')
marker = decode(raw)
need(isinstance(marker,dict) and set(marker) == READY_KEYS
and marker.get('schema') == READY_SCHEMA, 'child_ready_invalid')
need(all(marker.get(key) == value for key,value in expected.items()), 'child_ready_mismatch')
need(finite(marker['valid_until_epoch']) and finite(marker['receive_budget_seconds'])
and 0 < marker['receive_budget_seconds'] <= 15
and marker['valid_until_epoch'] > time.time(),
'ready_window_expired')
return marker
def claims(token, kind, now):
need(isinstance(token, str) and 0 < len(token) <= MAX_TOKEN, 'invalid_token_shape')
parts = token.split('.')
need(len(parts) == 3 and all(parts), 'invalid_token_shape')
try:
obj = decode(base64.urlsafe_b64decode(parts[1] + '=' * (-len(parts[1]) % 4)))
need(isinstance(obj, dict), 'invalid_token_shape')
exp, iat = obj.get('exp'), obj.get('iat')
need(type(exp) in (int,float) and type(iat) in (int,float)
and math.isfinite(exp) and math.isfinite(iat)
and iat <= now + 30 and iat < exp and exp >= now + 300, 'token_window_invalid')
need(obj.get('iss') == 'https://kc.coulomb.social', 'token_source_invalid')
if kind == 'human':
need(obj.get('principal_type') == 'human'
and obj.get('aud') == 'netkingdom-bootstrap-console'
and isinstance(obj.get('sub'), str) and obj['sub'], 'human_shape_invalid')
assurance = obj.get('assurance', {})
need(isinstance(assurance,dict) and assurance.get('level') == 'aal2'
and assurance.get('mfa') is True, 'human_mfa_shape_invalid')
else:
need(obj.get('principal_type') == 'service'
and obj.get('aud') == 'codex-railiance-platform'
and obj.get('sub') == 'service:codex:railiance-platform'
and obj.get('tenant') == 'tenant:platform'
and obj.get('roles') == ['coding-agent']
and obj.get('scope') == 'openbao:login', 'coding_agent_shape_invalid')
except Refused:
raise
except Exception:
raise Refused('invalid_token_shape') from None
return token
def load(path, kind, now):
p = Path(path)
need(p.is_absolute() and p.parent.resolve() == p.parent, 'private_path_required')
need(not any((ancestor / '.git').is_file() or (ancestor / '.git' / 'HEAD').exists()
for ancestor in p.parents), 'git_path_refused')
directory = os.open(p.parent, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
try:
ds = os.fstat(directory)
need(ds.st_uid == os.getuid() and stat.S_IMODE(ds.st_mode) == 0o700,
'private_directory_required')
fd = os.open(p.name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=directory)
try:
original = os.fstat(fd)
need(stat.S_ISREG(original.st_mode) and original.st_uid == os.getuid()
and stat.S_IMODE(original.st_mode) == 0o600 and original.st_nlink == 1
and 0 < original.st_size <= MAX_FILE, 'private_file_required')
raw = b''
while len(raw) <= MAX_FILE:
chunk = os.read(fd, MAX_FILE + 1 - len(raw))
if not chunk:
break
raw += chunk
need(len(raw) <= MAX_FILE, 'private_file_oversized')
after = os.fstat(fd)
need((original.st_size,original.st_mtime_ns,original.st_ctime_ns) ==
(after.st_size,after.st_mtime_ns,after.st_ctime_ns), 'private_file_changed')
obj = decode(raw)
need(isinstance(obj,dict) and obj.get('token_type','').lower() == 'bearer',
'invalid_cli_token_file')
token = claims(obj.get('access_token'), kind, now)
return token, directory, p.name, original
finally:
os.close(fd)
except BaseException:
os.close(directory)
raise
def consume(item):
# Comparison is not atomic inode-conditioned unlink. Continuous exclusive
# same-UID directory custody is mandatory; concurrent writers are outside scope.
_, directory, name, original = item
current = os.stat(name, dir_fd=directory, follow_symlinks=False)
need((current.st_dev,current.st_ino,current.st_uid,current.st_nlink,current.st_size,
current.st_mtime_ns,current.st_ctime_ns) ==
(original.st_dev,original.st_ino,original.st_uid,original.st_nlink,original.st_size,
original.st_mtime_ns,original.st_ctime_ns), 'private_file_changed')
os.unlink(name, dir_fd=directory)
def deliver(human_path, agent_path, fd, *, ready_fd=None, expected=None,
consume_owner_files=False, budget=5):
receipt = {'adapter_performs_native_backend_actions': False, 'native_accepted': False,
'private_input_delivery_confirmed': False, 'caller_path_removal_completed': False,
'exclusive_same_uid_custody_required': True, 'atomic_inode_delete_guaranteed': False,
'child_ready_matched': False,
'credential_files_remain_possible': True, 'credential_copy_cleanup_verified': False,
'private_input_delivery_unknown': False}
items = []
writing = False
try:
need(consume_owner_files is True, 'explicit_owner_file_consumption_required')
data_identity = endpoint(fd,True)
control_identity = endpoint(ready_fd,False)
need(data_identity != control_identity, 'data_control_pipe_alias_refused')
need(isinstance(expected,dict) and finite(expected.get('valid_until_epoch')),
'expected_child_pins_required')
budget = min(budget, expected['valid_until_epoch'] - time.time())
with deadline(budget):
marker = wait_ready(ready_fd,expected)
receipt['child_ready_matched'] = True
remaining_alarm = signal.getitimer(signal.ITIMER_REAL)[0]
remaining = min(remaining_alarm, marker['receive_budget_seconds'],
expected['valid_until_epoch'] - time.time())
need(remaining > 0, 'ready_window_expired')
# Clip the existing timer; never restart or extend its original budget.
signal.setitimer(signal.ITIMER_REAL, remaining)
delivery_deadline = time.monotonic() + remaining
now = time.time()
items.append(load(human_path, 'human', now))
items.append(load(agent_path, 'coding-agent', now))
need((items[0][3].st_dev,items[0][3].st_ino) !=
(items[1][3].st_dev,items[1][3].st_ino), 'distinct_files_required')
payload = json.dumps({'human_token':items[0][0], 'coding_agent_token':items[1][0]},
separators=(',', ':')).encode() + b'\n'
# Unlink inspected caller-owned inodes; Python memory is not a zeroization guarantee.
need(time.time() < expected['valid_until_epoch'] and
time.monotonic() < delivery_deadline, 'ready_window_expired')
for item in items:
consume(item)
receipt.update(caller_path_removal_completed=True)
writing = True
offset = 0
while offset < len(payload):
need(time.time() < expected['valid_until_epoch'] and
time.monotonic() < delivery_deadline, 'ready_window_expired')
written = os.write(fd, payload[offset:])
need(written > 0, 'private_delivery_failed')
offset += written
need(time.time() < expected['valid_until_epoch'] and
time.monotonic() < delivery_deadline, 'ready_window_expired')
receipt['private_input_delivery_confirmed'] = True
except Refused as error:
receipt['failure_code'] = str(error)
except Exception:
receipt['failure_code'] = 'private_delivery_failed'
finally:
if writing and not receipt['private_input_delivery_confirmed']:
receipt['private_input_delivery_unknown'] = True
for item in items:
os.close(item[1])
# Launcher owns these endpoints: EOF even after partial delivery.
for owned in {fd,ready_fd}:
if type(owned) is int and owned >= 3:
try:
os.close(owned)
except OSError:
pass
return receipt
# Deliberately no standalone ready-file/ready-boolean CLI. Only the reviewed
# source launcher supplies its own captured child-control endpoint and exact pins.