Skip to content

jco host: import taxonomy, stream-drop pin, key-material persistence note - #382

Merged
lann merged 1 commit into
mainfrom
jco-host-polish
Aug 12, 2026
Merged

jco host: import taxonomy, stream-drop pin, key-material persistence note#382
lann merged 1 commit into
mainfrom
jco-host-polish

Conversation

@lannbot

@lannbot lannbot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Closes #28, Closes #288 — the two jco-host maintenance lists, rolled up now that #28's remainder is documentation (per maintainer direction: the consumer-side pkcs8 route replaces host-side DER parsing). One file, textually overlapping small diffs; a PR apiece would pay two review passes on the same surface.

#288

  • Import-failure taxonomy (the one behavior change): invalidKey mapped every platform import rejection to invalid-key; a platform NotSupportedError — the engine declining the algorithm or form, not judging the material — now maps to unsupported, the taxonomy platformCall already documents. No conformance platform can observe the branch (every engine on the matrix serves what the host mints, so their importKey never reports NotSupportedError), hence the pin is a host-direct test with a stubbed platform importKey, both directions (test/import-failure-taxonomy.test.js).
  • The stream-drop assumption gets named and pinned. The host releases an output's admission reservation in the returned ReadableStream's cancel(), relying on the jco runtime lowering guest-side stream drop onto cancel() — an assumption that has ridden three toolchain generations (0.5.2 → fork 0.6.2 → upstream 0.7.0) as neither a documented convention nor a test. It is now the fourth entry in the header's jco-conventions list, and the admission suite pins the host half: a cancelled, undrained output releases its capacity (a regression means abandoned outputs leak the pool). The drop→cancel lowering itself remains a version-anchored convention — pinning it end-to-end needs a transpiled guest that drops a result stream, which no current harness produces; the header now says exactly that much honestly.
  • Doc fixes: the EcdsaAlgorithm typedef and the ECDSA_VARIANTS prose both claimed a curve-OID member "for the PKCS#8 wrapping" — no OID member exists and PKCS#8 imports are platform pass-throughs; eleven split prose-+-bare-@type JSDoc blocks merged so the prose attaches to its symbol (the count grew from the issue's list — a new one had accreted since); configure()'s objection-answering clause dropped (the rationale already lives in admission.test.js).
  • The errMessageTooLong ordering nit and the stale 0.5.2 header anchor resolved themselves in earlier PRs (Modernize the jco legs: fork transpiler, -I async, runtime import binding #347/Move the jco toolchain to upstream jco-transpile 0.7.0; drop the fork pins #368), per the issue's comment trail.

#28 (item 3, the last open box)

The item was filed when signing-key raw export derived from the platform JWK (d + atob) — that mechanic no longer exists (signing-key exports jwk/pkcs8 only, and b64urlDecode touches only a public point), so what remains is the exposure inherent to JWK itself. A "Key-material persistence" header section now states it:

  • JWK is a string format: export-key-jwk and to-wrap-input-jwk (the caller never sees that JWK, but the host still builds it) materialize secret members — k, d, the RSA CRT set — in JS strings, which are immutable, unscrubable, and live until GC. The platform's own exportKey("jwk") already returns strings, so this is inherent to serving JWK on a JS host.
  • The byte paths never do this: raw (symmetric) and pkcs8 (private) exports and wrap-inputs stay in ArrayBuffers end to end.
  • Consumers whose threat model includes host-memory persistence export private keys as pkcs8 and parse the fixed-layout DER themselves.

The four secret-JWK export sites carry one-line pointers to the section. The deltic host module mirrors the same platform techniques but is deliberately untouched: it is upstreamed verbatim from lann/deltic (its header's port-provenance rule), and its JWK helpers already reference this host's docs by convention, so the note travels with the reference rather than diverging the port.

Verification

  • just jco::test-host — 30 tests (27 baseline + the 3 added here), all pass
  • just jco::typecheck — clean
  • just demo::test-node — 23 checks through the real transpiled component
  • just conformance-ct::run-jco — jco-node, both suites: 19,089 + 770 results, 0 failed (the taxonomy change is invisible where platforms serve everything, as designed)
  • Cross-target risk checked: the rsa-verify-8192 decline probe deliberately pins no error variant, and the Firefox/WebKit ledger classes fail at operation stage or with DataError-class import rejections, so the engine legs (CI) see no verdict changes

…sistence note

Import-failure taxonomy: `invalidKey` routed every platform import
rejection to `invalid-key`, so an engine declining an algorithm at
importKey reported "bad key" where the host's own taxonomy (platformCall)
says `unsupported`. NotSupportedError now maps to `unsupported`. No
conformance platform can observe the branch (they all serve what the host
mints), so the pin is a host-direct test with a stubbed platform
importKey, both directions.

Stream drop: the assumption that the jco runtime lowers a guest dropping
a returned stream onto ReadableStream.cancel() has ridden three toolchain
generations (0.5.2, fork 0.6.2, upstream 0.7.0) unwritten and untested.
It is now a named convention in the header's list, and the admission
suite pins the host half: a cancelled, undrained output releases its
reservation — a regression there means abandoned outputs leak pool
capacity.

Key-material persistence (the last item of the host-hardening list): the
raw-export-derives-from-JWK mechanic the item was filed about no longer
exists (signature keys export jwk/pkcs8 only), so what remains is
documentation. A header section states which paths materialize secrets
in immortal JS strings — JWK exports and to-wrap-input-jwk, inherent to
serving a string format on a JS platform — and which stay in
ArrayBuffers (raw, pkcs8), with the consumer-side pkcs8 route for threat
models that include host-memory persistence. The four secret-JWK export
sites point at the section.

Doc polish: the stale EcdsaAlgorithm curve-OID claim (typedef and
ECDSA_VARIANTS prose — no OID member exists), eleven split JSDoc blocks
merged so the prose attaches to its symbol, and configure()'s
objection-answering clause dropped.
@lann
lann merged commit 060d1d2 into main Aug 12, 2026
10 checks passed
@lann
lann deleted the jco-host-polish branch August 12, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants