# Prüfberichte zum Launch-Audit

## Falsch-Positive (gegen das Repo geprüft)

## Prüfergebnis: Falsch-Positive und Belegfehler in der Launch-Checkliste

Geprüft READ-ONLY per Grep/Read/Python-Messung in `c:\Users\roman\Downloads\CLOAKDROP`. **Kein einziger Punkt verlangt etwas, das im Repo schon fertig existiert** — es gibt keine `.git`, `package.json`, `LICENSE`, `README`, `robots.txt`, `sitemap.xml`, `404.html`, `manifest`, `_headers`, `.well-known/`, kein `fetch/XHR/WebSocket/sendBeacon`, kein `<form action>`, keine externe URL, kein CSP-Tag. Die Substanz der Liste hält also. Zu streichen bzw. zu korrigieren sind aber die folgenden Stellen.

---

### A) STREICHEN — Handlungsanweisung sachlich falsch

**[184] – Teilstreichung: „Die 8 leeren mailto:-Verweise reparieren oder entfernen"**
Es gibt keine leeren mailto-Links. Die 8 Vorkommen sind JS-erzeugte Menüeinträge in den 4 App-Seiten, die zur Laufzeit die Empfängeradresse einsetzen:
`dashboard.html:3879` → `{ label: 'Email ' + mail, icon: I.mail, href: 'mailto:' + mail }` (je 2× in dashboard/received/expired/settings). Der Rest von [184] (abuse@, security@, security.txt, SPF/DKIM/DMARC, Antwortzeit) bleibt gültig.

**[214], [215], [219] – „in tasks/build_app.py korrigieren, weil index.html eine der 4 gebauten Seiten ist"**
Falsch. `tasks/build_app.py:856-867` — `PAGES = [dashboard.html, received.html, expired.html, settings.html]`; der Docstring (Zeile 2) sagt es wörtlich: „Generator fuer die vier App-Seiten". **index.html ist Handarbeit.** Die Funde selbst stimmen ([214]: `index.html:2` `class="light-theme"`, `index.html:6` `content="light"` gegen `light dark` in den 19 anderen; [215]: index.html ist die einzige Datei ohne `name="description"`; [216]/[258]: 4× `<h1>` nur in index.html) — nur der Umsetzungsweg muss auf „manuell patchen bzw. index.html erst in einen Generator überführen" geändert werden.

**[204] – Begründung falsch**
„Cloudflare Pages liefert jede Datei unter zwei Pfaden aus (/about und /about.html)" widerspricht direkt [35] und [205] derselben Liste: Pages *kanonisiert* per 308-Redirect, liefert also nicht beide. Der echte Duplikat-Raum ist `*.pages.dev` inkl. Branch-Previews plus apex/www ([43] deckt das ab). Forderung (rel=canonical) bleibt, Begründung austauschen.

---

### B) BELEG/ZAHL FALSCH — Punkt bleibt, Beleg korrigieren

| Index | Falsche Angabe | Gemessen |
|---|---|---|
| **[40]** | „16 data:font/woff2;base64 in index.html" | **2** (2 `@font-face`, Base64-Nutzlasten 97.356 + 53.976 Zeichen); 40 über alle 20 Dateien |
| **[58], [137], [291]** | „All systems operational" im Footer **aller 20** Seiten | **16 Dateien**; `grep -L` → dashboard/received/expired/settings haben die Pille **nicht**. [176] zählt korrekt |
| **[106]** | „0 Treffer für GDPR, Art. 6, legitimate, controller" | **Falsch**: privacy.html:1887 „Under the GDPR", :1895 „legitimate interest", :1888/:1910/:1914/:1916 „controller". Substanz (keine Art.-6-Zuordnung je Verarbeitung) bleibt |
| **[131]** | „grep invoice/Rechnung: 0 Treffer in allen .html" | **Falsch**: expired.html:2960/2963 `invoice-2209.pdf`, 6 Vorkommen. [272] belegt es korrekt |
| **[236]** | „hello@cloakdrop.com 40-mal über 9 Seiten"; „cloakdrop.com 40× über 15 Dateien" | **17** Textvorkommen über **7** Seiten (about, api, contact, docs, glossary, help, imprint), davon **10** mailto-Links; `cloakdrop.com` **70×** über 15 Dateien (so korrekt in [30]) |
| **[280]** | „docs.html:1841 (Up to 3 GB per drop on Free)" | docs.html enthält **keine** 3-GB-Aussage; 1841 ist „Open the app…". Fehlende Fundstellen: **api.html:2160, api.html:2208**, expired/received/settings.html:2922 |
| **[227]** | „18 `<form>` im Projekt", „auf 17 von 20 Seiten" | **21** form-Tags (16 `newsletter-form` + blog `bsub` + contact `cfform` + help `hcform` + index `login`/`signup`); Newsletter auf **16** Seiten |
| **[237]** | „17 Newsletter-Formulare" | **16** (`<form class="newsletter-form">`), plus blog.html `bsub` |
| **[175], [291]** | „alle 90 Balken class=nodata" / „87× nodata und 3× ok" | **264 nodata + 6 ok** über 3 Karten (= 88/2 je Karte). „100% uptime since 22 Jul" steht in **status.html:1787** (nicht 1788) |
| **[59]** | „pricing.html (7 Vorkommen von 50 GB)" | **6** (index 1, api 2, roadmap 1) |
| **[203]** | „4 Inline-PNGs in about.html" | **3** verschiedene Bilder (5 Vorkommen). Repoweit 11 eindeutige / 90 Vorkommen ✓ (so korrekt in [158]) |
| **[66]** | „`src="data:image/png…"` 2-6× in jeder Datei" | index.html **15**, changelog.html **9**, übrige 5-6 |
| **[113]** | Banner-Link „identisch in allen 20 Dateien" | 19× `href="index.html#faq"`, **index.html selbst `href="#faq"`** |
| **[129], [269]** | „0 Treffer für VAT/MwSt/Umsatzsteuer" | **1 Treffer**: imprint.html:1755 „VAT identification number — not filled in". Substanz (keine USt-Auszeichnung an den Preisen) bleibt |
| **[235]** | Banner „auf jeder der 16 Marketing-Seiten" | `cookie-pop` in **allen 20** Dateien; nur „Cookie settings" im Footer 16× ([226]/[279] korrekt) |
| **[83]** | `localStorage.setItem('cloakdrop-account'…)` bei :2635 | **index.html:2634** (hashPw 2514-2515 ✓) |
| **[19], [89]** | „keine Feature-Detection im Repo vorhanden" | Eine existiert: `navigator.clipboard && window.isSecureContext` (settings.html:4076, identisch in den 4 App-Seiten / `tasks/app.js`). Für `crypto.subtle` in index.html stimmt der Fund (0 Treffer für `isSecureContext`) |
| **[297]** | „kein Test im Repo belegt das" | Präzisieren: **kein automatisierter** Test. `tasks/todo.md:596-604` dokumentiert Handtests: 9-MiB-Rundlauf byte-identisch, gekipptes Bit → „Failed", gelöschter Chunk → „Failed", falscher Schlüssel → „The key doesn't fit" |
| **[7]** | Beleg unvollständig | `.delete(`/`.clear(`/`deleteDatabase` = 0 ✓, aber der stärkste Beleg fehlt: **privacy.html:1898-1903** delegiert Löschung ausdrücklich an „clear site data" im Browser |
| **[13]** | „Manifest ließe sich unter anderer Id ausliefern, ohne dass die GCM-Prüfung anschlägt" | Trägt für Vertraulichkeit nicht: ohne passenden Schlüssel scheitert GCM. Umhängen/Substituieren ist ein **Verfügbarkeits-**, kein Leseangriff — so korrekt formuliert in [82]. AAD-Forderung bleibt |

---

### C) REDUNDANZEN — dieselbe Sache mehrfach als eigener Blocker gezählt

Die Liste zählt Aufwand und Blocker mehrfach. Zusammenlegen, sonst ist die Priorisierung unbrauchbar:

- **Relay/API fehlt (2× XL):** [0] ≡ [45]
- **Burn (6×):** [8], [9], [49], [78], [88], [287]
- **Ablauf/Löschung (9×):** [6], [7], [47], [87], [132], [180], [181], [191], [282]
- **Quoten/DoS/Kosten (5×):** [12], [51], [93], [182], [276]
- **Download im RAM / Streaming (3×):** [3], [86], [243]
- **CSP (6×):** [38], [39], [63], [64], [174], teilw. [135]
- **HSTS (2×):** [34], [70]
- **Secure-Context-Gate (2×):** [19], [89]
- **404-Seite (3×):** [41], [196], [210]
- **Statusseite/Monitoring (10×):** [27], [58], [175], [176], [177], [191], [192], [199], [234], [291]
- **Fragment nach Import bereinigen (2×):** [18], [79]
- **Git/Backup/Deploy/Rollback (8×):** [36], [44], [147], [148], [149], [150], [170], [183]
- **Cookie-Banner/Analytics (7×):** [55], [112], [113], [114], [197], [235], [279]
- **Newsletter-Attrappe (3×):** [138], [227], [278]
- **Impressum/Rechtsträger (5×):** [102], [103], [105], [270], [300]
- **AGB + toter Terms-Link (3×):** [115], [116], [224]/[268]
- **Abuse/DSA (7×):** [26], [61], [90], [117], [118], [119], [277]
- **Zahlung/Steuern/Verbraucherrecht (je 2×):** [126]≡[274], [128]≡[271], [129]≡[269], [130]≡[273], [131]≡[272], [28]/[59]/[145]/[266]
- **Konto-Widerspruch (4×):** [60], [83], [267], [295]
- **Fonts auslagern (4×):** [66], [157], [229], [299]
- **Testabdeckung/Krypto-Audit (6×):** [29], [81], [95], [161], [162], [244]
- **Barrierefreiheitserklärung (2×):** [125], [246]

---

### D) Nachgemessen und BESTÄTIGT (keine Korrektur nötig)

Damit klar ist, was tragfähig ist: `85` tote `href="#"` mit exakter Verteilung (docs 17, blog 7, index 6, App-Seiten 0) ✓ · `648` interne `.html`-Links ✓ · erster `<style>`-Block **152.071 B, SHA 08d0e9ba, identisch 20/20** ✓ · index-Block2 **72.262 B** = Gemeinprefix aller Seiten; App-Seiten-Block2 134.680 B (Differenz 62.418 B) ✓ · 90 `data:image`, **11 eindeutig, 8 als image/png deklariert mit JPEG-Magic `ffd8ffe0`** ✓ ([158] vollständig bestätigt) · 9 localStorage-Keys gegen „Four localStorage keys" in privacy.html:1735 ✓ · 4× `<h1>` in index.html, 1× in allen anderen; **keine doppelten IDs** ✓ · keine `imprint/privacy`-Links in den 4 App-Seiten ✓ · 0 `setInterval`, 0 `beforeunload/pagehide/visibilitychange`, 0 `navigator.storage`, 0 `abort/cancel` in index.html, 0 `scope=`, 0 Skip-Link, 0 `aria-live`, 0 `target=_blank/noopener/referrer`, 0 `@supports`, 0 `100vh`, 0 `forced-colors`, 0 `font-display` ✓ · bei gleichzeitig **34 `:has(`, 53 `color-mix(`, 52 `dvh`, 8 `@container`, 82 `backdrop-filter`** ✓ ([242] tragfähig) · `rel=` existiert ausschließlich als `rel="icon"` (20×) ✓ · `.nav-dd-panel`/`.theme-menu` nur `opacity`+`pointer-events` (index.html:205-225, 1437-1444) ✓ · `--text-quaternary: #8b8d98` plus Selbstkommentar dashboard.html:1779 ✓ · docs-Anker `overview/receiving/troubleshooting/the-link` existieren ✓ · help.html **34** `hcart` ✓ · `verify_app.py`: `as_uri()` (Z. 15/57), `chromium.launch()` (Z. 48), nur 4 App-Seiten, `WIDTHS` ab 400, Screenshots ohne Vergleich, Kommentar „file:// ist kein sicherer Kontext" (Z. 257) ✓ · `build_app.py`: liest die Zieldatei selbst (Z. 806), `logo_html` aus dem Artefakt (798-802), `newline=''` (848), Zusicherungen 886/894/921 ✓ · beide Alt-Generatoren mit `raise SystemExit` (build_dashboard_pages.py:25, build_received.py:26) ✓ · `tasks/_shots` 6,0 MB, `_baseline` 592 KB/2 Dateien, todo.md 1.600 Zeilen ✓ · docs.html:2042 „A 256-bit key from crypto.getRandomValues" gegen index.html:2419 `crypto.subtle.generateKey` ✓ ([101] korrekt) · `expo.dev`-Kommentar in **20/20** Dateien ✓ · `theme-color content="#fff"` 20/20 ✓ · Version 0.9.2 (settings.html:3002) gegen „Preview 0.3" ✓ · [143] korrekt: die 3 ODR-Treffer in imprint.html liegen ausschließlich in Base64-Fontdaten, im Text steht kein ODR-Hinweis.

## Fachliche Richtigkeit

## Fachliche Korrekturen zur Launch‑Checkliste (Cloudflare / Security / Recht)

Geprüft wurde nur die Sachrichtigkeit. Repo‑Stand bestätigt: kein `_headers`, `_redirects`, `robots.txt`, `404.html`, `LICENSE`, `package.json`, `.git` im Wurzelverzeichnis (`ls -a` → 20 .html + `tasks/`); `grep -c "Content-Security-Policy" *.html` = 0 überall; `MAX_TOTAL = 500 * 1024 * 1024`, `EXPIRE_MS = 7 * 24 * 3600 * 1000` in index.html.

---

### A) Cloudflare — falsch benannte oder falsch beschriebene Produkte

**A1 · [175] Cloudflare Health Checks können keine Ergebnisse in KV oder D1 schreiben.**
Falsch: „Cloudflare Health Checks … schreiben Ergebnisse in KV oder D1". Health Checks (Standalone) erzeugen ausschließlich Health‑Check‑Analytics und Notifications; sie haben keinen Schreibpfad in Storage‑Produkte. Richtig: entweder ein Cron‑Worker prüft selbst und schreibt `/status.json` nach KV, oder die Health‑Check‑Notification geht per Webhook an einen Worker, der schreibt. Zusatzfehler: [177] empfiehlt Health Checks ohne Planhinweis, [58] sagt korrekt „Pro+". Standalone Health Checks sind Pro‑Feature — [177] ist auf Free nicht umsetzbar.

**A2 · [43] Cloudflare Access kann `*.workers.dev` nicht schützen.**
Access‑Applications setzen einen Hostnamen in einer Zone voraus, die dem Konto gehört. `workers.dev` gehört Cloudflare. Richtig für den in [35] gewählten Workers‑Pfad: `workers_dev = false` **und** Versions‑Preview‑URLs abschalten (`preview_urls = false`), nicht Access davorlegen. Nur bei **Pages** existiert die eingebaute Access‑Integration für Preview‑Deployments.

**A3 · [33] und [70] „Zonen starten in Flexible" ist veraltet — und der TLS‑Modus ist hier weitgehend wirkungslos.**
Neu angelegte Zonen erhalten seit 2023 „Automatic SSL/TLS" und werden auf die sicherste funktionierende Stufe gesetzt; „Flexible" wird für Neuzonen nicht mehr vorbelegt. Wichtiger sachlicher Punkt: Der SSL/TLS‑Verschlüsselungsmodus regelt ausschließlich die Verbindung **Cloudflare → Origin**. Bei Workers Static Assets bzw. Pages gibt es keinen Origin‑Fetch, der Modus hat also keine Schutzwirkung. Der wirksame Riegel ist „Always Use HTTPS" + HSTS + Minimum TLS 1.2 — genau darauf sollte der Punkt zugespitzt werden, statt Full (strict) als „einzige korrekte Wahl" zu begründen.

**A4 · [32] `0 issuewild ";"` bricht Universal SSL.**
Universal SSL stellt für die Zone ein Zertifikat inklusive `*.cloakdrop.com` aus. Ein CAA‑Record, der Wildcard‑Ausstellung generell verbietet, blockiert genau dieses Renewal. Richtig: dieselben CAs sowohl in `issue` als auch in `issuewild` freigeben (Universal SSL: Google Trust Services `pki.goog`, Let's Encrypt `letsencrypt.org`; bei Advanced Certificate Manager zusätzlich `ssl.com` und `sectigo.com`) und die Liste vor jeder Zertifikatsumstellung nachziehen. `0 iodef "mailto:…"` ist korrekt.

**A5 · [42] Cache Rules setzen keine Response‑Header.**
Falsch: „Bypass Cache plus Origin‑Header `Cache-Control: no-store`" in einer Cache Rule. Cache Rules steuern Cache‑Verhalten, Edge‑TTL und Browser‑TTL — Response‑Header setzt man mit einer **Response Header Transform Rule**. Korrekt ist der Hinweis, dass Page Rules abgekündigt sind.

**A6 · [46] Location Hint und Jurisdiction sind zwei verschiedene Dinge.**
Der Location Hint ist best effort und rechtlich wertlos. Nur die **Jurisdiction `eu`** bindet die Objekte an die EU — sie muss **bei der Bucket‑Erstellung** gesetzt werden, ist nachträglich nicht änderbar, und der Zugriff läuft nur über den jurisdiktionsbehafteten Endpunkt bzw. ein entsprechend konfiguriertes Binding. Nur diese Variante trägt die Aussage in [56]/[110].

**A7 · [7] und [47] `deleteObjects` ist der S3‑Name, nicht der Worker‑Name.**
Im R2‑Binding heißt es `bucket.delete(keys: string[])` (max. 1000 Keys pro Aufruf); `DeleteObjects` gilt nur für die S3‑API. Im Code‑Kontext der Punkte ([47] „per R2 DeleteObjects") ist der Name falsch.

**A8 · [40] und [74] Zone‑Features teils falsch verortet / plan­abhängig.**
„Network → Cloudflare Fonts" ist falsch: Cloudflare Fonts sitzt unter Speed → Optimization → Content Optimization und betrifft ohnehin nur Google‑Fonts‑Referenzen (im Projekt: keine). **Mirage** ([74]) ist ein Pro+‑Feature und auf einer Free‑Zone nicht aktivierbar. **Automatic Platform Optimization** ist WordPress‑spezifisch. **Speed Brain** injiziert Speculation Rules, kein Fremdskript. Die Liste sollte nach Plan­verfügbarkeit sortiert werden, sonst schaltet man Schalter ab, die es nicht gibt, und übersieht die relevanten (Email Obfuscation, Rocket Loader, Bot Fight Mode, Browser Insights/RUM).

**A9 · [31] Zone Hold ≠ „Pause Cloudflare on Site".**
„Zone‑Holdout/'Pause Cloudflare on Site' nach dem Go‑Live deaktivieren" vermischt zwei Features mit gegenläufiger Empfehlung: **Zone Hold** verhindert, dass jemand anders dieselbe Domain in ein fremdes Cloudflare‑Konto aufnimmt — der sollte **aktiv bleiben**. **Pause Cloudflare on Site** umgeht den Proxy und muss aus sein.

**A10 · [185] Cloudflare Browser Rendering ist Chromium‑only.**
Die geforderte Matrix Chromium + Firefox + WebKit ist über Browser Rendering nicht fahrbar; nur GitHub Actions (oder ein eigener Runner) liefert alle drei Engines. Die Empfehlung, den synthetischen Test „alle 15 min gegen die Live‑Domain" über Cloudflare Cron + Browser Rendering zu fahren, deckt also nur Chromium ab — und genau WebKit ist laut [185] die wahrscheinlichste Bruchstelle.

---

### B) Grenzen und Zahlen

**B1 · [53] Subrequest‑Limit falsch angegeben.**
„Max. 1.000 Subrequests pro Invocation" gilt für **Workers Paid**. Auf **Free sind es 50**. Da der Punkt ausdrücklich Free‑Limits dokumentiert (10 ms CPU, 100.000 Requests/Tag), ist die Zahl an dieser Stelle falsch.

**B2 · [46] Kostenrechnung ist unvollständig, nicht falsch.**
Class A stimmt (768.000 von 1 Mio. frei). Nicht genannt: das R2‑Free‑Kontingent für **Speicher liegt bei 10 GB/Monat** — 1.000 Drops à 3 GB sind ~3 TB und damit um Faktor ~300 darüber (die 10–11 USD in [52] rechnen das korrekt, [46] suggeriert „im Freikontingent"). Ebenfalls fehlt **Class B** beim Abruf: 768 GET pro Drop, 0,36 USD/Mio., plus je eine Worker‑Invocation. Für die Preisentscheidung, die [46] ausdrücklich vorbereiten will, ist die Rechnung damit zu optimistisch.

**B3 · [45] CORS `Max-Age 86400` ist wirkungslos, `Vary: Origin` fehlt.**
Chromium kappt `Access-Control-Max-Age` bei **7200 s**. Und weil `Access-Control-Allow-Origin` auf einen konkreten Origin gesetzt wird, muss `Vary: Origin` mit, sonst cacht ein Zwischen‑Cache die Antwort für den falschen Origin.

**B4 · [2] widerspricht [46] und würde das Kryptoformat brechen.**
[2] verlangt „Chunkgröße dynamisch statt fix 4 MiB (Mobilfunk 1 MiB, Desktop 8–16 MiB) **oder** R2‑Multipart nutzen". [46] begründet korrekt, dass 4 MiB durch das IV‑Zählerschema fixiert ist und Multipart wegen der **5‑MiB‑Mindestgröße je Teil** (außer letztem, max. 10.000 Teile) im Browser‑Pfad ausfällt. Dynamische Chunkgrößen sind ein Formatbruch und müssten — wenn überhaupt — als `chunkSize` im authentifizierten Manifest v2 ([13]) landen. Ohne diese Kopplung ist [2] fachlich falsch.

**B5 · [11] gegen [47]/[49]: 404 und 410 sind nicht gleichzeitig möglich.**
[11] fordert „einheitliche 404‑Antwort für unbekannt, abgelaufen und verbrannt (gleicher Body, gleiche Latenz), keine Existenzbestätigung". [47] und [49] fordern **410 Gone** nach Ablauf bzw. Burn. 410 bestätigt genau die Existenz, die [11] verbergen will. Entscheidung erzwingen: 404 für alles (Enumerationsschutz, schlechtere Diagnose) **oder** 410 (bessere UX, Existenz‑Leak). Ein Kompromiss ist 404 ohne Token und 410 nur gegenüber dem Inhaber eines gültigen Owner‑Tokens ([23]).

---

### C) Header und CSP

**C1 · [174] Meta‑CSP mit `frame-ancestors 'none'` ist wirkungslos.**
[68] stellt selbst richtig fest, dass `frame-ancestors` und `report-uri`/`report-to` im `<meta http-equiv>` ignoriert werden (dasselbe gilt für `sandbox`). [174] schlägt dann aber genau diese Direktive „in ein Meta‑CSP‑Tag" vor. Konsequenz: CSP ausschließlich als HTTP‑Header aus `_headers`; Meta‑CSP höchstens als zusätzliche Rückfallebene ohne die drei genannten Direktiven.

**C2 · [63] `worker-src 'none'` und `manifest-src 'none'` blockieren zwei geplante Kernfunktionen.**
Direktiven‑Fallback: `worker-src → child-src → script-src → default-src`. Explizites `worker-src 'none'` verhindert die **Service‑Worker‑Registrierung**, die [3] und [4] zum Pflichtpfad für Streaming‑Download und ZIP machen. `manifest-src 'none'` blockiert das Web‑Manifest aus [212]. [39] setzt beide auf `'self'` — die beiden Punkte widersprechen sich, und die [63]‑Variante wäre die kaputte.

**C3 · [63]/[39]/[66] CSP deckt die eigenen Feature‑Pläne nicht ab.**
Bei `default-src 'none'` fehlen: `blob:` in `img-src` (Objekt‑URL‑Vorschau nach [24]; der reine Download über `<a download>` braucht es nicht), **`media-src`** (`<video>`/`<audio>`‑Vorschau ist ohne eigene Direktive blockiert, es gibt keinen Fallback auf `img-src`), **`frame-src`** (das in [24] vorgeschlagene sandboxed iframe ist tot). Weiter: [66] sagt „kein `'self'` bei `font-src` nötig, es gibt keine Datei‑Fonts" — das kollidiert direkt mit [157]/[229], die die beiden WOFF2 nach `/fonts/*.woff2` auslagern. Danach ist `font-src 'self'` zwingend, `data:` kann entfallen.

**C4 · [38] und [72] `interest-cohort=()` ist eine wirkungslose Direktive.**
FLoC ist eingestellt, das Permissions‑Policy‑Feature `interest-cohort` existiert nicht mehr und wird von aktuellen Browsern ignoriert. Korrekt ist ausschließlich `browsing-topics=()` (in [72] richtig, in [38] fehlt es, dort steht nur die veraltete Form). Der Rest der Liste ([72]) ist gültig; `clipboard-write` muss nicht explizit erlaubt werden, weil der Default‑Allowlist `self` ist.

**C5 · [38]/[63] `form-action 'none'` kollidiert mit zwei anderen Blockern.**
Solange kein `<form action>` existiert, ist `'none'` korrekt. [227] verlangt einen echten POST‑Endpunkt für den Newsletter, [118] ein Meldeformular nach Art. 16 DSA, [144] ein Kontaktformular. Sobald eines davon als HTML‑Formular abgeschickt wird, blockiert `form-action 'none'` es (auch bei `<form>` **ohne** action, weil das auf die aktuelle URL postet). Richtig: entweder alle Formulare per `fetch` abschicken (dann greift `connect-src`, `form-action 'none'` kann bleiben) oder `form-action 'self'` setzen. Diese Entscheidung muss vor dem CSP‑Enforcement fallen.

**C6 · [73] Begründung für Cross‑Origin‑Isolation ist falsch.**
`crossOriginIsolated` (COOP `same-origin` + COEP `require-corp`) ist Voraussetzung für **SharedArrayBuffer**, `performance.measureUserAgentSpecificMemory()` und hochauflösende Timer — **nicht** für Streaming. Streams, File System Access und Service‑Worker‑Responses ([3], [4], [243]) funktionieren ohne COI. Der Punkt bleibt sinnvoll („heute gratis, später teuer"), die technische Begründung ist zu streichen.

**C7 · [51] Turnstile ist mit `COEP: require-corp` nicht verträglich — und braucht mehr CSP als genannt.**
Turnstile lädt ein Skript **und** ein iframe von `challenges.cloudflare.com`. Unter `require-corp` müssen eingebettete Cross‑Origin‑Ressourcen CORP/CORS mitliefern; Turnstile tut das nicht zuverlässig. Wer Turnstile will, braucht `COEP: credentialless` (oder gar keine COEP) **und** in der CSP `script-src https://challenges.cloudflare.com` *plus* `frame-src https://challenges.cloudflare.com` — [51] nennt nur „challenges.cloudflare.com in die CSP aufnehmen", [63]/[39] haben überhaupt kein `frame-src`. Das ist genau die Konstellation, die am Launchtag zur weißen Seite führt.

**C8 · [67] Origin‑Name weicht vom Rest der Liste ab.**
`https://relay.cloakdrop.tld` gegen `https://api.cloakdrop.com` in [30], [31], [39], [45], [237]. Da [67] ausdrücklich fordert, die Origin in **einer** Build‑Konstante zu halten, muss auch der Punkt selbst den echten Hostnamen nennen.

**C9 · [65] richtig, aber die Alternative fehlt.**
Hashes deckt `<style>`‑Elemente, nicht `style=""`‑Attribute — korrekt. Die zweite zulässige Lösung neben `style-src-attr 'unsafe-inline'` ist `'unsafe-hashes'` mit Hashes der Attributwerte. Und der Hinweis, dass per CSSOM gesetzte Styles (`el.style.x`) CSP‑frei sind, ist richtig.

---

### D) Krypto und Protokoll

**D1 · [82] Risikoeinstufung des unauthentifizierten `ivPrefix` ist zu mild.**
Richtig ist: der IV geht bei GCM in die GHASH‑Berechnung ein, deshalb scheitert Chunk‑Vertauschung — aber der IV ist **kein AAD**, und die Bewertung „Verfügbarkeit, nicht Vertraulichkeit" gilt nur für den **Lesepfad**. Übernimmt der Client das `ivPrefix` jemals von der Serverseite (Resume, Wiederaufnahme eines halben Uploads nach [5], erneutes Verschlüsseln), kann ein böswilliges Relay **Nonce‑Reuse unter demselben Schlüssel** erzwingen — und das kostet bei GCM Authentizität **und** Vertraulichkeit (XOR zweier Klartexte, Authentication‑Key‑Recovery). Genau das ist der Grund, das Präfix deterministisch aus der Drop‑ID abzuleiten bzw. ins AEAD‑geschützte Manifest zu nehmen; der Punkt sollte das als Vertraulichkeitsrisiko im Sendepfad führen, nicht als Komfortproblem.

**D2 · [13]/[82] „Chunk‑Gesamtzahl als AAD" ist beim Streaming‑Upload nicht implementierbar.**
Die Gesamtzahl steht erst nach dem letzten Chunk fest, verschlüsselt wird aber vorher. Richtig: AAD = Drop‑ID ‖ Formatversion ‖ Rolle (`manifest`/`chunk`) ‖ Chunk‑Index. Die Gesamtzahl gehört ins **authentifizierte Manifest**, das ohnehin zuletzt geschrieben und in [1] als Seal‑Operation definiert ist.

**D3 · [78] Der „Nachweis ohne Schlüssel" ist so nicht prüfbar.**
Ein HMAC über die Drop‑ID mit einem per HKDF abgeleiteten Unterschlüssel kann der Server nur verifizieren, wenn er den Unterschlüssel kennt — dann kennt er Schlüsselmaterial. Richtig: der **Client** übergibt beim Anlegen (`POST /v1/drops`) den erwarteten Wert als Commit‑Token; beim Löschen vergleicht der Server nur zwei Werte, ohne je einen Schlüssel oder ein Derivat zu besitzen, das eine Entschlüsselung erlaubt. Ohne diese Präzisierung ist der Punkt nicht umsetzbar.

**D4 · [49] widerspricht [9] und [77] — und ist die gefährlichere Variante.**
[49] lässt „den ersten `GET /manifest`" burnen. [9] und [77] verlangen ausdrücklich das Gegenteil, weil Link‑Vorschau‑Bots, Virenscanner‑Proxys und Prefetch genau das Manifest holen. Auslöser muss der abgeschlossene **Nutzdaten**‑Abruf sein (Lease + `POST /complete`), nicht der Manifest‑Abruf. So wie [49] es formuliert, ist es ein reproduzierbarer Datenverlust.

**D5 · [3] Verfügbarkeit der beiden Download‑Pfade.**
Korrekt, dass `showSaveFilePicker()`/`createWritable()` nur in Chromium existiert; [243] nennt es richtig. Ergänzen sollte man, dass Firefox die File System Access API für Nutzerpfade **nicht** unterstützt (nur OPFS) — der Service‑Worker‑Pfad ist damit nicht „Fallback", sondern der Hauptpfad für zwei der drei Engines und muss entsprechend priorisiert getestet werden.

---

### E) Recht — falsch benannte oder veraltete Normen

**E1 · [61] „§ 10 DDG" gibt es als Hosting‑Privileg nicht.**
Mit dem DDG (in Kraft seit 14.05.2024) sind die Haftungsprivilegien der §§ 7–10 TMG entfallen; für Hostingdienste gilt unmittelbar **Art. 6 DSA**, die Definition steht in **Art. 3 lit. g Ziff. iii DSA**. Richtig zitieren: Art. 6 DSA (Haftungsausschluss), Art. 16 DSA (Melde‑ und Abhilfeverfahren als Voraussetzung dafür, es aufrechtzuerhalten), § 33 DDG (Bußgeldtatbestände für DSA‑Verstöße), § 12 DDG (Koordinierungsstelle bei der Bundesnetzagentur). [90] und [118] zitieren korrekt — nur [61] ist falsch.

**E2 · [92] „Transparenzbericht — DSA Art. 24 verlangt ihn ohnehin" ist falsch.**
Art. 24 DSA gilt nur für **Online‑Plattformen**. [146] begründet selbst tragend, dass CloakDrop keine Online‑Plattform ist (Art. 3 lit. i DSA: keine öffentliche Verbreitung). Für reines Hosting ist **Art. 15 DSA** die Norm — und **Art. 15 Abs. 2 DSA** nimmt Kleinst‑ und Kleinunternehmen ausdrücklich aus. [124] sagt das richtig; [92] widerspricht sich damit selbst und erzeugt eine Pflicht, die nicht besteht.

**E3 · [126] und [274] § 356 BGB falsch angewandt — es gibt beim Abo kein „Erlöschen".**
Pro ist eine digitale **Dienstleistung**, also **§ 356 Abs. 4 BGB** (Abs. 5 betrifft digitale Inhalte — [274] zitiert falsch). Und Abs. 4 lässt das Widerrufsrecht nur bei **vollständiger Erbringung** erlöschen; bei einem laufenden Monatsabo tritt das nie ein. Richtig: der Verbraucher kann 14 Tage widerrufen; mit vorherigem ausdrücklichen Verlangen und Belehrung schuldet er Wertersatz **pro rata** nach **§ 357 Abs. 8 BGB**. Die Checkliste baut damit einen Checkout‑Schritt („Zustimmung zum Erlöschen"), der beim Abo rechtlich leerläuft — nötig ist die Zustimmung zum vorzeitigen Leistungsbeginn **plus** die Wertersatz‑Belehrung, sonst entfällt sogar der Wertersatz.

**E4 · [130] Umsatzsteuer‑Teil ist auf Stand 2024, nicht 2026 — drei Fehler.**
(a) „Die Kleinunternehmerregelung nach § 19 UStG deckt Auslandsumsätze nicht" ist seit **01.01.2025** überholt: die **EU‑Kleinunternehmerregelung** (§ 19a UStG, Kleinunternehmer‑Meldeverfahren beim BZSt, unionsweite Schwelle 100.000 €) erlaubt genau die Steuerbefreiung in anderen Mitgliedstaaten. (b) Es fehlt die Schwelle des **§ 3a Abs. 5 UStG**: unterhalb von **10.000 €** EU‑weitem Umsatz mit auf elektronischem Weg erbrachten Leistungen bleibt der Leistungsort Deutschland — ohne OSS, ohne Länder‑Steuersätze. Für einen Launch mit 4,99 €/Monat ist das der realistische Ausgangszustand, und die Checkliste treibt in eine Registrierung, die zunächst nicht nötig ist. (c) § 19 UStG hat seit 2025 neue Grenzen: **25.000 € Vorjahr / 100.000 € laufendes Jahr, netto** (nicht 22.000/50.000).

**E5 · [129] und [269] Preisangaben — ein Repo‑Fehler und eine falsche Norm.**
(a) „Jahrestarif als '2 months free' beworben, ohne dass irgendwo ein Jahrespreis steht" ist **sachlich falsch**: `pricing.html:2244` („Billed yearly as &euro;49.90 — 2 months free.") und `:2245` („€49.90/yr") nennen ihn — er wird nur per Umschalter gerendert. [266] zitiert dieselbe Stelle korrekt. Der echte Mangel ist ein anderer: der Jahrespreis ist ohne JavaScript nicht sichtbar und die Rabattaussage nennt keinen Bezugspreis.
(b) PAngV: Die seit 28.05.2022 geltende PAngV verlangt in **§ 3** den **Gesamtpreis**, und „Gesamtpreis" ist nach **§ 2 Nr. 3 PAngV** definitionsgemäß der Preis **einschließlich Umsatzsteuer**. Ein zusätzlicher Textbaustein „inkl. MwSt." ist nach PAngV nicht mehr zwingend — ein Verstoß liegt nur vor, wenn 4,99 € ein **Netto**preis ist. Die Pflicht, über Steuern zu informieren, folgt aus **Art. 246a § 1 Abs. 1 Nr. 4 EGBGB**. Und der Verweis auf „§ 11 PAngV‑Logik zur Preisreduzierung" passt nicht: § 11 betrifft Preisermäßigungen gegenüber einem vorherigen Preis; „2 months free" ist ein Tarifvergleich und wird nach **§ 5 UWG** beurteilt.

**E6 · [125] und [246] BFSG‑Pflichten falsch benannt.**
Für private Dienstleistungsanbieter gilt **§ 14 BFSG i.V.m. § 3 BFSGV und Anlage 3 BFSGV**: „Informationen zur Barrierefreiheit" — bereitzustellen in den AGB bzw. mit der Dienstleistung, mit Beschreibung, wie die Anforderungen erfüllt werden. Die in beiden Punkten geforderte **„Erklärung zur Barrierefreiheit" samt Feedback‑Mechanismus und Hinweis auf die Durchsetzungsstelle ist öffentlich‑rechtliches Recht** (§ 12b BGG, BITV 2.0) und gilt für einen privaten Anbieter nicht. Zuständig ist die **Marktüberwachungsstelle der Länder für Barrierefreiheit von Produkten und Dienstleistungen (MLBF)**, Schlichtung nach **§ 16 BGG**. Richtig sind: Geltung seit **28.06.2025**, Maßstab **EN 301 549 / WCAG 2.1 AA**, Kleinstunternehmensausnahme für Dienstleistungen nach **§ 3 Abs. 3 BFSG** (< 10 Beschäftigte **und** ≤ 2 Mio. € Jahresumsatz oder Bilanzsumme).

**E7 · [115] Zustimmungsfiktion im Änderungsvorbehalt ist gegenüber Verbrauchern unwirksam.**
„Änderungsvorbehalt mit Zustimmungsfiktion nach BGH‑Maßstab" verkennt die Rechtslage nach **BGH XI ZR 26/20 (27.04.2021)**: pauschale Zustimmungsfiktionsklauseln in Verbraucher‑AGB sind nach § 307 BGB unwirksam. Tragfähig ist nur ein auf konkrete, benannte Anlässe begrenzter Änderungsvorbehalt plus ausdrückliche Zustimmung oder ein Sonderkündigungsrecht mit Ankündigungsfrist. Das betrifft direkt auch [302] (Änderungsvorbehalt für Free‑Limits).

**E8 · [109] „DPA im Dashboard akzeptieren" — Stand prüfen.**
Bei den Self‑Serve‑Plänen ist das Cloudflare Data Processing Addendum in die Online‑Vertragsbedingungen einbezogen; ein separater Annahmeklick existiert dort nicht mehr. Die Art.‑28‑Anforderung ist damit erfüllt, der **Nachweis** besteht aus der archivierten DPA‑Fassung mit Datum plus Subprozessorenliste — nicht aus einem Screenshot eines Akzeptieren‑Dialogs. Inhaltlich richtig bleibt: SCCs Modul 2 sind Bestandteil, und die Produkte (Workers, R2, WAF, Turnstile) müssen einzeln gegen den Scope geprüft werden.

**E9 · [111] Einordnung von `cloakdrop-account` ist nicht haltbar.**
`cloakdrop-theme`, `cloakdrop-consent` und die IndexedDB‑Stores als einwilligungsfrei nach **§ 25 Abs. 2 Nr. 2 TDDDG** einzuordnen, ist vertretbar. Für `cloakdrop-account` (E‑Mail + Passwort‑Digest, [83]) gilt das nicht: Es gibt keinen Dienst, für dessen Erbringung die Speicherung „unbedingt erforderlich" wäre — die Kontoansicht ist laut [295] eine Attrappe. Damit ist die Speicherung entweder einwilligungspflichtig oder — der richtige Weg — zu entfernen. Zusätzlich ist die Liste unvollständig: [289] belegt neun Schlüssel, [111] bewertet vier.

**E10 · [142] Exportkontrolle — Zeitpunkt der US‑Pflicht präzisieren.**
EU‑Seite korrekt (VO (EU) 2021/821, Kategorie 5 Teil 2, 5A002/5D002, Kryptografie‑Anmerkung, Allgemeine Software‑Anmerkung; erwartbar keine Genehmigungspflicht). Die US‑Notifikation nach **15 CFR § 742.15(b)** greift erst mit der **Veröffentlichung des Quellcodes** — heute ist nichts veröffentlicht ([151]: keine LICENSE, kein Repo). Die Pflicht entsteht also zeitgleich mit dem AGPL‑Release und gehört in dessen Checkliste, nicht in die des Website‑Launches. Der Betrieb der Website löst sie nicht aus.

**E11 · [59] und [266] Merchant‑of‑Record‑Landschaft ist nicht mehr aktuell.**
„Stripe Billing = kein MoR" gilt nicht mehr pauschal: Stripe bietet inzwischen ein Merchant‑of‑Record‑Modell an (Managed Payments), das die USt‑Pflicht übernimmt. **Lemon Squeezy** gehört seit 2024 zu Stripe und wird dort konsolidiert — als Neuempfehlung für 2026 unsicher. Paddle bleibt tragfähig. Vor der Anbieterwahl bitte den aktuellen Produktstand prüfen, statt der in der Liste fixierten Zuordnung zu folgen.

**E12 · [146] NIS‑2 lässt sich datiert verneinen, nicht nur „prüfen".**
Das deutsche NIS2‑Umsetzungsgesetz ist inzwischen verabschiedet. Ein Ein‑Personen‑Betrieb liegt unter den Größenschwellen (50 Beschäftigte / 10 Mio. € Umsatz) und fällt in keinen Sektor der Anlagen 1/2 — die Verneinung ist begründbar und sollte mit Datum abgelegt werden, statt als offener Prüfpunkt in den Launch zu gehen. Die übrige Einordnung in [146] (Hostingdienst ja, Online‑Plattform nein, NIKD nein nach § 3 Nr. 40 TKG) ist korrekt und trägt auch E2.

**E13 · [131] Ergänzung, kein Fehler.**
Die Fristen sind richtig (8 Jahre für Rechnungen/Buchungsbelege nach § 147 Abs. 3 AO, § 14b UStG in der Fassung seit 01.01.2025; 10 Jahre für Handelsbücher und Abschlüsse). Nicht genannt: die **E‑Rechnungspflicht nach § 14 UStG** — Empfangspflicht im inländischen B2B seit 01.01.2025, Ausstellungspflicht gestaffelt ab 2027/2028. Für B2C irrelevant, für Rechnungen an Geschäftskunden relevant.

---

### F) Weitere innere Widersprüche, bei denen zwangsläufig eine Seite falsch ist

**F1 · [204] gegen [35]/[205]: Cloudflare Pages liefert **nicht** zwei Pfade aus.**
[204] begründet den Canonical damit, dass „jede Datei unter zwei Pfaden ausgeliefert" werde (`/about` und `/about.html`). [35] und [205] beschreiben korrekt, dass Pages die `.html`‑Form per **308 auf die extensionslose Form umleitet** — es gibt also keine zwei ausgelieferten Adressen. Das echte Duplikat‑Risiko sind `*.pages.dev`/Preview‑Hostnamen ([43]). Der Canonical bleibt richtig, die Begründung ist falsch.

**F2 · [167], [183], [227] gegen [35]: Plattformentscheidung ist in der Liste nicht durchgezogen.**
[35] verwirft Pages ausdrücklich (648 `.html`‑Links, `html_handling = "none"` nur bei Workers Static Assets). [167] will „Cloudflare Pages ans GitHub‑Repo binden", [183] argumentiert mit dem „Pages‑Rollback‑Knopf", [227] mit einer „Pages Function unter /functions/api/subscribe.ts". Auf dem Workers‑Pfad heißen die Äquivalente: `wrangler versions upload` / `versions deploy` / `wrangler rollback` ([44] nennt das korrekt), Route im selben Worker statt Pages Function, und Preview über Versions‑Preview‑URLs statt Pages‑Preview‑Deployments. Drei Punkte sind auf der verworfenen Plattform formuliert.

**F3 · [235] gegen [55] und [197]: gegensätzliche Empfehlung, beide als hoher Rang.**
[55] empfiehlt Web Analytics **nicht** zu aktivieren (verify.html‑Check 1 würde sichtbar scheitern), [235] empfiehlt es zu **aktivieren**. Zusätzlich ist die Begründung in [235] zu absolut: cookiefrei heißt einwilligungsfrei **nach § 25 TDDDG** (kein Zugriff auf Endgeräte‑Informationen), aber es bleibt eine Verarbeitung nach DSGVO, die eine Rechtsgrundlage (Art. 6 Abs. 1 lit. f) und die Aufnahme von Cloudflare als Auftragsverarbeiter samt Drittlandbewertung braucht — genau die Punkte [56], [109], [110]. „Nach TDDDG einwilligungsfrei" trägt die Entscheidung also nicht allein.

**F4 · [209] verweist auf einen stillgelegten Generator.**
„über tasks/build_app.py bzw. build_dashboard_pages.py, da diese Seiten generiert werden" — `tasks/build_dashboard_pages.py` ist laut [172] stillgelegt (`raise SystemExit`) und soll gelöscht werden. Der `noindex`‑Patch gehört ausschließlich in `build_app.py`.

**F5 · [239] gegen [125]/[246]: Kontrastwerte sind korrekt gerechnet.**
Nachgerechnet: `#8b8d98` auf `#fff` = 3,30:1, auf `#f0f0f3` = 2,87:1, Zielwert `#6e7178` = 4,89:1. Die Zahlen in [239] stimmen — kein Fehler, aber der Punkt ist damit ein belastbarer Beleg für die BFSG‑Bewertung in E6 und sollte dort verlinkt werden.

---

### Was ich geprüft und **korrekt** gefunden habe (keine Änderung nötig)

Workers Free 10 ms CPU / 100.000 Requests pro Tag; Workers Paid 5 USD mit 30 Mio. CPU‑ms; Paid CPU‑Limit Standard 30 s, konfigurierbar bis 5 min; Isolate‑Speicher 128 MB; Request‑Body 100 MB auf Free/Pro; Static Assets 20.000 Dateien / 25 MiB je Datei; R2 Multipart min. 5 MiB und max. 10.000 Teile; R2 Deletes kostenfrei, Egress 0, Class A 4,50 USD/Mio., Class B 0,36 USD/Mio., Speicher 0,015 USD/GB‑Monat; KV eventual consistent bis ~60 s, ein Write/s je Key, Wert max. 25 MiB; D1 max. 10 GB, einzelnes Primary; Durable Objects mit `blockConcurrencyWhile` und `setAlarm` als einziger Serialisierungspunkt; presigned URLs max. 7 Tage; Rate Limiting Rules Free = 1, Custom Rules Free = 5; Turnstile bis 1 Mio. Prüfungen frei; Access Free bis 50 Nutzer; ACM 10 USD/Monat, Universal SSL nur eine Subdomain‑Ebene; Data Localization Suite Enterprise‑only, R2‑EU‑Jurisdiction dagegen auf allen Plänen; zonenweites HTTP‑Logpush Enterprise, Workers Trace Events Logpush ab Paid; Cloudflare Email Routing empfängt nur, SPF‑Include `_spf.mx.cloudflare.net`, Null‑MX nach RFC 7505; Cloudflares AUP‑Einschränkung für großvolumige Nicht‑HTML‑Auslieferung über den CDN‑Cache auf Nicht‑Enterprise‑Plänen; `_headers`/`_redirects` auch bei Workers Static Assets; Page Rules abgekündigt; `navigate-to` aus dem CSP‑Standard entfernt; `frame-ancestors`/`report-uri` im Meta‑Tag unwirksam; HSTS‑Preload‑Mindest‑max‑age 31536000; DSA‑Zitate Art. 3 lit. g Ziff. iii, 6, 9, 10, 11, 12, 13, 14, 15 Abs. 2, 16, 17, 18, 19, 24 Abs. 5; DDG § 5 (Impressum) und § 18 Abs. 2 MStV; TDDDG § 25 (nicht mehr TTDSG); § 36 VSBG samt Zehn‑Beschäftigten‑Ausnahme und korrekt entfernter EU‑ODR‑Hinweis (Plattform am 20.07.2025 eingestellt); § 312j Abs. 3 BGB Buttonbeschriftung und § 312k BGB Kündigungsbutton; § 7 Abs. 2 Nr. 2 UWG für Werbe‑E‑Mails; Art. 30 Abs. 5 DSGVO‑Ausnahme greift hier nicht; Art. 33/34 mit 72 Stunden und Art. 34 Abs. 3 lit. c; § 38 BDSG ab 20 Personen; PBKDF2‑SHA‑256 ≥ 600.000 Iterationen bzw. Argon2id; OFL‑1.1 für Inter und JetBrains Mono; AGPL § 13 Netzwerknutzung und SPDX `AGPL-3.0-or-later`; RFC 9116 für security.txt; die WCAG‑Zuordnungen 1.4.3, 1.4.4, 1.4.10, 2.4.1, 2.4.3, 2.4.4, 2.4.7, 2.4.11, 2.5.8, 3.1.1/3.1.2, 3.3.1; Safari‑7‑Tage‑Räumung nicht‑persistenten Storage, iOS‑Zoom unter 16 px, `:has()`/`dvh` ab Safari 15.4, `color-mix()` ab Safari 16.2, Container Queries ab Firefox 110.
