Changes: Accept the isolated S00-018 ingress experiment and bounded ADR; record independent security findings and review handoff. Preserve all browser, live WSS, interoperability and production resource limitations. Validation: Fresh-cache npm ci and all 55 integrated tests passed on Node 24.21.0/npm 11.19.0; six additional adversarial documents rejected before real JXT.parse with stale callback/fresh attempt checks. Verified ten dependency licenses/integrities, 340 unchanged Stanza files, upstream saxes license bytes and matching digests. Stock defects reproduced; make check passed 72 tooling tests. Initial getter-only tracing attempt failed and is recorded separately. Scope/whitespace inspected; no browser or live WSS test. Task: S00-018 Agent: auto-security-64dc2d9fa232 Role: security Action: review Status: done Task title: Validate bounded standalone XML ingress for the XMPP candidate
117 lines
7.4 KiB
Markdown
117 lines
7.4 KiB
Markdown
# ADR 0002 parser remediation: bounded standalone XML ingress
|
|
|
|
- Status: accepted for the bounded S00-018 fixture experiment; independent review: `state/evidence/S00-018-review.md`.
|
|
- Task: S00-018 (remediation of S00-003).
|
|
- Date: 2026-10-15.
|
|
- Owner: auto-protocol-89fe6563092c (protocol).
|
|
- Reviewer: auto-security-64dc2d9fa232 (security), 2026-10-15.
|
|
|
|
Input: reviewed scope/stack/SASL ADRs, S00-003 parser blocker and bounded probe,
|
|
shipped Stanza parser/transport declarations and RFC 7395. Output: one isolated
|
|
runnable ingress experiment in experiments/0002-xmpp-library/parser-validation.
|
|
It does not select Stanza for the product, define production limits/shared
|
|
contracts, change authentication, or replace remaining S00-003 evidence.
|
|
|
|
The stock 12.22.1 transport keeps a StreamParser across WebSocket messages and
|
|
converts payloads to text. The finite diagnostic reproduced a 1 MiB body, depth
|
|
256 and 18 separate writes forming one 64 KiB body without errors. StreamParser
|
|
has no supported resource-budget options. The standalone-frame obligation is
|
|
[RFC 7395 §3.2/3.3.3](https://www.rfc-editor.org/rfc/rfc7395.html#section-3.3.3).
|
|
Inference from the shipped transport: stock parser reassembly violates that
|
|
obligation when each probe write represents a separate WebSocket message.
|
|
The RFC does not prescribe our numerical limits.
|
|
|
|
Propose a new public Client.transports constructor registration, selected alone
|
|
through config. The public Transport interface lists connect/disconnect/restart/
|
|
send; Client.connect instantiates the registered constructor with Client,
|
|
StreamManagement and Registry. This is a supported shipped integration surface,
|
|
not a private subclass override or ignored vendor patch. Exact shipped API:
|
|
node_modules/stanza/Client.d.ts, index.d.ts, Client.js, jxt/index.d.ts,
|
|
jxt/Parser.js and jxt/Registry.js. Upstream [Client source](https://github.com/legastero/stanza/blob/master/src/Client.ts)
|
|
is supporting context; installed 12.22.1 declarations/behavior are authoritative
|
|
for this experiment. Future stability is not promised.
|
|
|
|
Before any JXT tree/import, require text and finite bytes, then use a new
|
|
namespace-aware saxes XML 1.0 document parser and close it for completeness.
|
|
Throw on malformed input or exceeded event counters, reject restricted constructs,
|
|
and only then run the actual JXT.parse and Registry.import. Local ceilings:
|
|
|
|
| Resource | Maximum, inclusive | Enforcement |
|
|
| --- | --- | --- |
|
|
| Encoded message | 4096 UTF-8 bytes | Unit precheck, bounded TextEncoder, before SAX |
|
|
| Depth including root | 8 | SAX opentagstart |
|
|
| Elements including root | 32 | SAX opentagstart |
|
|
| Attributes including xmlns | 16 total/document | SAX attribute |
|
|
| Attribute name+decoded value units | 1024 total/document | SAX attribute |
|
|
| Element/attribute name units | 128 each | SAX event |
|
|
| Decoded attribute value units | 256 each | SAX attribute |
|
|
|
|
SAX itself stores tokens/namespaces before callbacks; their worst-case source
|
|
is capped by bytes first. No handwritten XML security parser or regex syntax
|
|
approximation is introduced. XML declarations, comments, PI and DTDs are
|
|
conservatively rejected; CDATA/built-in/numeric entities work. No entity fetch
|
|
or expansion extension is enabled. The entire document is validated before
|
|
any earlier root can emit. Incomplete documents/multiple roots/binary messages
|
|
are terminal and cannot be completed by subsequent messages. Close detaches
|
|
callbacks, clears attempt stream/config state and rejects stale callbacks.
|
|
Fresh connect creates a fresh transport. No payload is logged or raw-evented.
|
|
|
|
Alternatives rejected: post-import counters (too late), stock persistent parser
|
|
(no budgets/framing), regex or custom security parser (incorrect/unvetted),
|
|
DOMParser (tree allocated before structural checks), private source patches
|
|
(unsupported integration). A strict event parser plus byte ceiling provides a
|
|
bounded preflight without authoring XML syntax. Double parsing retains actual
|
|
candidate behavior, with bounded cost; complete normalization equivalence is
|
|
not established and is not claimed.
|
|
|
|
New exact dependencies: saxes 6.0.0 (ISC), xmlchars 2.2.0 (MIT); Stanza remains
|
|
12.22.1 (MIT). Ten exact installed packages are locked with HTTPS registry URLs,
|
|
SHA512 integrity, full license inventory and notices. Saxes published tarball
|
|
omits LICENSE; preserved upstream [v6.0.0 license](https://raw.githubusercontent.com/lddubeau/saxes/v6.0.0/LICENSE)
|
|
includes ISC lineage and an historical MIT notice. Public API/browser constraints
|
|
were checked against [versioned saxes README](https://raw.githubusercontent.com/lddubeau/saxes/v6.0.0/README.md)
|
|
and installed saxes.d.ts/source, not assumptions about a parser's strict mode.
|
|
Sources inspected 2026-10-15. Saxes has an older release; maintenance/advisory
|
|
clearance is not inferred from version pinning. README conformance claims are
|
|
upstream claims; our evidence is the bounded tests.
|
|
|
|
Verification: pinned Node 24.21.0/npm 11.19.0, fresh-cache npm ci, 55 integrated
|
|
tests passing; aggregate boundary document and Unicode boundary actually
|
|
import; all original failure categories and adversarial cases reject without
|
|
stanza emission/late revival. Registry tracing calls the real import. All 340
|
|
installed Stanza files byte-match the original candidate; no vendor edits.
|
|
make check passes 72 tooling tests (foundation only). Commands and setup details
|
|
are in the experiment README and state/evidence/S00-018-auto-protocol-89fe6563092c.md.
|
|
|
|
Browser use requires a CommonJS-capable bundler, native WebSocket/URL/TextEncoder
|
|
and private fields. Default uses browser WebSocket, fixture injection captures
|
|
socket effects only; actual Client.connect and its queue/registry/parser execute.
|
|
No browser build/runtime, actual WSS/TLS handshake, independent server/client
|
|
interoperability or exhaustion benchmark was run. Byte checks begin after
|
|
browser message allocation, and do not bound aggregate traffic/Client queues or
|
|
downstream work. This minimal transport does not implement all error reporting,
|
|
close negotiation, redirect, keepalive, language inheritance or XMPP sequencing.
|
|
No TLS/CORS bypass, invented channel binding, crypto primitive, production policy
|
|
or shared contract is added. ACC-004/NET-003/NET-004 remain obligations of the
|
|
original selection/product work; existing accepted SASL experiment is unchanged.
|
|
|
|
Independent security reviewer must inspect enforcement order/API integration,
|
|
repeat clean install/tests/license/vendor checks and record acceptance before
|
|
this ADR or S00-018 is accepted. Coordinator alone commits/launches further work.
|
|
|
|
|
|
## Independent acceptance
|
|
|
|
Security reviewer auto-security-64dc2d9fa232 independently repeated fresh-cache
|
|
npm ci and all 55 integrated tests on Node 24.21.0/npm 11.19.0. Ten exact
|
|
installed dependency licenses/integrities and all 340 unchanged Stanza files
|
|
were checked; the preserved saxes license byte-matches the versioned upstream
|
|
HTTPS source. Additional six adversarial inputs rejected before traced real
|
|
JXT.parse, all four stale socket callbacks left fresh attempts unaffected, and
|
|
fresh attempts imported the exact aggregate-boundary body. make check passed
|
|
72 tooling tests. Public shipped constructor/Transport APIs and enforcement
|
|
order were inspected. The bounded experiment is accepted; every browser,
|
|
WSS/interoperability, aggregate resource, sequencing and production-policy
|
|
limitation above remains. Report records commands and the failed initial tracing
|
|
attempt. Coordinator owns the review commit and subsequent fresh assignment.
|