9189870090c5
bump the pairing invitation prefix to peach-pair-v2 with the pinned static now mandatory (PB-26-103, v5.0.47)
- time
- 2026-09-02 06:30 UTC · 1 month ago
- machine
- koh2
- agent
- hy4-preview
- session
- assessment remediation corrections
- parent
- 056807d6a42e
↓ peach-browser-91898700.tar.gzatlas at this save
files changed8 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/relay-adoption-bg.test.ts+2 −2src/popup/views/AdoptVaultView.test.tsx+2 −2src/popup/views/PairDevice.test.tsx+2 −2src/shared/pairing-invitation.test.ts+2 −2src/shared/pairing-invitation.ts+8 −1manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "5.0.46",4+ "version": "5.0.47",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -2,7 +2,7 @@22 "manifest_version": 3,33 "minimum_chrome_version": "116",44 "name": "Peach Password Manager",5- "version": "5.0.46",5+ "version": "5.0.47",66 "description": "Zero-knowledge password manager",77 "action": {88 "default_popup": "src/popup/index.html",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "5.0.46",3+ "version": "5.0.47",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/background/relay-adoption-bg.test.ts+2 −2
@@ -258,7 +258,7 @@258258 void resolveSourceSasConfirmation; // imported for the post-approval call below259259 260260 const started = await startRelayAdoptionSource();261- expect(started.invitation).toMatch(/^peach-pair-v1:/);261+ expect(started.invitation).toMatch(/^peach-pair-v2:/);262262 await waitForPush("ADOPTION_SOURCE_SAS");263263 // The adoption must NOT be complete yet — the popup's SAS decision is the264264 // prerequisite for forwarding the root key.@@ -535,7 +535,7 @@535535 it("opens a relay socket and returns an empty invitation", async () => {536536 const { startRelayAdoptionReceiver } = await import("./relay-adoption-bg");537537 const started = await startRelayAdoptionReceiver("receiver-password");538- expect(started.invitation).toMatch(/^peach-pair-v1:/);538+ expect(started.invitation).toMatch(/^peach-pair-v2:/);539539 // The QR carries vault:"empty" so the phone knows to act as source.540540 const decoded = decodeInvitation(started.invitation);541541 expect(decoded?.vault).toBe("empty");src/popup/views/AdoptVaultView.test.tsx+2 −2
@@ -196,7 +196,7 @@196196 callback: (response: unknown) => void,197197 ) {198198 if (message.type === "START_ADOPTION_RECEIVER") {199- callback({ ok: true, data: { invitation: "peach-pair-v1:test" } });199+ callback({ ok: true, data: { invitation: "peach-pair-v2:test" } });200200 } else {201201 callback({ ok: true });202202 }@@ -259,7 +259,7 @@259259 callback: (response: unknown) => void,260260 ) {261261 if (message.type === "START_ADOPTION_RECEIVER") {262- callback({ ok: true, data: { invitation: "peach-pair-v1:test" } });262+ callback({ ok: true, data: { invitation: "peach-pair-v2:test" } });263263 } else {264264 callback({ ok: true });265265 }src/popup/views/PairDevice.test.tsx+2 −2
@@ -16,7 +16,7 @@1616 vi.mock("../../shared/bridge", () => ({ sendMessage: mockSendMessage }));1717 1818 const SOURCE_INVITATION =19- "peach-pair-v1:eyJyZWxheSI6IndzczovL3JlbGF5LnBlYWNocGFzc3dvcmRzLmNvbSIsInRva2VuIjoiQUFFQ0F3UUZCZ2NJQ1FvTERBME9EdyJ9";19+ "peach-pair-v2:eyJyZWxheSI6IndzczovL3JlbGF5LnBlYWNocGFzc3dvcmRzLmNvbSIsInRva2VuIjoiQUFFQ0F3UUZCZ2NJQ1FvTERBME9EdyJ9";2020 2121 import { PairDeviceView } from "./PairDevice";2222 import { renderView } from "./view-test-utils";@@ -87,7 +87,7 @@8787 expect(container.querySelector("svg.peach-pair-qr")).toBeTruthy();8888 expect(container.querySelector("#pairing-invitation-manual")).toBeNull();8989 expect(container.textContent).not.toContain("Show a code instead");90- expect(container.textContent).not.toContain("peach-pair-v1:");90+ expect(container.textContent).not.toContain("peach-pair-v2:");9191 });9292 9393 // Moment 1 (docs/sync-relay-surfacing.md): present, but never a step.src/shared/pairing-invitation.test.ts+2 −2
@@ -13,11 +13,11 @@1313 // Cross-client compact vector (pinned in the changelog). Adding a top-level key1414 // would change the byte length, so this is a separate vector from the v1 one.1515 const KAT_POPULATED =16- "peach-pair-v1:eyJyZWxheSI6IndzczovL3JlbGF5LnBlYWNocGFzc3dvcmRzLmNvbSIsInRva2VuIjoiQUFFQ0F3UUZCZ2NJQ1FvTERBME9EdyIsInZhdWx0IjoicG9wdWxhdGVkIn0";16+ "peach-pair-v2:eyJyZWxheSI6IndzczovL3JlbGF5LnBlYWNocGFzc3dvcmRzLmNvbSIsInRva2VuIjoiQUFFQ0F3UUZCZ2NJQ1FvTERBME9EdyIsInZhdWx0IjoicG9wdWxhdGVkIn0";1717 // A pre-vaultStatus invitation as it would have circulated before the field1818 // existed. Older phones parse this and fall back to "populated".1919 const KAT_LEGACY =20- "peach-pair-v1:eyJyZWxheSI6IndzczovL3JlbGF5LnBlYWNocGFzc3dvcmRzLmNvbSIsInRva2VuIjoiQUFFQ0F3UUZCZ2NJQ1FvTERBME9EdyJ9";20+ "peach-pair-v2:eyJyZWxheSI6IndzczovL3JlbGF5LnBlYWNocGFzc3dvcmRzLmNvbSIsInRva2VuIjoiQUFFQ0F3UUZCZ2NJQ1FvTERBME9EdyJ9";2121 2222 describe("relay pairing invitation", () => {2323 it("pins the cross-client compact JSON/base64url vector", () => {src/shared/pairing-invitation.ts+8 −1
@@ -4,7 +4,7 @@44 normalizeRelayUrl,55 } from "./rendezvous";66 7-export const PAIRING_INVITATION_PREFIX = "peach-pair-v1:";7+export const PAIRING_INVITATION_PREFIX = "peach-pair-v2:";88 99 export type VaultStatus = "empty" | "populated";1010 @@ -29,6 +29,13 @@2929 * QR/manual first-pair payload. Reachability plus a credential-free relay URL,3030 * a fresh 128-bit bearer token, and the issuer's vault state — no identity or3131 * vault material is present before Noise and the two-person SAS gate.32+ *33+ * PB-26-103: `static` pins the issuer's Noise static public key so the scanning34+ * device can authenticate the cleartext PEACH_HELLO_ACK instead of trusting it.35+ * Both adoption directions on Android now refuse an invitation without it,36+ * which is a behaviour change rather than a format change — hence the v237+ * prefix, so a mismatched pair fails at the scan instead of mid-handshake.38+ * There is no interop with v1; both clients move together.3239 */3340 export function createPairingInvitation(3441 relayUrl: string,