Sechs Review-Punkte: Speicherort, Domain-Pruefung, Rechte, Generator, Notizen, Bearbeiten

1. Passwort mehrstufiger Logins nicht mehr auf der Platte: Der Auftrag lag
   als __pendingFill in chrome.storage.local, also im Klartext auf der
   Festplatte. Die 30-Sekunden-Pruefung verhinderte nur die Verwendung,
   nicht die Speicherung - wurde der zweite Schritt nie erreicht, blieb
   das Passwort liegen. Er liegt jetzt ausschliesslich im Speicher des
   Service Workers, je Tab, und wird beim Abholen verbraucht, nach 30 s
   verworfen, beim Sperren geleert und beim Schliessen des Tabs entfernt.
   Reste frueherer Versionen raeumt onInstalled ab.

2. Domain-Warnung: fillDomainMatches akzeptierte mit
   eh.endsWith('.' + pageHost) auch die Gegenrichtung - ein Eintrag fuer
   vpn.firma.de galt auf firma.de als passend und die Warnung blieb aus.
   Diese Klausel entfaellt.

3. scripting und activeTab werden nicht mehr angefordert; beide waren
   unbenutzt (das Content-Script laeuft ueber content_scripts, der
   Tab-Zugriff ueber host_permissions). PERMISSIONS.md begruendete
   scripting mit dem nativen Value-Setter, was nichts damit zu tun hat.

4. Passwort-Generator: buf lieferte dieselben Werte fuer Zeichenwahl und
   Mischreihenfolge, wodurch die Permutation mit dem Inhalt korrelierte.
   Beides zieht jetzt getrennt ueber randomBelow(), das den obersten,
   unvollstaendigen Block verwirft (gleichverteilt statt Rest-Modulo).
   Laenge (12-48) und Sonderzeichen sind waehlbar.

5. Notizen laufen ueber copySecret und werden damit ebenfalls aus der
   Zwischenablage entfernt; sie enthalten in der Praxis oft
   Wiederherstellungscodes. copyToClipboard entfaellt.

6. urlmatch.js buendelt die drei abweichenden matchUrl-Fassungen zu einer
   Regel, geladen in Service Worker, Popup und Seiten. escAttr escapt jetzt
   auch & < > und Apostroph, traegt also in jedem Attributkontext.
   Eintraege lassen sich im Popup bearbeiten und loeschen; beim Bearbeiten
   bedeutet ein leeres Passwortfeld unveraendert, sodass das Passwort das
   Popup nicht verlaesst.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G12DRMpe4UjYDwuRU1sjy1
This commit is contained in:
Claude 2026-07-31 13:17:23 +00:00
parent 32cc4de8be
commit 65a07fbc18
No known key found for this signature in database
10 changed files with 305 additions and 107 deletions

63
extension/urlmatch.js Normal file
View file

@ -0,0 +1,63 @@
'use strict';
/*
* Gemeinsame Zuordnung Eintrag Seite.
*
* Wird in allen drei Kontexten geladen (Service Worker via importScripts,
* Popup via <script>, Seiten via content_scripts) damit Vorschlagsliste und
* Sicherheitswarnung dieselbe Regel anwenden.
*
* Regel: Ein Eintrag passt zu einer Seite, wenn deren Host dem hinterlegten Host
* entspricht oder eine Subdomain davon ist. Die Gegenrichtung gilt bewusst nicht
* ein Eintrag für `vpn.firma.de` passt nicht zu `firma.de`.
*/
var VaultUrl = (function () {
/**
* Host einer Adresse in vergleichbarer Form (klein, ohne `www.`).
* @param {string} raw Adresse mit oder ohne Schema
* @return {string} Host oder '' wenn nicht bestimmbar
*/
function host(raw) {
const s = String(raw || '').trim();
if (!s) return '';
try {
return new URL(s.includes('://') ? s : 'https://' + s).hostname.replace(/^www\./, '').toLowerCase();
} catch {
return s.replace(/^www\./, '').toLowerCase();
}
}
/** Hinterlegte Adressen eines Eintrags (mehrzeilig) als Liste. */
function list(entryUrls) {
if (Array.isArray(entryUrls)) return entryUrls.map(u => String(u || '').trim()).filter(Boolean);
return String(entryUrls || '').split('\n').map(u => u.trim()).filter(Boolean);
}
/**
* Passt einer der hinterlegten Hosts zur Seite?
* @param {string|string[]} entryUrls Adressen des Eintrags
* @param {string} pageUrl Adresse der Seite
* @return {boolean}
*/
function matches(entryUrls, pageUrl) {
const pageHost = host(pageUrl);
if (!pageHost) return false;
return list(entryUrls).some(raw => {
let eh = host(raw);
if (eh.startsWith('*.')) eh = eh.slice(2);
if (!eh) return false;
return pageHost === eh || pageHost.endsWith('.' + eh);
});
}
/** Wie `matches`, wertet einen Eintrag ohne hinterlegte Adresse aber als passend. */
function matchesOrUnset(entryUrls, pageUrl) {
if (!list(entryUrls).length) return true;
return matches(entryUrls, pageUrl);
}
return { host, list, matches, matchesOrUnset };
})();
// Im Service Worker steht `self` zur Verfügung, im Popup/Content-Script `window`.
if (typeof self !== 'undefined') self.VaultUrl = VaultUrl;