docs(adr): openab-pty composable runtime (3 deployment profiles) - #1478
docs(adr): openab-pty composable runtime (3 deployment profiles)#1478chaodu-obk[bot] wants to merge 9 commits into
Conversation
…-process PTY mode)
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…ery contract, token control plane
… attach CAS, frame validation
… credential protection
…ory hashes, no signing key
…separation to optional hardening
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- F1: resolution asymmetry -- broker resolves [secrets.refs]; PTY runtime fails closed on any [secrets.refs] table or unresolved cloud reference; config example split into logical source vs delivered projection - F2: Phase 4 bridge redesigned as broker-pull over pod-local loopback -- no bridge secret ever enters the PTY container; events are display-only rate-limited hints; push+HMAC allowed only with an external signer or the runtime/child privilege boundary (non-MVP) - F3: hard kill domain MUST -- per-session cgroup.kill or pidfd descendant reaper; pgid is the first signal path, not the containment guarantee - F4: same-UID residual risks converted to a MUST checklist (read-only mounts, constant-time verify, token zeroize, 128-bit admin entropy floor, Linux-only PR_SET_DUMPABLE assumption, accepted SIGKILL risk) - F5: filesystem layout specified (/run/openab-pty runtime-only, /etc/openab-pty read-only projection, workspace as sole writable mount) - F6: session renew defined (admin-authenticated, process survives, generation bump, one-time token) and added to the Phase 1 CLI - F7: stale pre-keyless terms swept (PTY auth secret -> admin bootstrap credential; Phase 4 signing-key mention removed)
|
Important CHANGES REQUESTED What This PR DoesRecords the ADR for How It Works (round-4 delta)Commit
Round-3 ReconciliationThe round-3 record contains two parallel finding sets published under the same round key (an audit-trail irregularity this round reconciles):
Findings
Finding Details🔴 F1: Close the admin-credential delivery channelsWith UID separation demoted to optional hardening, the entire MVP control-plane security rests on "the plaintext admin credential is never observable by the same-UID shell child". The ADR forbids only the child's environment and filesystem view. Requested change: state as MUST that the plaintext exists only on the operator's trusted side; in-container verification accepts the credential only via short-lived non-echoing stdin or a UDS message body (never argv-style flags, which are world-readable via 🟡 F2-F4: The three long-running carriesEach has a one-paragraph fix: (F2) add a failure throttle and bounded-work policy on every admin credential verifier, not just WS upgrades; (F3) replace "origin policy ... carry over" with the accurate statement that Origin gating is a keyless-mode-only 🟡 F5-F6: Make the two new MUST sections implementable(F5) add a dedicated writable tmpfs/emptyDir for 🟡 F7-F17Bounded spec repairs; see the table. F8 and F9 are the residuals of the two partially-resolved prior criticals: F8 needs preempt audit/rate-limit plus a lock-ordered revoke/attach/replay sequence; F9 needs the precise threat-model wording, load-bearing dumpable, and admin-credential zeroization. Baseline Check
Addressing External Reviewer FeedbackNo external reviewer (bot or human) has commented on this PR; the requested reviewer has not yet reviewed. This section will address any such feedback in future rounds. What's Good (🟢)
5. Three Reasons We Might Not Need This PR
DispositionVerdict: CHANGES REQUESTED -- the direction is confirmed and the round-3 F1-F7 repairs all held up under mechanism-level scrutiny. F1 is the single blocker: close the plaintext delivery channels for the credential the whole MVP model depends on. F2-F4 are multi-round carries that deserve priority; F5-F17 are bounded spec repairs, most of them one-sentence to one-paragraph fixes. A revised head addressing F1 and the carries has a clear path to LGTM. |
- F1 (critical): close all admin-credential delivery channels -- plaintext
only on the operator side; in-container presentation via non-echoing
stdin or UDS body only; never argv (/proc/cmdline), env, temp files, or
logs; entropy floor raised to 256 bits matching attach tokens
- F2: admin control plane throttled (failure backoff, bounded bodies,
verification concurrency cap, audited failures) -- not just WS upgrades
- F3: Origin policy decided explicitly: bearer-only trust boundary;
the /acp Origin gate is keyless-loopback-only and does not carry over
- F4: web client token storage contract: memory-only, never
localStorage/URLs/cookies; CSP; Phase 2 acceptance criterion
- F5: /run/openab-pty backed by dedicated tmpfs; 'only writable mount'
corrected; path separation reworded as convention, not kernel boundary
- F6: kill domain deployable: pidfd reaper + PR_SET_CHILD_SUBREAPER as
MVP default; cgroup.kill gated on subtree delegation; fail-closed
startup probe; added to the Phase 1 checklist
- F7: startup guard explicitly rejects ${secrets.*} interpolation
- F8: takeover preempts audited + rate-limited; revoke/attach/replay
total order under one session lock
- F9: threat model reworded (runtime is the mint; at-rest vs transient);
PR_SET_DUMPABLE=0 load-bearing MUST with stated /proc+ptrace dependency;
admin credential buffer zeroized after verification
- F10: renew-while-attached evicts the active connection (defined)
- F11: session restart <name> added to the Phase 1 CLI
- F12: output path bounded (handoff queue, slow-client backlog; gap or
fail-closed disconnect)
- F13: Phase 4 pull-stream resource contract (single stream, heartbeat,
capped backoff, bounded coalescing queue, drop-oldest)
- F14: admin-credential rotation = redeploy new hash + restart, stated
- F15: sha256 verifier example with 256-bit-CSPRNG rationale (argon2
targets human-chosen secrets, forbidden here)
- F16: delivered-projection example satisfies the fail-closed TLS guard;
bridge diagram arrow reads as broker-initiated pull
- F17: rollback contract detailed; Helm owns projection generation with
a CI guard test (poisoned projection must be rejected)
Summary
Replaces the in-process "PTY Mode" proposal (#1477) with
openab-pty: an independently runnable, composable runtime — one codebase, two composable runtimes, three deployment modes (ACP only / PTY only / ACP+PTY colocated). The broker binary is untouched in every profile;openab-ptyowns its own session manager, listener, token model, and failure domain. Not deployed by default; highest-reversibility path.Review Contract
Goal
Record the design decision for delivering remote sandboxed terminal sessions as a separate
openab-ptyruntime (standalone or colocated sidecar), resolving the five blocker clusters from the #1477 review: positioning vs the Thin Bridge pillar, same-pod blast radius, auth below shell-equivalent capability, scrollback secrets contradiction, and ACP-pool lifecycle incompatibility.Non-goals
identity-trust-none.md); no A2A messaging (future ADR); no pod-lifetime session durability; no notification capability in the PTY-only profile (colocated profile only, relayed via the broker).Accepted Residual Risks
openab-ptyas a small product in OpenDray's category; scope is bounded by an explicit statement that it never grows platform adapters, orchestration, or memory features.Acceptance Criteria
Follow-ups
Architecture
flowchart LR subgraph clients_acp["ACP clients (unchanged)"] discord["Discord / Slack / Telegram"] end subgraph clients_pty["Terminal clients (new)"] web["Web terminal (xterm.js)"] mobile["Mobile / desktop terminal"] end subgraph pod["K8s Pod — profile 3 (colocated); profile 2 deploys the green half alone"] subgraph broker["Container: openab (broker) — byte-identical in all profiles"] adapters["Platform adapters"] pool["ACP session pool"] end subgraph sidecar["Container: openab-pty (ghcr.io/openabdev/openab-pty)"] ws["GET /pty/{session}<br/>WSS + per-session tokens"] mgr["PTY session manager (own)"] cli["CLI subprocess<br/>(operator-configured command)"] end vol[("Shared workspace volume")] end discord --> adapters --> pool web --> ws mobile --> ws ws --> mgr --> cli pool -.-> vol cli -.-> vol mgr -. "Phase 4 (colocated only):<br/>one-way notification webhook" .-> adapters style sidecar fill:#1a7f37,color:#fff style ws fill:#116329,color:#fff style mgr fill:#116329,color:#fff style cli fill:#116329,color:#fffKey points:
openab), PTY only (openab-ptystandalone with workspace PVC +[pty]config + PTY auth secret only), ACP+PTY (colocated, shared workspace volume). Helm:openab.enabled/pty.enabledor--set profile=acp|pty|full./pty/*).config.tomlvia the configUrl flow; broker ignores[pty];openab-ptyreads only[pty]plus shared basics./acpimplementation, short-lived per-session tokens from a signing secret via the secrets resolver.Notes
Context: Discord thread discussion on 2026-08-15.