104b98beeb6a
password generator default 20→64; policy detection: multi-ID aria-describedby, title/placeholder/aria-label, N-M char range pattern (v2.38.2)
- time
- 2026-05-10 18:39 UTC · 5 months ago
- machine
- koh2
- parent
- 48bbe718accd
↓ peach-browser-104b98be.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/policy.ts+29 −8src/popup/components/GeneratorDrawer.tsx+1 −1manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "2.38.1",4+ "version": "2.38.2",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "2.38.1",4+ "version": "2.38.2",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "2.38.1",3+ "version": "2.38.2",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/content/policy.ts+29 −8
@@ -21,23 +21,38 @@2121 2222 export function extractNearbyText(el: HTMLInputElement | null): string {2323 if (!el) return "";24+ const parts: string[] = [];25+26+ // aria-describedby may be a space-separated list of IDs2427 const describedBy = el.getAttribute("aria-describedby");2528 if (describedBy) {26- const desc = el.ownerDocument.getElementById(describedBy);27- if (desc) return desc.textContent ?? "";29+ for (const id of describedBy.trim().split(/\s+/)) {30+ const desc = el.ownerDocument.getElementById(id);31+ if (desc?.textContent) parts.push(desc.textContent);32+ }2833 }34+35+ // Field-level attributes that sometimes carry requirements36+ if (el.title) parts.push(el.title);37+ if (el.placeholder) parts.push(el.placeholder);38+ const ariaLabel = el.getAttribute("aria-label");39+ if (ariaLabel) parts.push(ariaLabel);40+41+ const HINT_SELECTOR =42+ "p, span, li, small, label, div, .hint, .requirement, .password-rule, .password-hint, .password-rules, .field-hint, .helper-text, .input-hint";43+2944 let node: Element | null = el;3045 for (let i = 0; i < 5; i++) {3146 node = node?.parentElement ?? null;3247 if (!node) break;33- const hints = node.querySelectorAll(34- "p, span, li, small, label, div, .hint, .requirement, .password-rule, .password-hint, .password-rules, .field-hint, .helper-text, .input-hint"35- );48+ const hints = node.querySelectorAll(HINT_SELECTOR);3649 if (hints.length > 0) {37- return Array.from(hints).map((h) => h.textContent).join(" ");50+ parts.push(Array.from(hints).map((h) => h.textContent).join(" "));51+ break;3852 }3953 }40- return "";54+55+ return parts.filter(Boolean).join(" ");4156 }4257 4358 export function detectPasswordPolicy(doc: Document = document): PasswordPolicy {@@ -67,10 +82,16 @@6782 6883 const text = extractNearbyText(passwordField);6984 85+ // "8-20 characters", "8–20 characters", "8 - 20 chars" — most common real-world format86+ const dashRangeMatch = text.match(/(\d+)\s*[-–—]\s*(\d+)\s*char/i);87+ // "between 8 and 20", "between 8-20"88+ const betweenMatch = text.match(/between\D*?(\d+)\D+?(\d+)/i);7089 const minMatch = text.match(/(?:at least|minimum|min(?:imum)?|must be (?:at least )?)(?:\D*?)(\d+)/i);7190 const maxMatch = text.match(/(?:at most|maximum|max(?:imum)?|up to|cannot exceed|must not exceed|no more than|must be (?:no more than |at most )?)(?:\D*?)(\d+)/i);72- const betweenMatch = text.match(/between\D*?(\d+)\D+?(\d+)/i);7391 const exactMatch2 = text.match(/(?:exactly|must be)\D*?(\d+)\s*characters?/i);92+ // dash range takes priority as the most unambiguous format93+ if (dashRangeMatch && !policy.minLength) policy.minLength = parseInt(dashRangeMatch[1]!);94+ if (dashRangeMatch && !policy.maxLength) policy.maxLength = parseInt(dashRangeMatch[2]!);7495 if (betweenMatch && !policy.minLength) policy.minLength = parseInt(betweenMatch[1]!);7596 if (betweenMatch && !policy.maxLength) policy.maxLength = parseInt(betweenMatch[2]!);7697 if (minMatch && !policy.minLength) policy.minLength = parseInt(minMatch[1]!);src/popup/components/GeneratorDrawer.tsx+1 −1
@@ -14,7 +14,7 @@1414 1515 export function GeneratorDrawer(props: GeneratorDrawerProps) {1616 const [password, setPassword] = createSignal("");17- const [length, setLength] = createSignal(20);17+ const [length, setLength] = createSignal(64);1818 const [upper, setUpper] = createSignal(true);1919 const [lower, setLower] = createSignal(true);2020 const [numbers, setNumbers] = createSignal(true);