jhkaktus 8d488f44ff fix: edit and delete buttons now actually fire their handlers
The shared el() helper passed every attr through setAttribute(); for
event handlers like onclick a function value gets stringified into an
attribute the DOM compiles as an expression that is evaluated but never
called - so Bearbeiten/Löschen (wired through el()) were silent no-ops,
while addEventListener-wired buttons (Neuer Datensatz, Sperren) worked.

el() now assigns on*-function attrs as properties (node[k] = v), preserving
the real handler. Verified with a DOM-semantics shim: fixed path fires,
legacy setAttribute path provably compiles but never calls.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-10-11 00:43:21 +02:00
2026-10-10 21:50:03 +00:00

crushdb – verschlüsselte Datensätze zwischen Personen

Sichere Datenbank für Transaktionen zwischen zwei Personen (Person A, Person B, Timestamp-Start, Timestamp-Ende, Aktion).

Alle Datensätze werden im Browser verschlüsselt (XChaCha20-Poly1305, Schlüsselableitung per Argon2id aus der Passphrase). Der Server sieht ausschließlich Chiffretext – auch bei vollständiger Serverkompromittierung bleiben die Inhalte ohne Passphrase vertraulich.

Erster Start (UX)

  • Beim ersten Besuch (Datenbank leer): Die App zeigt "Willkommen" und fragt zweimal nach einer neuen Passphrase (mindestens 8 Zeichen). Diese schützt ab sofort alle Datensätze – es gibt keine Zurücksetzung.
  • Bei weiteren Besuchen: Die App bittet einmal um die bekannte Passphrase ("Entsperren").
  • Der Server speichert niemals die Passphrase selbst, nur verschlüsselte Datensätze.

Starten (HTTP; TLS über Reverse Proxy)

python3 -m venv venv
./venv/bin/pip install -r requirements.txt
./venv/bin/python run.py
# → http://127.0.0.1:8085

Die App lauscht auf dem lokalen HTTP-Port (default 127.0.0.1:8085, überschreibbar via HOST/PORT). Ein vertrauenswürdiger Reverse Proxy (nginx, caddy, …) beendet HTTPS und reicht intern an die App weiter. Die App selbst muss von außen nicht erreichbar sein.

Per systemd laufen lassen

Ein fertiges Unit-File liegt in deploy/crushdb.service (Reinheit: nur instance/ schreibbar, keine Capabilities, Restart on failure). Nach dem Kopieren nach /etc/systemd/system/ und daemon-reload startet systemctl enable --now crushdb die App dauerhaft.

Tests

./venv/bin/python -m pytest backend/test_integrity.py -v   # Integrität/Replay
node .e2e.mjs  # optional: End-to-End (siehe unten)

Architektur

Komponente Datei Aufgabe
Speicher backend/storage.py SQLite, speichert nur Chiffretext (uuid, ciphertext, nonce, salt, version)
API backend/app.py Flask REST API, Validierung nur von Form/Größe, Versions-Konfliktprüfung
Krypto (Client) backend/static/crypto.js Argon2id-KDF + XChaCha20-Poly1305, uuid+salt als AEAD-AAD gebunden
UI backend/static/ui.js + index.html/style.css Tabelle, Erstellen, Bearbeiten, Löschen; Passphrase bleibt im RAM
Server run.py HTTP intern hinter Reverse Proxy, DB-Init

Sicherheitsmodell

  • Passphrase → Argon2id (t=2, m=16 MiB) → 256-Bit-Sitzungsschlüssel, ausschließlich im Browser/im Arbeitsspeicher; niemals am Server.
  • Jeder Datensatz: eigene zufällige 16-Byte-Salt + 24-Byte-Nonce. uuid + salt sind als AEAD-Associated-Data gebunden – Vertauschen oder Um-Salten wird beim Entschlüsseln sicher erkannt.
  • Monotoner Versionszähler pro Datensatz: veraltete Updates/Deletes (Replay alter Stände) → 409, Duplikate → 409.
  • Klartexte werden clientseitig nie persistiert; beim Sperren/Beenden wird der Schlüssel verworfen (wipeKey).
  • HTTPS wird außen vom vertrauenswürdigen Reverse Proxy beendet (nginx/caddy/…); die App lauscht nur intern auf HTTP.

Bekannte Grenzen

  • Datenverlust (Löschen/Überschreiben) durch Angreifer bleibt möglich – Vertraulichkeit ist geschützt, Verfügbarkeit nicht.
  • Backups der SQLite-Datei enthalten ausschließlich Chiffretext.
  • Backups des Passphrasen-Verlusts = Datenverlust (by design).

Siehe spezification.txt für die Anforderungen.

S
Description
a secure database for saving transaktions betwen persons
Readme MIT
208 KiB
Languages
Python 78.3%
JavaScript 19.1%
CSS 2.6%