Commit 8f46b89
fix(launcher): match the ca-trust guard to what node's CA loader accepts
The guard shipped in cnighswonger#283 disagreed with a real handshake on 8 of 20
measured bundle shapes (node v24.11.1 / openssl 3.6.1).
Seven were needless refusals of healthy bundles. Every PEM block was
parsed as a certificate, so any non-certificate block a merged bundle
legitimately carries — a CRL, a public key, key material — threw and
voided the whole file; and the torn-block check counted raw occurrences
of "-----BEGIN ", so a provenance comment that merely mentioned the
marker made a healthy bundle look torn. Refusing is not the safe
direction: the fallback drops every sibling and corporate CA for that
session, which is the failure this contract exists to prevent.
The eighth was the dangerous direction. Our own CA relabelled
TRUSTED CERTIFICATE parses to byte-identical DER, so the guard reported
"carries our CA" while node's loader skipped the block entirely, leaving
the session trusting nothing and failing every request with
UNABLE_TO_VERIFY_LEAF_SIGNATURE. The guard's own comment promised it was
"allowed to be conservative, never permissive" — this was permissive.
Markers are now anchored to line starts, non-CERTIFICATE blocks are
skipped the way node skips them, and the DER match must land on a
CERTIFICATE block. Where the guard cannot tell — a block damaged AFTER
ours, whose truncated body may or may not still decode, since openssl's
base64 reader treats the next '-' as end-of-data rather than an error —
it refuses.
The decision moved to bin/ca-trust.mjs so the tests drive the shipped
code. It was inline in a top-level script with a hand-copied twin in the
test file under a "change one, change both" comment; measured, mutating
the real one left the entire suite green. The test file's oracle was
also wrong: it verified through tls.connect({ca}), which ACCEPTS the
relabelled bundle that NODE_EXTRA_CA_CERTS rejects, so it was certifying
the guard against a mechanism the launcher does not use.
Three related defects in the same block:
- The orphan reaper shared the publish try, so rename() throwing skipped
it. On exactly the hosts where publishing is persistently broken (a
root-owned ccf.pem, a read-only mount, ENOSPC) each launch abandoned
one full-CA temp and collected none.
- Our own CA was parsed inside the bundle try, so an unparseable ca.pem
was reported as `ignoring <ca-trust.pem> (...)` — naming a file that
may be healthy — and then fell back to the file that had just failed
to parse.
- The spawned proxy's `export NODE_EXTRA_CA_CERTS=<our ca.pem>` recipe
was relayed to the operator immediately after the launcher had wired
claude via ca-trust.d, telling them to undo it. The launcher now drops
those lines from the stderr it relays; standalone the recipe carries a
same-host-MITM caveat.
Every clause is covered in both directions: five mutations of the guard
and one of each fix above were each caught by exactly one test.
prod +58 code / +101 comment, tests +199 (3.4x), 1 new file, 0 new env
vars. Full suite 1501 pass / 2 fail, both EMFILE from an fs.watch test
that fails identically at the merge base (inotify max_user_instances=128
on this host, unrelated to this change).
Co-Authored-By: Claude <noreply@anthropic.com>1 parent 23346ac commit 8f46b89
7 files changed
Lines changed: 414 additions & 127 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
8 | 16 | | |
9 | 17 | | |
10 | 18 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
80 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
81 | 88 | | |
82 | 89 | | |
83 | 90 | | |
| |||
124 | 131 | | |
125 | 132 | | |
126 | 133 | | |
127 | | - | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
133 | | - | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
134 | 163 | | |
135 | 164 | | |
136 | 165 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
0 commit comments