S00-017: Validate SCRAM server-final before authenticated SASL completion

Changes: Add an isolated Stanza 12.22.1 SASL feature replacement using existing SCRAM crypto, exact dependency lock and MIT notices, 44 integrated fixture checks, proposed ADR and review handoff. Leave production adapters and the original library experiment unchanged.
Validation: Fresh-cache npm ci and npm test passed on Node 24.21.0/npm 11.19.0; 44 tests cover RFC SHA-1/SHA-256 proofs and verified finals, stock defect reproduction, invalid/premature finals and async cleanup. make check passed 72 tooling tests. Pins/licenses/notices, unchanged vendor source and whitespace verified. Browser/WSS/server interoperability and constant-time assurance remain unverified; independent security review is pending.

Task: S00-017
Agent: auto-protocol-599343cd9e3f
Role: protocol
Action: claim
Status: review

Task title: Establish fail-closed SCRAM server-final validation
This commit is contained in:
wt committed 2026-10-15 02:17:46 +07:00
1 parent c73d9cef45
commit 493fbaebe7
18 files changed
+1043 -8

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
# ADR 0002-SASL: Fail-closed SCRAM server-final experiment
- Status: proposed; requires independent security review of S00-017.
- Task: S00-017, remediation for S00-003.
- Date: 2026-10-15.
- Owner: auto-protocol-599343cd9e3f (protocol).
- Reviewer: assigned security role, decision pending.
- Requirements: ACC-004, NET-003, NET-004; accepted ADR 0000/0001 preserved.
## Question and bounded result
Can the pinned StanzaJS candidate validate SCRAM server authentication before
emitting success/restarting without modifying vendor source or implementing
cryptographic primitives? Output is one isolated, runnable feature experiment,
not selection of Stanza as the production library. S01 shared contracts remain
unaccepted; no application adapter or interface is created here.
The original blocker is reproducible in 12.22.1: its SASL success branch marks
negotiation/transport authenticated, emits auth:success and requests restart
without processing additional-data or calling the SCRAM verifier. Empty and
forged success before challenge/proof are accepted. The isolated tests retain
this baseline reproduction and exercise the replacement through the real XML
registry and feature dispatcher.
## Options and proposed decision
1. **Replace the named SASL feature through public APIs.** Chosen for this bounded
experiment. `Client.use` and `registerFeature` are exposed in installed package
declarations; the registry assigns handlers by name. SCRAM already exposes
`processSuccess` and `finalize`. Keep all crypto in that existing implementation
and add fail-closed ordering, strict fixture-profile input and cleanup.
2. **Patch ignored vendor code or intercept success after emission.** Rejected:
ignored patches are not reproducible deliverables, and a post-success listener
cannot prevent earlier authenticated effects. No upstream patch is required.
3. **Replace the entire client/mechanism stack.** Deferred to S00-003. The original
evidence's xmpp.js source comparison also lacked server-final processing;
no installed/tested alternative or maintenance/security superiority is claimed.
Install the proposed plugin on a fresh Client before connect/feature events;
never permit the stock SASL handler to start first. The named registration
replaces its callback without editing registry internals. The API appends an
order entry; the actual dispatcher exits on restart/disconnect, with tests
confirming one completion and successful later negotiation after reset.
Only after proof send completes does success data reach library `processSuccess`
and `finalize`; both authenticated and mutuallyAuthenticated must be true before
any authenticated flag, event or restart. Missing, forged, erroneous, duplicated,
malformed or premature final is terminal failure. Reset removes attempt listeners
and cancels asynchronous continuation. Proof/credential/send exceptions fail
without logging credentials, peer data or crypto state. No cache is persisted.
## Sources, pins and licenses
Primary sources inspected on 2026-10-15:
- [RFC 5802 §5](https://www.rfc-editor.org/rfc/rfc5802.html#section-5): mutual
authentication and SHA-1 exchange; the public user/pencil fixture is copied as
test data, not used as an account. Invalid verifier must fail the exchange.
- [RFC 7677 §3](https://www.rfc-editor.org/rfc/rfc7677.html#section-3): SHA-256
exchange used as a second fixed primary-spec transcript.
- [Stanza 12.22.1 registry record](https://registry.npmjs.org/stanza/12.22.1):
exact package provenance location/license and locked tarball. Installed shipped
`Client.js/.d.ts`, `plugins/features.js/.d.ts`, `plugins/sasl.js` and
`lib/sasl/index.js/.d.ts` are the exact API/implementation evidence. Upstream
[Client source](https://github.com/legastero/stanza/blob/master/src/Client.ts)
corroborates plugin usage, but is not version-pinned acceptance evidence.
Dependency: stanza 12.22.1, MIT. Exact transitive pins: @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. Separate manifest/lock under
`experiments/0002-xmpp-library/sasl-validation` does not alter the parent's
unaccepted exploratory graph. Every locked package has HTTPS registry URL,
SHA512 integrity and inspected installed license text; inventory and notices
are preserved. Runtime stays Node 24.21.0/npm 11.19.0 from accepted ADR 0001.
No new crypto dependency, crypto primitive or vendor-source edit is introduced.
## Runnable evidence and constraints
See experiment README and `state/evidence/S00-017-auto-protocol-599343cd9e3f.md`.
Clean npm ci with an empty cache and npm test passed: 44 tests, including two
RFC client proofs/verified finals, original insecure baseline, missing/forged/
premature rejection, duplicate/out-of-order inputs, send/credential failure,
asynchronous reset and replay rejection. Tests observe real feature-dispatcher
restart/disconnect calls and transport/negotiation flags; they are local parsed
XML integration tests with captured outbound effects, not WSS/server tests.
The experiment accepts only non-PLUS SHA-256/SHA-1, selecting the strongest of
those offered and failing if neither is available. No plaintext fallback or
invented browser TLS binding. SHA-512, complete mechanism policy and compatibility
remain S00-003. Server-final is accepted only as success additional-data; separate
final challenge, SCRAM extensions and extra challenge round trips are rejected.
Decoded-data and iteration bounds (4096 bytes / 100000) are local experiment
limits. A success racing a pending send is conservatively rejected. These
restrictions can reduce availability and need explicit future integration review.
Node exercises Stanza's SCRAM composition and Node crypto primitives. Browser
Stanza uses bundled synchronous hashing/HMAC, global crypto RNG, WebSocket and
fetch; its backend/build is not tested in this task. No browser support matrix,
constant-time comparison guarantee (Stanza compares verifier strings), advisory/
provenance clearance, WSS/TLS/channel-binding test, independent-client/server
interoperability or production readiness is implied. No live credentials,
network authentication, certificate/CORS bypass or persisted secrets are used.
## Handoff and consequences
Independent security reviewer must reproduce the integrated positive/negative
cases and assess extension API, cleanup, license/pin evidence and the stated
restrictions. This ADR stays proposed until review. Reviewed acceptance permits
a fresh S00-003 executor to evaluate this approach in its own experiment; it does
not implement that original task or change the accepted stack/shared contracts.
Any required broader policy/interface or vendor replacement is a separately
scoped plan decision. Coordinator owns transition commits and fresh processes.