2a773119080b
fix: LV-8 replace unmaintained zxcvbn with @zxcvbn-ts/core 3.0.4 (+language-common 3.0.4, language-en 3.0.2 — no 3.0.4 published); Lock.tsx lazy loader with dictionaries
- time
- 2026-06-10 02:53 UTC · 4 months ago
- machine
- koh2
- agent
- claude-opus-4-8
- session
- audit fix integration
- parent
- eb5ade2fb949
↓ peach-browser-2a773119.tar.gzatlas at this save
files changed4 files
bun.lock+11 −6package.json+3 −2src/popup/views/Lock.tsx+14 −3src/popup/views/zxcvbn-strength.test.ts+33 −0bun.lock+11 −6
@@ -13,6 +13,9 @@1313 "@stablelib/sha256": "^2.0.1",1414 "@stablelib/x25519": "^2.0.1",1515 "@subspace/reed-solomon-erasure.wasm": "^0.2.5",16+ "@zxcvbn-ts/core": "3.0.4",17+ "@zxcvbn-ts/language-common": "3.0.4",18+ "@zxcvbn-ts/language-en": "3.0.2",1619 "@zxing/library": "^0.23.0",1720 "bwip-js": "^4.10.1",1821 "hash-wasm": "^4.12.0",@@ -22,7 +25,6 @@2225 "pdf-lib": "^1.17.1",2326 "pdfjs-dist": "^5.7.284",2427 "tldts": "^7.0.28",25- "zxcvbn": "^4.4.2",2628 },2729 "devDependencies": {2830 "@crxjs/vite-plugin": "^2.4.0",@@ -31,7 +33,6 @@3133 "@types/node": "^22.0.0",3234 "@types/pako": "^2.0.4",3335 "@types/papaparse": "^5.5.2",34- "@types/zxcvbn": "^4.4.5",3536 "@vitest/ui": "^4.1.4",3637 "chrome-webstore-upload-cli": "^4.0.0",3738 "jsdom": "^29.0.2",@@ -377,8 +378,6 @@377378 378379 "@types/papaparse": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA=="],379380 380- "@types/zxcvbn": ["@types/[email protected]", "", {}, "sha512-FZJgC5Bxuqg7Rhsm/bx6gAruHHhDQ55r+s0JhDh8CQ16fD7NsJJ+p8YMMQDhSQoIrSmjpqqYWA96oQVMNkjRyA=="],381-382381 "@vitest/expect": ["@vitest/[email protected]", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.4", "@vitest/utils": "4.1.4", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww=="],383382 384383 "@vitest/mocker": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/spy": "4.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw"] }, "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg=="],@@ -397,6 +396,12 @@397396 398397 "@webcomponents/custom-elements": ["@webcomponents/[email protected]", "", {}, "sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww=="],399398 399+ "@zxcvbn-ts/core": ["@zxcvbn-ts/[email protected]", "", { "dependencies": { "fastest-levenshtein": "1.0.16" } }, "sha512-aQeiT0F09FuJaAqNrxynlAwZ2mW/1MdXakKWNmGM1Qp/VaY6CnB/GfnMS2T8gB2231Esp1/maCWd8vTG4OuShw=="],400+401+ "@zxcvbn-ts/language-common": ["@zxcvbn-ts/[email protected]", "", {}, "sha512-viSNNnRYtc7ULXzxrQIVUNwHAPSXRtoIwy/Tq4XQQdIknBzw4vz36lQLF6mvhMlTIlpjoN/Z1GFu/fwiAlUSsw=="],402+403+ "@zxcvbn-ts/language-en": ["@zxcvbn-ts/[email protected]", "", {}, "sha512-Zp+zL+I6Un2Bj0tRXNs6VUBq3Djt+hwTwUz4dkt2qgsQz47U0/XthZ4ULrT/RxjwJRl5LwiaKOOZeOtmixHnjg=="],404+400405 "@zxing/library": ["@zxing/[email protected]", "", { "dependencies": { "ts-custom-error": "^3.3.1" }, "optionalDependencies": { "@zxing/text-encoding": "~0.9.0" } }, "sha512-6fkkoFwP8CHxl6ugnPsj74PLJgX2iRv5zczGAyt5OBzQgxFhuhF0NCEc4t4OvSr8xAv2MRLlI0Iu9ZGDZQ2urA=="],401406 402407 "@zxing/text-encoding": ["@zxing/[email protected]", "", {}, "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA=="],@@ -621,6 +626,8 @@621626 622627 "fast-uri": ["[email protected]", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],623628 629+ "fastest-levenshtein": ["[email protected]", "", {}, "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="],630+624631 "fastq": ["[email protected]", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],625632 626633 "fdir": ["[email protected]", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],@@ -1164,8 +1171,6 @@11641171 "yocto-queue": ["[email protected]", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],11651172 11661173 "zip-dir": ["[email protected]", "", { "dependencies": { "async": "^3.2.0", "jszip": "^3.2.2" } }, "sha512-uhlsJZWz26FLYXOD6WVuq+fIcZ3aBPGo/cFdiLlv3KNwpa52IF3ISV8fLhQLiqVu5No3VhlqlgthN6gehil1Dg=="],1167-1168- "zxcvbn": ["[email protected]", "", {}, "sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ=="],11691174 11701175 "@babel/core/convert-source-map": ["[email protected]", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],11711176 package.json+3 −2
@@ -45,7 +45,9 @@4545 "pdf-lib": "^1.17.1",4646 "pdfjs-dist": "^5.7.284",4747 "tldts": "^7.0.28",48- "zxcvbn": "^4.4.2"48+ "@zxcvbn-ts/core": "3.0.4",49+ "@zxcvbn-ts/language-common": "3.0.4",50+ "@zxcvbn-ts/language-en": "3.0.2"4951 },5052 "devDependencies": {5153 "@crxjs/vite-plugin": "^2.4.0",@@ -54,7 +56,6 @@5456 "@types/node": "^22.0.0",5557 "@types/pako": "^2.0.4",5658 "@types/papaparse": "^5.5.2",57- "@types/zxcvbn": "^4.4.5",5859 "@vitest/ui": "^4.1.4",5960 "chrome-webstore-upload-cli": "^4.0.0",6061 "jsdom": "^29.0.2",src/popup/views/Lock.tsx+14 −3
@@ -22,12 +22,23 @@2222 '</svg>'2323 )}`;2424 25-// Lazy-load zxcvbn — it's heavy (~800 kB) and only needed during vault creation25+// Lazy-load @zxcvbn-ts — heavy (dictionaries ~700 kB) and only needed during26+// vault creation. Replaces the unmaintained [email protected] (LV-8); result.score27+// keeps the same 0-4 scale so downstream UI is unchanged.2628 let _zxcvbn: ((password: string) => { score: number }) | null = null;2729 async function getZxcvbn() {2830 if (_zxcvbn) return _zxcvbn;29- const mod = await import("zxcvbn");30- _zxcvbn = mod.default;31+ const [{ zxcvbn, zxcvbnOptions }, common, en] = await Promise.all([32+ import("@zxcvbn-ts/core"),33+ import("@zxcvbn-ts/language-common"),34+ import("@zxcvbn-ts/language-en"),35+ ]);36+ zxcvbnOptions.setOptions({37+ translations: en.translations,38+ graphs: common.adjacencyGraphs,39+ dictionary: { ...common.dictionary, ...en.dictionary },40+ });41+ _zxcvbn = (password: string) => zxcvbn(password);3142 return _zxcvbn;3243 }3344 src/popup/views/zxcvbn-strength.test.ts+33 −0
@@ -0,0 +1,33 @@1+// LV-8: the @zxcvbn-ts replacement must score the master-password strength2+// meter on the same 0-4 scale as the old zxcvbn, with dictionaries loaded3+// (language-common is what keeps weak passwords from scoring optimistically).4+import { beforeAll, describe, expect, it } from "vitest";5+import { zxcvbn, zxcvbnOptions } from "@zxcvbn-ts/core";6+import * as common from "@zxcvbn-ts/language-common";7+import * as en from "@zxcvbn-ts/language-en";8+9+beforeAll(() => {10+ // Mirror the loader in Lock.tsx exactly.11+ zxcvbnOptions.setOptions({12+ translations: en.translations,13+ graphs: common.adjacencyGraphs,14+ dictionary: { ...common.dictionary, ...en.dictionary },15+ });16+});17+18+describe("LV-8 password strength scoring", () => {19+ it("scores a common weak password low (<= 1)", () => {20+ expect(zxcvbn("password123").score).toBeLessThanOrEqual(1);21+ });22+23+ it("scores a long random passphrase at the top of the scale (4)", () => {24+ expect(zxcvbn("9xQ!vZ2pLm74Tr#Ke8Wd5Bn").score).toBe(4);25+ });26+27+ it("keeps the 0-4 integer scale", () => {28+ const s = zxcvbn("correct horse battery staple").score;29+ expect(Number.isInteger(s)).toBe(true);30+ expect(s).toBeGreaterThanOrEqual(0);31+ expect(s).toBeLessThanOrEqual(4);32+ });33+});