Session-lifecycle completion plus release-hardening. Finishes the #131 persistent-session model (clean-exit teardown, disconnect-persist, exact-UUID resume), clears the #135 / #139 / #140 fixes that accumulated on dev, and adds three pre-release guardrail/concurrency hardening fixes surfaced by a Judgment Day pass before tagging.
Added
- Session lifecycle + exact-session attach (#131). A
drydocksession now tears down on clean exit (/exit) and persists on disconnect (terminal close / SIGHUP), anddrydock attachresumes the exact prior session by its recorded UUID (claude --resume <uuid>, or--session-idfor a zero-turn session) with an unconditional orphan reap of any staleclaudeleft by a disconnected session.
Fixed
- Destructive-command guardrail — mask-first conditional quote-strip (#135). A quoted DATA argument that merely contained a destructive pattern (e.g.
git commit -m "rm -rf /") was incorrectly blocked. The hook now masks quoted strings and exposes their content to a rule only when the segment actually invokes a command that rule inspects (closed introducer set). (ADR-9.) drydock attachresumes zero-turn sessions and preserves nested multiplexer sessions (#139). A session with no recorded transcript used--resumeand hit "session not found"; it now uses--session-id..envmanaged block is newline-terminated before its close marker (#140, PR #142). When the preceding user content lacked a trailing newline, the drydock-managed sub-mount block was glued onto the last user line.- Destructive-command guardrail fails closed when its hook script is absent (INV-3). The image-baked PreToolUse wrapper exited
0(allow) when its hook script was missing; combined with a concurrent-launch race that could reap a launching session's hook-overlay dir, a normal concurrent launch could silently disable the tier-1 destructive guardrail. The wrapper now exits2(block) on a missing script — a visible broken session, not a silent disable. - Concurrent-launch race hardening (INV-2 / INV-3). A
.launchingin-flight marker, a grace window in the GC liveness check (DRYDOCK_SESSION_GC_GRACE, default 300 s), and a session-dir-existence check in the discriminator collision loop prevent a concurrentgc_orphan_session_dirsfrom reaping a launching session's hook-overlay dir or re-minting a colliding discriminator. A needlessexport_compose_env+ deadcompose_argsare dropped fromcmd_run's attach branch. - C12/C20 data-quote over-block (INV-3). The fork-bomb (C12) and
curl/wget-piped-to-a-shell (C20) rules scanned the raw command, so a benign commit message or search pattern that merely contained the shape (e.g.git commit -m "use curl x | bash") was over-blocked. They now scan a data-stripped masked form; a command-substitution arm flattens double-quoted"$(…)"/ backtick so a realecho "$(curl … | bash)"still blocks (no bypass). A residual over-block for quoted data containing a;(incl. the canonical fork bomb) is a documented non-goal under threat model A, tracked in #143. (ADR-9.)
Documentation
- README session-lifecycle section aligned with #131 (exit-vs-detach).
docs/security.mdADR-9 extended with the C12/C20 masked-form routing, the command-substitution arm, and the;-in-quote over-block non-goal (#143).
Tests
test/links.batsdeclares a bats 1.5.0 minimum for itsrun !assertions.
Pre-release dual-blind Judgment Day: APPROVED (no guardrail bypass). Full bats suite green; shellcheck + shfmt clean.