From e2b4dbdb7628f5bf3c821fa89f69df8a6095926f Mon Sep 17 00:00:00 2001 From: TolaMironcenko Date: Thu, 15 Oct 2026 02:31:33 +0700 Subject: [PATCH] S00-018: Bound standalone XML ingress before Stanza parsing and import Changes: Add an isolated public transport integration with finite byte, depth, element and attribute budgets; validate complete text XML with pinned saxes before real JXT parsing/import. Add adversarial and boundary fixtures, terminal cleanup tests, dependency notices and a proposed parser ADR. Validation: Fresh-cache npm ci and npm test passed all 55 tests with Node 24.21.0/npm 11.19.0. Reproduced three stock parser failures, checked ten exact dependency licenses/integrities and 340 unchanged Stanza files; make check passed 72 tooling tests. Scope and whitespace inspected. Browser runtime, live WSS and interoperability were not tested. Task: S00-018 Agent: auto-protocol-89fe6563092c Role: protocol Action: claim Status: review Task title: Validate bounded standalone XML ingress for the XMPP candidate --- docs/decisions/0002-xmpp-parser-validation.md | 100 +++ .../parser-validation/.npmrc | 3 + .../parser-validation/LICENSE_INVENTORY.md | 16 + .../parser-validation/README.md | 96 +++ .../parser-validation/THIRD_PARTY_NOTICES.md | 637 ++++++++++++++++++ .../parser-validation/bounded-ingress.mjs | 146 ++++ .../parser-validation/check-dependencies.py | 42 ++ .../licenses/saxes-6.0.0-LICENSE | 64 ++ .../parser-validation/package-lock.json | 119 ++++ .../parser-validation/package.json | 10 + .../parser-validation/test/fixtures.mjs | 68 ++ .../test/integration.test.mjs | 151 +++++ planning/TASK_MATRIX.md | 4 +- planning/stages/S00.md | 2 +- planning/task_matrix.csv | 2 +- .../auto-protocol-89fe6563092c.txt | 4 + .../S00-018-auto-protocol-89fe6563092c.md | 118 ++++ state/handoffs/S00-018.md | 39 ++ state/tasks.json | 35 +- 19 files changed, 1648 insertions(+), 8 deletions(-) create mode 100644 docs/decisions/0002-xmpp-parser-validation.md create mode 100644 experiments/0002-xmpp-library/parser-validation/.npmrc create mode 100644 experiments/0002-xmpp-library/parser-validation/LICENSE_INVENTORY.md create mode 100644 experiments/0002-xmpp-library/parser-validation/README.md create mode 100644 experiments/0002-xmpp-library/parser-validation/THIRD_PARTY_NOTICES.md create mode 100644 experiments/0002-xmpp-library/parser-validation/bounded-ingress.mjs create mode 100644 experiments/0002-xmpp-library/parser-validation/check-dependencies.py create mode 100644 experiments/0002-xmpp-library/parser-validation/licenses/saxes-6.0.0-LICENSE create mode 100644 experiments/0002-xmpp-library/parser-validation/package-lock.json create mode 100644 experiments/0002-xmpp-library/parser-validation/package.json create mode 100644 experiments/0002-xmpp-library/parser-validation/test/fixtures.mjs create mode 100644 experiments/0002-xmpp-library/parser-validation/test/integration.test.mjs create mode 100644 state/commit_messages/auto-protocol-89fe6563092c.txt create mode 100644 state/evidence/S00-018-auto-protocol-89fe6563092c.md create mode 100644 state/handoffs/S00-018.md diff --git a/docs/decisions/0002-xmpp-parser-validation.md b/docs/decisions/0002-xmpp-parser-validation.md new file mode 100644 index 0000000..3042883 --- /dev/null +++ b/docs/decisions/0002-xmpp-parser-validation.md @@ -0,0 +1,100 @@ +# 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. diff --git a/experiments/0002-xmpp-library/parser-validation/.npmrc b/experiments/0002-xmpp-library/parser-validation/.npmrc new file mode 100644 index 0000000..d253cac --- /dev/null +++ b/experiments/0002-xmpp-library/parser-validation/.npmrc @@ -0,0 +1,3 @@ +engine-strict=true +strict-ssl=true +registry=https://registry.npmjs.org/ diff --git a/experiments/0002-xmpp-library/parser-validation/LICENSE_INVENTORY.md b/experiments/0002-xmpp-library/parser-validation/LICENSE_INVENTORY.md new file mode 100644 index 0000000..a621039 --- /dev/null +++ b/experiments/0002-xmpp-library/parser-validation/LICENSE_INVENTORY.md @@ -0,0 +1,16 @@ +# Exact dependency license inventory + +Inspected 2026-10-15. Node 24.21.0 / npm 11.19.0. Saxes tarball omits its license text: preserved upstream v6.0.0 LICENSE in licenses/saxes-6.0.0-LICENSE, retrieved via verified HTTPS. Other texts are shipped installed files. No advisory/provenance clearance is claimed. + +| Package | Version | SPDX | License/notice source | +| --- | --- | --- | --- | +| @types/node | 22.20.5 | MIT | LICENSE | +| @types/punycode | 2.1.4 | MIT | LICENSE | +| @types/ws | 8.18.2 | MIT | LICENSE | +| punycode | 2.3.1 | MIT | LICENSE-MIT.txt | +| saxes | 6.0.0 | ISC | saxes-6.0.0-LICENSE | +| sdp | 3.2.2 | MIT | LICENSE | +| stanza | 12.22.1 | MIT | LICENSE.md, NOTICE.md | +| undici-types | 6.21.0 | MIT | LICENSE | +| ws | 8.22.0 | MIT | LICENSE | +| xmlchars | 2.2.0 | MIT | LICENSE | diff --git a/experiments/0002-xmpp-library/parser-validation/README.md b/experiments/0002-xmpp-library/parser-validation/README.md new file mode 100644 index 0000000..9b65e86 --- /dev/null +++ b/experiments/0002-xmpp-library/parser-validation/README.md @@ -0,0 +1,96 @@ +# Bounded standalone XML ingress experiment (S00-018) + +This fixture experiment uses real Stanza 12.22.1 Client dispatch, JXT.parse and +registry.import, preceded by saxes 6.0.0 XML 1.0 namespace-aware validation. +It adds a constructor to the public Client.transports registry and selects only +that transport via configuration. It does not use the stock persistent parser, +subclass private transport fields, intercept already imported events, or patch +vendor files. It is not a production transport or a library selection. + +From project root, reuse the reviewed isolated Node runtime (macOS ARM64): + +```sh +export PATH=/private/tmp/luma-stack-runtime/node-v24.21.0-darwin-arm64/bin:$PATH +npm ci --prefix experiments/0002-xmpp-library/parser-validation --ignore-scripts --no-audit --no-fund --cache /private/tmp/luma-parser-review-cache --fetch-retries=0 --fetch-timeout=15000 +npm test --prefix experiments/0002-xmpp-library/parser-validation +python3 experiments/0002-xmpp-library/parser-validation/check-dependencies.py +node experiments/0002-xmpp-library/parser-probe/reproduce.mjs +``` + +The last command reproduces stock defects; exit 0 is NOT safe parsing. +The dependency checker also requires the original candidate already installed in +experiments/0002-xmpp-library/node_modules for byte comparison. It checks all 340 +Stanza files and ten exact locked installed package licenses/integrities. The +saxes tarball omits LICENSE: the upstream v6.0.0 license is preserved locally, +with source https://raw.githubusercontent.com/lddubeau/saxes/v6.0.0/LICENSE. + +## Enforcement order and local policy + +1. Require primitive string. Reject Blob/ArrayBuffer/typed arrays before coercion. +2. Reject more than 4096 UTF-16 units without encoding; then reject more than + 4096 UTF-8 bytes using TextEncoder. The temporary encoding buffer is bounded + by the unit check (at most 12288 bytes for any input); it is not an unbounded + copy. First character must be `<` as required by RFC 7395. +3. New saxes parser per message, fragment=false, xmlns=true, XML 1.0 forced, + no inherited namespace resolver. Throw on the first error; close() checks + document completion. No tree is built during this phase. +4. At opentagstart reject depth >8 (including root), elements >32, name >128 + UTF-16 units. At attribute reject total attributes >16 (including xmlns), + name >128, decoded value >256, or aggregate decoded attribute name+value + units >1024. Checks occur before JXT tree construction/import. Saxes has + already stored one completed attribute at its callback; lexical tokens, + namespace tables and DTD buffers before callbacks are bounded by 4096 input + bytes. No claim of allocation-free SAX processing is made. +5. Reject DTDs, comments, processing instructions and XML declarations. Only + built-in/numeric entities are available; no DTD fetching/entity expansion is + enabled. CDATA is accepted. Declaration rejection is local conservative + policy; RFC 7395 discourages but does not forbid declarations. +6. Only after complete validation call real JXT.parse and registry.import. An + unknown top-level root or candidate parser/import error closes the attempt. + Parsed output is delivered through Client stream:data only after import + succeeds. No invalid document's earlier root is emitted. +7. On rejection close the socket, detach all four callbacks, clear config and + stream references before emitting a payload-free error code. Every old + captured callback checks the terminal flag. Parsers/trees are synchronous + function locals, never retained between messages. A fresh Client.connect + creates another transport; restart on a failed transport cannot revive it. + +The combined boundary fixture hits 4096 bytes, depth 8, 32 elements, +16 attributes and 1024 attribute units simultaneously and imports a real body. +Separate tests cover name/value equality, multibyte byte equality, one-over +bounds, stock 1 MiB/depth-256/18-message cases, malformed tails, incomplete XML, +binary, duplicate attributes/namespaces/entities/invalid characters, callback +replay, fresh connect, stream open/close/restart and outgoing registry export. +Tracing registry.import calls the real implementation; it never fakes success. +FixtureSocket replaces only browser socket effects; tests use actual Client +connect/transport selection and stanza queue events. + +## Constraints + +The default socket constructor is globalThis.WebSocket; browser use requires a +CommonJS-capable bundler for Stanza/saxes/xmlchars, native WebSocket, URL and +TextEncoder plus support for private class fields. The injected fixture socket +is explicitly separate and never chosen by the default path. Only explicit +wss URLs and the xmpp subprotocol are allowed. TLS/CORS/binding are unchanged; +no real WSS handshake is exercised here. Browser build/runtime and selected +browser versions are NOT verified. Node tests run 24.21.0/npm 11.19.0. + +Limits protect application parsing after the browser delivers a complete message. +They cannot prevent the browser/network stack from allocating a huge inbound +message, nor bound total traffic, Client queue growth, CPU rate, reconnect +policy or downstream extension work. Those need separate product/provider +policies. The experiment closes locally on violations rather than implementing +full XMPP stream-error reporting/close negotiation/redirect/keepalive behavior. +It does not enforce complete XMPP stream sequencing or preserve inherited stream +language. JXT interpretation/normalization is retained; SAX and JXT equivalence +for all accepted XML forms is not established. No auth/crypto changes, complete +NET-003 policy, OMEMO, independent server/client interoperability, exhaustion +benchmark, advisory/provenance audit, or release acceptance is claimed. + +All ten package license texts/notices are in THIRD_PARTY_NOTICES.md and exact +versions in LICENSE_INVENTORY.md/package-lock.json. Saxes is a mature existing +strict parser with documented XML/namespace conformance and public event APIs; +6.0.0 is an older release, and no ongoing-maintenance SLA is assumed. Its +absence of built-in token limits is why the mandatory byte precheck comes first. +See proposed docs/decisions/0002-xmpp-parser-validation.md. Independent security +review is required; S00-003 remains a separate assignment after reviewed done. diff --git a/experiments/0002-xmpp-library/parser-validation/THIRD_PARTY_NOTICES.md b/experiments/0002-xmpp-library/parser-validation/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..fe9576e --- /dev/null +++ b/experiments/0002-xmpp-library/parser-validation/THIRD_PARTY_NOTICES.md @@ -0,0 +1,637 @@ +# Third-party notices + +Verbatim license/notice texts for this isolated experiment. + +## @types/node 22.20.5 — LICENSE + +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +## @types/punycode 2.1.4 — LICENSE + +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +## @types/ws 8.18.2 — LICENSE + +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +## punycode 2.3.1 — LICENSE-MIT.txt + +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## saxes 6.0.0 — saxes-6.0.0-LICENSE + +The ISC License + +Copyright (c) Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +==== + +The following license is the one that governed sax, from which saxes +was forked. Isaac Schlueter is not *directly* involved with saxes so +don't go bugging him for saxes issues. + +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +==== + +`String.fromCodePoint` by Mathias Bynens is no longer used, but it can +still be found in old commits. It was once used according to terms of +MIT License, as follows: + + Copyright Mathias Bynens + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## sdp 3.2.2 — LICENSE + +Copyright (c) 2017 Philipp Hancke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +## stanza 12.22.1 — LICENSE.md + +The MIT License (MIT) + +Copyright © 2013-2019 Lance Stout + +Copyright © 2013-2019 &yet + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## stanza 12.22.1 — NOTICE.md + +# StanzaJS + +Copyright © 2013-2019 Lance Stout + +Copyright © 2013-2019 &yet + +Portions of this code were adapted from existing projects: + +## SASL + +- saslmechanisms +- sasl-anonymous +- sasl-digest-md5 +- sasl-external +- sasl-plain +- sasl-x-oauth2 + +(The MIT License) + +Copyright © 2012-2013 Jared Hanson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## ltx + +Copyright © 2010 Stephan Maka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## xhtml-im.js + +The MIT License (MIT) + +Copyright © 2013 Waqas Hussain + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## wildemitter + +The MIT License (MIT) + +Copyright © 2012 Henrik Joreteg + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## hash-base + +The MIT License (MIT) + +Copyright (c) 2016 Kirill Fomichev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## cipher-base + +The MIT License (MIT) + +Copyright (c) 2017 crypto-browserify contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +## sha.js + +Copyright (c) 2013-2018 sha.js contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Copyright (c) 1998 - 2009, Paul Johnston & Contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the author nor the names of its contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## md5.js + +The MIT License (MIT) + +Copyright (c) 2016 Kirill Fomichev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## create-hash + +The MIT License (MIT) + +Copyright (c) 2017 crypto-browserify contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +## create-hmac + +The MIT License (MIT) + +Copyright (c) 2017 crypto-browserify contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +## randombytes + +MIT License + +Copyright (c) 2017 crypto-browserify + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +## uuid + +The MIT License (MIT) + +Copyright (c) 2010-2016 Robert Kieffer and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +## strict-event-emitter-types + +ISC License (ISC) + +Copyright 2018 Brian Terlson + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +## capsdb + +capsdb - A collection of XMPP caps with hashes + +Written in 2015 by Thijs Alkemade me@thijsalkema.de + +To the extent possible under law, the author(s) have dedicated all copyright and +related and neighboring rights to this software to the public domain worldwide. +This software is distributed without any warranty. + +You should have received a copy of the CC0 Public Domain Dedication along with +this software. If not, see http://creativecommons.org/publicdomain/zero/1.0/. + +## undici-types 6.21.0 — LICENSE + +MIT License + +Copyright (c) Matteo Collina and Undici contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +## ws 8.22.0 — LICENSE + +Copyright (c) 2011 Einar Otto Stangvik +Copyright (c) 2013 Arnout Kazemier and contributors +Copyright (c) 2016 Luigi Pinca and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## xmlchars 2.2.0 — LICENSE + +Copyright Louis-Dominique Dubeau and contributors to xmlchars + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/experiments/0002-xmpp-library/parser-validation/bounded-ingress.mjs b/experiments/0002-xmpp-library/parser-validation/bounded-ingress.mjs new file mode 100644 index 0000000..8dcfee0 --- /dev/null +++ b/experiments/0002-xmpp-library/parser-validation/bounded-ingress.mjs @@ -0,0 +1,146 @@ +import {SaxesParser} from 'saxes'; +import jxt from 'stanza/jxt/index.js'; + +// Experiment policy only. The byte ceiling bounds SAX token/namespace storage +// even before callbacks can inspect a completed name or attribute value. +export const LIMITS = Object.freeze({bytes: 4096, depth: 8, elements: 32, + attributes: 16, attributeChars: 1024, nameChars: 128, valueChars: 256}); +class IngressError extends Error { + constructor(code) { super(code); this.code = code; } +} +const fail = code => { throw new IngressError(code); }; + +export function validateMessage(data) { + if (typeof data !== 'string') fail('text-required'); + // Avoid encoding an attacker-sized string. UTF-8 bytes >= UTF-16 units for + // valid XML; isolated surrogates are subsequently rejected by saxes. + if (data.length > LIMITS.bytes) fail('bytes'); + if (new TextEncoder().encode(data).length > LIMITS.bytes) fail('bytes'); + if (!data.startsWith('<')) fail('framing'); + let depth = 0, elements = 0, attributes = 0, attributeChars = 0, ended = false; + const parser = new SaxesParser({xmlns: true, fragment: false, + defaultXMLVersion: '1.0', forceXMLVersion: true}); + parser.on('error', () => fail('invalid-xml')); + for (const event of ['doctype', 'comment', 'processinginstruction', 'xmldecl']) { + parser.on(event, () => fail('restricted-xml')); + } + parser.on('opentagstart', tag => { + if (++depth > LIMITS.depth) fail('depth'); + if (++elements > LIMITS.elements) fail('elements'); + if (tag.name.length > LIMITS.nameChars) fail('name'); + }); + parser.on('attribute', attr => { + if (++attributes > LIMITS.attributes) fail('attributes'); + if (attr.name.length > LIMITS.nameChars) fail('name'); + if (attr.value.length > LIMITS.valueChars) fail('attribute-value'); + attributeChars += attr.name.length + attr.value.length; + if (attributeChars > LIMITS.attributeChars) fail('attribute-chars'); + }); + parser.on('closetag', () => { depth--; }); + parser.on('end', () => { ended = true; }); + // close() detects unfinished XML. Throwing from callbacks aborts immediately; + // there is no recovery listener, DTD fetch, custom entity or tree construction. + parser.write(data).close(); + if (!ended || depth !== 0 || elements === 0) fail('framing'); +} + +export function importMessage(data, registry, config) { + validateMessage(data); + // Only bounded, complete documents reach the actual candidate parser/import. + const xml = jxt.parse(data, {allowComments: false}); + if (!xml) fail('invalid-xml'); + const kind = registry.getImportKey(xml); + const stanza = registry.import(xml, {lang: config.lang, + acceptLanguages: config.acceptLanguages}); + if (!kind || !stanza) fail('unknown-root'); + return {kind, stanza}; +} + +// Public Transport interface and Client.transports constructor registry. +// Socket injection is solely for finite fixtures; default is browser WebSocket. +export function installBoundedTransport(client, Socket = globalThis.WebSocket) { + class BoundedTransport { + #client; #registry; #sm; #socket; #config; #closed = true; + hasStream = false; + stream; + constructor(agent, sm, registry) { + this.#client = agent; this.#sm = sm; this.#registry = registry; + } + connect(config) { + if (this.#socket || !this.#closed) throw new Error('Fresh transport required'); + if (new URL(config.url).protocol !== 'wss:') throw new Error('WSS required'); + this.#config = config; + this.#closed = false; + const socket = this.#socket = new Socket(config.url, 'xmpp'); + socket.onopen = () => { + if (this.#closed) return; + if (socket.protocol !== 'xmpp') return this.#reject('subprotocol'); + this.#sm.started = false; + this.#client.emit('connected'); + this.restart(); + }; + socket.onmessage = event => { + if (this.#closed) return; + let parsed; + try { parsed = importMessage(event.data, this.#registry, this.#config); } + catch (error) { return this.#reject(error instanceof IngressError ? error.code : 'invalid-xml'); } + // No raw payload event/log or async parser continuation escapes ingress. + if (parsed.kind === 'stream') { + if (parsed.stanza.action === 'open') { + this.hasStream = true; + this.stream = parsed.stanza; + this.#client.emit('stream:start', parsed.stanza); + } else { + this.#client.emit('stream:end'); + this.disconnect(false); + } + } else { + this.#client.emit('stream:data', parsed.stanza, parsed.kind); + } + }; + socket.onerror = () => this.#reject('socket-error'); + socket.onclose = () => this.disconnect(false); + } + #reject(code) { + if (this.#closed) return; + // Close/clear before notifying consumers; a reentrant callback cannot + // revive the attempt. Never expose parser error text containing input. + this.disconnect(false); + this.#client.emit('ingress:rejected', code); + } + disconnect(clean = true) { + if (this.#closed) return; + this.#closed = true; + const socket = this.#socket; + this.#socket = undefined; + if (socket) { + socket.onopen = socket.onmessage = socket.onerror = socket.onclose = null; + if (clean && this.hasStream && socket.readyState === 1) { + socket.send(this.#registry.export('stream', {action: 'close'}).toString()); + } + socket.close(); + } + this.hasStream = false; + this.stream = undefined; + this.#config = undefined; + if (this.#client.transport === this) this.#client.emit('--transport-disconnected'); + } + restart() { + if (this.#closed) return; + this.hasStream = false; + this.stream = undefined; + this.#write(this.#registry.export('stream', {action: 'open', + lang: this.#config.lang, to: this.#config.server, version: '1.0'}).toString()); + } + #write(data) { + if (this.#closed || this.#socket?.readyState !== 1) throw new Error('Socket closed'); + this.#socket.send(data); + } + async send(name, data) { + const xml = this.#registry.export(name, data); + if (!xml) throw new Error('Unknown outgoing stanza'); + this.#write(xml.toString()); + } + } + client.transports['bounded-websocket'] = BoundedTransport; +} diff --git a/experiments/0002-xmpp-library/parser-validation/check-dependencies.py b/experiments/0002-xmpp-library/parser-validation/check-dependencies.py new file mode 100644 index 0000000..de5bf4b --- /dev/null +++ b/experiments/0002-xmpp-library/parser-validation/check-dependencies.py @@ -0,0 +1,42 @@ +"""Check exact installed graph, preserved notices, and unchanged candidate files.""" +import hashlib +import json +from pathlib import Path + +root = Path(__file__).resolve().parent +lock = json.loads((root / 'package-lock.json').read_text()) +manifest = json.loads((root / 'package.json').read_text()) +assert manifest['engines'] == {'node': '24.21.0', 'npm': '11.19.0'} +assert manifest['dependencies'] == {'saxes': '6.0.0', 'stanza': '12.22.1', 'xmlchars': '2.2.0'} +notices = (root / 'THIRD_PARTY_NOTICES.md').read_text() +inventory = (root / 'LICENSE_INVENTORY.md').read_text() +count = 0 +for key, entry in lock['packages'].items(): + if not key: + continue + count += 1 + assert entry['resolved'].startswith('https://registry.npmjs.org/') + assert entry['integrity'].startswith('sha512-') + installed = root / key + meta = json.loads((installed / 'package.json').read_text()) + assert meta['version'] == entry['version'] + assert meta['license'] == entry['license'] + assert f"| {meta['name']} | {meta['version']} | {meta['license']} |" in inventory + files = [p for p in installed.iterdir() if p.is_file() and + (p.name.lower().startswith('license') or p.name.lower().startswith('notice'))] + if meta['name'] == 'saxes': + files = [root / 'licenses/saxes-6.0.0-LICENSE'] + assert files + for path in files: + assert path.read_text().strip() in notices +assert count == 10 +# Independent previously installed candidate; no ignored vendor patches. +candidate = root / 'node_modules/stanza' +original = root.parent / 'node_modules/stanza' +files = [p for p in candidate.rglob('*') if p.is_file()] +assert files +for path in files: + assert path.read_bytes() == (original / path.relative_to(candidate)).read_bytes(), path +print(f'{count} exact dependency licenses/integrities; {len(files)} unchanged Stanza files') +for name in ['package-lock.json', 'bounded-ingress.mjs', 'licenses/saxes-6.0.0-LICENSE']: + print(f'{name} SHA256 {hashlib.sha256((root / name).read_bytes()).hexdigest()}') diff --git a/experiments/0002-xmpp-library/parser-validation/licenses/saxes-6.0.0-LICENSE b/experiments/0002-xmpp-library/parser-validation/licenses/saxes-6.0.0-LICENSE new file mode 100644 index 0000000..187f3ba --- /dev/null +++ b/experiments/0002-xmpp-library/parser-validation/licenses/saxes-6.0.0-LICENSE @@ -0,0 +1,64 @@ +The ISC License + +Copyright (c) Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +==== + +The following license is the one that governed sax, from which saxes +was forked. Isaac Schlueter is not *directly* involved with saxes so +don't go bugging him for saxes issues. + +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +==== + +`String.fromCodePoint` by Mathias Bynens is no longer used, but it can +still be found in old commits. It was once used according to terms of +MIT License, as follows: + + Copyright Mathias Bynens + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/experiments/0002-xmpp-library/parser-validation/package-lock.json b/experiments/0002-xmpp-library/parser-validation/package-lock.json new file mode 100644 index 0000000..9346ad3 --- /dev/null +++ b/experiments/0002-xmpp-library/parser-validation/package-lock.json @@ -0,0 +1,119 @@ +{ + "name": "luma-bounded-xml-ingress-experiment", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "luma-bounded-xml-ingress-experiment", + "version": "0.0.0", + "dependencies": { + "saxes": "6.0.0", + "stanza": "12.22.1", + "xmlchars": "2.2.0" + }, + "engines": { + "node": "24.21.0", + "npm": "11.19.0" + } + }, + "node_modules/@types/node": { + "version": "22.20.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/punycode": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/punycode/-/punycode-2.1.4.tgz", + "integrity": "sha512-trzh6NzBnq8yw5e35f8xe8VTYjqM3NE7bohBtvDVf/dtUer3zYTLK1Ka3DG3p7bdtoaOHZucma6FfVKlQ134pQ==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/sdp": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.2.tgz", + "integrity": "sha512-xZocWwfyp4hkbN4hLWxMjmv2Q8aNa9MhmOZ7L9aCZPT+dZsgRr6wZRrSYE3HTdyk/2pZKPSgqI7ns7Een1xMSA==", + "license": "MIT" + }, + "node_modules/stanza": { + "version": "12.22.1", + "resolved": "https://registry.npmjs.org/stanza/-/stanza-12.22.1.tgz", + "integrity": "sha512-WwoSl3u5cCEAbed5ao+yt8ByH6AsBJcPDY0gax3JGQkBX/7wEZ2oxu9nMJ7O1tm8slDyH4FBYIpeluHrT+g/LA==", + "license": "MIT", + "dependencies": { + "@types/node": "^22.0.0", + "@types/punycode": "^2.1.0", + "@types/ws": "^8.18.1", + "punycode": "^2.1.1", + "sdp": "^3.0.2", + "ws": "^8.18.2" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + } + } +} diff --git a/experiments/0002-xmpp-library/parser-validation/package.json b/experiments/0002-xmpp-library/parser-validation/package.json new file mode 100644 index 0000000..504f9fb --- /dev/null +++ b/experiments/0002-xmpp-library/parser-validation/package.json @@ -0,0 +1,10 @@ +{ + "name": "luma-bounded-xml-ingress-experiment", + "version": "0.0.0", + "private": true, + "type": "module", + "engines": {"node": "24.21.0", "npm": "11.19.0"}, + "packageManager": "npm@11.19.0", + "scripts": {"test": "node --test test/*.test.mjs"}, + "dependencies": {"saxes": "6.0.0", "stanza": "12.22.1", "xmlchars": "2.2.0"} +} diff --git a/experiments/0002-xmpp-library/parser-validation/test/fixtures.mjs b/experiments/0002-xmpp-library/parser-validation/test/fixtures.mjs new file mode 100644 index 0000000..944a444 --- /dev/null +++ b/experiments/0002-xmpp-library/parser-validation/test/fixtures.mjs @@ -0,0 +1,68 @@ +export const opening = ''; +export const closing = ''; +export const message = text => opening + text + closing; +export const valid = message('finite fixture'); +export const deep = n => '' + ''.repeat(n - 1) + + ''.repeat(n - 1) + ''; +export const elements = n => '' + ''.repeat(n - 1) + ''; +export const attributes = n => ' ` a${i}="x"`).join('') + '/>'; +export function boundary() { + // 16 attributes / 1024 name+value UTF-16 units including xmlns. + let attrs = ' xmlns="jabber:client"'; + let chars = 'xmlns'.length + 'jabber:client'.length; + for (let i = 0; i < 15; i++) { + const name = `a${i}`; + const value = 'v'.repeat(i === 14 ? 1024 - chars - name.length : 60); + attrs += ` ${name}="${value}"`; + chars += name.length + value.length; + } + // Root + body + chain of 7 + 23 siblings = 32 elements; depth 8. + const before = ``; + const after = '' + ''.repeat(7) + ''.repeat(7) + ''.repeat(23) + ''; + return {xml: before + 'z'.repeat(4096 - before.length - after.length) + after, + body: 'z'.repeat(4096 - before.length - after.length)}; +} +export const invalid = [ + ['large stock body', message('b'.repeat(1048576)), 'bytes'], + ['deep stock XML', deep(257), 'depth'], + ['unfinished opening', opening, 'invalid-xml'], + ['unfinished attribute', '', 'invalid-xml'], + ['depth over', deep(9), 'depth'], + ['elements over', elements(33), 'elements'], + ['attributes over', attributes(17), 'attributes'], + ['attribute value over', ``, 'attribute-value'], + ['attribute chars over', ' `a${i}="${'x'.repeat(256)}"`).join(' ') + '/>', 'attribute-chars'], + ['oversized name', `<${'x'.repeat(129)}/>`, 'name'], + ['oversized attribute name', ``, 'name'], + ['oversized unfinished token', '<' + 'x'.repeat(4096), 'bytes'], + ['UTF-8 bytes over with bounded units', message('🦉'.repeat(1100)), 'bytes'], + ['binary buffer', new Uint8Array([60, 120, 47, 62]), 'text-required'], + ['binary ArrayBuffer', new ArrayBuffer(4), 'text-required'], + ['binary Blob', new Blob([valid]), 'text-required'], + ['boxed string', new String(valid), 'text-required'], + ['comment', '' + valid, 'restricted-xml'], + ['DTD internal entity', ']>' + message('&x;'), 'restricted-xml'], + ['external DTD', '' + valid, 'restricted-xml'], + ['processing instruction', '' + valid, 'restricted-xml'], + ['XML declaration', '' + valid, 'restricted-xml'], + ['duplicate attribute', '', 'invalid-xml'], + ['duplicate expanded attribute', '', 'invalid-xml'], + ['unbound namespace', '', 'invalid-xml'], + ['illegal namespace binding', '', 'invalid-xml'], + ['unknown entity', message('&unknown;'), 'invalid-xml'], + ['illegal control', message('\u0000'), 'invalid-xml'], + ['unpaired surrogate', message('\ud800'), 'invalid-xml'], + ['invalid char reference', message('�'), 'invalid-xml'], + ['unknown root', '', 'unknown-root'] +]; diff --git a/experiments/0002-xmpp-library/parser-validation/test/integration.test.mjs b/experiments/0002-xmpp-library/parser-validation/test/integration.test.mjs new file mode 100644 index 0000000..d59cb13 --- /dev/null +++ b/experiments/0002-xmpp-library/parser-validation/test/integration.test.mjs @@ -0,0 +1,151 @@ +import assert from 'node:assert/strict'; +import {test} from 'node:test'; +import clientModule from 'stanza/Client.js'; +import {installBoundedTransport, validateMessage, LIMITS} from '../bounded-ingress.mjs'; +import {opening, closing, message, valid, deep, elements, attributes, boundary, invalid} from './fixtures.mjs'; +const Client = clientModule.default; +const tick = () => new Promise(resolve => setImmediate(resolve)); + +async function harness() { + const sockets = []; + class FixtureSocket { + readyState = 1; protocol = 'xmpp'; sent = []; closes = 0; + constructor(url, protocol) { this.url = url; this.requestedProtocol = protocol; sockets.push(this); } + send(data) { this.sent.push(data); } + close() { this.closes++; this.readyState = 3; } + } + const client = new Client({autoReconnect: false, server: 'example.invalid'}); + installBoundedTransport(client, FixtureSocket); + client.updateConfig({transports: {'bounded-websocket': 'wss://example.invalid/xmpp'}, + transportPreferenceOrder: ['bounded-websocket']}); + const received = [], errors = [], raw = [], stanzas = [], starts = []; + client.on('stream:data', (stanza, kind) => received.push({stanza, kind})); + client.on('stanza', stanza => stanzas.push(stanza)); + client.on('stream:start', stanza => starts.push(stanza)); + client.on('ingress:rejected', code => errors.push(code)); + client.on('raw', (...args) => raw.push(args)); + // Tracing calls the real registry.import; proves rejection before import. + let imports = 0; + const original = client.stanzas.import.bind(client.stanzas); + client.stanzas.import = (...args) => { imports++; return original(...args); }; + await client.connect(); + const socket = sockets.at(-1); + socket.onopen(); + return {client, sockets, socket, received, errors, raw, stanzas, starts, + imports: () => imports, finish: async () => {client.transport?.disconnect(false); await tick();}}; +} + +for (const [name, xml, code] of invalid) { + test(`terminal rejection: ${name}`, async () => { + const h = await harness(); + const late = h.socket.onmessage; + late({data: xml}); + assert.deepEqual(h.errors, [code]); + assert.equal(h.socket.closes, 1); + assert.equal(h.socket.onmessage, null); + assert.equal(h.received.length, 0); + assert.equal(h.starts.length, 0); + if (code !== 'unknown-root') assert.equal(h.imports(), 0); + // Old captured handlers and subsequent messages cannot revive an attempt. + late({data: closing}); late({data: valid}); + await tick(); + assert.equal(h.stanzas.length, 0); + assert.equal(h.received.length, 0); + assert.deepEqual(h.errors, [code]); + assert.equal(h.client.transport, undefined); + assert.equal(h.raw.length, 0); + await h.finish(); + }); +} + +for (const [name, xml, body] of [ + ['all aggregate limits equal', boundary().xml, boundary().body], + ['depth equal', deep(LIMITS.depth)], + ['element count equal', elements(LIMITS.elements)], + ['attribute count equal', attributes(LIMITS.attributes)], + ['attribute value equal', ``], + ['name equal', `<${'x'.repeat(128)}/>`], + ['UTF-8 byte equality', message('🦉'.repeat(1000) + 'a'.repeat(4096 - message('').length - 4000)), '🦉'.repeat(1000) + 'a'.repeat(4096 - message('').length - 4000)], + ['escaped text and CDATA', message('<&]]>'), '<&'], + ['prefixed namespace', 'namespace', 'namespace'] +]) { + test(`actual parser/import accepts ${name}`, async () => { + const h = await harness(); + h.socket.onmessage({data: xml}); + await tick(); + assert.deepEqual(h.errors, []); + assert.equal(h.received.length, 1); + assert.equal(h.received[0].kind, 'message'); + if (body !== undefined) assert.equal(h.received[0].stanza.body, body); + assert.equal(h.stanzas.length, 1); + assert.ok(h.imports() > 0); + await h.finish(); + }); +} + +test('bytes one over and UTF-8 one over reject before import', async () => { + for (const xml of [boundary().xml + ' ', message('🦉'.repeat(1000) + 'a'.repeat(4097 - message('').length - 4000))]) { + const h = await harness(); h.socket.onmessage({data: xml}); + assert.deepEqual(h.errors, ['bytes']); assert.equal(h.imports(), 0); + await h.finish(); + } +}); + +test('stock 18-message partial document never accumulates or revives; fresh connect works', async () => { + const h = await harness(); const old = h.socket.onmessage; + old({data: opening}); + for (let i = 0; i < 16; i++) old({data: 'a'.repeat(4096)}); + old({data: closing}); + await tick(); + assert.equal(h.received.length, 0); assert.equal(h.imports(), 0); + assert.deepEqual(h.errors, ['invalid-xml']); + await h.client.connect(); const fresh = h.sockets.at(-1); fresh.onopen(); + old({data: valid}); assert.equal(h.received.length, 0); + fresh.onmessage({data: valid}); await tick(); + assert.equal(h.received.length, 1); assert.equal(h.stanzas.length, 1); + assert.equal(h.received[0].stanza.body, 'finite fixture'); + await h.finish(); +}); + +test('successive complete documents have no shared namespace/parser state', async () => { + const h = await harness(); + h.socket.onmessage({data: valid}); h.socket.onmessage({data: valid}); await tick(); + assert.equal(h.stanzas.length, 2); + h.socket.onmessage({data: ''}); await tick(); + assert.deepEqual(h.errors, ['invalid-xml']); assert.equal(h.stanzas.length, 2); + await h.finish(); +}); + +test('open, restart, message, outgoing export and close use actual registry', async () => { + const h = await harness(); + h.socket.onmessage({data: ''}); + assert.equal(h.client.transport.hasStream, true); assert.equal(h.starts.length, 1); + h.client.transport.restart(); assert.equal(h.client.transport.hasStream, false); + await h.client.transport.send('message', {body: 'outgoing fixture'}); + assert.ok(h.socket.sent.at(-1).includes('outgoing fixture')); + h.socket.onmessage({data: ''}); + await tick(); assert.equal(h.socket.closes, 1); assert.equal(h.client.transport, undefined); + assert.equal(h.errors.length, 0); + await h.finish(); +}); + +test('failed subprotocol, socket error, clean disconnect and stale callbacks are terminal', async () => { + for (const mode of ['subprotocol', 'error', 'clean']) { + const h = await harness(); const old = h.socket.onmessage; + if (mode === 'subprotocol') {h.socket.protocol = 'wrong'; h.socket.onopen();} + if (mode === 'error') h.socket.onerror(new Error('fixture')); + if (mode === 'clean') { + h.socket.onmessage({data: ''}); + h.client.transport.disconnect(); assert.ok(h.socket.sent.at(-1).includes('close')); + } + old({data: valid}); await tick(); + assert.equal(h.received.length, 0); assert.equal(h.socket.closes, 1); + await h.finish(); + } +}); + +test('SAX framing validation retains no attempt object', () => { + assert.throws(() => validateMessage(opening)); + assert.throws(() => validateMessage(closing)); + assert.doesNotThrow(() => validateMessage(valid)); +}); diff --git a/planning/TASK_MATRIX.md b/planning/TASK_MATRIX.md index dba3ab9..0ae2aae 100644 --- a/planning/TASK_MATRIX.md +++ b/planning/TASK_MATRIX.md @@ -1,6 +1,6 @@ # Матрица задач -Generated; state revision **43**. Canonical status: state/tasks.json. +Generated; state revision **45**. Canonical status: state/tasks.json. | ID | Этап | Оператор | Reviewer | Статус | Owner | Зависимости | Задача | | --- | --- | --- | --- | --- | --- | --- | --- | @@ -20,7 +20,7 @@ Generated; state revision **43**. Canonical status: state/tasks.json. | [S00-015](tasks/S00-015.md) | S00 | ci-cd | qa | done | auto-ci-cd-84e209103f24 | S00-001, S00-011, S00-013, S00-014, S00-016 | Устранить прерывание: Fix runner prompt delivery after partial stdin timeout | | [S00-016](tasks/S00-016.md) | S00 | ci-cd | qa | done | auto-ci-cd-351a0cbbabef | S00-001, S00-011, S00-013, S00-014 | Restore reviewer model availability after provider capacity failure | | [S00-017](tasks/S00-017.md) | S00 | protocol | security | done | auto-protocol-599343cd9e3f | S00-001, S00-002 | Establish fail-closed SCRAM server-final validation | -| [S00-018](tasks/S00-018.md) | S00 | protocol | security | todo | | S00-001, S00-002, S00-017 | Validate bounded standalone XML ingress for the XMPP candidate | +| [S00-018](tasks/S00-018.md) | S00 | protocol | security | review | auto-protocol-89fe6563092c | S00-001, S00-002, S00-017 | Validate bounded standalone XML ingress for the XMPP candidate | | [S00-010](tasks/S00-010.md) | S00 | qa | orchestrator | todo | | S00-001, S00-002, S00-003, S00-004, S00-005, S00-006, S00-007, S00-008, S00-009 | Gate: Профиль и проверка стека | | [S01-001](tasks/S01-001.md) | S01 | orchestrator | security | todo | | S00-010 | Описать identity/model contracts | | [S01-002](tasks/S01-002.md) | S01 | orchestrator | security | todo | | S00-010, S01-001 | Описать async account context | diff --git a/planning/stages/S00.md b/planning/stages/S00.md index de4697d..6ae7ed5 100644 --- a/planning/stages/S00.md +++ b/planning/stages/S00.md @@ -22,7 +22,7 @@ - [S00-015](../tasks/S00-015.md): Устранить прерывание: Fix runner prompt delivery after partial stdin timeout — ci-cd; done. - [S00-016](../tasks/S00-016.md): Restore reviewer model availability after provider capacity failure — ci-cd; done. - [S00-017](../tasks/S00-017.md): Establish fail-closed SCRAM server-final validation — protocol; done. -- [S00-018](../tasks/S00-018.md): Validate bounded standalone XML ingress for the XMPP candidate — protocol; todo. +- [S00-018](../tasks/S00-018.md): Validate bounded standalone XML ingress for the XMPP candidate — protocol; review. - [S00-010](../tasks/S00-010.md): Gate: Профиль и проверка стека — qa; todo. ## Граница этапа diff --git a/planning/task_matrix.csv b/planning/task_matrix.csv index a8507bc..417c356 100644 --- a/planning/task_matrix.csv +++ b/planning/task_matrix.csv @@ -15,7 +15,7 @@ S00-014,S00,ci-cd,qa,done,auto-ci-cd-14343249da62,"S00-001, S00-011, S00-013",Fi S00-015,S00,ci-cd,qa,done,auto-ci-cd-84e209103f24,"S00-001, S00-011, S00-013, S00-014, S00-016",Устранить прерывание: Fix runner prompt delivery after partial stdin timeout S00-016,S00,ci-cd,qa,done,auto-ci-cd-351a0cbbabef,"S00-001, S00-011, S00-013, S00-014",Restore reviewer model availability after provider capacity failure S00-017,S00,protocol,security,done,auto-protocol-599343cd9e3f,"S00-001, S00-002",Establish fail-closed SCRAM server-final validation -S00-018,S00,protocol,security,todo,,"S00-001, S00-002, S00-017",Validate bounded standalone XML ingress for the XMPP candidate +S00-018,S00,protocol,security,review,auto-protocol-89fe6563092c,"S00-001, S00-002, S00-017",Validate bounded standalone XML ingress for the XMPP candidate S00-010,S00,qa,orchestrator,todo,,"S00-001, S00-002, S00-003, S00-004, S00-005, S00-006, S00-007, S00-008, S00-009",Gate: Профиль и проверка стека S01-001,S01,orchestrator,security,todo,,S00-010,Описать identity/model contracts S01-002,S01,orchestrator,security,todo,,"S00-010, S01-001",Описать async account context diff --git a/state/commit_messages/auto-protocol-89fe6563092c.txt b/state/commit_messages/auto-protocol-89fe6563092c.txt new file mode 100644 index 0000000..ea3baff --- /dev/null +++ b/state/commit_messages/auto-protocol-89fe6563092c.txt @@ -0,0 +1,4 @@ +Bound standalone XML ingress before Stanza parsing and import + +Changes: Add an isolated public transport integration with finite byte, depth, element and attribute budgets; validate complete text XML with pinned saxes before real JXT parsing/import. Add adversarial and boundary fixtures, terminal cleanup tests, dependency notices and a proposed parser ADR. +Validation: Fresh-cache npm ci and npm test passed all 55 tests with Node 24.21.0/npm 11.19.0. Reproduced three stock parser failures, checked ten exact dependency licenses/integrities and 340 unchanged Stanza files; make check passed 72 tooling tests. Scope and whitespace inspected. Browser runtime, live WSS and interoperability were not tested. diff --git a/state/evidence/S00-018-auto-protocol-89fe6563092c.md b/state/evidence/S00-018-auto-protocol-89fe6563092c.md new file mode 100644 index 0000000..187c2dd --- /dev/null +++ b/state/evidence/S00-018-auto-protocol-89fe6563092c.md @@ -0,0 +1,118 @@ +# S00-018 executor evidence + +Agent: auto-protocol-89fe6563092c (protocol). Date: 2026-10-15. +Result: bounded standalone XML ingress experiment and proposed ADR; independent security review pending. + +Input: assigned master/startup/role/workflow instructions; resume/show/packet; +required TESTING, decisions/README, blocker S00-018; exact ACC-004/NET-003/NET-004; +accepted scope/stack/SASL ADRs and all direct dependency evidence/reviews/handoffs; +original parser blocker evidence/handoff/probe; shipped Client/Transport/JXT and +saxes API/source. No missing required input. No new shared contract is needed. +Output: only experiments/0002-xmpp-library/parser-validation and + docs/decisions/0002-xmpp-parser-validation.md, plus workflow metadata. + +## Actual changes and demonstrated acceptance + +Public Client.transports constructor registration and explicit single transport +selection run real Client.connect. Per-message type/unit/UTF-8 byte checks precede +new saxes XML 1.0 namespace-aware document parsing. Callback depth/element/ +attribute/name/value budgets precede candidate JXT.parse/registry.import. +SAX close detects incompleteness; malformed/multiple-root documents never import +an earlier complete root. All XML syntax uses existing libraries. Global input +bytes bound unfinished/oversized tokens before event callbacks. No vendor patch, +late interception, fixture parser or handwritten XML security parser. + +Local inclusive budgets: 4096 UTF-8 bytes, depth 8, elements 32, attributes 16 +including namespace declarations, aggregate attribute name+decoded value units +1024, names 128, decoded attribute values 256. Combined boundary fixture meets +all aggregate ceilings simultaneously and yields its exact real imported body; +separate Unicode/name/value equality cases pass. Limits remain experiment policy, +not product/shared contract decisions. DTD/comment/PI/declaration reject; +namespace, entities, malformed chars and duplicate attributes checked by saxes. + +55 tests use actual Client/JXT/registry/queue. FixtureSocket captures only socket +effects; registry tracing calls the real import. Original 1 MiB body, depth 256, +and 18-message partial-document categories reject terminally. Bad input never +delivers stanza or stream:start, invalid syntax/budgets never reach registry +import, and old captured callbacks cannot revive the attempt. Callback detachment, +stream/config cleanup, fresh Client.connect, successive independent documents, +stream open/close/restart, export/send and subprotocol/socket failure are tested. +No peer payload/error text is logged or raw-evented. + +## Actual commands and results + +All Node/npm commands used +PATH=/private/tmp/luma-stack-runtime/node-v24.21.0-darwin-arm64/bin:$PATH, +the previously reviewed isolated Node 24.21.0/npm 11.19.0, from project root. +No system install or certificate override. Exact engine pins and strict SSL +remain in manifest/.npmrc. No new runtime archive verification claimed. + +| Command | Exit | Observed result | +| --- | --- | --- | +| python3 scripts/tasks.py resume --agent auto-protocol-89fe6563092c | 0 | Assigned S00-018 in_progress, no repeat claim | +| python3 scripts/tasks.py show S00-018; packet S00-018 --agent auto-protocol-89fe6563092c (separate invocations) | 0 each | Scope/prerequisites/requirements confirmed | +| node experiments/0002-xmpp-library/parser-probe/reproduce.mjs | 0 | Stock accepted 18 writes/65536 body bytes, 1048576-byte body, 256 nested children; defect reproduction, not safe result | +| npm view saxes@6.0.0 version license engines dependencies --json --cache /private/tmp/luma-parser-cache --fetch-retries=0 --fetch-timeout=15000 --strict-ssl=true | 0 | 6.0.0, ISC, Node >=v12.22.7, xmlchars ^2.2.0 | +| npm install --prefix experiments/0002-xmpp-library/parser-validation --ignore-scripts --no-audit --no-fund --cache /private/tmp/luma-parser-cache --fetch-retries=0 --fetch-timeout=15000 | 0 | Ten packages, independent lock generated | +| npm test --prefix experiments/0002-xmpp-library/parser-validation | 0 | 55 passed, zero failed/skipped/cancelled, 378.193208 ms | +| npm ci --prefix experiments/0002-xmpp-library/parser-validation --ignore-scripts --no-audit --no-fund --cache /private/tmp/luma-parser-clean-89fe6563092c --fetch-retries=0 --fetch-timeout=15000 | 0 | Actual clean install with fresh cache, ten packages, scripts disabled | +| npm test --prefix experiments/0002-xmpp-library/parser-validation after ci | 0 | 55 passed, zero failed/skipped/cancelled, 341.101792 ms; runtime/test source unchanged afterward | +| curl --fail --silent --show-error https://raw.githubusercontent.com/lddubeau/saxes/v6.0.0/LICENSE -o /private/tmp/luma-parser-saxes-LICENSE | 0 | Upstream full ISC lineage/historical MIT notice retrieved through verified HTTPS; preserved in experiment | +| Corrected inline Python license collector | 0 | Ten exact installed versions/licenses/HTTPS/SHA512 entries; preserved shipped notices plus upstream saxes LICENSE | +| python3 experiments/0002-xmpp-library/parser-validation/check-dependencies.py | 0 | Ten exact graph/license checks; all 340 Stanza files byte-match original candidate | +| make check | 0 | 283 tasks, 22 stages, 221 requirements, 211 Web requirements, revision 44; 72 tooling tests in 18.439s | +| git diff --check; git status --short --untracked-files=all (separate checks) | 0 each | Scoped source only plus coordinator state claim before evidence | +| Inline Python authored-file whitespace/final-newline checks | 0 | All 12 authored experiment/ADR files checked, including untracked files | + +Exploratory failures are not passed checks: guessed transports/index.d.ts absent +(actual Transport API is index.d.ts); first license collector failed because +saxes tarball omits LICENSE (Python AssertionError, surrounding shell ended 0 +because following npm ci succeeded). Guessed upstream root saxes.ts URL returned +404; subsequent head/rg saw no file (combined command exit 2). Actual upstream +LICENSE fetch succeeded; no required artifact is missing and no source patch +was attempted. These failures do not affect the reproduced acceptance checks. + +## Primary sources and dependencies + +Opened RFC 7395 §3.2/3.3.3 through web tools; versioned saxes README/package source, +upstream Stanza Client source; inspected installed version-pinned declarations +and implementation. Direct links and constraints are in ADR. Sources inspected +2026-10-15. The supported extension claim rests on public shipped constructor +registry/Transport interface and actual Client selection, not private fields. + +New direct pins saxes 6.0.0 ISC / xmlchars 2.2.0 MIT, existing Stanza 12.22.1 MIT. +Other exact installed packages: @types/node 22.20.5, @types/punycode 2.1.4, +@types/ws 8.18.2, punycode 2.3.1, sdp 3.2.2, undici-types 6.21.0, ws 8.22.0, +all MIT. Inventory/notices and lock are preserved. Saxes release is older; +continued support/advisory/provenance clearance is not assumed. Node-focused +CommonJS packages require browser bundling. Default adapter uses native browser +WebSocket, URL, TextEncoder and private fields; fixture injection is explicit. + +SHA256: +- package-lock.json: 35b177d57aaf81778eaf3d3321b3f77eb1e755a46abee784ef62b8959aedd63c +- bounded-ingress.mjs: e6399ff581f59d1f09b471234d6e4b8f2ed6b61804aa4eb0eef7dd3f8e3f2dbc +- licenses/saxes-6.0.0-LICENSE: 0fac2374380621b22e6b50451057721a9c52935b02d16d106a9f04897f061d0e + +## Limits and independent review input + +No browser build/runtime, actual WSS/TLS/server handshake, auth changes, +independent client interoperability, load/exhaustion benchmark or security audit +was performed. No TLS/CORS bypass, real credentials/conversations, custom crypto +or invented binding. Original candidate, reviewed SASL plugin, app modules, +shared contracts, immutable specs and plan definitions unchanged. Git mutations +and additional task/agent launch were not performed. + +Browser WebSocket allocates a complete inbound message before JavaScript checks; +this experiment bounds parser work afterward, not network-stack memory. SAX can +store one token/attribute before a callback, bounded by the byte precheck. Total +traffic, Client queues, downstream extension work and CPU rate are not bounded. +Minimal transport lacks full stream-error/close negotiation/redirect/keepalive/ +language inheritance/stream sequencing. Complete SAX/JXT semantic equivalence +is not certified. See README/ADR; these limits prohibit production-readiness +claims but do not invalidate the assigned local parser/framing experiment. + +Separate security reviewer must reproduce README clean install/tests/license +checks, inspect actual scope/order/API/cleanup and compare this evidence and +executor commit message with actual patch. ADR remains proposed and task is +submitted for review, not done. Coordinator commits and launches fresh review; +this executor stops after submit and does not resume S00-003. diff --git a/state/handoffs/S00-018.md b/state/handoffs/S00-018.md new file mode 100644 index 0000000..9660efa --- /dev/null +++ b/state/handoffs/S00-018.md @@ -0,0 +1,39 @@ +# S00-018 handoff for independent security review + +Executor: auto-protocol-89fe6563092c (protocol), 2026-10-15. +Outcome: isolated bounded XML ingress experiment and proposed parser ADR. +No blocker; implementation awaits independent security review. + +Actual result: new public Client.transports constructor selected by actual +Client.connect, finite byte precheck, fresh saxes strict namespace-aware XML 1.0 +validation/close per message, then actual JXT.parse/registry.import. Explicit +local byte/depth/element/attribute/name/value limits apply before tree/import. +Malformed/incomplete/multiple-root/binary/budget violations close attempt, +detach callbacks, clear stream/config; stale callbacks cannot revive it. A fresh +connection works. No persistent parser, vendor patch or custom XML syntax parser. + +Pinned Node 24.21.0/npm 11.19.0 fresh-cache npm ci and 55 integrated tests passed; +stock probe reproduced all three failures; license checks for all ten packages +and byte comparison of all 340 Stanza files passed. make check: 72 tests passed. +Commands/digests/failures/limits: state/evidence/S00-018-auto-protocol-89fe6563092c.md. + +Next belongs only to security reviewer: inspect proposed + docs/decisions/0002-xmpp-parser-validation.md, experiment source/tests/notices, +executor diff/message and README. Run README commands with the reviewed isolated +runtime at /private/tmp/luma-stack-runtime/node-v24.21.0-darwin-arm64/bin and a +fresh reviewer cache. Verify budget/framing checks precede JXT allocations/import, +actual Client registry selection, boundary success, zero delivery for bad input, +terminal callback replay/cleanup and fresh attempts. check-dependencies.py also +needs the original parent candidate installed for vendor comparison. Saxes +published package omits LICENSE; preserved v6.0.0 upstream LICENSE and source are +recorded. Keep ADR proposed until independent acceptance. + +Limits: fixture socket only, no browser build/runtime or real WSS/server/client +interop; byte checks begin after browser allocates the message. No aggregate +traffic/Client queue/rate bounds or production policy. Minimal transport lacks +full stream-error/close negotiation/redirect/keepalive/language inheritance/ +sequencing, and exhaustive SAX/JXT equivalence is not certified. Auth/crypto and +accepted SASL experiment unchanged. No specs/shared interfaces/app modules or +original candidate files changed. S00-003 remains separate; only reviewed done +and coordinator commit allow a fresh original executor to continue it. This +executor exits after submit; no commit/next agent/original work is performed. diff --git a/state/tasks.json b/state/tasks.json index afb3701..227db9c 100644 --- a/state/tasks.json +++ b/state/tasks.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "revision": 43, + "revision": 45, "tasks": { "S00-001": { "status": "done", @@ -1236,10 +1236,14 @@ "completed_at": "2026-10-14T19:20:57+00:00" }, "S00-018": { - "status": "todo", - "owner": null, + "status": "review", + "owner": "auto-protocol-89fe6563092c", "external_wait": false, - "reason": null + "reason": null, + "started_at": "2026-10-14T19:24:01+00:00", + "updated_at": "2026-10-14T19:31:32+00:00", + "evidence": "state/evidence/S00-018-auto-protocol-89fe6563092c.md", + "handoff": "state/handoffs/S00-018.md" } }, "events": [ @@ -1798,6 +1802,29 @@ "from": "in_progress", "to": "blocked", "reason": "S00-003 cannot attest parser limits: Stanza 12.22.1 exposes no resource bounds and its persistent WebSocket parser accepts large/deep XML and reassembles incomplete documents across separate input messages." + }, + { + "revision": 44, + "at": "2026-10-14T19:24:01+00:00", + "action": "claim", + "task": "S00-018", + "agent": "auto-protocol-89fe6563092c", + "role": "protocol", + "from": "todo", + "to": "in_progress", + "reason": null + }, + { + "revision": 45, + "at": "2026-10-14T19:31:32+00:00", + "action": "submit", + "task": "S00-018", + "agent": "auto-protocol-89fe6563092c", + "role": null, + "from": "in_progress", + "to": "review", + "reason": null, + "evidence": "state/evidence/S00-018-auto-protocol-89fe6563092c.md" } ], "retired_agents": [