v3.18.0 — 2026-06-01
Hardening release from a multi-agent code audit (1 P0 + 14 P2 + 2 P3 findings, each adversarially verified and independently reviewed) plus three follow-ups (#123–#125). Every change shipped as its own PR with a fresh-context reviewer (Iron Rule 10). The single-user default path (AUTH_MODE=none, no TUI) is behavior-identical except the /health change in #109.
Security
- #109 (P0) —
/healthno longer advertisesPROXY_ANONYMOUS_KEYto remote callers by default. TheanonymousKeyfield is gated behind a newPROXY_ADVERTISE_ANON_KEY=1opt-in env var; localhost callers are always exempt. Prevents any LAN-reachable device from harvesting a working, quota-spending bearer credential from the unauthenticated/healthendpoint. Behavior change:ocp-connectzero-config Path A now requires the server to setPROXY_ADVERTISE_ANON_KEY=1; otherwise pass--keyor use anonymous access. - #114 — Dashboard escapes all DB-sourced strings (key names, usage rows) before
innerHTML; the revoke button uses adata-attribute + listener instead of an inlineonclicka quote could break out of;POST /api/keysvalidates key names server-side ([A-Za-z0-9 ._-]{1,64}). - #124 — Dashboard status/plan summary cards escaped too (uniform defense-in-depth over all
innerHTMLsinks). - #111 — Streaming error paths strip filesystem paths from claude error text / stderr before sending them to clients (
sanitizeError), matching the non-streaming path.
Reliability / correctness
- #110 — Non-array
messagesis rejected with a 400 (was silently hanging the connection until socket timeout); OpenAI arraycontentis flattened into the prompt instead of dumped as raw JSON; a streamed upstream error now emits an SSEerrorframe instead of a success-lookingfinish_reason:"stop". - #111 —
res.on("close")escalates SIGTERM→SIGKILL on client disconnect (closes a narrow re-occurrence of the #37 concurrency-slot leak on the hottest exit path);overallTimeris cleared on semantic completion so a slow-exiting child can't record a spurious post-success timeout; per-key quota is documented as best-effort (bounded overshoot ≤MAX_CONCURRENT, cache hits uncounted). - #113 — CLI/installer hardening:
ocp-pluginrestart uses the live uid +dev.ocp.proxy/ocp-proxylabels and drops the unsafepkillfallback;ocp-connectquotes +chmod 600s the persisted key;setup.mjsXML-escapes and newline-validates injected service-unit secrets.
Alignment / governance
- #112 — OAuth token-refresh host (
platform.claude.com/v1/oauth/token) re-verified against the compiled cli.js v2.1.154 (strings, no live probe) and recorded inALIGNMENT.md; usage-probe and default request model now derive frommodels.json(ADR 0003 SPOT) instead of hardcoded IDs. - #123 — The legacy
console.anthropic.com/v1/oauth/tokenhost is pinned in thealignment.ymlblacklist so a future OAuth-host drift hard-fails CI; the blacklist now documents its dual purpose (known hallucinations + pinned wrong-host variants of a verified Class A endpoint).
TUI
- #115 — The TUI LAN gate refuses any non-loopback bind (not just literal
0.0.0.0); the achievedcc_entrypointis asserted each turn and atui_entrypoint_mismatchwarning is logged on a silent degrade to the metered sdk-cli pool.
Refactor
- #125 —
isLoopbackBindextracted tolib/net.mjs, shared byserver.mjsand the test suite (was duplicated via a copy-paste mirror).
New environment variables
PROXY_ADVERTISE_ANON_KEY— opt-in (default off); advertisePROXY_ANONYMOUS_KEYon the public/healthbody for remote zero-config discovery (#109).