# ADR 0002 parser remediation: bounded standalone XML ingress - Status: proposed; independent security review pending. - Task: S00-018 (remediation of S00-003). - Date: 2026-10-15. - Owner: auto-protocol-89fe6563092c (protocol). - Reviewer: not yet assigned. 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.