Compliance
DSGVO
Login-lose Adresseingabe — der Sonderfall
Auch ohne User-Konto ist die Adresse ein personenbezogenes Datum im Sinne der DSGVO, sobald sie mit Kontextdaten (Kataster, Leerstand, Förderhinweise) verknüpft wird — sie lässt indirekt Rückschlüsse auf den Eigentümer zu.
Pflichten vor Phase-2-Launch:
- Privacy-Notice klar sichtbar vor Eingabe
- Opt-in-Checkbox („Ich verstehe, dass die Auswertung keine rechtsverbindliche Vermessung ersetzt")
- AVV mit allen Sub-Prozessoren (Hosting, LLM, Geo-APIs)
- Klarer Speicher-/Löschzyklus (z. B. anonymisierte Aggregate, Roh-Adresse max 30 Tage)
- Rate-Limit zur Verhinderung von Mass-Scraping (10 / Tag pro IP)
Anonyme Sessions (anonymous_sessions, ADR-0026 / Issue #105)
/report ist login-los, der Anonymous→Registriert-Funnel braucht
trotzdem eine wiedererkennbare Pseudo-Session, um beim späteren Login
den bisherigen Report-Verlauf zum User zu migrieren. Stand 2026-05-21
aktiv (Issue #105 — vormals dokumentierter Folge-Schritt aus ADR-0026).
| Aspekt | Umsetzung |
|---|---|
| Cookie | ln_anon, HttpOnly, SameSite=Lax, Secure (in Prod), Path=/, Max-Age=90 Tage; gesetzt in proxy.ts beim ersten /report-Aufruf, wenn der Cookie fehlt |
| Token-Format | 32 Bytes Web-Crypto → 64 Hex-Zeichen (passt zu anonymous_sessions.cookie_token varchar(64) unique) |
| DB-Zeile | anonymous_sessions(cookie_token, ip_hash, user_agent_hash, last_seen_at); UPSERT über cookie_token via POST /api/anon/touch (Node), Folge-Aufrufe aktualisieren nur last_seen_at |
| Pseudonyme Felder | ip_hash/user_agent_hash = SHA-256(value + AUTH_SECRET) (kein Klartext, dasselbe Muster wie rate_limits.ip_hash) |
| Conversion | beim ersten Post-Login (/go) setzt convertAnonymousByEmail converted_to_user_id; danach gehört die Zeile dem User |
| Retention (DSGVO) | Hard-Delete nach 12 Monaten ohne Conversion (created_at < now() - interval '12 months' AND converted_to_user_id IS NULL). Konvertierte Zeilen bleiben — sie sind Teil der User-Historie und unterliegen der allgemeinen Account-Retention. Automatisiert via täglichem Vercel-Cron 30 3 * * * (/api/cron/anon-cleanup, ADR-0026 §3 / Issue #105) — analog zum reports-cleanup-Cron (runCron-Wrapper, cron_runs-Telemetry, Failure-Alert). Der Cron löscht ausschließlich converted_to_user_id IS NULL → konvertierte Zeilen bleiben unberührt. |
| Fail-open | DB-Fehler beim Touch crashen /report nie (POST /api/anon/touch antwortet 204, console.error-Log); manipulierte Cookies (≠ 64-Hex) werden ignoriert, der nächste GET stellt einen frischen Cookie aus |
API-Sicherheits-Hardening (ADR-0023)
| Maßnahme | Umsetzung |
|---|---|
| Eingabe-Caps | Adresse max 180 Zeichen + Control-Char-Strip; lat/lon nur innerhalb Österreich-BBox; JSON-Body max 2 KB (lib/api/guard.ts) |
| Scraping-Schutz GeoJSON | Karten-Endpunkte (parcel-/widmung-geojson) jetzt rate-limitiert (eigener map:-Bucket, Default 300/Tag) |
| CORS | bewusst offen für /api/v1/* (öffentliche, KI-nutzbare API) ohne Credentials; Schutz = Rate-Limit/Key, nicht Origin |
| Header | X-Content-Type-Options: nosniff, X-Frame-Options: DENY global; Report-Antworten Cache-Control: private, no-store (keine Adress-Zwischenspeicher) |
| Pseudonyme Limits | Rate-Limit speichert nur SHA-256(ip+secret), kein Klar-IP. Tagesbasierte Zähler (rate_limits(ip_hash, day), Migration 0006); alte Zeilen werden automatisiert via wöchentlichem Vercel-Cron 30 2 * * 0 (/api/cron/ratelimit-prune, DELETE … WHERE day < (now() - interval '90 days')::date) entfernt. Keine Compliance-Frist — pseudonyme Zähler, reine Wachstums-Begrenzung der Tabelle (runCron-Wrapper, cron_runs-Telemetry). |
| Permalink-IDs (für Report-Persistenz, ADR-0022) | CSPRNG ≥128 Bit base62 — nicht enumerierbar (IDOR-Schutz); /r/{id} noindex; Retention expires_at Default 90 Tage (anonyme Reports), Hard-Delete automatisiert via täglichem Vercel-Cron 0 3 * * * (/api/cron/reports-cleanup, Issue #107). Konto-gebundene Reports (reports.user_id gesetzt — eingeloggt erstellt oder per ?claim=1 beansprucht, Migration 0021) haben expires_at = NULL und laufen nicht ab: sie sind Teil der User-Daten (Speicherung auf Wunsch des Nutzers, abrufbar unter „Meine Reports") und unterliegen der allgemeinen Account-Retention, nicht der 90-Tage-Frist. Der Cron löscht ausschließlich expires_at < now() → NULL-Zeilen bleiben unberührt. Anonyme Reports tragen optional reports.anon_token (= ln_anon-Cookie, Migration 0022) für den Funnel-Auto-Claim beim Login; der Token wird beim Claim auf NULL gesetzt (Datenminimierung) und verschwindet spätestens mit dem 90-Tage-Hard-Delete. Granulares Einzel-Löschen (DSGVO Art. 17 / Datenminimierung): Nutzer können einen einzelnen Konto-Report owner-scoped hard-löschen (DELETE /api/v1/me/reports/{id} bzw. „Löschen" auf /account/reports, … WHERE id AND user_id) — unabhängig von der kompletten Kontolöschung; ebenso Label/Notiz setzen (PATCH, Migration 0024). Beim Konto-Löschen werden die zugehörigen Reports mitentfernt (siehe nächste Zeile). |
| Recht auf Vergessenwerden (DSGVO Art. 17) | Konto-Löschung über /account/delete (UI, Server-Action) und DELETE /api/v1/me (headless, AI-Parität) — Single Source lib/account/delete.ts. Erasure-Kaskade in FK-sicherer Reihenfolge: reports, memberships (nur eigene Zeilen löschen; auf fremden Zeilen nur invited_by nullen — sonst verlöre ein eingeladener Nutzer seinen Org-Zugang), invitations (selbst versendet), anonymous_sessions (konvertiert), verification_tokens (per E-Mail), users; accounts + sessions cascaden per DB-FK (ON DELETE CASCADE). Bestätigung durch erneute Eingabe der eigenen E-Mail (Schutz vor versehentlicher/automatischer Löschung). Guard: blockiert (409), solange der Nutzer alleiniger owner einer Organisation ist (Eigentum erst übertragen) — fail-closed bei DB-Fehler (im Zweifel NICHT löschen). Bewusst aufbewahrt (Art. 17 Abs. 3 lit. b, Rechtsansprüche): Betriebs-/Audit-Logs (z. B. impersonation_log, cron_runs) — keine FK auf users, enthalten keine Report-Inhalte. |
Legacy /api/report | Deprecation/Sunset-Header gesetzt; Entfernung nach UI-Umzug auf v1 |
Datenminimierung im LLM-Pipeline
| Pflicht | Umsetzung |
|---|---|
| Keine PII im LLM-Prompt | Adresse → Koordinate → Hash; Eigentümer-Name nie an LLM |
| Anonymisierung der Gemeinderatsprotokolle | NER-basierte Klarnamen-Entfernung vor RAG-Indexing |
| Retention der Logs | Anthropic 7 Tage Abuse-Monitoring; eigene Logs ≤ 30 Tage |
| Aggregat-Daten dürfen nicht re-identifizierbar sein | Benchmark-Daten nie auf Einzeladresse runterbrechen (z. B. „14 Brachflächen in dieser Gemeinde", nicht „diese eine Adresse") |
Verzeichnis von Verarbeitungstätigkeiten (VVT)
Pflicht gemäß Art. 30 DSGVO. Eintrag pro Sub-Prozessor:
| Sub-Prozessor | Zweck | Rechtsgrundlage | Rechenzentrum |
|---|---|---|---|
| Vercel (Hosting + Edge + AI Gateway) | Hosting, Edge Functions, LLM-Routing | Art. 6 (1) (b) Vertragserfüllung | global, EU-Region wählbar; AI Gateway routet pro Provider auf EU-Region |
| Neon (Postgres-Hosting) | PostgreSQL + PostGIS + pgvector | Art. 6 (1) (b) | EU (Frankfurt) |
| Cloudflare R2 (Object Storage) | PDF-Reports, Förderrichtlinien, Fassaden-Uploads | Art. 6 (1) (b) | EU-Jurisdiction-Buckets (Western Europe). SCCs als Annex zum AVV — US-Muttergesellschaft, daher Schrems-II-Argumentation dokumentieren |
| Upstash Redis | Cache | Art. 6 (1) (b) | EU-Region |
| Inngest (Background-Jobs) | Workflow-Orchestrierung, Job-Queues | Art. 6 (1) (b) | EU-Cloud-Region (eu.app.inngest.com); SCCs als Annex zum AVV |
| Sentry (Error-Tracking + APM) | Error-Reporting, Performance-Monitoring | Art. 6 (1) (f) berechtigtes Interesse | EU-Region Frankfurt; PII-Scrubbing über beforeSend-Hook |
| BetterStack (Phase 3+ Logs + Uptime) | zentrale Log-Aggregation, Status-Page | Art. 6 (1) (f) | EU-Region; AVV vor Aktivierung |
| Posthog (Phase 3+ Product Analytics) | Funnel-Analyse, Cohort-Tracking | Art. 6 (1) (a) Einwilligung (Cookie-Banner) | EU-Cloud (eu.posthog.com); Session-Replay nur opt-in |
| Stripe | Payments | Art. 6 (1) (b) | EU + global |
| Anthropic (LLM-Modelle) | Standard- und Premium-Reports, Tool-Use | Art. 6 (1) (b) | über AWS Bedrock EU (Frankfurt) oder Vertex AI EU; SCCs |
| OpenAI (LLM-Modelle) | Klassifikation, Embeddings, Standard-Reports (Alternative) | Art. 6 (1) (b) | EU-Region (direkte OpenAI EU-API); SCCs |
| Google (LLM-Modelle, Gemini) | PDF-/Bild-Extraktion, Klassifikation | Art. 6 (1) (b) | Vertex AI EU-Region; SCCs |
| AWS (falls Bedrock aktiv für Anthropic) | Hosting für Anthropic-Modelle | Art. 6 (1) (b) | Frankfurt |
| Komoot (Photon) (Geo-API) | Adress-Autovervollständigung (Type-Ahead, login-loser Free-Pfad) | Art. 6 (1) (b) | Komoot GmbH (DE/EU); öffentliche Instanz photon.komoot.io. Eingabe wird nicht serverseitig gespeichert (no-store). Für das Ranking wird ein grober Standort-Bias mitgesendet (IP-Geo stadtgenau bzw. Browser-Geolocation auf ~1 km gerundet — keine hausgenaue Position), nicht gespeichert. AVV vor Launch zu klären, alternativ Self-Hosting (PHOTON_BASE_URL) für volle Datenkontrolle |
| OpenStreetMap Foundation (Nominatim) (Geo-API) | Einzel-Geocoding (Report) + Suggest-Fallback | Art. 6 (1) (b) | OSMF (UK; EU-Angemessenheitsbeschluss UK). Nur Einzel-Suche — Autocomplete laut Usage-Policy untersagt; Eingabe nicht serverseitig gespeichert (no-store) |
EU Data Residency (Schrems II)
Multi-Provider über Vercel AI Gateway
Mit Multi-Provider-Strategie (ADR-0004) muss jeder aktiv genutzte Provider einzeln EU-konform konfiguriert sein:
| Provider | EU-Daten-Residency | Wie konfiguriert |
|---|---|---|
| Anthropic Claude | über AWS Bedrock EU (Frankfurt) oder Vertex AI EU | AI Gateway routet, Region pro Provider in Vercel-Settings |
| OpenAI | direkte EU-Region-API (verfügbar seit 2024) | bei API-Aufruf EU-Region setzen; Gateway routet |
| Google Gemini | Vertex AI EU-Regionen | über AI Gateway oder direkt Vertex |
Vercel AI Gateway sitzt davor und garantiert Zero Data Retention: Daten werden nicht protokolliert, nicht für Training verwendet, nicht länger als für die Antwort nötig zwischengespeichert.
Standardvertragsklauseln (SCCs)
Trotz EU Data Residency formaler Abschluss von:
- AVV / DPA mit Vercel gemäß Art. 28 DSGVO (deckt Gateway ab)
- AVV mit jedem aktiv genutzten Provider (Anthropic, OpenAI, Google) — auch wenn nur über Gateway aufgerufen
- AVV mit AWS (falls Bedrock für Anthropic-Modelle verwendet)
- SCCs als Annex zu allen AVVs (EU-Kommissions-Beschluss 2021/914)
- Subprocessor-Liste aktuell halten (alle drei Provider publizieren das)
Training-Opt-out
Nicht nötig — API-Daten aller drei Provider (anders als Consumer-Pläne) werden default nicht für Modelltraining verwendet. Im AVV bestätigen lassen. Vercel AI Gateway leitet ohne Logging an die jeweilige API weiter.
Lizenz-Attribution
Pflichten pro CC-BY-4.0-Quelle
| Pflicht | Wo |
|---|---|
| Quellen-Block im Frontend (Footer-Komponente) | Persistent, sichtbar auf jeder Seite |
| Datenquellen-Block in PDF-Reports | Letzte Seite oder Footer |
/datenquellen-Seite | Vollständige Liste mit Lizenz, Version, Last-Update |
docs/DATA-SOURCES.md als Quelle der Wahrheit | Maintenance-Pflicht beim Einbinden neuer Quellen |
| Link zur Lizenz | https://creativecommons.org/licenses/by/4.0/ mindestens auf der Datenquellen-Seite |
| Hinweis bei Änderungen | „Abgeleitete Karte basierend auf basemap.at Orthofoto" o. Ä. |
Was wir explizit NICHT dürfen
- Endorsement implizieren (so tun, als ob basemap.at uns empfiehlt)
- Attribution entfernen / verstecken (auch in Cache, API, PDF)
- Strengere Lizenz darüberlegen („all rights reserved" auf einem CC-BY-Bündel)
- DRM verwenden, das Lizenzrechten widerspricht
Quellen-spezifische Lizenz-Gates
BEV Kataster
- Anzeige und Lookup okay
- Kommerzieller Planer-Export lizenzrechtlich kritisch — vor Phase 3 klären
- Speichern, Anzeigen, Exportieren, kommerzielle Weitergabe sind getrennte Fragen
HORA / LFRZ
- Reseller-Vertrag möglicherweise nötig für kommerzielle Nutzung
- Workaround: WFS-Vektoren ziehen, eigene Intersection berechnen, keine WMS-Bilddaten reproduzieren
- Status klären vor Phase-3-Launch
Google Street View · Bing Streetside
- Maps Platform ToS verbieten ML-Training, Bulk-Download, dauerhaftes Caching > 30 Tage, Datenbank-Aufbau
- Verstoß = API-Konto-Sperre + zivilrechtliche Schritte
- Nicht für AI-Auswertung verwenden
- Saubere Alternative: Mapillary (CC BY-SA) + eigene Fassaden-Erfassung
Microsoft Building Footprints (ODbL)
- Kommerziell erlaubt
- Share-Alike-Klausel auf abgeleitete Datensätze beachten — wenn wir aus Building Footprints eine eigene DB ableiten, muss die unter ODbL stehen
KPC / LEADER / Förderungen
- Keine offizielle API
- Web-Scraping verstößt gegen ToS mancher Portale
- Sauberer Weg: RAG mit kuratierten PDF-Richtlinien, manuelle quartalsweise Validierung
Output-Disclaimer (in jedem Report)
Diese Auswertung dient ausschließlich der Information und ersetzt keine
rechtsverbindliche Vermessung, behördliche Auskunft oder fachliche
Prüfung durch Ziviltechniker oder Sachverständige. Datengrundlage:
[Auflistung der verwendeten Quellen mit Lizenz und Stand].
Diese Disclaimer-Zeile ist technisch erzwingbar über System-Prompt der LLM-Pipeline und HTML-Template — nicht optional.
Compliance-Tasks vor Live-Schaltung
- AVV mit allen Sub-Prozessoren unterschrieben (Anthropic via Bedrock, AWS, Vercel, Neon, Cloudflare R2, Upstash, Stripe)
- Cloudflare R2: EU-Jurisdiction-Bucket konfiguriert, SCCs als Annex zum AVV dokumentiert
- Neon: EU-Region (Frankfurt), Backup-Strategie definiert
- VVT-Eintrag vollständig
- Geo-APIs: AVV/DPA mit Komoot (Photon) geklärt oder Self-Hosting (
PHOTON_BASE_URL); Nominatim/OSMF-Nutzungsbedingungen dokumentiert (Autocomplete-Verbot eingehalten — läuft über Photon) - AWS Bedrock EU-Region konfiguriert
- Privacy-Notice und Opt-in-Checkbox im Frontend
- Attribution-Footer +
/datenquellen-Seite live - Output-Disclaimer in jedem Report
- Rate-Limit-Mechanismus aktiv
- Logging mit Retention ≤ 30 Tage konfiguriert
- HORA-Lizenz-Status geklärt
- BEV-Export-Rechtsprüfung für Planer-Tier