Replace the v1/v2 Security Envelope With Per-Session v3/v4 Protocols - #156
Merged
Merged
Conversation
…pen, and password stretching
v1 keyed every packet from a password anyone can compute from the clock and v2 paid the same per-packet PBKDF2 twice, so concurrent channels shared one machine-wide derivation budget and a receiver whose queue aged past the password window discarded frames without reporting them. v3 and v4 agree AES-GCM-256 keys once per session from an ephemeral P-256 agreement (v4 mixes in a PBKDF2 stretch of the pre-shared key), seal each packet under a counter nonce with the ten-byte header as authenticated data, and reject a replayed or foreign frame before decrypting it. One seal stage and one open stage replace the encrypt, serialize, and obfuscate queues; every rejected packet reaches the channel's onDrop with a protocol error code. BREAKING CHANGE: the browser/v1, browser/v2, node/v1, and node/v2 entries, the bundle/v1 and bundle/v2 builds, the password-keyed packet and data exports, and the six staged queues are removed; createChannel takes an options object with a session, ProtocolProvider receives the session, and Data no longer carries a key.
…fore security-ready
The transport owns the session: it posts the protocol's hello after ACCEPT or OPEN and again every requestRetryMs, seals a confirmation once the peer's hello keys the session, and treats the first inbound frame that authenticates as proof that the peer derived the same keys. Nothing authenticated within connectTimeoutMs ends the session silently with reason security-unconfirmed. The responder attaches its transport only when it composes ACCEPT, both sides pin the protocol the channel asked for, ACCEPT and OPEN must answer the pending process, and a plaintext action other than the six handshake actions is dropped on a secured channel.
BREAKING CHANGE: SecurityProtocolVersion is 'none' | 'v3' | 'v4'; the protocol loader, the default protocol, shared key, and refresh-rate settings, the per-channel sharedKey and refreshRate, the security-negotiated event, SecurityTransport.isReady, and the securityReady channel state are removed; security-ready carries { protocol } and fires only after the counterpart's first sealed frame authenticates; security-error codes are the protocol's kebab-case verdicts; the peer dependency is @hyperfrontend/network-protocol 2.0.0.
…hared-key rules
A selected envelope now opens on that protocol or not at all: registerSecurity returns fail-closed channel settings, so a counterpart that cannot run the pinned protocol is denied instead of silently downgraded to plaintext. v4 requires a shared key of at least 16 characters, a key given with v3 or none throws, and both checks run in the caller's frame. A refused handshake ends at once: the host destroys the mount on a deny or a counterpart's cancel (error reason handshake-cancelled) and the feature's ready() rejects, instead of both waiting out the open deadline. A packet the envelope drops reaches the shell and the feature as error { reason: 'security-error' } with the protocol's code.
BREAKING CHANGE: SecurityProtocol is 'none' | 'v3' | 'v4' across createShell, createFeature, feature.config, hf build --protocol, and the build executor schema; v1 and v2 no longer build, negotiation is fail-closed, and v4 rejects a shared key shorter than 16 characters.
…e the session envelope
…k-protocol, lib-nexus
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
View your CI Pipeline Execution ↗ for commit d5de79d
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Replaces the
v1/v2security envelope with two per-session protocols,v3andv4, across@hyperfrontend/cryptography,@hyperfrontend/network-protocol,@hyperfrontend/nexus, and@hyperfrontend/features.The old envelope ran PBKDF2 (100,000 iterations, about 17 ms) on every message on both sides because each packet carried a fresh random salt, so concurrent channels shared one machine-wide derivation budget: a host with seven open features received about nine messages a second in total and silently dropped the rest (showcase finding F-011, cleared in this PR).
v1also keyed that work from a clock-derived password anyone can compute,v2paid the derivation twice, the envelope sequence was never checked, and a secured channel still routed plaintext actions.v3andv4agree AES-GCM-256 keys once per session from an ephemeral P-256 key agreement (v4mixes in one PBKDF2 stretch of the pre-shared key at 600,000 iterations), expand them per direction with HKDF bound to the protocol and both endpoint ids, seal each packet under a counter nonce with the ten-byte frame header as authenticated data, and reject a replayed or foreign frame before decrypting it. nexus runs the hello exchange inside its secure transport, treats the counterpart's first authenticated frame as the session confirmation, closes a session nobody confirms, pins the negotiated protocol on both sides, and drops plaintext on a secured channel. features registers the envelope fail-closed, enforces thev4key rules, forwards every dropped packet as anerror, and ends a refused handshake at once.Measured on the envelope alone (Node, in-process wiring, quiet machine): seven channels at 100 Hz deliver 100% at about 663 messages/s with the process 79% idle, against 9% delivered in the finding's browser measurement; session setup costs 5.8 ms (
v3) or 192 ms (v4) once per session.Type of Change
Changes Made
createKeyAgreement(ECDH P-256, non-extractable private key, peer point validation),expandKey(HKDF-SHA256 into usage-restricted AES-GCM-256 keys),seal/open(AES-GCM with additional authenticated data),stretchPassword(PBKDF2deriveBits), and a sharedkeyStretchingConfigthatgenerateKeynow reads.v3andv4session protocols on one implementation (99-byte plaintext hello, 10-byte authenticated data-frame header, counter nonces, replay rejection, typedProtocolErrorcodes); one seal stage and one open stage with an array-backed FIFO;onDropreporting;createChannel(label, { send, receive, protocolProvider, session, onDrop? }); thebrowser|node/v1|v2entries,bundle/v1|v2builds, password-keyed packet and data exports, six staged queues, time-window helpers, and the in-band dynamic key are removed; CDN default moves tobundle/v4.requestRetryMs, sealed[nexus] security-confirmed,security-readyon the first authenticated inbound frame,security-unconfirmedclose afterconnectTimeoutMs); responder attaches its transport only when composing ACCEPT; protocol pin on both sides; ACCEPT and OPEN must answer the pending process; plaintext gate for every non-handshake action; typedSecurityErrorCode; the protocol loader, broker-wide default key settings, per-channelsharedKey/refreshRate, the never-emittedsecurity-negotiatedevent, andSecurityTransport.isReadyare removed; peer dependency on network-protocol 2.0.0.SecurityProtocol = 'none' | 'v3' | 'v4'acrosscreateShell,createFeature,feature.config,hf build --protocol, the build executor schema, generators, and the debug UI;registerSecurityreturns fail-closed settings, requires a 16-characterv4key, and rejects a key given with another protocol;security-errorforwarded aserror { reason: 'security-error', code }; the host destroys the mount ondenyor a counterpartcancel(handshake-cancelled) and the feature'sready()rejects at once.v1/v2entry pages replaced byv3/v4, security concept page, quick start, guides, article snippet, and decision-framework notes and dataset updated; root ARCHITECTURE, MANIFESTO, README, LIBRARY_COMPATIBILITY, and the build-executor README follow.Testing
v4pairs, fail-closed mismatches between host and feature, and the deny/cancel teardown on both sides.v3/v4entries, bundle globals, hello exchange, seal/open across two channels, replay and wrong-key rejection, and the shared-key gate against the built package; the features CLI check builds with--protocol v4.f011-envelope-bench.ts, not committed):v37 × 100 Hz 100% at 663/s (79% idle),v47 × 100 Hz 100% at 667/s,v47 × 1000 Hz 100% at 4,255/s with a 0.46 s drain.Checklist
npm run commitfor conventional commit messagesAI Assistance
GitHub Copilot was used to generate this PR description.
Additional Notes
v1andv2are deleted, not deprecated. An old build and a new build advertise disjoint identifiers, negotiatenone, and a fail-closed channel is denied withsecurity-unavailable; both sides of a channel move together. Migration is a mode mapping:v1(keyless) becomesv3,v2with asharedKeybecomesv4with asharedKeyof at least 16 characters (a generated key of 128 bits or more is the guarantee).v3defeats scripts that can only listen; any script that can post to a peer's window with a genuinesourcecan complete av3session as that peer, sov3does not authenticate the counterpart.v4is the control against the documented co-resident-script adversary. The handshake and the hello frames remain plaintext by design.security-readynow carries{ protocol }and fires after the counterpart's first sealed frame authenticates;security-error.codeis the protocol's kebab-case verdict;CloseReasongains'security-unconfirmed';Datano longer carrieskey; the features SDK is fail-closed by default (nexus keepsfail-openfor direct users).protocol: 'v1'and keep working on that release until they are explicitly re-vendored.