Skip to content

feat(v2): use a separately generated pairing secret as the handshake auth key#36

Open
o1x3 wants to merge 1 commit into
lunel-dev:mainfrom
o1x3:feat/v2-pairing-secret
Open

feat(v2): use a separately generated pairing secret as the handshake auth key#36
o1x3 wants to merge 1 commit into
lunel-dev:mainfrom
o1x3:feat/v2-pairing-secret

Conversation

@o1x3
Copy link
Copy Markdown

@o1x3 o1x3 commented May 14, 2026

Fixes #34.

The v2 handshake auth key was the session password, which the relay also holds. An active relay could swap pubkeys in client_hello / server_hello and forge matching auth tags on both legs. This PR adds a 32-byte pairing secret that the CLI generates locally and puts in the QR. The manager and proxy never see it.

What changes

CLI:

  • Generates a pairing secret per session and persists it in the saved-session config so reattach after a CLI restart still works.
  • QR payload goes from a bare code to lunel://connect?code=<code>&ps=<pairingSecret>. The URL form already parses on the app side.
  • V2SessionTransport now gets sessionSecret = pairingSecret ?? password. The password fallback covers legacy QR payloads and pre-upgrade saved sessions.

App:

  • parseConnectPayload returns { code, pairingSecret } and extracts the ps query param.
  • pairingSecretRef threads the value into V2SessionTransport.
  • PairedSession persists pairingSecret in SecureStore so resumeSession restores it.

Backward compat

  • Legacy CLIs emit bare codes. Parser returns pairingSecret: null and the app falls back to wsPassword. Existing pairings keep working.
  • Legacy saved sessions on either side missing pairingSecret also fall back to the password.

Forward compat

A new CLI paired with a pre-upgrade app will fail the handshake because the app uses the password while the CLI uses the pairing secret. The app needs to ship with this change in lockstep with the CLI. Hot-updater should make this manageable.

After the fix

The relay still terminates WSS and still gets the session password in a URL param (separate concern, see #11). It does not see the pairing secret, so it cannot forge handshake auth tags.

Tested

  • CLI tsc clean.
  • Manually traced both fresh-pairing and saved-session resume flows in the diff. Have not yet exercised reattach after both sides bounce, or reattach when the manager rotates the password mid-flight. Flagging for review.

Notes

  • URL form is lunel://connect?code=<code>&ps=<secret> and reuses the existing lunel://connect?code=... scheme already handled by parseConnectPayload. Easy to change the param name if ps clashes with anything planned.

The v2 handshake auth key was the session password, which the relay
also holds. An active relay could swap pubkeys in client_hello /
server_hello and forge matching auth tags on both legs.

This change adds a 32-byte pairing secret generated on the CLI and put
in the QR alongside the assemble code. Both peers use the pairing
secret as sessionSecret. The manager and proxy never see it.

CLI:
- Generates pairingSecret per session and persists it in the saved
  session config so reattach after CLI restart still works.
- QR payload goes from a bare code to
  lunel://connect?code=<code>&ps=<pairingSecret>. The URL form already
  parses on the app side.
- V2SessionTransport now gets sessionSecret = pairingSecret ?? password.
  The password fallback covers legacy QR payloads and pre-upgrade saved
  sessions.

App:
- parseConnectPayload returns { code, pairingSecret } and extracts the
  ps query param.
- pairingSecretRef threads the value into V2SessionTransport.
- PairedSession persists pairingSecret in SecureStore so resumeSession
  restores it.

Backward compat: legacy CLIs emit bare codes; the parser returns
pairingSecret = null and the app falls back to wsPassword. Legacy saved
sessions on either side missing pairingSecret also fall back to the
password.

Forward compat: a new CLI paired with a pre-upgrade app will fail the
handshake because the app uses the password while the CLI uses the
pairing secret. The app needs to ship with this change in lockstep with
the CLI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v2 handshake auth key is the session password, which the relay holds

1 participant