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
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/policy.ts+29 −8src/popup/components/GeneratorDrawer.tsx+1 −1
manifest.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": {

view file

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",

view file

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": {

view file

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]!);

view file

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);

view file