S00-019: Validate browser SCRAM-SHA-512 through public mechanism composition
Changes: Add an isolated pinned mechanism wrapper using Stanza's public constructor and factory APIs, preserve the SCRAM-SHA-512 wire name and fail-closed final guard, and document the proposed ADR, browser bundle evidence, independent Scramp vectors, notices and limits. Leave parent selection, dependency experiments, specs and shared contracts unchanged. Validation: Fresh-cache npm ci and Vite browser build pass; 87 focused tests pass, including RFC HMAC vectors, original defect reproduction, independent full SHA-512 exchanges and rejected invalid finals/order/reset paths. Hashed Scramp oracle fixture comparison passes. Dependency check verifies 49 locked entries, 25 installed pins/licenses and 340 unmodified Stanza files. make check passes 72 tooling tests. Native browsers, WSS/live server interoperability and production readiness are untested; independent security review remains pending. Task: S00-019 Agent: auto-crypto-c68801170fa4 Role: crypto Action: claim Status: review Task title: Validate browser-compatible SCRAM-SHA-512 through a public mechanism API
This commit is contained in:
1 parent
e69372c6db
commit
07c9967d02
26 files changed
+6399
-8
No files matched your search
@@ -0,0 +1,129 @@
|
||||
# ADR 0002 SHA-512 remediation: public mechanism composition
|
||||
|
||||
- Status: proposed; independent security review required.
|
||||
- Task: S00-019, remediation of S00-003.
|
||||
- Date: 2026-10-15.
|
||||
- Owner: auto-crypto-c68801170fa4 (crypto).
|
||||
- Reviewer: pending, security role.
|
||||
- Requirements: ACC-004, NET-003, NET-004.
|
||||
|
||||
Input: original SHA-512 blocker, accepted scope/stack/SASL/parser decisions,
|
||||
exact pinned Stanza browser source/public declarations and primary vectors.
|
||||
Output is one isolated runnable experiment in
|
||||
experiments/0002-xmpp-library/sha512-validation. No production library selection,
|
||||
parent WSS work, shared contract, stack change or original spec change.
|
||||
|
||||
## Defect and options
|
||||
|
||||
The built Stanza 12.22.1 browser backend reproduces the RFC 4231 case 1 failure
|
||||
for createHmac('sha-512'). Source chooses 128-byte blocks only for sha512;
|
||||
SCRAM's normal wire name derives sha-512. The browser digest implementation
|
||||
already accepts both spellings; HMAC does not normalize the hyphen. Direct
|
||||
public SCRAM('SCRAM-SHA-512') produces the wrong full client proof as well.
|
||||
|
||||
1. Compose the existing exported SCRAM through its public constructor with the
|
||||
working algorithm alias, expose the correct independent wire name, and
|
||||
register the wrapper through public Factory APIs. Proposed for this exact
|
||||
version, with complete browser-backend vectors guarding the alias behavior.
|
||||
2. Replace crypto or the complete mechanism with another library. Not needed
|
||||
for this outcome: introduces new runtime crypto/dependency/API assumptions.
|
||||
Scramp is used only as an independent test oracle, not a browser candidate.
|
||||
3. Patch private algorithm/vendor HMAC, mutate vendor prototypes, rewrite imports
|
||||
to a replacement crypto backend, rename the wire mechanism or downgrade.
|
||||
Rejected; no such patch/workaround is used.
|
||||
|
||||
## Proposed integration
|
||||
|
||||
Sha512Mechanism implements the shipped public Mechanism interface and delegates
|
||||
all crypto to a new public SCRAM('SCRAM-SHA512'). Constructor typing accepts a
|
||||
string; exact shipped source derives sha512, supported by createHash/Hmac. The
|
||||
wrapper exposes SCRAM-SHA-512 on the wire and never accesses/changes a Stanza
|
||||
private field. The delegate retains its internal alias name throughout. This
|
||||
uses documented public constructor/method signatures and version-inspected alias
|
||||
behavior, not an upstream promise of a named SCRAM-SHA512 wire mechanism.
|
||||
Future upgrades must revalidate this composition; an incompatible API is a new
|
||||
blocker, not permission for a private patch.
|
||||
|
||||
Public Factory.disable/register replaces SHA-512 before negotiation; fresh
|
||||
Client.use installs the isolated S00-017 guard copy. Guard is narrowed to SHA-512
|
||||
and its 88-character verifier, while preserving strict validation, ordering,
|
||||
mutual authentication, cancellation and no-success-on-error behavior. Delegate
|
||||
processSuccess/finalize must confirm both flags before auth event/flags/restart.
|
||||
Library-derived keys remain attempt-local, getCacheableCredentials returns null;
|
||||
no storage/cache update path is added. Crypto primitives, SCRAM composition,
|
||||
SASLprep and verifier remain entirely existing library code.
|
||||
|
||||
Only non-PLUS SHA-512 is permitted by this fixture profile. PLUS-only/plaintext/
|
||||
unsupported offers fail; compatible mixed offers select SHA-512. tlsUnique input
|
||||
fails. GS2 n,, and c=biws reflect no channel binding. Full NET-003 fallback policy
|
||||
and real WSS ACC-004 stay with S00-003; this experiment makes no complete product
|
||||
requirement or library-selection claim.
|
||||
|
||||
## Pins, public API and sources
|
||||
|
||||
Inspected primary sources on 2026-10-15:
|
||||
|
||||
- [RFC 4231 sections 4.2–4.8](https://www.rfc-editor.org/rfc/rfc4231.html#section-4.2)
|
||||
supplies seven fixed HMAC-SHA-512 cases, including 131-byte keys and truncation.
|
||||
- [RFC 6234 test driver](https://www.rfc-editor.org/rfc/rfc6234.html#section-8.5)
|
||||
supplies SHA-512 abc digest. No primitive code is copied from the RFC.
|
||||
- [Stanza source](https://github.com/legastero/stanza/blob/master/src/lib/sasl/index.ts)
|
||||
supplies public API context; exact shipped 12.22.1 lib/sasl/index.d.ts/.js,
|
||||
Client.d.ts/.js, plugins/features.d.ts/.js and platform/browser/crypto files
|
||||
are authoritative version-specific evidence. Factory accepts a constructor;
|
||||
wrapper calls only declared public methods. Stanza license is MIT with bundled
|
||||
browser crypto ancestry notices preserved.
|
||||
- [events upstream](https://github.com/browserify/events) documents browser
|
||||
EventEmitter API, ES5 support, MIT license and limited maintainer resources.
|
||||
events 3.3.0 supplies the explicit polyfill required by Stanza's browser graph.
|
||||
- [Vite build](https://vite.dev/guide/build.html) supports library output/browser
|
||||
targets; accepted Vite 8.3.4 is reused, not a new stack selection.
|
||||
- [Scramp 1.4.6 API](https://pypi.org/project/scramp/1.4.6/) documents SHA-512
|
||||
and public client/server methods. Test oracle pins Scramp 1.4.6 (MIT-0) and
|
||||
asn1crypto 1.5.1 (MIT), exact wheel SHA256 hashes, Python >=3.8. Version selected
|
||||
for available Python 3.9 host compatibility; not a latest-version/audit claim.
|
||||
|
||||
Direct npm pins: stanza 12.22.1, events 3.3.0 (MIT), Vite 8.3.4 (MIT, build only).
|
||||
49 exact locked entries, 25 installed on this macOS ARM64 host. All lock entries
|
||||
have HTTPS registry URL, SHA512 integrity and license inventory; installed
|
||||
notices include Stanza NOTICE browser code lineage and build third-party texts.
|
||||
Native rolldown binding omits a license file; owning rolldown MIT/third-party
|
||||
licenses preserved. Optional platform entries are inventory-only. Build graph
|
||||
includes MIT/ISC/BSD/Apache/MPL packages; no modified covered vendor source.
|
||||
Neither pins nor these checks certify advisory/provenance/security clearance.
|
||||
|
||||
Runtime remains accepted Node 24.21.0/npm 11.19.0. Normal HTTPS, strict engines,
|
||||
disabled install scripts; no global configuration change. Browser bundle uses
|
||||
package browser mapping and explicit events alias, with no Node crypto/backend
|
||||
or external builtin. Build graph assertions and executed artifact supply backend
|
||||
evidence; Node crypto appears only as the separate primitive oracle in tests.
|
||||
|
||||
## Actual validation and limits
|
||||
|
||||
Fresh-cache npm ci, browser library build and 87 tests passed. All seven fixed
|
||||
RFC HMAC cases match sha512/SHA512; SHA digest aliases match abc, varied HMAC keys
|
||||
0..256 bytes match independent Node crypto. Stock hyphenated HMAC/full SCRAM
|
||||
failures reproduce. Three full fixed SHA-512 exchanges match independent Scramp
|
||||
client/server output (ordinary/131-byte/Unicode passwords). Scramp regeneration
|
||||
verifies client proof and server-final independently before comparing fixtures.
|
||||
|
||||
Actual browser-built Client, XML registry, feature dispatcher and mechanism run
|
||||
positive and missing/forged/premature/malformed/duplicate/racing-final checks.
|
||||
Fixture send/transport effects are captured explicitly; crypto is never stubbed.
|
||||
Fail never authenticates/restarts; reset/error/replay cannot revive it. Server
|
||||
silence after proof remains unauthenticated until external reset. All 340 Stanza
|
||||
files byte-match original pinned candidate. Detailed commands, checks and hashes
|
||||
are in README and executor evidence; independent review remains mandatory.
|
||||
|
||||
Bundle execution host is Node, using browser backend and host native web APIs.
|
||||
Actual Chrome/Firefox/Safari runtime, WSS/TLS, live independent server/client,
|
||||
parent parser integration, workers/performance and production readiness are not
|
||||
claimed. Targets Chrome/Edge 111, Firefox 114, Safari 16.4 are accepted build
|
||||
floors, not a certified product matrix; native crypto RNG/fetch/WebSocket needed.
|
||||
Synchronous SCRAM derivation can block UI; no constant-time verifier guarantee
|
||||
(Stanza compares strings), timeout or exhaustive SASLprep claim. Only success
|
||||
additional-data final, extension-free syntax, 4096 decoded bytes/100000 iteration
|
||||
fixture limits; separate final challenge conservatively rejects. Accepted
|
||||
S00-017/S00-018 source is untouched. Independent security reviewer must assess
|
||||
public alias composition and all limits, repeat commands and record acceptance
|
||||
before the coordinator resumes the parent in another fresh process.
|
||||
@@ -0,0 +1,3 @@
|
||||
engine-strict=true
|
||||
strict-ssl=true
|
||||
registry=https://registry.npmjs.org/
|
||||
@@ -0,0 +1,55 @@
|
||||
# Locked dependency license inventory
|
||||
|
||||
All optional build-host entries are included; installed graph checked separately.
|
||||
|
||||
| Package | Version | License |
|
||||
| --- | --- | --- |
|
||||
| @oxc-project/types | 0.154.0 | MIT |
|
||||
| @rolldown/binding-android-arm-eabi | 1.2.14 | MIT |
|
||||
| @rolldown/binding-android-arm64 | 1.2.14 | MIT |
|
||||
| @rolldown/binding-darwin-arm64 | 1.2.14 | MIT |
|
||||
| @rolldown/binding-darwin-x64 | 1.2.14 | MIT |
|
||||
| @rolldown/binding-freebsd-x64 | 1.2.14 | MIT |
|
||||
| @rolldown/binding-linux-arm-gnueabihf | 1.2.14 | MIT |
|
||||
| @rolldown/binding-linux-arm64-gnu | 1.2.14 | MIT |
|
||||
| @rolldown/binding-linux-arm64-musl | 1.2.14 | MIT |
|
||||
| @rolldown/binding-linux-ppc64-gnu | 1.2.14 | MIT |
|
||||
| @rolldown/binding-linux-s390x-gnu | 1.2.14 | MIT |
|
||||
| @rolldown/binding-linux-x64-gnu | 1.2.14 | MIT |
|
||||
| @rolldown/binding-linux-x64-musl | 1.2.14 | MIT |
|
||||
| @rolldown/binding-openharmony-arm64 | 1.2.14 | MIT |
|
||||
| @rolldown/binding-win32-arm64-msvc | 1.2.14 | MIT |
|
||||
| @rolldown/binding-win32-x64-msvc | 1.2.14 | MIT |
|
||||
| @rolldown/pluginutils | 1.0.1 | MIT |
|
||||
| @types/node | 22.20.5 | MIT |
|
||||
| @types/punycode | 2.1.4 | MIT |
|
||||
| @types/ws | 8.18.2 | MIT |
|
||||
| detect-libc | 2.1.2 | Apache-2.0 |
|
||||
| events | 3.3.0 | MIT |
|
||||
| fdir | 6.5.0 | MIT |
|
||||
| fsevents | 2.3.3 | MIT |
|
||||
| lightningcss | 1.33.0 | MPL-2.0 |
|
||||
| lightningcss-android-arm64 | 1.33.0 | MPL-2.0 |
|
||||
| lightningcss-darwin-arm64 | 1.33.0 | MPL-2.0 |
|
||||
| lightningcss-darwin-x64 | 1.33.0 | MPL-2.0 |
|
||||
| lightningcss-freebsd-x64 | 1.33.0 | MPL-2.0 |
|
||||
| lightningcss-linux-arm-gnueabihf | 1.33.0 | MPL-2.0 |
|
||||
| lightningcss-linux-arm64-gnu | 1.33.0 | MPL-2.0 |
|
||||
| lightningcss-linux-arm64-musl | 1.33.0 | MPL-2.0 |
|
||||
| lightningcss-linux-x64-gnu | 1.33.0 | MPL-2.0 |
|
||||
| lightningcss-linux-x64-musl | 1.33.0 | MPL-2.0 |
|
||||
| lightningcss-win32-arm64-msvc | 1.33.0 | MPL-2.0 |
|
||||
| lightningcss-win32-x64-msvc | 1.33.0 | MPL-2.0 |
|
||||
| nanoid | 3.3.20 | MIT |
|
||||
| picocolors | 1.1.1 | ISC |
|
||||
| picomatch | 4.0.7 | MIT |
|
||||
| postcss | 8.5.30 | MIT |
|
||||
| punycode | 2.3.1 | MIT |
|
||||
| rolldown | 1.2.14 | MIT |
|
||||
| sdp | 3.2.2 | MIT |
|
||||
| source-map-js | 1.2.2 | BSD-3-Clause |
|
||||
| stanza | 12.22.1 | MIT |
|
||||
| tinyglobby | 0.2.17 | MIT |
|
||||
| undici-types | 6.21.0 | MIT |
|
||||
| vite | 8.3.4 | MIT |
|
||||
| ws | 8.22.0 | MIT |
|
||||
@@ -0,0 +1,47 @@
|
||||
# Independent oracle licenses
|
||||
|
||||
Test-only Python dependencies; not browser/app/runtime dependencies.
|
||||
|
||||
## scramp 1.4.6
|
||||
|
||||
MIT No Attribution
|
||||
|
||||
Copyright The 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.
|
||||
|
||||
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.
|
||||
|
||||
## asn1crypto 1.5.1
|
||||
|
||||
Copyright (c) 2015-2022 Will Bond <will@wbond.net>
|
||||
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
# Isolated browser SCRAM-SHA-512 validation
|
||||
|
||||
S00-019 fixture experiment, proposed for independent security review. No live
|
||||
account/transport or parent library selection. Node executes the **browser-built
|
||||
backend**, not Stanza's Node crypto. This is browser bundle/backend evidence,
|
||||
not an actual Chrome/Firefox/Safari execution or WSS interoperability result.
|
||||
|
||||
## Reproduce
|
||||
|
||||
Use accepted Node 24.21.0 / npm 11.19.0. Existing reviewed macOS ARM64 runtime:
|
||||
|
||||
```sh
|
||||
export PATH=/private/tmp/luma-stack-runtime/node-v24.21.0-darwin-arm64/bin:$PATH
|
||||
npm ci --prefix experiments/0002-xmpp-library/sha512-validation --ignore-scripts --no-audit --no-fund --cache /private/tmp/luma-sha512-review-cache --fetch-retries=0 --fetch-timeout=15000
|
||||
npm test --prefix experiments/0002-xmpp-library/sha512-validation
|
||||
python3 experiments/0002-xmpp-library/sha512-validation/check-dependencies.py
|
||||
```
|
||||
|
||||
Use a new empty cache for independent clean-install evidence. The dependency
|
||||
check also reads the original installed candidate at the parent node_modules
|
||||
for byte comparison; it does not modify it. If unavailable, reproduce its pinned
|
||||
install under the parent task's authority or compare Stanza against its locked
|
||||
tarball separately; do not change the parent from this task.
|
||||
|
||||
`npm test` builds before running 87 checks. Vite's default browser resolution
|
||||
selects stanza/platform/browser. events 3.3.0 is the explicit browser EventEmitter
|
||||
polyfill. Build fails if module IDs show Node backend/builtins/browser externals
|
||||
or no browser HMAC. Output `dist/modules.json` lists resolved modules;
|
||||
`dist/browser.mjs` is a self-contained ESM bundle. Backend tests inspect graph,
|
||||
no runtime imports/require, and exported platform name `browser`. All integrated
|
||||
exchanges import this artifact, never Node source. Node crypto is an independent
|
||||
primitive oracle in tests only; the integration suite has no Node crypto import.
|
||||
The native RNG path uses host getRandomValues, not a fabricated success value.
|
||||
|
||||
## Independent full-exchange oracle
|
||||
|
||||
The checked-in `test/scramp-vectors.json` is fixed output from **Scramp 1.4.6**
|
||||
client and server (Python test-only MIT-0 dependency, asn1crypto 1.5.1 MIT).
|
||||
Both client proof and server-final are independently verified. Inputs combine
|
||||
public RFC 7677 nonce/salt/4096-iteration data with SHA-512 and synthetic passwords
|
||||
pencil, 131 A characters and péncil. These SHA-512 transcripts are independently
|
||||
derived vectors, not a claim that RFC 7677 specifies SHA-512 examples.
|
||||
|
||||
```sh
|
||||
python3 -m venv /private/tmp/luma-sha512-review-oracle
|
||||
/private/tmp/luma-sha512-review-oracle/bin/python -m pip install --require-hashes --only-binary=:all: --no-cache-dir -r experiments/0002-xmpp-library/sha512-validation/oracle-requirements.txt
|
||||
/private/tmp/luma-sha512-review-oracle/bin/python experiments/0002-xmpp-library/sha512-validation/oracle.py
|
||||
```
|
||||
|
||||
Python >=3.8; each wheel version/hash is exact. Default oracle mode compares
|
||||
existing fixed fixtures; it does not rewrite them. `--write` is an explicit
|
||||
fixture-generation operation. Scramp is excluded from the app/browser graph.
|
||||
1.4.6 supports the available Python 3.9 host and the documented APIs; no current
|
||||
release or supply-chain/advisory clearance is inferred. Notices are preserved.
|
||||
|
||||
## Public mechanism path
|
||||
|
||||
`Sha512Mechanism` composes Stanza's exported `SCRAM` using its public string
|
||||
constructor with `SCRAM-SHA512`, selecting the existing documented-by-shipped-code
|
||||
`sha512` alias. Its separate Mechanism.name is always **SCRAM-SHA-512**. Internal
|
||||
alias never goes on the wire. Public Factory.disable/register installs that
|
||||
constructor. No Stanza private field is read/changed, no subclass/private-method
|
||||
hook or vendor/bundler crypto patch. Actual cryptographic primitives, PBKDF-style
|
||||
SCRAM composition, SASLprep and verifier stay in unmodified Stanza library code.
|
||||
This is version-specific public API composition, not an upstream endorsement of
|
||||
a future stable alias contract; pins and full vectors are mandatory safeguards.
|
||||
|
||||
The isolated feature handler is copied from reviewed S00-017, narrowed to SHA-512
|
||||
and 88-character base64 verifier, and registers the new mechanism. Install via
|
||||
Client.use on a fresh Client before any negotiation; never install after stock
|
||||
SASL begins. Mechanism priorities filter to SHA-512 even alongside PLUS/256/1/PLAIN
|
||||
offers; unsupported-only offers fail. Complete product fallback policy stays
|
||||
with S00-003. No real channel binding exists; tlsUnique input fails rather than
|
||||
creating PLUS or a fabricated binding. Initial GS2 is n,, and c=biws.
|
||||
|
||||
Guard requires completed proof send before success, strict bounded first/final
|
||||
syntax, then real processSuccess/finalize with both authenticated flags. Missing,
|
||||
forged, malformed, premature/duplicate/racing final and replay fail without auth
|
||||
or restart. Async reset/send failures discard continuation. No derived cache is
|
||||
persisted/emitted and no raw credentials/peer data is logged by authored code.
|
||||
Captured send/transport/disconnect effects are explicit fixtures; actual Client,
|
||||
registry/XML parser, feature dispatcher, mechanism and crypto run.
|
||||
|
||||
## Limits
|
||||
|
||||
Only server-final in success additional-data, no SCRAM extensions/separate final
|
||||
challenge. Limits 4096 decoded bytes and 100000 iterations are fixture policy.
|
||||
Synchronous library PBKDF work can block the UI; no worker/performance acceptance.
|
||||
No constant-time claim: Stanza compares verifier strings. No timeout is added;
|
||||
server silence stays unauthenticated until external reset/cancellation. No
|
||||
arbitrary Unicode/SASLprep conformance certification beyond the fixed case.
|
||||
Native browser engine/RNG execution, WSS/TLS, independent live server/client,
|
||||
parent bounded-ingress integration, complete mechanism policy and release
|
||||
readiness remain outside this experiment. No TLS/certificate/CORS bypass or
|
||||
plaintext downgrade. Browser syntax floors match accepted Vite targets
|
||||
Chrome/Edge 111, Firefox 114, Safari 16.4; API requirements include crypto RNG,
|
||||
fetch and WebSocket. These are build floors, not a certified product matrix.
|
||||
|
||||
Licenses: full locked graph in LICENSE_INVENTORY.md, installed notices including
|
||||
Stanza browser crypto ancestry in THIRD_PARTY_NOTICES.md; oracle notices separate.
|
||||
Vite/build native components are not browser runtime crypto. events maintenance
|
||||
has few contributors per upstream; Stanza crypto is reviewed here with vectors,
|
||||
not newly audited. Dependency/advisory/provenance review remains release work.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,10 @@
|
||||
import client from 'stanza/Client.js';
|
||||
import jxt from 'stanza/jxt/index.js';
|
||||
import platform from 'stanza/platform';
|
||||
import sasl from 'stanza/lib/sasl/index.js';
|
||||
export {default as validatedSasl} from './validated-sasl.mjs';
|
||||
export {Sha512Mechanism} from './sha512-mechanism.mjs';
|
||||
export const Client = client.default;
|
||||
export const parse = jxt.parse;
|
||||
export const {Buffer, createHash, createHmac, name: backend, randomBytes} = platform;
|
||||
export const StockScram = sasl.SCRAM;
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Verify exact installed pins/notices, immutable vendor files and browser graph."""
|
||||
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())
|
||||
inventory = (root / 'LICENSE_INVENTORY.md').read_text()
|
||||
notices = (root / 'THIRD_PARTY_NOTICES.md').read_text()
|
||||
installed = 0
|
||||
for section in ['dependencies', 'devDependencies']:
|
||||
for name, version in manifest[section].items():
|
||||
assert lock['packages']['node_modules/' + name]['version'] == version
|
||||
for path, entry in lock['packages'].items():
|
||||
if not path:
|
||||
continue
|
||||
name = path.split('node_modules/')[-1]
|
||||
assert entry['resolved'].startswith('https://registry.npmjs.org/')
|
||||
assert entry['integrity'].startswith('sha512-')
|
||||
assert f"| {name} | {entry['version']} | {entry['license']} |" in inventory
|
||||
package = root / path
|
||||
if not package.exists():
|
||||
assert entry.get('optional'), name
|
||||
continue
|
||||
meta = json.loads((package / 'package.json').read_text())
|
||||
assert meta['version'] == entry['version'], name
|
||||
assert meta['license'] == entry['license'], name
|
||||
licenses = [f for f in package.iterdir() if f.is_file() and
|
||||
('license' in f.name.lower() or f.name.lower().startswith(('copying', 'notice')))]
|
||||
if name == '@rolldown/binding-darwin-arm64':
|
||||
assert not licenses
|
||||
licenses = [root / 'node_modules/rolldown/LICENSE',
|
||||
root / 'node_modules/rolldown/THIRD-PARTY-LICENSE']
|
||||
assert licenses, name
|
||||
for file in licenses:
|
||||
assert file.read_text().rstrip() in notices, file
|
||||
installed += 1
|
||||
# Parent is read-only evidence of unmodified pinned candidate, not a dependency.
|
||||
original = root.parent / 'node_modules/stanza'
|
||||
current = root / 'node_modules/stanza'
|
||||
files = [f for f in current.rglob('*') if f.is_file()]
|
||||
assert {f.relative_to(current) for f in files} == {
|
||||
f.relative_to(original) for f in original.rglob('*') if f.is_file()}
|
||||
for file in files:
|
||||
assert file.read_bytes() == (original / file.relative_to(current)).read_bytes(), file
|
||||
modules = json.loads((root / 'dist/modules.json').read_text())
|
||||
assert any(x.endswith('/stanza/platform/browser/crypto/Hmac.js') for x in modules)
|
||||
assert not any('/platform/node/' in x or x.startswith('node:') or 'browser-external' in x for x in modules)
|
||||
print(f"{len(lock['packages']) - 1} locked entries; {installed} installed licenses/pins; "
|
||||
f"{len(files)} unchanged Stanza files; browser backend confirmed")
|
||||
for name in ['package-lock.json', 'sha512-mechanism.mjs', 'validated-sasl.mjs',
|
||||
'test/scramp-vectors.json', 'dist/browser.mjs', 'dist/modules.json']:
|
||||
print(name, hashlib.sha256((root / name).read_bytes()).hexdigest())
|
||||
@@ -0,0 +1,2 @@
|
||||
scramp==1.4.6 --hash=sha256:a0cf9d2b4624b69bac5432dd69fecfc55a542384fe73c3a23ed9b138cda484e1
|
||||
asn1crypto==1.5.1 --hash=sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Independent existing SCRAM client/server; synthetic public fixture data only."""
|
||||
import base64
|
||||
import json
|
||||
import sys
|
||||
from importlib.metadata import version
|
||||
from pathlib import Path
|
||||
from scramp import ScramClient, ScramMechanism
|
||||
|
||||
assert version("scramp") == "1.4.6"
|
||||
assert version("asn1crypto") == "1.5.1"
|
||||
path = Path(__file__).parent / "test" / "scramp-vectors.json"
|
||||
nonce = "rOprNGfwEbeRWgbNEkqO"
|
||||
server_nonce = "%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0"
|
||||
salt = base64.b64decode("W22ZaJ0SNY7soEsUEjb6gQ==")
|
||||
results = []
|
||||
for password in ["pencil", "A" * 131, "p\u00e9ncil"]:
|
||||
mechanism = ScramMechanism("SCRAM-SHA-512")
|
||||
auth = mechanism.make_auth_info(password, iteration_count=4096, salt=salt)
|
||||
def auth_fn(username):
|
||||
assert username == "user"
|
||||
return auth
|
||||
server = mechanism.make_server(auth_fn, s_nonce=server_nonce)
|
||||
client = ScramClient(["SCRAM-SHA-512"], "user", password, c_nonce=nonce)
|
||||
initial = client.get_client_first()
|
||||
server.set_client_first(initial)
|
||||
first = server.get_server_first()
|
||||
client.set_server_first(first)
|
||||
proof = client.get_client_final()
|
||||
server.set_client_final(proof) # Independent server verifies proof.
|
||||
final = server.get_server_final()
|
||||
client.set_server_final(final) # Independent client verifies server.
|
||||
results.append(dict(mechanism="SCRAM-SHA-512", username="user", password=password,
|
||||
nonce=nonce, initial=initial, first=first, proof=proof, final=final))
|
||||
if sys.argv[1:] == ["--write"]:
|
||||
path.write_text(json.dumps(results, indent=2) + "\n")
|
||||
else:
|
||||
assert not sys.argv[1:]
|
||||
assert json.loads(path.read_text()) == results
|
||||
print("scramp 1.4.6: three fixed SHA-512 client/server exchanges verified")
|
||||
@@ -0,0 +1,925 @@
|
||||
{
|
||||
"name": "luma-browser-scram-sha512-experiment",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "luma-browser-scram-sha512-experiment",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"events": "3.3.0",
|
||||
"stanza": "12.22.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "8.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "24.21.0",
|
||||
"npm": "11.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.154.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz",
|
||||
"integrity": "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/oxc-project"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm-eabi": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz",
|
||||
"integrity": "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.14.tgz",
|
||||
"integrity": "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.14.tgz",
|
||||
"integrity": "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.14.tgz",
|
||||
"integrity": "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.14.tgz",
|
||||
"integrity": "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.14.tgz",
|
||||
"integrity": "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.14.tgz",
|
||||
"integrity": "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.14.tgz",
|
||||
"integrity": "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.14.tgz",
|
||||
"integrity": "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.14.tgz",
|
||||
"integrity": "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.14.tgz",
|
||||
"integrity": "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.14.tgz",
|
||||
"integrity": "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.14.tgz",
|
||||
"integrity": "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.14.tgz",
|
||||
"integrity": "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.14.tgz",
|
||||
"integrity": "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
|
||||
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"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/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
|
||||
"integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"lightningcss-android-arm64": "1.33.0",
|
||||
"lightningcss-darwin-arm64": "1.33.0",
|
||||
"lightningcss-darwin-x64": "1.33.0",
|
||||
"lightningcss-freebsd-x64": "1.33.0",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.33.0",
|
||||
"lightningcss-linux-arm64-gnu": "1.33.0",
|
||||
"lightningcss-linux-arm64-musl": "1.33.0",
|
||||
"lightningcss-linux-x64-gnu": "1.33.0",
|
||||
"lightningcss-linux-x64-musl": "1.33.0",
|
||||
"lightningcss-win32-arm64-msvc": "1.33.0",
|
||||
"lightningcss-win32-x64-msvc": "1.33.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-android-arm64": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
|
||||
"integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-arm64": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
|
||||
"integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-x64": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
|
||||
"integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-freebsd-x64": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
|
||||
"integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
|
||||
"integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
|
||||
"integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-musl": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
|
||||
"integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
|
||||
"integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-musl": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
|
||||
"integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
|
||||
"integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-x64-msvc": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
|
||||
"integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.20",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
|
||||
"integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
|
||||
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.30",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz",
|
||||
"integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.20",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"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/rolldown": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.14.tgz",
|
||||
"integrity": "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.154.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm-eabi": "1.2.14",
|
||||
"@rolldown/binding-android-arm64": "1.2.14",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.14",
|
||||
"@rolldown/binding-darwin-x64": "1.2.14",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.14",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.14",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.14",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.14",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.14",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.14",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.14",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.14",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.14",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.14",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.14"
|
||||
}
|
||||
},
|
||||
"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/source-map-js": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
|
||||
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"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/tinyglobby": {
|
||||
"version": "0.2.17",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"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/vite": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz",
|
||||
"integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.33.0",
|
||||
"picomatch": "^4.0.7",
|
||||
"postcss": "^8.5.29",
|
||||
"rolldown": "~1.2.12",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^20.19.0 || >=22.12.0",
|
||||
"@vitejs/devtools": "^0.7.1",
|
||||
"esbuild": "^0.27.0 || ^0.28.0",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "^4.0.0",
|
||||
"sass": "^1.70.0",
|
||||
"sass-embedded": "^1.70.0",
|
||||
"stylus": ">=0.54.8",
|
||||
"sugarss": "^5.0.0",
|
||||
"terser": "^5.16.0",
|
||||
"tsx": "^4.8.1",
|
||||
"yaml": "^2.4.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitejs/devtools": {
|
||||
"optional": true
|
||||
},
|
||||
"esbuild": {
|
||||
"optional": true
|
||||
},
|
||||
"jiti": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
},
|
||||
"tsx": {
|
||||
"optional": true
|
||||
},
|
||||
"yaml": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "luma-browser-scram-sha512-experiment",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "24.21.0",
|
||||
"npm": "11.19.0"
|
||||
},
|
||||
"packageManager": "npm@11.19.0",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"test": "npm run build && node --test test/*.test.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"stanza": "12.22.1",
|
||||
"events": "3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "8.3.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import sasl from 'stanza/lib/sasl/index.js';
|
||||
|
||||
// Public constructor selects the existing browser backend's supported hash alias.
|
||||
// The delegate name never goes on the wire. No private field is read or changed.
|
||||
export class Sha512Mechanism {
|
||||
name = 'SCRAM-SHA-512';
|
||||
providesMutualAuthentication = true;
|
||||
#delegate;
|
||||
constructor(name) {
|
||||
if (name !== this.name) throw new Error('Unsupported mechanism');
|
||||
this.#delegate = new sasl.SCRAM('SCRAM-SHA512');
|
||||
}
|
||||
getExpectedCredentials() { return this.#delegate.getExpectedCredentials(); }
|
||||
// Keep derived credentials attempt-local; no cache leaves this boundary.
|
||||
getCacheableCredentials() { return null; }
|
||||
createResponse(credentials) {
|
||||
if (credentials.tlsUnique) throw new Error('Channel binding unavailable');
|
||||
return this.#delegate.createResponse(credentials);
|
||||
}
|
||||
processChallenge(value) { this.#delegate.processChallenge(value); }
|
||||
processSuccess(value) { this.#delegate.processSuccess(value); }
|
||||
finalize() { return this.#delegate.finalize(); }
|
||||
}
|
||||
|
||||
export function registerSha512(client) {
|
||||
client.sasl.disable('SCRAM-SHA-512');
|
||||
client.sasl.register('SCRAM-SHA-512', Sha512Mechanism, 1000);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {readFileSync} from 'node:fs';
|
||||
import {createHash as oracleHash, createHmac as oracleHmac} from 'node:crypto';
|
||||
import {Buffer as BrowserBuffer, createHash, createHmac, backend, StockScram, Sha512Mechanism, randomBytes} from '../dist/browser.mjs';
|
||||
const hmacVectors = JSON.parse(readFileSync(new URL('./hmac-vectors.json', import.meta.url)));
|
||||
const scramVectors = JSON.parse(readFileSync(new URL('./scramp-vectors.json', import.meta.url)));
|
||||
|
||||
test('bundle resolves browser backend and contains no external runtime imports', () => {
|
||||
assert.equal(backend, 'browser');
|
||||
const modules = JSON.parse(readFileSync(new URL('../dist/modules.json', import.meta.url)));
|
||||
assert.ok(modules.some(id => id.endsWith('/platform/browser/crypto/Hmac.js')));
|
||||
assert.ok(modules.some(id => id.endsWith('/platform/browser/crypto/SHA-512.js')));
|
||||
assert.ok(!modules.some(id => /platform\/node\/|^node:|browser-external/.test(id)));
|
||||
const bundle = readFileSync(new URL('../dist/browser.mjs', import.meta.url), 'utf8');
|
||||
assert.ok(!/^import\s|\brequire\(/m.test(bundle));
|
||||
});
|
||||
|
||||
for (const vector of hmacVectors) {
|
||||
for (const algorithm of ['sha512', 'SHA512']) {
|
||||
test(`RFC 4231 case ${vector.case}, browser ${algorithm}`, () => {
|
||||
const key = BrowserBuffer.from(vector.key, 'hex');
|
||||
const data = BrowserBuffer.from(vector.data, 'hex');
|
||||
const actual = createHmac(algorithm, key).update(data).digest('hex');
|
||||
assert.equal(actual.slice(0, vector.digest.length), vector.digest);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
test('reproduce browser hyphenated HMAC mismatch, keep Node independent control', () => {
|
||||
const vector = hmacVectors[0];
|
||||
const key = BrowserBuffer.from(vector.key, 'hex');
|
||||
const data = BrowserBuffer.from(vector.data, 'hex');
|
||||
const broken = createHmac('sha-512', key).update(data).digest('hex');
|
||||
assert.notEqual(broken, vector.digest);
|
||||
assert.equal(broken, '9656975ee5de55e75f2976ecce9a04501060b9dc22a6eda2eaef638966280182477fe09f080b2bf564649cad42af8607a2bd8d02979df3a980f15e2326a0a22a');
|
||||
assert.equal(oracleHmac('sha512', Buffer.from(vector.key, 'hex')).update(Buffer.from(vector.data, 'hex')).digest('hex'), vector.digest);
|
||||
});
|
||||
|
||||
const abc = 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192' +
|
||||
'992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f';
|
||||
for (const algorithm of ['sha512', 'sha-512', 'SHA512', 'SHA-512']) {
|
||||
test(`fixed SHA512 abc digest, browser ${algorithm}`, () => {
|
||||
assert.equal(createHash(algorithm).update('abc').digest('hex'), abc);
|
||||
});
|
||||
}
|
||||
|
||||
for (const length of [0, 1, 63, 64, 65, 127, 128, 129, 131, 256]) {
|
||||
test(`browser HMAC alias key length ${length} agrees with Node crypto`, () => {
|
||||
const key = Buffer.alloc(length, 0xab);
|
||||
const data = Buffer.alloc(257, 0xcd);
|
||||
assert.equal(createHmac('sha512', BrowserBuffer.from(key)).update(BrowserBuffer.from(data)).digest('hex'),
|
||||
oracleHmac('sha512', key).update(data).digest('hex'));
|
||||
assert.equal(createHash('sha512').update(BrowserBuffer.from(data)).digest('hex'),
|
||||
oracleHash('sha512').update(data).digest('hex'));
|
||||
});
|
||||
}
|
||||
|
||||
test('stock browser SHA512 proof mismatch; public alias delegate matches independent vector', () => {
|
||||
const vector = scramVectors[0];
|
||||
const credentials = {username: vector.username, password: vector.password, clientNonce: vector.nonce};
|
||||
const stock = new StockScram('SCRAM-SHA-512');
|
||||
stock.createResponse(credentials);
|
||||
stock.processChallenge(BrowserBuffer.from(vector.first));
|
||||
assert.notEqual(stock.createResponse(credentials).toString(), vector.proof);
|
||||
const selected = new Sha512Mechanism('SCRAM-SHA-512');
|
||||
assert.equal(selected.name, vector.mechanism);
|
||||
assert.equal(selected.createResponse(credentials).toString(), vector.initial);
|
||||
selected.processChallenge(BrowserBuffer.from(vector.first));
|
||||
assert.equal(selected.createResponse(credentials).toString(), vector.proof);
|
||||
selected.processSuccess(BrowserBuffer.from(vector.final));
|
||||
assert.deepEqual(selected.finalize(), {authenticated: true, mutuallyAuthenticated: true});
|
||||
assert.equal(selected.getCacheableCredentials(), null);
|
||||
});
|
||||
|
||||
test('native browser RNG path works without fixed nonce, no invented channel binding', () => {
|
||||
assert.equal(randomBytes(32).length, 32);
|
||||
const selected = new Sha512Mechanism('SCRAM-SHA-512');
|
||||
const initial = selected.createResponse({username: 'user', password: 'pencil'}).toString();
|
||||
assert.match(initial, /^n,,n=user,r=[0-9a-f]{64}$/);
|
||||
assert.throws(() => new Sha512Mechanism('SCRAM-SHA-512-PLUS'));
|
||||
assert.throws(() => new Sha512Mechanism('SCRAM-SHA-512').createResponse({tlsUnique: BrowserBuffer.alloc(32)}));
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
[
|
||||
{
|
||||
"case": 1,
|
||||
"key": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
|
||||
"data": "4869205468657265",
|
||||
"digest": "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
|
||||
},
|
||||
{
|
||||
"case": 2,
|
||||
"key": "4a656665",
|
||||
"data": "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
|
||||
"digest": "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737"
|
||||
},
|
||||
{
|
||||
"case": 3,
|
||||
"key": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"data": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
"digest": "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb"
|
||||
},
|
||||
{
|
||||
"case": 4,
|
||||
"key": "0102030405060708090a0b0c0d0e0f10111213141516171819",
|
||||
"data": "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
|
||||
"digest": "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd"
|
||||
},
|
||||
{
|
||||
"case": 5,
|
||||
"key": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
|
||||
"data": "546573742057697468205472756e636174696f6e",
|
||||
"digest": "415fad6271580a531d4179bc891d87a6"
|
||||
},
|
||||
{
|
||||
"case": 6,
|
||||
"key": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"data": "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374",
|
||||
"digest": "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"
|
||||
},
|
||||
{
|
||||
"case": 7,
|
||||
"key": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"data": "5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e",
|
||||
"digest": "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,302 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {readFileSync} from 'node:fs';
|
||||
import {Client, parse, validatedSasl, StockScram} from '../dist/browser.mjs';
|
||||
const vectors = JSON.parse(readFileSync(new URL('./scramp-vectors.json', import.meta.url)));
|
||||
const tick = () => new Promise(resolve => setImmediate(resolve));
|
||||
const deferred = () => {
|
||||
let resolve;
|
||||
const promise = new Promise(r => {resolve = r;});
|
||||
return {promise, resolve};
|
||||
};
|
||||
|
||||
function harness(vector = vectors[1], fixed = true, mechanisms = [vector.mechanism]) {
|
||||
const client = new Client({jid: 'user@example.invalid', autoReconnect: false,
|
||||
credentials: {username: vector.username, password: vector.password, clientNonce: vector.nonce}});
|
||||
const result = {sent: [], success: 0, failed: 0, restart: 0, disconnect: 0, verification: []};
|
||||
// Capture fixture effects; use the real Client, XML registry, feature dispatcher,
|
||||
// mechanism factory and SCRAM crypto. Never connect to a real provider.
|
||||
client.send = async (kind, stanza) => {result.sent.push({kind, stanza});};
|
||||
client.transport = {authenticated: false, restart: () => {result.restart++;}};
|
||||
client.disconnect = async () => {result.disconnect++;};
|
||||
client.on('auth:success', () => {result.success++;});
|
||||
client.on('auth:failed', () => {result.failed++;});
|
||||
if (!fixed) client.sasl.register('SCRAM-SHA-512', StockScram, 1000);
|
||||
if (fixed) {
|
||||
client.use(validatedSasl);
|
||||
const original = client.sasl.createMechanism.bind(client.sasl);
|
||||
client.sasl.createMechanism = names => {
|
||||
const mech = original(names);
|
||||
if (mech) for (const method of ['processSuccess', 'finalize']) {
|
||||
const fn = mech[method].bind(mech);
|
||||
mech[method] = (...args) => {result.verification.push(method); return fn(...args);};
|
||||
}
|
||||
return mech;
|
||||
};
|
||||
}
|
||||
const receive = async (type, value) => {
|
||||
const body = value === undefined ? '' : Buffer.from(value).toString('base64');
|
||||
const xml = parse(`<${type} xmlns="urn:ietf:params:xml:ns:xmpp-sasl">${body}</${type}>`);
|
||||
const stanza = client.stanzas.import(xml);
|
||||
assert.equal(stanza.type, type);
|
||||
client.emit('sasl', stanza);
|
||||
await tick();
|
||||
};
|
||||
const start = async () => {
|
||||
client.emit('features', {sasl: {mechanisms}});
|
||||
await tick();
|
||||
};
|
||||
return {client, result, receive, start};
|
||||
}
|
||||
function rejected(h) {
|
||||
assert.equal(h.result.success, 0);
|
||||
assert.equal(h.result.restart, 0);
|
||||
assert.equal(h.result.failed, 1);
|
||||
assert.equal(h.result.disconnect, 1);
|
||||
assert.equal(h.client.features.negotiated.sasl, false);
|
||||
assert.equal(h.client.transport.authenticated, false);
|
||||
assert.equal(h.client.listenerCount('sasl'), 0);
|
||||
}
|
||||
|
||||
for (const vector of vectors) {
|
||||
test(`${vector.mechanism}: independent Scramp proof and verified final through feature dispatcher`, async () => {
|
||||
const h = harness(vector);
|
||||
await h.start();
|
||||
assert.equal(h.result.sent[0].stanza.mechanism, vector.mechanism);
|
||||
assert.equal(h.result.sent[0].stanza.value.toString(), `n,,n=user,r=${vector.nonce}`);
|
||||
await h.receive('challenge', vector.first);
|
||||
assert.equal(h.result.sent[1].stanza.value.toString(), vector.proof);
|
||||
assert.equal(h.result.success, 0);
|
||||
assert.equal(h.result.restart, 0);
|
||||
assert.equal(h.client.transport.authenticated, false);
|
||||
await h.receive('success', vector.final);
|
||||
assert.deepEqual(h.result.verification, ['processSuccess', 'finalize']);
|
||||
assert.equal(h.result.success, 1);
|
||||
assert.equal(h.result.restart, 1);
|
||||
assert.equal(h.result.disconnect, 0);
|
||||
assert.equal(h.client.features.negotiated.sasl, true);
|
||||
assert.equal(h.client.transport.authenticated, true);
|
||||
await h.receive('success', vector.final);
|
||||
assert.equal(h.result.success, 1);
|
||||
assert.equal(h.result.restart, 1);
|
||||
});
|
||||
for (const [label, final] of [
|
||||
['missing', undefined],
|
||||
['forged', 'v=' + (vector.final[2] === 'A' ? 'B' : 'A') + vector.final.slice(3)],
|
||||
['error', 'e=invalid-proof'],
|
||||
['conflicting error', `${vector.final},e=invalid-proof`],
|
||||
['duplicate verifier', `${vector.final},${vector.final}`],
|
||||
['bad base64', 'v=***'],
|
||||
['oversized', 'v=' + 'A'.repeat(4096)]
|
||||
]) {
|
||||
test(`${vector.mechanism}: reject ${label} server-final after proof`, async () => {
|
||||
const h = harness(vector);
|
||||
await h.start();
|
||||
await h.receive('challenge', vector.first);
|
||||
await h.receive('success', final);
|
||||
rejected(h);
|
||||
if (label === 'forged') assert.deepEqual(h.result.verification, ['processSuccess', 'finalize']);
|
||||
await h.receive('success', vector.final);
|
||||
rejected(h);
|
||||
});
|
||||
}
|
||||
test(`${vector.mechanism}: reject valid-looking final before proof`, async () => {
|
||||
const h = harness(vector);
|
||||
await h.start();
|
||||
await h.receive('success', vector.final);
|
||||
rejected(h);
|
||||
assert.deepEqual(h.result.verification, []);
|
||||
});
|
||||
}
|
||||
|
||||
for (const [label, final] of [['missing', undefined], ['forged', 'v=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=']]) {
|
||||
test(`stock 12.22.1 reproduces ${label} premature acceptance`, async () => {
|
||||
const h = harness(vectors[1], false);
|
||||
await h.start();
|
||||
await h.receive('success', final);
|
||||
assert.equal(h.result.success, 1);
|
||||
assert.equal(h.result.restart, 1);
|
||||
assert.equal(h.client.transport.authenticated, true);
|
||||
});
|
||||
test(`replacement rejects original ${label} premature defect`, async () => {
|
||||
const h = harness();
|
||||
await h.start();
|
||||
await h.receive('success', final);
|
||||
rejected(h);
|
||||
});
|
||||
}
|
||||
|
||||
for (const [label, first] of [
|
||||
['wrong nonce', vectors[1].first.replace('r=rOpr', 'r=wrong')],
|
||||
['unextended nonce', `r=${vectors[1].nonce},s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096`],
|
||||
['iteration overflow', vectors[1].first.replace('i=4096', 'i=100001')],
|
||||
['invalid iterations', vectors[1].first.replace('i=4096', 'i=NaN')],
|
||||
['duplicate attribute', vectors[1].first + ',i=4096'],
|
||||
['mandatory extension', 'm=required,' + vectors[1].first]
|
||||
]) {
|
||||
test(`reject ${label} before library proof work`, async () => {
|
||||
const h = harness();
|
||||
await h.start();
|
||||
await h.receive('challenge', first);
|
||||
rejected(h);
|
||||
assert.equal(h.result.sent.length, 1);
|
||||
});
|
||||
}
|
||||
|
||||
test('reject success while proof send is pending; no late revival', async () => {
|
||||
const h = harness();
|
||||
await h.start();
|
||||
const pending = deferred();
|
||||
h.client.send = () => pending.promise;
|
||||
await h.receive('challenge', vectors[1].first);
|
||||
await h.receive('success', vectors[1].final);
|
||||
rejected(h);
|
||||
pending.resolve();
|
||||
await tick();
|
||||
await h.receive('success', vectors[1].final);
|
||||
rejected(h);
|
||||
});
|
||||
|
||||
test('duplicate challenge while sending proof fails closed', async () => {
|
||||
const h = harness();
|
||||
await h.start();
|
||||
const pending = deferred();
|
||||
h.client.send = () => pending.promise;
|
||||
await h.receive('challenge', vectors[1].first);
|
||||
await h.receive('challenge', vectors[1].first);
|
||||
rejected(h);
|
||||
pending.resolve();
|
||||
await tick();
|
||||
rejected(h);
|
||||
});
|
||||
|
||||
test('reset while awaiting credentials prevents late auth and listener leak', async () => {
|
||||
const h = harness();
|
||||
const pending = deferred();
|
||||
h.client.getCredentials = () => pending.promise;
|
||||
await h.start();
|
||||
h.client.emit('--reset-stream-features');
|
||||
pending.resolve({username: 'user', password: 'pencil', clientNonce: vectors[1].nonce});
|
||||
await tick();
|
||||
await h.receive('success', vectors[1].final);
|
||||
assert.equal(h.result.sent.length, 0);
|
||||
assert.equal(h.result.success, 0);
|
||||
assert.equal(h.result.restart, 0);
|
||||
assert.equal(h.client.listenerCount('sasl'), 0);
|
||||
});
|
||||
|
||||
test('reset after proof, new negotiation succeeds without old handlers', async () => {
|
||||
const h = harness();
|
||||
await h.start();
|
||||
await h.receive('challenge', vectors[1].first);
|
||||
h.client.emit('--reset-stream-features');
|
||||
await h.receive('success', vectors[1].final);
|
||||
assert.equal(h.result.success, 0);
|
||||
assert.equal(h.client.listenerCount('sasl'), 0);
|
||||
await h.start();
|
||||
assert.equal(h.client.listenerCount('sasl'), 1);
|
||||
await h.receive('challenge', vectors[1].first);
|
||||
await h.receive('success', vectors[1].final);
|
||||
assert.equal(h.result.success, 1);
|
||||
assert.equal(h.result.restart, 1);
|
||||
});
|
||||
|
||||
test('fixture policy filters PLUS and plaintext; strongest available non-PLUS selected', async () => {
|
||||
const h = harness(vectors[1], true, ['PLAIN', 'SCRAM-SHA-1', 'SCRAM-SHA-512-PLUS', 'SCRAM-SHA-256', 'SCRAM-SHA-512']);
|
||||
await h.start();
|
||||
assert.equal(h.result.sent[0].stanza.mechanism, 'SCRAM-SHA-512');
|
||||
});
|
||||
for (const names of [['PLAIN'], ['SCRAM-SHA-512-PLUS'], []]) {
|
||||
test(`no allowed mechanism (${names.join(',')}) disconnects`, async () => {
|
||||
const h = harness(vectors[1], true, names);
|
||||
await h.start();
|
||||
rejected(h);
|
||||
assert.equal(h.result.sent.length, 0);
|
||||
});
|
||||
}
|
||||
for (const type of ['failure', 'abort']) {
|
||||
test(`${type} is terminal; valid final cannot revive it`, async () => {
|
||||
const h = harness();
|
||||
await h.start();
|
||||
await h.receive(type);
|
||||
await h.receive('success', vectors[1].final);
|
||||
rejected(h);
|
||||
});
|
||||
}
|
||||
|
||||
test('success during pending initial credentials fails closed', async () => {
|
||||
const h = harness();
|
||||
const pending = deferred();
|
||||
h.client.getCredentials = () => pending.promise;
|
||||
await h.start();
|
||||
await h.receive('success', vectors[1].final);
|
||||
rejected(h);
|
||||
pending.resolve({username: 'user', password: 'pencil', clientNonce: vectors[1].nonce});
|
||||
await tick();
|
||||
assert.equal(h.result.sent.length, 0);
|
||||
rejected(h);
|
||||
});
|
||||
|
||||
test('reset during proof send cancels continuation', async () => {
|
||||
const h = harness();
|
||||
await h.start();
|
||||
const pending = deferred();
|
||||
h.client.send = () => pending.promise;
|
||||
await h.receive('challenge', vectors[1].first);
|
||||
h.client.emit('--reset-stream-features');
|
||||
pending.resolve();
|
||||
await tick();
|
||||
await h.receive('success', vectors[1].final);
|
||||
assert.equal(h.result.success, 0);
|
||||
assert.equal(h.result.restart, 0);
|
||||
assert.equal(h.client.listenerCount('sasl'), 0);
|
||||
});
|
||||
|
||||
for (const stage of ['credentials', 'initial send', 'proof send']) {
|
||||
test(`${stage} rejection disconnects without success`, async () => {
|
||||
const h = harness();
|
||||
const reject = async () => {throw new Error('synthetic fixture failure');};
|
||||
if (stage === 'credentials') h.client.getCredentials = reject;
|
||||
if (stage === 'initial send') h.client.send = reject;
|
||||
await h.start();
|
||||
if (stage === 'proof send') {
|
||||
h.client.send = reject;
|
||||
await h.receive('challenge', vectors[1].first);
|
||||
}
|
||||
rejected(h);
|
||||
});
|
||||
}
|
||||
|
||||
test('repeat challenge after completed proof is rejected', async () => {
|
||||
const h = harness();
|
||||
await h.start();
|
||||
await h.receive('challenge', vectors[1].first);
|
||||
await h.receive('challenge', vectors[1].first);
|
||||
rejected(h);
|
||||
});
|
||||
|
||||
|
||||
test('synthetic binding input fails without auth or plaintext fallback', async () => {
|
||||
const h = harness();
|
||||
h.client.config.credentials.tlsUnique = Buffer.alloc(32);
|
||||
await h.start();
|
||||
rejected(h);
|
||||
assert.equal(h.result.sent.length, 0);
|
||||
});
|
||||
|
||||
test('server silence after proof never authenticates; reset discards attempt', async () => {
|
||||
const h = harness();
|
||||
let cacheEvents = 0;
|
||||
h.client.on('credentials:update', () => {cacheEvents++;});
|
||||
await h.start();
|
||||
await h.receive('challenge', vectors[0].first);
|
||||
assert.equal(h.result.success, 0);
|
||||
assert.equal(h.result.restart, 0);
|
||||
assert.equal(h.client.transport.authenticated, false);
|
||||
assert.equal(cacheEvents, 0);
|
||||
assert.equal(h.client.config.credentials.saltedPassword, undefined);
|
||||
h.client.emit('--reset-stream-features');
|
||||
await h.receive('success', vectors[0].final);
|
||||
assert.equal(h.result.success, 0);
|
||||
assert.equal(h.result.restart, 0);
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"mechanism": "SCRAM-SHA-512",
|
||||
"username": "user",
|
||||
"password": "pencil",
|
||||
"nonce": "rOprNGfwEbeRWgbNEkqO",
|
||||
"initial": "n,,n=user,r=rOprNGfwEbeRWgbNEkqO",
|
||||
"first": "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096",
|
||||
"proof": "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=gMGXRcevScNtxZ6/8lQYpGtnsNAc3mGcmNomv+xnoOMw+3R2xNJdMNnzMlTN8PPC6wdp6dybEmDYXYTxwnYPJQ==",
|
||||
"final": "v=ZQnYEgWQMFmmsM8aQMF0nDDCy/AgCzkwk8CmMZYcMg0vSVlKDanekLtifDSeVGT4+5ZxXnJq199RVG2rR7N7Zw=="
|
||||
},
|
||||
{
|
||||
"mechanism": "SCRAM-SHA-512",
|
||||
"username": "user",
|
||||
"password": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"nonce": "rOprNGfwEbeRWgbNEkqO",
|
||||
"initial": "n,,n=user,r=rOprNGfwEbeRWgbNEkqO",
|
||||
"first": "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096",
|
||||
"proof": "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=FQ+Ds6nMR5kU+jnkwyhJGBK8lA2hTFKTqEBVSUCvSgmexvd4GGT4uAqD3iACaCkLYP0Bc6JHwnHMWkpGNOX2rg==",
|
||||
"final": "v=dXX1mz64UYbnX15NloHSoWfqoMVVvFg23i8KXwHigIVa+U6w+eJBBwhNg47qeIqkRLhhVnRnvcVvQMXpqav+4A=="
|
||||
},
|
||||
{
|
||||
"mechanism": "SCRAM-SHA-512",
|
||||
"username": "user",
|
||||
"password": "p\u00e9ncil",
|
||||
"nonce": "rOprNGfwEbeRWgbNEkqO",
|
||||
"initial": "n,,n=user,r=rOprNGfwEbeRWgbNEkqO",
|
||||
"first": "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096",
|
||||
"proof": "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=Jwk58tl/01ikMIUvnr0MXiTPWZJOiqtTyIAKx2A7IL85TXsL9S/fvBEIYvd/a0QIAQrNp47BSvResR5QB63TSg==",
|
||||
"final": "v=QwFhP0JOInTkJ/uTsZ2DRt00V0dQtkZKotpq815cU8X2SpF/5qbOd8sd6TCj8NjX/GPke8Xj4yg6whLHu7KNlw=="
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,111 @@
|
||||
// Isolated S00-019 copy of the reviewed S00-017 ordering guard.
|
||||
import {registerSha512} from './sha512-mechanism.mjs';
|
||||
// No production transport, credential persistence, or channel binding is provided.
|
||||
const SUPPORTED = new Set(['SCRAM-SHA-512']);
|
||||
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
||||
|
||||
function text(value) {
|
||||
// SASL's parsed additional-data is a Buffer, not raw XML/base64 text.
|
||||
if (!value || typeof value.toString !== 'function' || value.length > 4096) {
|
||||
throw new Error('Invalid SASL data');
|
||||
}
|
||||
const result = value.toString('utf8');
|
||||
if (!/^[\x21-\x7e]*$/.test(result)) throw new Error('Invalid SASL data');
|
||||
return result;
|
||||
}
|
||||
|
||||
function firstMessage(value, nonce) {
|
||||
// Deliberately narrow, no extensions. Bound work before calling library crypto.
|
||||
const match = /^r=([\x21-\x2b\x2d-\x7e]+),s=([A-Za-z0-9+/=]+),i=([1-9][0-9]*)$/.exec(text(value));
|
||||
if (!match || !BASE64.test(match[2]) || !match[1].startsWith(nonce) ||
|
||||
match[1].length <= nonce.length || Number(match[3]) > 100000) {
|
||||
throw new Error('Invalid SCRAM server-first');
|
||||
}
|
||||
}
|
||||
|
||||
function finalMessage(value, name) {
|
||||
const final = text(value);
|
||||
if (name !== 'SCRAM-SHA-512') throw new Error('Unsupported mechanism');
|
||||
const length = 88;
|
||||
if (!final.startsWith('v=') || final.length !== length + 2 || !BASE64.test(final.slice(2))) {
|
||||
throw new Error('Invalid SCRAM server-final');
|
||||
}
|
||||
}
|
||||
|
||||
export default function validatedSasl(client) {
|
||||
registerSha512(client);
|
||||
let active;
|
||||
// Public plugin + named feature registration replaces the stock handler before use.
|
||||
client.registerFeature('sasl', 100, async (features, done) => {
|
||||
if (active) active();
|
||||
const names = features.sasl.mechanisms.filter(name => SUPPORTED.has(name));
|
||||
const mech = client.sasl.createMechanism(names);
|
||||
let phase = 'starting';
|
||||
let credentials;
|
||||
let nonce;
|
||||
const cleanup = () => {
|
||||
client.off('sasl', handler);
|
||||
client.off('--reset-stream-features', reset);
|
||||
if (active === reset) active = undefined;
|
||||
};
|
||||
const reset = () => {
|
||||
phase = 'closed';
|
||||
cleanup();
|
||||
client.features.negotiated.sasl = false;
|
||||
};
|
||||
const fail = () => {
|
||||
if (phase === 'closed') return;
|
||||
phase = 'closed';
|
||||
cleanup();
|
||||
client.features.negotiated.sasl = false;
|
||||
if (client.transport) client.transport.authenticated = false;
|
||||
client.emit('auth:failed');
|
||||
done('disconnect', 'SCRAM validation failed'); // Never log peer data/credentials.
|
||||
};
|
||||
const handler = async sasl => {
|
||||
if (phase === 'closed') return;
|
||||
try {
|
||||
if (sasl.type === 'challenge' && phase === 'server-first') {
|
||||
phase = 'sending-proof'; // Reject duplicate challenges and racing success.
|
||||
firstMessage(sasl.value, nonce);
|
||||
mech.processChallenge(sasl.value);
|
||||
const response = mech.createResponse(credentials);
|
||||
await client.send('sasl', {type: 'response', value: response});
|
||||
if (phase !== 'closed') phase = 'server-final';
|
||||
return;
|
||||
}
|
||||
if (sasl.type !== 'success' || phase !== 'server-final') return fail();
|
||||
finalMessage(sasl.value, mech.name);
|
||||
mech.processSuccess(sasl.value);
|
||||
const result = mech.finalize();
|
||||
if (!result.authenticated || !result.mutuallyAuthenticated) return fail();
|
||||
phase = 'closed';
|
||||
cleanup();
|
||||
client.features.negotiated.sasl = true;
|
||||
if (client.transport) client.transport.authenticated = true;
|
||||
client.emit('auth:success', client.config.credentials);
|
||||
done('restart');
|
||||
} catch {
|
||||
fail();
|
||||
}
|
||||
};
|
||||
active = reset;
|
||||
client.features.negotiated.sasl = false;
|
||||
client.on('sasl', handler);
|
||||
client.once('--reset-stream-features', reset);
|
||||
try {
|
||||
if (!mech || !SUPPORTED.has(mech.name)) return fail();
|
||||
credentials = await client.getCredentials();
|
||||
if (phase === 'closed') return;
|
||||
// Restrict this experiment to non-PLUS; never invent TLS binding bytes.
|
||||
if (credentials.tlsUnique) return fail();
|
||||
const response = mech.createResponse(credentials);
|
||||
nonce = /,r=([^,]+)$/.exec(response.toString('utf8'))?.[1];
|
||||
if (!nonce) return fail();
|
||||
await client.send('sasl', {type: 'auth', mechanism: mech.name, value: response});
|
||||
if (phase !== 'closed') phase = 'server-first';
|
||||
} catch {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import {defineConfig} from 'vite';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
export default defineConfig({
|
||||
resolve: {alias: {events: fileURLToPath(new URL('./node_modules/events/events.js', import.meta.url))}},
|
||||
build: {
|
||||
target: ['chrome111', 'edge111', 'firefox114', 'safari16.4'],
|
||||
minify: false,
|
||||
lib: {entry: 'browser-entry.mjs', formats: ['es'], fileName: () => 'browser.mjs'},
|
||||
rollupOptions: {plugins: [{
|
||||
name: 'backend-evidence',
|
||||
generateBundle() {
|
||||
const modules = [...this.getModuleIds()].map(id => id.replace(process.cwd(), '.')).sort();
|
||||
if (!modules.some(id => id.endsWith('/stanza/platform/browser/crypto/Hmac.js'))) {
|
||||
throw new Error('Browser HMAC missing');
|
||||
}
|
||||
if (modules.some(id => /stanza\/platform\/node\/|^node:|browser-external/.test(id))) {
|
||||
throw new Error('Unexpected Node runtime backend');
|
||||
}
|
||||
this.emitFile({type: 'asset', fileName: 'modules.json', source: JSON.stringify(modules, null, 2) + '\n'});
|
||||
}
|
||||
}]}
|
||||
}
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
# Матрица задач
|
||||
|
||||
Generated; state revision **48**. Canonical status: state/tasks.json.
|
||||
Generated; state revision **50**. Canonical status: state/tasks.json.
|
||||
|
||||
| ID | Этап | Оператор | Reviewer | Статус | Owner | Зависимости | Задача |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
@@ -21,7 +21,7 @@ Generated; state revision **48**. Canonical status: state/tasks.json.
|
||||
| [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 | done | auto-protocol-89fe6563092c | S00-001, S00-002, S00-017 | Validate bounded standalone XML ingress for the XMPP candidate |
|
||||
| [S00-019](tasks/S00-019.md) | S00 | crypto | security | todo | | S00-001, S00-002, S00-017, S00-018 | Validate browser-compatible SCRAM-SHA-512 through a public mechanism API |
|
||||
| [S00-019](tasks/S00-019.md) | S00 | crypto | security | review | auto-crypto-c68801170fa4 | S00-001, S00-002, S00-017, S00-018 | Validate browser-compatible SCRAM-SHA-512 through a public mechanism API |
|
||||
| [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 |
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
- [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; done.
|
||||
- [S00-019](../tasks/S00-019.md): Validate browser-compatible SCRAM-SHA-512 through a public mechanism API — crypto; todo.
|
||||
- [S00-019](../tasks/S00-019.md): Validate browser-compatible SCRAM-SHA-512 through a public mechanism API — crypto; review.
|
||||
- [S00-010](../tasks/S00-010.md): Gate: Профиль и проверка стека — qa; todo.
|
||||
|
||||
## Граница этапа
|
||||
|
||||
@@ -16,7 +16,7 @@ S00-015,S00,ci-cd,qa,done,auto-ci-cd-84e209103f24,"S00-001, S00-011, S00-013, S0
|
||||
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,done,auto-protocol-89fe6563092c,"S00-001, S00-002, S00-017",Validate bounded standalone XML ingress for the XMPP candidate
|
||||
S00-019,S00,crypto,security,todo,,"S00-001, S00-002, S00-017, S00-018",Validate browser-compatible SCRAM-SHA-512 through a public mechanism API
|
||||
S00-019,S00,crypto,security,review,auto-crypto-c68801170fa4,"S00-001, S00-002, S00-017, S00-018",Validate browser-compatible SCRAM-SHA-512 through a public mechanism API
|
||||
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
|
||||
|
||||
|
@@ -0,0 +1,4 @@
|
||||
Validate browser SCRAM-SHA-512 through public mechanism composition
|
||||
|
||||
Changes: Add an isolated pinned mechanism wrapper using Stanza's public constructor and factory APIs, preserve the SCRAM-SHA-512 wire name and fail-closed final guard, and document the proposed ADR, browser bundle evidence, independent Scramp vectors, notices and limits. Leave parent selection, dependency experiments, specs and shared contracts unchanged.
|
||||
Validation: Fresh-cache npm ci and Vite browser build pass; 87 focused tests pass, including RFC HMAC vectors, original defect reproduction, independent full SHA-512 exchanges and rejected invalid finals/order/reset paths. Hashed Scramp oracle fixture comparison passes. Dependency check verifies 49 locked entries, 25 installed pins/licenses and 340 unmodified Stanza files. make check passes 72 tooling tests. Native browsers, WSS/live server interoperability and production readiness are untested; independent security review remains pending.
|
||||
@@ -0,0 +1,157 @@
|
||||
# S00-019 executor evidence
|
||||
|
||||
Agent: auto-crypto-c68801170fa4 (crypto), 2026-10-15.
|
||||
Result: isolated SCRAM-SHA-512 experiment and proposed ADR; security review pending.
|
||||
|
||||
## Input/output and scope
|
||||
|
||||
Read master/startup/common/crypto/protocol/workflow/testing/Git instructions;
|
||||
assigned resume/show/context and required blocker; accepted scope/stack/SASL/
|
||||
parser ADRs, direct prerequisite evidence/reviews/handoffs, original S00-003
|
||||
blocked evidence/handoff and exact ACC-004/NET-003/NET-004. No earlier S00-019
|
||||
handoff existed. No new shared S01 contract is needed for this isolated experiment.
|
||||
|
||||
Only source changes: experiments/0002-xmpp-library/sha512-validation/** and
|
||||
docs/decisions/0002-xmpp-sha512-validation.md. Evidence/handoff/commit message
|
||||
are workflow metadata. Initial Git status showed only coordinator state claim.
|
||||
Original candidate, accepted S00-017/018 source, specs, planning definitions,
|
||||
shared interfaces and application modules unchanged. No Git mutations, other
|
||||
task selection, agent spawn, secrets/real accounts or TLS/CORS bypass.
|
||||
|
||||
Public Mechanism wrapper composes exported SCRAM constructor with SHA512 alias;
|
||||
wire name stays SCRAM-SHA-512. Factory.disable/register and Client.use/
|
||||
registerFeature are public shipped APIs. No library private field/method is
|
||||
accessed/mutated; no vendor/prototype/crypto import patch. All primitives,
|
||||
SCRAM derivation, proof and verifier stay in existing unmodified Stanza. This
|
||||
is pinned version-specific alias composition, not upstream endorsement of a
|
||||
nonstandard wire name or future API stability. Reviewer must assess it explicitly.
|
||||
|
||||
Guard is an isolated copy of accepted S00-017 narrowed to SHA-512/88-byte-base64
|
||||
verifier text length and new registration. Actual processSuccess/finalize both
|
||||
flags precede authenticated state/success/restart. No cache persistence/emission;
|
||||
no invented channel binding/plaintext fallback. Non-PLUS-only fixture profile.
|
||||
|
||||
## Actual commands and exit codes
|
||||
|
||||
All npm/Node commands use
|
||||
PATH=/private/tmp/luma-stack-runtime/node-v24.21.0-darwin-arm64/bin:$PATH.
|
||||
Reused accepted runtime; no new runtime download/checksum verification claimed.
|
||||
Source inspections/rg/web reads supplied exact declarations and primary docs.
|
||||
|
||||
- python3 scripts/tasks.py resume --agent auto-crypto-c68801170fa4; show S00-019;
|
||||
packet S00-019 --agent auto-crypto-c68801170fa4: each exit 0. Assignment,
|
||||
dependencies done, scope and exact requirements confirmed; no repeat claim.
|
||||
- npm install --prefix experiments/0002-xmpp-library/sha512-validation
|
||||
--ignore-scripts --no-audit --no-fund --cache /private/tmp/luma-sha512-install
|
||||
--fetch-retries=0 --fetch-timeout=15000: exit 0, initially 24 packages;
|
||||
exact events 3.3.0 added to manifest and same command exit 0, one added.
|
||||
- npm view events@3.3.0 version license engines repository --json
|
||||
--cache /private/tmp/luma-sha512-install --strict-ssl=true: exit 0, MIT,
|
||||
Node >=0.8.x; shipped README/public API/browser constraints inspected before use.
|
||||
- npm run build --prefix experiments/0002-xmpp-library/sha512-validation:
|
||||
corrected build exit 0, 166 modules, self-contained ESM 637.01 kB / gzip
|
||||
128.84 kB; module graph 8.04 kB. Browser HMAC/SHA512 resolved, no Node backend,
|
||||
builtin or browser external. Output ignored; repeatable via locked build.
|
||||
- python3 -m venv /private/tmp/luma-sha512-oracle: exit 0. Its Python is 3.9.6.
|
||||
- /private/tmp/luma-sha512-oracle/bin/python -m pip install --require-hashes
|
||||
--only-binary=:all: --no-cache-dir -r
|
||||
experiments/0002-xmpp-library/sha512-validation/oracle-requirements.txt:
|
||||
exit 0, exact Scramp 1.4.6/asn1crypto 1.5.1 wheels. No scripts/system install.
|
||||
- /private/tmp/luma-sha512-oracle/bin/python
|
||||
experiments/0002-xmpp-library/sha512-validation/oracle.py --write: exit 0,
|
||||
three independent client/server verified fixed SHA-512 transcripts generated.
|
||||
Same oracle.py without --write: exit 0, fixed fixture comparison passed.
|
||||
- Python urllib canonical RFC 4231 .txt retrieval/vector extraction: exit 0;
|
||||
all seven HMAC SHA512 expected outputs copied from RFC, not generated by target.
|
||||
- npm test --prefix experiments/0002-xmpp-library/sha512-validation:
|
||||
exit 0 after harness correction, 87 tests passed, zero fail/skip/cancel.
|
||||
- npm ci --prefix experiments/0002-xmpp-library/sha512-validation
|
||||
--ignore-scripts --no-audit --no-fund
|
||||
--cache /private/tmp/luma-sha512-clean-c68801170fa4
|
||||
--fetch-retries=0 --fetch-timeout=15000: exit 0, new empty cache, 25 packages.
|
||||
Strict HTTPS/engine settings retained; lifecycle scripts disabled.
|
||||
- npm test after clean install, output /private/tmp/luma-sha512-final-tests.log:
|
||||
exit 0, rebuilt and 87 passed, duration 3049.997417ms. Added exact original
|
||||
broken-digest assertion afterward, reran npm test with output
|
||||
/private/tmp/luma-sha512-submit-tests.log: exit 0, 87 passed. No implementation
|
||||
changes after clean install; final source/test suite now fixed.
|
||||
- python3 experiments/0002-xmpp-library/sha512-validation/check-dependencies.py:
|
||||
exit 0, 49 locked entries, 25 installed exact pins/licenses, all 340 Stanza
|
||||
files byte-match original candidate, browser graph verified. Repeated after
|
||||
final test assertion; exit 0 and artifact/source hashes unchanged.
|
||||
- Corrected npm notice collector and independent Python oracle notice collector:
|
||||
exit 0; full installed texts including Stanza NOTICE ancestry and rolldown
|
||||
third-party licenses preserved. Scramp MIT-0 and asn1crypto MIT preserved.
|
||||
- make check > /private/tmp/luma-S00-019-check.log 2>&1: exit 0,
|
||||
284 tasks / 22 stages / 221 requirements, 72 tooling tests, 19.252s.
|
||||
Foundation integrity only; does not prove messenger readiness.
|
||||
- git diff --check; git status --short --untracked-files=all;
|
||||
git rev-parse --show-toplevel: exit 0, project is independent Git root,
|
||||
scoped source plus allowed state metadata. Authored untracked files separately
|
||||
checked for final newline/trailing whitespace before submit.
|
||||
|
||||
Exploratory/development failures are not passed checks. Initial guessed
|
||||
SCRAM.js/browser/Hmac.js paths were absent (cat exit 1), located actual index.js
|
||||
and browser/crypto files. rg /private/tmp hit one unrelated permission-denied
|
||||
socket directory, found accepted runtime; no required artifact inaccessible.
|
||||
First build exit 1: evidence writer attempted nonexistent dist/modules.json;
|
||||
changed to public bundler emitFile. It also exposed unresolved events builtin;
|
||||
resolved with pinned existing browser polyfill, not a crypto backend patch.
|
||||
Initial integration run exit 1: 51/53 passed, two stock baseline cases lacked
|
||||
SHA512 factory registration (stock Client doesn't register it by default);
|
||||
corrected fixtures to register original SCRAM through public API, then reproduced
|
||||
premature acceptance. Initial npm license collector asserted missing optional
|
||||
native binding license, inner Python failed but following oracle collector made
|
||||
combined shell exit 0; corrected collector preserves owning rolldown notices.
|
||||
Some primary registry/GitHub pages failed web retrieval; normal HTTPS registry
|
||||
metadata/local pinned source supplied exact facts. No unresolved blocker.
|
||||
|
||||
## Demonstrated checks and backend separation
|
||||
|
||||
32 backend checks plus 55 feature-dispatcher cases = 87. Seven RFC 4231 cases
|
||||
including short/25-byte/131-byte keys and truncation match sha512/SHA512. Fixed
|
||||
SHA512 abc matches four hash spellings. Additional 0/1/63/64/65/127/128/129/131/256
|
||||
key lengths match independent Node crypto. Exact original sha-512 broken digest
|
||||
and wrong stock browser SCRAM proof reproduce. Selected mechanism matches three
|
||||
Scramp fixed full exchanges including long and Unicode synthetic passwords.
|
||||
Scramp server verifies proof and Scramp client verifies server-final independently.
|
||||
|
||||
All integration imports are from the Vite browser artifact. Backend name and
|
||||
module graph confirm browser code; artifact has no external runtime imports or
|
||||
require. Node native crypto is test-only independent primitive oracle. Browser
|
||||
RNG path calls host getRandomValues. XML registry/parser and feature dispatcher
|
||||
are real. Only send/transport/disconnect effects are explicit fixtures. Public
|
||||
verification-method tracing calls real delegate methods and records order.
|
||||
|
||||
Missing/forged/premature/error/conflicting/duplicate/bad-base64/oversized finals,
|
||||
invalid first/nonce/iteration, duplicate challenges, pending sends/credentials,
|
||||
reset, send errors and replay fail without authentication/restart. Correct wire
|
||||
name and mixed-offer SHA512 selection pass. PLUS/plaintext/empty-only offers
|
||||
fail; synthetic tlsUnique fails. Silence never authenticates; reset cancels it.
|
||||
No derived cache is persisted/emitted.
|
||||
|
||||
## SHA256 artifacts
|
||||
|
||||
- package-lock.json: e0e129e46d3f8f86f39895a9c5fb73de761eae59108122d3684a35ab60bce399
|
||||
- sha512-mechanism.mjs: f8afc29ff467c947942495eca59ec9078e32f00448b7838da39e819b8360aaeb
|
||||
- validated-sasl.mjs: 7d2ba7c4e7033e844a6604ec899604cb9542ab57a55ea892c1e2ac474885c7d9
|
||||
- test/scramp-vectors.json: fbf8c17b3e45c0a9a88bf6f01ba63ff91b6e51a22097af6ce2f6e3de898b8321
|
||||
- dist/browser.mjs: 0b4fb1ef7684d7eab5cb1cd2c6078cdb84bff55f5cb3986e1e79a1d7418cf72a
|
||||
- dist/modules.json: c07df6bb81f9adafe4b3d5df71454f282bd115f369005b91f46ddb0c003119e6
|
||||
|
||||
## Limits and next input
|
||||
|
||||
Browser-built backend executes in Node with host native web APIs. No actual
|
||||
Chrome/Firefox/Safari run, WSS/TLS/server/live independent client, parent parser
|
||||
integration, complete fallback policy, worker/performance, constant-time,
|
||||
exhaustive SASLprep, advisory/provenance or release readiness result claimed.
|
||||
No new primitive audit: existing library code is vetted here by source/API and
|
||||
known/interoperability vectors, not certified audited. String verifier equality,
|
||||
synchronous derivation, success-only final layout, no extensions, fixture byte/
|
||||
iteration ceilings and no timeout remain explicit limits. See README/ADR.
|
||||
|
||||
Independent security reviewer must inspect actual diff/message, public alias
|
||||
composition, graph/pins/notices, rerun README clean install/build/tests, oracle
|
||||
fixture comparison and vendor checks, assess limits, then accept/reject ADR via
|
||||
separate workflow. This executor submits for review and stops; coordinator alone
|
||||
commits/launches fresh review or parent continuation. No done/acceptance claimed.
|
||||
@@ -0,0 +1,36 @@
|
||||
# S00-019 handoff for independent security review
|
||||
|
||||
Executor: auto-crypto-c68801170fa4 (crypto), 2026-10-15.
|
||||
Outcome: isolated pinned browser-compatible SCRAM-SHA-512 experiment and proposed
|
||||
docs/decisions/0002-xmpp-sha512-validation.md. No blocker; acceptance pending.
|
||||
|
||||
Public Mechanism composition uses exported SCRAM('SCRAM-SHA512') constructor to
|
||||
select existing browser HMAC alias while wrapper wire name stays SCRAM-SHA-512.
|
||||
Factory.disable/register and fresh Client.use/registerFeature only; no library
|
||||
private-field/vendor/primitive patch. S00-017 guard copied/narrowed inside scope;
|
||||
accepted parent/dependency sources remain unchanged.
|
||||
|
||||
Clean-cache npm ci and actual browser bundle/build + 87 tests passed. Seven RFC
|
||||
HMAC cases and key-length boundaries pass, exact old hyphenated HMAC/full proof
|
||||
defects reproduce, three fixed independent Scramp client/server SHA512 exchanges
|
||||
match, final/order/error/reset/replay cases reject before auth/restart. 49 locked
|
||||
entries/25 installed pins/notices and 340 unchanged Stanza files verified.
|
||||
make check: exit 0, 72 tests. Detailed commands/hashes/development failures:
|
||||
state/evidence/S00-019-auto-crypto-c68801170fa4.md.
|
||||
|
||||
Reviewer: inspect executor diff/message and public constructor alias composition
|
||||
explicitly. Follow experiment README with accepted Node 24.21.0/npm 11.19.0 and
|
||||
new cache, run npm test and check-dependencies.py; independently recreate hashed
|
||||
Python oracle environment and run oracle.py (default compares fixed fixtures).
|
||||
Parent installed pinned candidate is read-only vendor comparison input. Review
|
||||
licenses including Stanza browser ancestry, build binding owner notices and
|
||||
separate MIT-0/MIT oracle, actual feature dispatcher and both verifier flags.
|
||||
ADR must remain proposed until your separate acceptance decision.
|
||||
|
||||
Limits: browser bundle executes in Node, no native-browser/WSS/live-server/client
|
||||
run or parent parser integration; no full fallback policy/production selection.
|
||||
No invented binding/plaintext downgrade. Synchronous derivation, string equality
|
||||
verifier, no timeout, success-only final, no extensions, local bounds and limited
|
||||
Unicode vector remain. No audit/provenance/release clearance. Coordinator owns
|
||||
commit and next fresh process; executor stops after submit, does not continue
|
||||
S00-003 or mark done.
|
||||
+31
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"revision": 48,
|
||||
"revision": 50,
|
||||
"tasks": {
|
||||
"S00-001": {
|
||||
"status": "done",
|
||||
@@ -1249,10 +1249,14 @@
|
||||
"completed_at": "2026-10-14T19:35:55+00:00"
|
||||
},
|
||||
"S00-019": {
|
||||
"status": "todo",
|
||||
"owner": null,
|
||||
"status": "review",
|
||||
"owner": "auto-crypto-c68801170fa4",
|
||||
"external_wait": false,
|
||||
"reason": null
|
||||
"reason": null,
|
||||
"started_at": "2026-10-14T19:38:43+00:00",
|
||||
"updated_at": "2026-10-14T19:48:39+00:00",
|
||||
"evidence": "state/evidence/S00-019-auto-crypto-c68801170fa4.md",
|
||||
"handoff": "state/handoffs/S00-019.md"
|
||||
}
|
||||
},
|
||||
"events": [
|
||||
@@ -1878,6 +1882,29 @@
|
||||
"from": "in_progress",
|
||||
"to": "blocked",
|
||||
"reason": "Stanza 12.22.1 browser HMAC sha-512 fails RFC 4231 Test Case 1: wrong 64-byte block selection. SCRAM-SHA-512 derives that hyphenated algorithm; private algorithm mutation/vendor patches are unsupported. Parent cannot attest browser strongest-mechanism compatibility from Node-only success."
|
||||
},
|
||||
{
|
||||
"revision": 49,
|
||||
"at": "2026-10-14T19:38:43+00:00",
|
||||
"action": "claim",
|
||||
"task": "S00-019",
|
||||
"agent": "auto-crypto-c68801170fa4",
|
||||
"role": "crypto",
|
||||
"from": "todo",
|
||||
"to": "in_progress",
|
||||
"reason": null
|
||||
},
|
||||
{
|
||||
"revision": 50,
|
||||
"at": "2026-10-14T19:48:39+00:00",
|
||||
"action": "submit",
|
||||
"task": "S00-019",
|
||||
"agent": "auto-crypto-c68801170fa4",
|
||||
"role": null,
|
||||
"from": "in_progress",
|
||||
"to": "review",
|
||||
"reason": null,
|
||||
"evidence": "state/evidence/S00-019-auto-crypto-c68801170fa4.md"
|
||||
}
|
||||
],
|
||||
"retired_agents": [
|
||||
|
||||
Reference in new issue
Block a user