Zum Inhalt springen

Open Source und Technik des Einheiten-Erfassungsbogens

Diese Seite beschreibt, wie der digitale Einheiten-Erfassungsbogen technisch gebaut ist: woraus die Anwendung besteht, wo Eingaben liegen, wann überhaupt etwas das Gerät verlässt und wie das Datenformat aussieht. Sie richtet sich an alle, die das Werkzeug in einer Behörde oder Hilfsorganisation prüfen wollen, bevor sie es einsetzen oder empfehlen.

Abgrenzung: Rechtlich verbindlich ist die Datenschutzerklärung — dort stehen Verantwortlicher, Rechtsgrundlagen, Betroffenenrechte und der Widerspruch gegen die Reichweitenmessung. Diese Seite ist die technische Ergänzung dazu und ersetzt sie nicht. Jede Aussage hier lässt sich im Quellcode nachlesen; die Fundstelle steht jeweils dabei.

Aufbau der Anwendung

Die Anwendung ist eine Web-App aus React und TypeScript, gebaut mit Vite. Es gibt keinen Anwendungsserver und keine Datenbank im Hintergrund — der gesamte Programmablauf, einschließlich PDF-Erzeugung und QR-Codierung, läuft im Browser beziehungsweise in der App auf dem Gerät.

FassungTechnikHerkunft der Dateien
Web / installierte PWA Vite-Build, Service Worker über vite-plugin-pwa erfassungsbogen.app (GitHub Pages)
Windows, macOS, Linux Electron; dieselbe Web-App im Paket lokal aus dem Installationspaket (file://)
iOS, Android Capacitor; dieselbe Web-App im App-Paket lokal aus dem App-Paket

Der Electron-Hauptprozess lädt ausschließlich das mitgelieferte Bundle und betreibt das Fenster ohne Node-Zugriff (contextIsolation, sandbox, kein nodeIntegration); erteilt werden nur die Berechtigungen für Kamera und Zwischenablage, alle anderen Anfragen lehnt der Hauptprozess ab (electron/main.js). Externe Links öffnet er im Systembrowser statt im App-Fenster.

Wo die eingegebenen Daten liegen

Alle Angaben eines Bogens werden im lokalen Speicher (localStorage) des Browsers beziehungsweise der App gehalten. Eine IndexedDB wird nicht verwendet, Cookies werden nicht gesetzt. Gespeichert sind unter anderem:

Diese Einträge werden nicht an einen Server übertragen. Löschen lassen sie sich über „Alle Daten löschen“ in der Fußzeile der App, über das Löschen der Websitedaten im Browser oder durch Deinstallation. „Datensicherung“ in derselben Fußzeile gibt sie als Datei aus und spielt sie wieder ein.

Wann Daten das Gerät verlassen

Bogeninhalte verlassen das Gerät nur, wenn sie selbst weitergegeben werden — als PDF, als Datei, als QR-Code oder als Link. Unabhängig davon gibt es aber Verbindungen, die die Anwendung von sich aus aufbaut. Sie sind hier vollständig aufgeführt:

Nicht von der Anwendung ausgelöst, aber der Vollständigkeit halber: Das Betriebssystem sichert den Speicher der App in ein eingeschaltetes Systembackup (iCloud, Google One) mit. Auf Android bleibt allowBackup dafür bewusst gesetzt, damit ein Gerätewechsel oder ein Defekt die Arbeit nicht vernichtet. Abschalten lässt sich das nur in den Systemeinstellungen des Geräts.

1. Auslieferung der Website

Die Web-Fassung wird über GitHub Pages ausgeliefert. Beim Aufruf verarbeitet GitHub die üblichen Verbindungsdaten (unter anderem die IP-Adresse) in Server-Logs. Die installierten Fassungen (Desktop, iOS, Android) laden ihre Dateien aus dem eigenen Paket und rufen dafür nichts ab. In der Web-Fassung werden einzelne Bestandteile — etwa ein angeklickter Beispielbogen — bei Bedarf von derselben Adresse nachgeladen.

2. Reichweitenmessung (GoatCounter)

Bei jedem Kaltstart sendet die Anwendung einen einzelnen Zählimpuls an erfassungsbogen.goatcounter.com (src/app/statistik.ts). Das gilt auf allen Plattformen, also auch in der Desktop- und in der Mobil-App. Übertragen werden dabei nur drei Angaben:

Der Zählimpuls trägt bewusst weder location.search noch das Adressfragment, weil die App Bogendaten im Fragment transportiert. Es wird kein Fremdskript nachgeladen — die Zähl-URL wird selbst gebaut und per sendBeacon, ersatzweise als Bildabruf, abgesetzt. In der lokalen Entwicklung (localhost) unterbleibt der Impuls. Ohne Internetverbindung schlägt er still fehl.

Widerspruch: in der App unter „Datenschutz“ die Option Nicht mitzählen aktivieren; sie setzt den Eintrag skipgc im lokalen Speicher, und der Zählimpuls unterbleibt ab dem nächsten Start. Welche Angaben GoatCounter darüber hinaus aus der Verbindung ableitet und auf welcher Rechtsgrundlage das geschieht, steht in der Datenschutzerklärung.

3. Prüfung auf neue Versionen

Bogeninhalte werden bei keiner dieser Verbindungen übertragen.

Offline-Betrieb

Die Web-Fassung registriert einen Service Worker, der die App-Shell — JavaScript, CSS, HTML, Icons, Manifest und die eingebettete Schrift — in den Vorrat legt. Nach dem ersten Aufruf startet die Seite damit ohne Netz. Der Service Worker läuft im Modus „prompt“: eine neue Fassung wird erst nach Bestätigung aktiviert, nie automatisch mitten in der Arbeit. In der Electron- und der Capacitor-Fassung wird er bewusst nicht registriert; dort liegt die App ohnehin lokal (Konfiguration in vite.config.ts).

Datenformat und Signatur

Ein Bogen wird für die Weitergabe binär codiert (src/codec.ts): feste Feldreihenfolge, Varint-Längen, UTF-8-Strings, anschließend Deflate. Das Ergebnis trägt die Kennung EEB2, in signierter Form EEB2C. Für den Transport wird es als Text codiert — Base64url oder das platzsparendere Base41 — und steckt im QR-Code, in der Datei und, bei einem Link, hinter dem # der Adresse. Was hinter dem # steht, sendet ein Browser nicht an den Server.

Jeder weitergegebene Bogen wird mit einem Ed25519-Schlüssel des ausstellenden Geräts signiert; wird ein bereits signierter Bogen unverändert weitergereicht, hängt die App eine weitere Stufe an die Kette an. Verwendet wird @noble/ed25519 statt der Ed25519-Funktion der WebCrypto-API, weil diese in älteren WebViews und in Electron nicht überall vorhanden ist. Der private Schlüssel wird auf dem Gerät erzeugt und erscheint nie in QR-Code, Link oder Datei (src/app/geraete-schluessel.ts, src/signatur.ts).

Das Vertrauensmodell ist bewusst schlicht: Die Prüfung belegt, dass der Bogen seit der Signatur unverändert ist und von diesem Schlüssel stammt. Sie belegt nicht, welche Person oder Dienststelle hinter dem Schlüssel steht — es gibt keine Zertifikatsstelle. Eine fehlgeschlagene Prüfung blockiert das Einlesen nicht; sie erscheint als Anzeigestatus.

Content-Security-Policy

Der Build setzt eine Content-Security-Policy als <meta> in den Kopf der Seite (Plugin in vite.config.ts). Sie begrenzt Netzzugriffe auf die eigene Herkunft und genau einen fremden Host, den Zähl-Endpunkt:

default-src 'self' file:
base-uri 'none'
object-src 'none'
form-action 'none'
script-src 'self' file: 'unsafe-inline'
style-src 'self' file: 'unsafe-inline'
img-src 'self' file: data: blob: https://erfassungsbogen.goatcounter.com
font-src 'self' file: data:
connect-src 'self' file: https://erfassungsbogen.goatcounter.com

'unsafe-inline' steht dort, weil Start-Gerüst, Design-Token und die eingebettete Schrift inline im Dokument liegen. Der Vite-Entwicklungsserver bekommt die Policy nicht — er braucht Verbindungen, die sie blockieren würde. Der praktische Gewinn liegt in default-src 'self', object-src 'none', base-uri 'none' und dem auf einen Host begrenzten connect-src: Eine Ausleitung von Daten an eine andere Adresse ist damit unterbunden.

Quellcode, Lizenz und Releases

Der Quellcode ist vollständig einsehbar; wer die Aussagen dieser Seite prüfen will, findet die genannten Dateien dort unter denselben Pfaden. Fehler und Verbesserungsvorschläge nimmt das GitHub-Projekt entgegen.

Projekt und Autor

Der Einheiten-Erfassungsbogen ist ein privates, nicht gewerbliches Projekt von Johannes Rudolph, entstanden aus der ehrenamtlichen Arbeit im Technischen Hilfswerk. Es besteht keine Verbindung zu einer Behörde oder Hilfsorganisation; genannte Organisationen und ihre Bezeichnungen dienen allein der fachlichen Zuordnung der Vorlagen.

Kontakt für Fragen, Fehler und Wünsche: johannes.rudolph@thw-oldenburg.de. Anschrift und Anbieterkennzeichnung stehen im Impressum.