<!-- Ergebnis einer Pruefung mit 14 Agenten am 30.07.2026.
     33 Funde gemeldet, 21 in der Gegenpruefung widerlegt, 25 haltbar.
     Jeder Punkt ist belegt, nicht geschaetzt. -->

# Was jetzt noch fehlt — Stand heute, 30.07.

## Der ehrliche Kern

Am Hosting fehlt genau **eine** Sache: die Domain ist nirgends mit dem Pages-Projekt verbunden. Das Projekt kennt nur `cloakdrop-3oh.pages.dev`, und in der Zone gibt es für `cloakdrop.com` weder A/AAAA noch CNAME — der Browser findet die Adresse gar nicht (`curl` → Status 000). Der Rest des Hostings ist fertig und läuft nachweislich: Deployment live, CSP/HSTS/Frame-Options gesetzt, 404, robots.txt, sitemap.xml, security.txt, og.png liefern alle 200.

Es fehlt **kein Server**. Es gibt keinen, es soll keiner sein, und für reine Statik im Free-Plan gibt es auch keinen Posten, der abrechnet. Alles Weitere auf dieser Liste ist entweder rechtlich (Impressum) oder Betriebssicherheit (Konto, Mail, Sicherung, Notfallweg).

Hinweis zu allen `curl`-Befehlen unten: **im Git-Bash-Fenster ausführen**, nicht in PowerShell — dort ist `curl` etwas anderes und die Optionen funktionieren nicht.

---

## Stufe A — blockiert den Start (muss HEUTE passieren)

### A1 · Custom Domain im Pages-Projekt anlegen — heute Abend, nicht morgen

**Wer:** du · **Dauer:** 15 Min. Klickarbeit, danach Wartezeit bis zu 1 Stunde (läuft nebenher)

Zwei Gründe, warum das heute passieren muss und nicht um 15:45:

- **Negatives DNS-Caching:** die SOA der Zone sagt 1800 Sekunden. Jeder Resolver, der `cloakdrop.com` jetzt abfragt, merkt sich „gibt es nicht" für 30 Minuten. DNSSEC ist scharf, die Nicht-Existenz ist signiert — kein Resolver fragt früher nach. Wer also um 15:55 verbindet, zeigt genau den Leuten, denen du den Link zuerst gibst, eine halbe Stunde lang einen Fehler.
- **Zertifikat:** Pages stellt es erst aus, nachdem der DNS-Eintrag steht. Bis der Status auf `Active` springt, liefert HTTPS eine rote Warnseite. Bei einer Verschlüsselungs-Seite ist das der schlechteste denkbare erste Eindruck.

**Ab jetzt bis zum Anlegen `cloakdrop.com` in keinem Browser aufrufen** — jede Anfrage pflanzt bei deinem Provider die 30 Minuten.

Klickpfad:

1. `dash.cloudflare.com` → linke Seitenleiste **Workers & Pages** → Projekt **cloakdrop** → Reiter **Custom domains**
2. **Set up a domain** → `cloakdrop.com` eintippen → **Continue** → **Activate domain**
3. Dasselbe ein zweites Mal für `www.cloakdrop.com` (gleicher Klickpfad, kostet 2 Minuten mehr und erspart dir, dass Leute mit `www` davor ins Leere laufen)
4. Cloudflare legt den Record selbst an (CNAME auf `cloakdrop-3oh.pages.dev`, am Apex per Flattening). **MX und SPF nicht anfassen** und keinen eigenen A-Record danebensetzen.
5. Warten, bis beide Einträge auf **Active** stehen — nicht `Initializing`, nicht `Pending Validation`.

Danach messen (erst wenn A2 deployt ist, siehe B-Block „Abnahme"):

```
curl -H 'accept: application/dns-json' 'https://cloudflare-dns.com/dns-query?name=cloakdrop.com&type=A'
```

Muss einen `Answer`-Block liefern, nicht nur `Authority`.

### A2 · Impressum ausfüllen

**Wer:** dein Vater (diktiert) + Claude (trägt ein) · **Dauer:** 45 Min.

`imprint.html` enthält 9-mal den Text „not filled in". Die Seite schreibt selbst: *„If this site is put at a public address and offered as a product, they have to be completed first."* Die Impressumspflicht (§5 DDG) greift in der Sekunde, in der die Domain öffentlich antwortet. Ein leeres Impressum ist in Deutschland abmahnfähig — auf Kosten deines Vaters, weil er drinsteht.

1. Vater diktiert vier Angaben: Name · Rechtsform (Privatperson bzw. Einzelunternehmer) · Straße + Hausnummer + PLZ + Ort · Telefonnummer
2. Claude setzt sie in `imprint.html` ein; die restlichen fünf Felder (Register, USt-IdNr., Aufsichtsbehörde) als „entfällt" kennzeichnen
3. `python tasks/build_footer.py`
4. `python tasks/build_headers.py` — **Pflicht**, sonst stimmen die CSP-Hashes nicht und die Seite bleibt stumm
5. `python tasks/verify_csp.py` — muss grün sein

Noch **nicht** deployen: erst kommt B3 dazu, dann ein einziger Deploy.

**Gesamtdauer Stufe A: 60 Minuten aktive Arbeit** (plus Wartezeit auf das Zertifikat, die nebenher läuft).

---

## Stufe B — vor dem Öffentlichmachen

Reihenfolge = Abarbeitungsreihenfolge, nicht Wichtigkeit.

### B1 · Zwei Zonen-Schalter ausmachen, bevor du die echte Domain zum ersten Mal aufrufst

**Wer:** du · **Dauer:** 10 Min.

Cloudflare schaltet in neuen Zonen **Email Address Obfuscation** standardmäßig ein. Das schreibt jeden `mailto:`-Link in der HTML um und schiebt ein Skript von `/cdn-cgi/` nach. Deine CSP erlaubt in `script-src` ausschließlich sha256-Hashes — kein `'self'`, kein `'unsafe-inline'`. Das Skript wird blockiert, und aus `hello@cloakdrop.com` wird sichtbar `[email protected]`. Auf allen 17 Seiten, auch im Impressum und in der Kontaktseite. Auf `pages.dev` passiert das nicht, weil der Host nicht durch die Zone läuft — der Fehler erscheint also erst mit der echten Domain.

- `dash.cloudflare.com` → Startseite → Domain **cloakdrop.com** anklicken → linke Seitenleiste **Scrape Shield** → **Email Address Obfuscation** auf **AUS**
- Gleiche Seitenleiste → **Speed** → **Optimization** → **Rocket Loader** auf **AUS** (schreibt ebenfalls Skripte um und bricht jeden Hash)

### B2 · TLS-Schalter der Zone durchsehen

**Wer:** du · **Dauer:** 6 Min.

Zone `cloakdrop.com` → **SSL/TLS**:

| Ort | Einstellung | Wert |
|---|---|---|
| SSL/TLS → Overview | Modus | `Full (strict)` oder `Automatic` — **nie** `Flexible` |
| SSL/TLS → Edge Certificates | Always Use HTTPS | EIN |
| " | Automatic HTTPS Rewrites | EIN |
| " | Minimum TLS Version | 1.2 |
| " | TLS 1.3 | EIN |
| " | HSTS | **AUS lassen** — der Header kommt aus `_headers`, zwei Quellen wären ein Konflikt |

Ehrlich zur Dringlichkeit: Pages leitet von sich aus auf HTTPS um, live gemessen (`http://cloakdrop-3oh.pages.dev/` → 301). Und der Schlüssel im `#fragment` verlässt den Browser ohnehin nie — er wird vor dem Request abgeschnitten. Das echte Risiko bei http ist ein anderes: im fremden WLAN könnte jemand das ausgelieferte HTML/JS austauschen, und dieses manipulierte Skript liest das Fragment dann lokal aus. Deshalb prüfen, aber nicht in Panik verfallen. Neue Zonen bekommen seit 2023 „Automatic", `Flexible` ist nicht mehr vorbelegt.

### B3 · Cookie-Satz in `privacy.html` richtigstellen

**Wer:** Claude · **Dauer:** 20 Min.

`privacy.html` sagt wörtlich: *„No cookies. Nothing on this site sets one, and nothing reads one."* Die Zugangswand setzt aber `cd_gate`, 7 Tage, HttpOnly. Solange die Wand steht, ist der Satz nachweisbar falsch — und der Notfallplan lautet überall „GATE_PIN wieder setzen". Bei einer Seite, deren einziges Argument ist, dass sie nicht lügt, ist das der teuerste Satz auf der ganzen Seite.

Ergänzen: während die Seite hinter der Zugangs-PIN steht, setzt sie ein technisch notwendiges Cookie `cd_gate` (7 Tage, HttpOnly, kein Tracking, keine Auswertung); ohne Wand setzt sie keines. Danach wieder `build_headers.py` + `verify_csp.py`.

### B4 · Ein Deploy, dann Abnahme auf der echten Domain

**Wer:** du · **Dauer:** 10 Min.

```
python tasks/build_dist.py
npx wrangler pages deploy dist --project-name=cloakdrop
```

Dann messen (alles muss 200 sein):

```
for p in "" about docs help limits privacy imprint verify status contact glossary api blog changelog roadmap opensource .well-known/security.txt sitemap.xml robots.txt assets/og.png; do curl -s -o /dev/null -w "$p %{http_code}\n" https://cloakdrop.com/$p; done
curl -sI http://cloakdrop.com/ | head -4          # muss 301 auf https zeigen
curl -sv https://cloakdrop.com/ 2>&1 | grep -i 'issuer:\|expire'
```

Und mit dem Auge: `https://cloakdrop.com/imprint` öffnen → DevTools-Konsole muss leer sein, die Mailadresse muss als Klartext dastehen (Gegenprobe zu B1).

### B5 · Wand hochziehen, bis morgen 16 Uhr

**Wer:** du · **Dauer:** 15 Min.

Zwei Probleme auf einmal gelöst: `cloakdrop-3oh.pages.dev` antwortet heute **200**, trägt am Projekt-Alias kein `X-Robots-Tag`, und die ausgelieferte robots.txt sagt `Allow: /`. Google kann den Preview-Host als eigenständige Kopie indexieren. Und der Not-Aus wurde nie gemessen.

```
npx wrangler pages secret put GATE_PIN --project-name=cloakdrop      # Wert aus tasks/_gate-credentials.txt
npx wrangler pages secret put GATE_SECRET --project-name=cloakdrop
curl -s -o /dev/null -w "%{http_code}\n" https://cloakdrop-3oh.pages.dev/
```

Kommt **401** → der Schalter wirkt sofort. Kommt **200** → Variablen hängen am Deployment, dann:

```
npx wrangler pages deploy dist --project-name=cloakdrop
curl -s -o /dev/null -w "%{http_code}\n" https://cloakdrop-3oh.pages.dev/    # jetzt 401
```

**Das gemessene Ergebnis in `tasks/deploy.md` schreiben** — dort steht heute an zwei Stellen Gegensätzliches. Die Erwartung ist 200/Redeploy nötig; genau deshalb wird gemessen und nicht geraten.

Danach, als letzter Schritt des Abends: Dashboard → **cloakdrop** → **Deployments** → bei `cdae9e4e` und `3fca147c` über das Drei-Punkte-Menü **Delete deployment**. Beide antworten heute 200 und umgehen die Wand dauerhaft. Das aktuell laufende Production-Deployment lässt sich nicht löschen — deshalb erst neu deployen, dann die dann alten entfernen. Und: **erst nach dem letzten Deploy des Abends aufräumen**, sonst putzt du mehrfach.

### B6 · DMARC-Record setzen

**Wer:** du · **Dauer:** 5 Min.

SPF und DKIM stehen (DKIM hat Email Routing automatisch gesetzt). DMARC fehlt komplett — ohne ihn darf jeder Fremde Mails mit Absender `hello@cloakdrop.com` verschicken, und SPF endet auf `~all` (Softfail), das wirft kaum ein Empfänger weg. Eine gefälschte „CloakDrop Support"-Mail, die um den Drop-Link bittet, ist der direkteste Angriff auf deine Nutzer.

Zone `cloakdrop.com` → **DNS** → **Records** → **Add record**:

| Feld | Wert |
|---|---|
| Type | TXT |
| Name | `_dmarc` |
| Content | `v=DMARC1; p=reject; rua=mailto:hello@cloakdrop.com` |
| TTL | Auto |

`p=reject` ist hier sofort gefahrlos, weil die Domain nichts verschickt — die übliche Rampe über `p=none` existiert nur, um eigene Versender nicht abzuwürgen. `sp`, `adkim=s`, `aspf=s` bewusst weglassen: `sp` erbt ohnehin von `p`, und strikte Ausrichtung wird zur Stolperfalle, sobald ein echter Mailanbieter dazukommt. **Den SPF-Record nicht von `~all` auf `-all` ändern** (auch wenn `launch.md:234` das vorschlägt) — Email Routing verwaltet den Record selbst, Handänderungen kippen den Routing-Status.

Prüfen: `nslookup -type=TXT _dmarc.cloakdrop.com 1.1.1.1`

### B7 · `hello@` auf ein Postfach des Vaters umstellen

**Wer:** dein Vater · **Dauer:** 20 Min.

Aktuell leitet die Regel auf `roman.grosz@icloud.com`. Empfangen ist unkritisch — die Adresse ist nicht sichtbar. Sie wird sichtbar, sobald jemand **antwortet**: dann geht Klarname und Privatadresse eines 14-Jährigen an jeden Fremden, auch an Sicherheitsmelder. Der stärkere Grund ist aber die Zuständigkeit: die Post muss beim Betreiber ankommen, nicht beim Kind.

1. Vater legt fest, welches Postfach: sein eigenes oder ein neues Gratis-Postfach ohne Klarnamen (`kontakt.cloakdrop@…`)
2. Dashboard → linke Seitenleiste **Email** → **Email Routing** → **Destination addresses** → **Add destination address** → Bestätigungsmail dort anklicken (Pflicht)
3. **Routing rules** → Regel `hello@cloakdrop.com` bearbeiten → auf die neue Zieladresse umstellen
4. Erst **nach** erfolgreichem Test (B8) `roman.grosz@icloud.com` unter Destination addresses entfernen

### B8 · Mail einmal wirklich durchlaufen lassen

**Wer:** du · **Dauer:** 8 Min.

Die Konfiguration ist heute Abend zwischen 18:23 und 18:51 UTC entstanden und nie benutzt worden. Email Routing schreibt den Envelope-Absender per SRS um; die SPF-Prüfung des Originalabsenders scheitert dadurch an der Weiterleitung, und iCloud sortiert so etwas gern in den Spam. Ab morgen steht `hello@cloakdrop.com` auf 17 Seiten und in `security.txt` als **einziger** Rückkanal — liegt die Post still im Spam, geht eine Schwachstellenmeldung verloren und niemand merkt es.

1. Von einem fremden Postfach (Gmail/GMX/Web.de, **nicht** von der Zieladresse selbst) an `hello@cloakdrop.com` schreiben
2. Im Zielpostfach **Posteingang UND Spam** prüfen; falls Spam: „kein Spam" + Filterregel anlegen
3. Aus dem Zielpostfach antworten und prüfen, ob die Antwort ankommt

### B9 · Quellstand sichern

**Wer:** du (Claude bereitet vor) · **Dauer:** 15 Min.

Was gerade live ist, existiert nirgends in Git: 8 Binärdateien (favicon.ico, assets/*.png) wurden nach dem letzten Commit neu erzeugt und mitdeployt. Cloudflare führt trotzdem beide Deployments unter Commit `e4f0be1` — das Etikett behauptet eine Nachvollziehbarkeit, die es nicht gibt. Und es gibt **keinen Git-Remote**: 57 getrackte Dateien liegen ausschließlich auf diesem Laptop. Aus einem Pages-Deployment bekommt man die HTML zurück, aber nicht die Python-Generatoren — und ohne die ist das Impressum nach dem Start nicht mehr änderbar.

```
cd C:\Users\roman\Downloads\CLOAKDROP
```
Erst `.wrangler/` in die `.gitignore` eintragen (steht dort **nicht** drin und enthält u.a. die Account-ID), dann gezielt:
```
git add assets favicon.ico tasks/build_icons.py .gitignore
git commit -m "Icon-Satz neu erzeugt, Stand des letzten Deploys"
```
**Kein `git add -A`** — der Trockenlauf nimmt 6 zusätzliche Dateien aus `.wrangler/` mit.

Dann eine Kopie außerhalb des Laptops. Sauber: privates (!) leeres Repo bei GitHub/Codeberg → `git remote add origin <url>` → `git push -u origin master`. Vorher `git log --all --oneline -- tasks/_gate-credentials.txt` — muss leer sein. Wenn das heute zu viel ist: Minimum ist der Ordner **ohne `.git/` und ohne `dist/`** als ZIP auf einen Stick oder in die Cloud, 2 Minuten.

**Die Pages-Auslieferung nicht auf Git umstellen** — sonst geht jeder Push automatisch live, und `tasks/` samt PIN würde mitwandern.

### B10 · `deploy.md` berichtigen + Notfallzettel

**Wer:** Claude · **Dauer:** 25 Min.

`tasks/deploy.md:163` empfiehlt wörtlich `npx wrangler pages deploy .`. Dieser Befehl lädt `tasks/` mit hoch — darin `_gate-credentials.txt` mit PIN und Secret, dazu der komplette Launch-Audit. Der Fehlgriff würde **nicht auffallen**, weil er funktioniert: `_headers` und `functions/` liegen im Wurzelverzeichnis, die Seite sähe danach normal aus. Nur hinge der ganze Innenraum daneben. Das ist der wichtigste Einzelschritt der Dokumentationsarbeit.

Zu berichtigen:
- Zeile 163 und 91–94: durch `python tasks/build_dist.py` + `npx wrangler pages deploy dist --project-name=cloakdrop` ersetzen, den Satz „den Projektordner hochladen" streichen, fette Warnzeile setzen
- Zeile 27: „dein Vater als Kontoinhaber" stimmt nicht — `wrangler whoami` zeigt `roman.grosz@icloud.com`
- Zeile 167–169: der Redeploy-freie Schalter — durch das in B5 gemessene Ergebnis ersetzen

**Notfallzettel** (eine Seite, ausgedruckt, neben den Laptop — nicht als Datei auf demselben Laptop):

| Fall | Griff | Dauer |
|---|---|---|
| Seite muss sofort weg | Zone → DNS → Eintrag `cloakdrop.com` und `www` auf **Paused** | Minuten; Wiederanschalten kostet bis zu 30 Min. |
| Seite soll nur zu | `GATE_PIN` setzen + `python tasks/build_dist.py` + `npx wrangler pages deploy dist --project-name=cloakdrop` | ein Deploy |
| Alter Stand | Dashboard → Workers & Pages → cloakdrop → Deployments → Menü am Eintrag → **Rollback to this deployment** | Sekunden |
| Kontakt | `hello@cloakdrop.com` · Projekt `cloakdrop` · Account `0d9ee4afc1592afe9f778c57d554f4bb` | |

**Nicht** die Custom Domain im Pages-Projekt löschen, wenn es schnell gehen muss: der DNS-Eintrag bliebe stehen und `cloakdrop.com` lieferte danach eine Cloudflare-Fehlerseite — sichtbar kaputt statt sauber abgeschaltet.

### B11 · Konto absichern und den Vater dranlassen

**Wer:** du + dein Vater · **Dauer:** 10 Min.

Das Konto, an dem Domain, Website, DNS und der Mailweg hängen, ist heute nur durch ein Passwort geschützt (`2FA enabled: False`, gemessen). Und es läuft auf die iCloud-Adresse eines 14-Jährigen, während der Vater ab morgen im Impressum haftet. Cloudflares Nutzungsbedingungen setzen außerdem Volljährigkeit voraus.

1. `dash.cloudflare.com` → oben rechts Profilsymbol → **My Profile** → **Authentication** → **Two-Factor Authentication** einschalten, per Authenticator-App (nicht SMS)
2. Recovery-Codes **ausdrucken** und außerhalb des Laptops verwahren
3. **Manage Account** → **Members** → **Invite** → private Adresse des Vaters, Rolle **Super Administrator** (ein einfacher „Administrator" kommt nicht an den Registrar-Bereich, und dort liegt die Domain). Ob Free-Konten Mitglieder-Einladungen zulassen, muss im Dashboard nachgesehen werden — falls nicht, ist das Minimum: Login und Recovery-Codes liegen dem Vater auf Papier vor.
4. Merksatz aufschreiben: **die Konto-E-Mail niemals auf `hello@cloakdrop.com` ändern** — dieses Postfach hängt am selben Konto, das man damit wiederherstellen will. Und die Konto-Adresse **nicht** einfach auf die des Vaters umstellen: das nimmt dir den Login, aus dem heraus deployt wird.

### B12 · Optional: `www` → Apex per 301

**Wer:** du · **Dauer:** 8 Min. · **kein Muss für morgen**

Wenn `www` in A1 als zweite Custom Domain mitläuft, reicht das — auf allen 16 Seiten steht ein `canonical` auf `https://cloakdrop.com/…`, Doppelindexierung ist damit abgedeckt. Der 301 ist die saubere Variante:

Zone `cloakdrop.com` → **Rules** → **Redirect Rules** → **Create rule**
- When: Field `Hostname`, Operator `equals`, Value `www.cloakdrop.com`
- Then: Type `Dynamic`, Expression `concat("https://cloakdrop.com", http.request.uri.path)`, Status `301`, **Preserve query string** anhaken

Zu bedenken: `Strict-Transport-Security` steht ohne `includeSubDomains` nur für den Host, der antwortet. Bei einer reinen Edge-Redirect-Rule bekommt `www` gar keinen HSTS-Header — als zweite Pages-Domain schon.

### B13 · Morgen, 15:45 bis 16:10 — der Startablauf

**Wer:** du · **Dauer:** 25 Min.

Nicht „GATE_PIN löschen und hoffen". Der saubere Start kommt ohne Secret-Semantik aus: `dist/` einmal **ohne** `functions/` bauen. Dann ist die Wand beweisbar weg, egal wie Cloudflare Variablen bindet — ein Befehl, ein Messwert.

| Zeit | Schritt |
|---|---|
| 15:45 | In `tasks/build_dist.py`: Zeile 20 `'functions'` aus `DIRS` entfernen **und** Zeile 56 (`assert (DIST / 'functions' / '_middleware.js').is_file()`) streichen. Beides, sonst bricht der Build ab. |
| 15:50 | `python tasks/build_dist.py` → Ausgabe muss **31 statt 32 Dateien** melden |
| 15:52 | `npx wrangler pages deploy dist --project-name=cloakdrop` |
| 15:55 | `curl -s -o /dev/null -w "%{http_code}\n" https://cloakdrop.com/` → **200** |
| 15:56 | `curl -sI https://cloakdrop.com/assets/og.png` → 200, `content-type: image/png` |
| 15:58 | `curl -s -o /dev/null -w "%{http_code}\n" https://cloakdrop.com/tasks/_gate-credentials.txt` → **404**. Dasselbe für `/tasks/launch.md` und `/tasks/todo.md`. |
| 16:00 | Ersten Link **an dich selbst** schicken (WhatsApp-Notiz / Signal an eigene Nummer). Vorschaukarte anschauen: Bild da, Titel da, Beschreibung da. |
| 16:05 | Erst wenn die Karte stimmt: Link an andere. |

Warum der Umweg über die eigene Chat-Notiz: Messenger holen die OG-Karte genau einmal und behalten das Ergebnis Tage bis Wochen — an ihren Cache kommt man von außen nicht heran. Wird der erste Link um 16:00:01 geteilt, während die Domain noch hakt, friert die leere Karte bei allen Empfängern gleichzeitig ein.

Nebeneffekt, der Geld und Ärger spart: `functions/_middleware.js` läuft heute **vor jeder einzelnen Datei**, auch vor Icons und robots.txt. Jede dieser Anfragen ist ein Workers-Aufruf gegen das Limit von 100.000/Tag im Free-Plan; bei Überschreitung liefert Cloudflare Error 1027 für die komplette Seite bis Mitternacht UTC. Ein kalter Seitenaufruf sind gemessen bis zu 6 Aufrufe → bei rund 12.500 Besuchen an einem Tag wäre Schluss. Ohne `functions/` ist die Seite zu 100 Prozent statisch und hat gar kein Tageslimit mehr — „requests to static assets are free and unlimited".

Der Preis: du gibst den Schnell-Notaus per `GATE_PIN` auf. `functions/_middleware.js` bleibt im Repo — wenn die Wand je wieder gebraucht wird, ist sie zwei Zeilen in `build_dist.py` entfernt, plus ein Deploy. Der DNS-Pause-Weg vom Notfallzettel bleibt als schnellster Griff bestehen.

**Gesamtdauer Stufe B: rund 2 Stunden 45 Minuten aktive Arbeit** — davon 25 Minuten erst morgen ab 15:45.

---

## Stufe C — erste Woche

| # | Was | Wer | Dauer |
|---|---|---|---|
| C1 | **Registrar + Billing durchsehen.** Postfach nach der ICANN-Verifizierungsmail durchsuchen (Betreff sinngemäß „Verify your email address"), im Profil muss **Verified** stehen — ohne Klick suspendiert die Registry die Domain nach 15 Tagen, und dann ist auch `hello@` tot. Dann **Domain Registration → Manage Domains → cloakdrop.com**: Auto-Renew EIN (Cloudflare setzt das per Vorgabe, einmal mit dem Auge kontrollieren), Ablauf 30.07.2027. **Billing → Payment Info**: Karte gültig über Juli 2027 hinaus. **Billing → Subscriptions**: darf nur die Domain enthalten, kein „Workers Paid", kein R2. Kalendereintrag 01.07.2027. | Vater | 12 Min. |
| C2 | **CAA-Records setzen — erst NACH grünem Zertifikat.** Ohne CAA darf weltweit jede CA ein gültiges Zertifikat auf `cloakdrop.com` ausstellen; bei einer Seite, deren Versprechen daran hängt, dass genau dieser Code aus genau dieser Quelle kommt, ist das der direkteste Angriff. Vorher messen, wer tatsächlich ausgestellt hat: `curl -sv https://cloakdrop.com/ 2>&1 \| grep -i issuer`. Dann Zone → DNS → Add record, Type **CAA**, Name `@`, Flags 0 — je einmal `issue` **und** `issuewild` für `letsencrypt.org`, `pki.goog`, `ssl.com`, `digicert.com`, `comodoca.com`. Universal SSL deckt auch `*.cloakdrop.com` ab; wer `issuewild` vergisst oder die Liste zu kurz hält, **sperrt sich bei der nächsten Erneuerung selbst aus**. Optional `iodef` → `mailto:hello@cloakdrop.com`. | du | 10 Min. |
| C3 | **HSTS hochziehen, 7 Tage nach dem Start.** `max-age=86400` ist als Startwert richtig — ein Jahr Gedächtnis wäre bei einem Fehlgriff ein Jahr lang nicht zurückzunehmen. Wenn die Domain stabil läuft: in `_headers` Zeile 5 auf `Strict-Transport-Security: max-age=31536000; includeSubDomains`, neu bauen, deployen, nachmessen. **hstspreload.org bewusst nicht einreichen** — die Aufnahme ist praktisch unumkehrbar und zwingt jede künftige Subdomain (auch einen späteren Relay) zu HTTPS. | Claude | 4 Min. |

**Gesamt Stufe C: rund eine halbe Stunde.**

---

## Stufe D — erst mit dem Relay (heute nur Dokumentation)

Nichts davon wird jetzt gebaut. Es sind drei Stellen in `tasks/relay.md`, die falsch sind und jemanden in die falsche Richtung bauen lassen würden.

| # | Was | Wer | Dauer |
|---|---|---|---|
| D1 | **`relay.md:60-65` (Rate-Limit-Tabelle) korrigieren.** Geplant sind vier Regeln mit „5 pro Stunde", „240 pro Minute" usw. Der Free-Plan kann davon **nichts**: genau 1 Regel, Zeitfenster ausschließlich 10 Sekunden, Zähler ausschließlich IP, Sperrdauer 10 Sekunden. Ein 1-Stunden-Fenster gibt es erst auf **Enterprise**, vier gleichzeitige Regeln erst auf **Business**. Vermerk eintragen: die Mengenbegrenzung gehört in den Worker (Kontingente in KV, Abschnitt 3), nicht an den Edge. **Heute keine Rate-Limit-Regel anlegen** — es gibt nichts zu schützen, und 50 Anfragen/10 s treffen bei 6 Anfragen je Seitenaufruf einen Nutzer mit mehreren Tabs. | Claude | 15 Min. |
| D2 | **`relay.md:42-43` (Billing-Alarme) korrigieren.** Nutzungsbasierte Billing-Notifications sind Pay-as-you-go/Professional vorbehalten, Security Events Alert ist Business/Enterprise. Auf diesem Konto kommt **keine** Mail, wenn Kosten entstehen. Wichtig und im Plan nicht erwähnt: die Verfügbarkeit hängt an der **Zonen**-Stufe (Pro = 20 USD/Monat), nicht am Workers-Plan — wer später Workers Paid aktiviert, bekommt die Notifications trotzdem nicht. Der stündliche Cron-Not-Aus ist damit nicht die Ergänzung zum Alarm, er ist der einzige Zaun. **Keinen DDoS-Alert einrichten** — die DDoS-Abwehr ist unmetered und läuft ohne Zutun, auf so eine Mail gibt es bei einer statischen Seite keine sinnvolle Reaktion. | Claude | 10 Min. |
| D3 | **`relay.md:36-38` und `:110-111` mit Zahlen füllen.** Not-Aus bei **8 GB belegtem R2-Speicher** (von 10 GB frei) und **30.000 Klasse-A-Operationen am Tag**, dazu die Tages-/IP-Kontingente aus `:77` als schnellerer dritter Auslöser — ein reiner Byte-Schwellwert reagiert gegen einen einzelnen Angreifer zu träge. Und die Korrektur sauber benennen, sonst wirkt sie wie ein Fehler: **je Drop** kostet Speicher nur Faktor 3 mehr als die Schreiboperationen (0,00175 vs. 0,00056 USD), aber beim **Freikontingent** läuft der Speicher rund 90-mal früher leer (86 gegen 8.000 Drops bei 7 Tagen Haltezeit). Egress ist bei R2 kostenlos — die Angst vor Download-Traffic ist unbegründet, die vor liegengebliebenen Dateien nicht. Kürzere Ablaufzeit ist der wirksamste Kostenhebel überhaupt. | Claude | 30 Min. |
| D4 | Vor dem Relay-Start eine **Prepaid- oder Limitkarte** hinterlegen statt der Hauptkarte. Cloudflare kennt keinen Ausgabendeckel — das ist der einzige echte. Gilt ab dem Relay, nicht ab morgen. | Vater | — |

---

## Was ausdrücklich NICHT zu tun ist

- **Keine `_routes.json`** heute bauen. Sie löst das Worker-Limit zu 85 Prozent, das Weglassen von `functions/` morgen löst es zu 100 — und die Datei würde Icons, robots.txt und sitemap.xml auch während der PIN-Phase ohne PIN freigeben.
- **Keine Deploy-Probe mit vier Deployments.** Jedes Test-Deployment ohne Wand hinterlässt eine dauerhaft offene eigene Adresse — genau das Loch, das B5 schließt.
- **`npx wrangler pages deploy .` nie ausführen.** Immer `dist`.
- **Die Pages-Auslieferung nicht auf Git umstellen.**
- **`cloakdrop.com` bis zum Anlegen der Custom Domain in keinem Browser aufrufen.**
- **CAA nicht vor dem ersten Zertifikat setzen**, SPF nicht von `~all` auf `-all` ändern, HSTS-Schalter in der Zone auslassen, kein Preload.

---

## Zusammenfassung der Zeiten

| Stufe | Aktive Arbeit | Wer trägt die Hauptlast |
|---|---|---|
| **A — blockiert den Start** | **60 Min.** (+ Wartezeit Zertifikat, läuft nebenher) | du (15 Min.) · Vater + Claude (45 Min.) |
| **B — vor dem Öffentlichmachen** | **2 h 45** (davon 25 Min. erst morgen 15:45) | du (ca. 1 h 20) · Claude (ca. 65 Min.) · Vater (20 Min.) |
| C — erste Woche | ~30 Min. | Vater, du, Claude |
| D — erst mit dem Relay | ~55 Min. reine Dokumentation | Claude |

A und B zusammen sind rund **3 Stunden 45 Minuten** — machbar heute Abend, wenn A1 als Allererstes geklickt wird, damit das Zertifikat währenddessen ausgestellt wird.