Projekt P-5050 · Kunde K-1050 · Dok-Nr. P-5050-SI · Stand 27.10.2026, 13:48 Uhr

Konzept im ÜberblickWie es gebaut ist

Kapitel 13 von 16

Sicherheit

Wie Bululu Konten, Bewerberdaten und Formulare vor Angriffen schützt, Missbrauch sperrbar macht und dabei ehrliche Nutzer nicht unnötig ausbremst.

  • Lesezeit rund 17 Min.
  • Für Alle

Auf einen Blick

Das Wichtigste aus diesem Kapitel in einer Minute. Alle Einzelheiten stehen darunter.

  • Sicher, aber nicht zu strengMaßstab sind die bekannten Prüflisten OWASP Top 10 und ASVS Stufe 2. Wo Strenge Nutzer vertreiben würde, ist die Abwägung offen beschrieben.
  • Sperren nach IP, E-Mail und DomainDie Verwaltung kann Störer mit Grund und Frist sperren. Solche Einträge treffen nur das Registrieren und Formulare ohne Konto, nie das Anmelden und Bewerben mit Konto.
  • Zweiter Faktor per App oder E-Mail-CodeBewerber und Firmennutzer wählen zwischen einer App und einem Code per E-Mail. Firmen dürfen den zweiten Faktor verlangen; die Verwaltung nutzt immer die App.
  • Wartezeit statt KontosperreNach mehreren falschen Kennwörtern wächst die Wartezeit, das Konto wird aber nie dauerhaft gesperrt. So kann niemand fremde Konten absichtlich aussperren.
  • Mail-Links wirken erst nach KnopfdruckVirenscanner in Firmenpostfächern öffnen Links vorab. Darum zeigt jeder Link aus einer Mail erst an, was passiert, und erst ein Knopf führt es aus.
10Zeichenhat ein Kennwort mindestens, Sonderzeichen werden nicht erzwungen
30Tagebleiben Anmeldungen mit IP-Adresse im Sicherheitsprotokoll
4StundenZiel für die Wiederherstellung aus der Sicherung, die zweimal täglich läuft

Stand 26.10.2026, Nomad. Auftrag Nomad (26.10.): „Kunden und Bewerber sperren können, auch nach IP; die Seite muss sicher vor Hacks sein, nur nichts zu strikt; 2FA mit Code per E-Mail, Kennwort vergessen und solche Dinge.“ Maßstab: OWASP Top 10 (2021) und ASVS Stufe 2 als Richtschnur, nicht als Pflichtenheft; wo Strenge Nutzer verliert, steht hier die bewusste Abwägung. Diese Datei ist der eine Ort für die Sicherheits-Prüfliste; der Plan (_berichte/PLAN-BACKEND-2026-10-21.html, Abschnitt „Sicherheit und Datenschutz“) verweist hierher, die Karten regeln die Einzelheiten an der Fundstelle. Was hier als „ergänzt“ steht, wurde am 26.10. in Plan oder Karte eingetragen.

Fundstellen: K0 Teil 4 = Karte 0 (_planung/karten/0-betrieb.md), K1a 2.2 = Karte 1a, K1b, K2a, K3, K4; P Sicherheit = Abschnitt im Plan; H… = Handlung in _planung/knopfliste.md; T = TEXTE-UND-MAILS.

1

Was neu dazukam (26.10.)

Neue Schutzfunktionen: Sperrliste, Anmeldeprotokoll, Code per E-Mail, Warnung bei neuem Gerät, Kennwortregeln und Alarm bei auffälligen Anmeldeversuchen.

ThemaKurzFundstelle
SperrlisteSperren nach IP-Adresse, E-Mail-Adresse, Mail-Domain (IP-Bereich erst 2b.9); je Eintrag Grund (Pillen), Frist (Standard 7 Tage bei IP, 30 Tage bei E-Mail und Domain), wer, wann; alle Einträge (IP, E-Mail, Domain) wirken nur auf Registrierung und die Formulare ohne Konto (Kontakt, Job-Alarm, Melden, Werbeanfrage), nie auf Anmelden und Bewerben mit Konto; gesperrte Nutzer sehen eine neutrale Meldung; ersetzt die alte „Adresse sperren“K3 1.15, Teil 3 sperrliste, H136
Sicherheitsprotokoll der AnmeldungenTabelle anmeldungen (IP, Gerät grob, Zeit, Ergebnis), 30 Tage; speist Geräte-Liste, Haken „auch IP-Adressen sperren“, Bremse und AlarmK2a Teil 3 anmeldungen, K3 1.16, H142
Zweiter Faktor per E-Mail-CodeBewerber und Firmennutzer wählen App oder E-Mail-Code (6 Ziffern, 10 Minuten); Firmen-Pflicht akzeptiert beides; Verwaltung bleibt bei AppK2a 1.2, 1.9 Nr. 4, H137, H138; Mail A12
Anmeldung von neuem GerätMail A13 mit „Das war ich nicht“ → Kennwort neu und überall abmeldenK2a 1.2, H139; Mail A13
Kennwort-Regeln mit Maßmindestens 10 Zeichen, keine Zwangs-Sonderzeichen, lokale Liste häufiger Kennwörter; Fehlversuche: wachsende Wartezeit je Konto (ab 5) und je IP (erst ab 50, eigener Satz) statt Kontosperre; „Kennwort vergessen?“ nach 3 FehlversuchenK2a 1.1 Nr. 3, 2.2 dienste/auth, H55, H140
Erneute Kennworteingabe vor heiklen Aktionenein Baustein, 5 Minuten gültig; Konten ohne Kennwort bestätigen im selben Fenster mit einem Code an ihre Adresse; Faktor abschalten oder umstellen, Codes neu erzeugen, Konto löschen, Firma löschen, Verwaltung: Konto oder Firma löschen, E-Mail ändernK2a 2.1 kennwortbestaetigung/, H141
Sicherheits-Kopfzeilen und Cookiesgenaue Liste (CSP mit Nonce, HSTS, Referrer-Policy, Permissions-Policy, X-Content-Type-Options, frame-ancestors) und Cookie-AttributeAbschnitt 2 Nr. 5 bis 7 hier, P Sicherheit „Webschutz“
Server-seitige AbrufeCOPLA-Adresse nur aus fester Liste erlaubter Hosts, keine privaten Netze, keine Weiterleitungen folgenK3 1.13
AlarmHäufung fehlgeschlagener Anmeldungen → Betriebsmail + Zeile beim Super Admin mit Sprung zur SperrlisteK3 1.16, H142
Einfachheit (26.10., Bericht EF)Wartezeit je IP erst ab 50 Fehlversuchen mit eigenem Satz (Nr. 11); IP-Sperren nur Registrieren und Formulare ohne Konto, 7 Tage Standard, IP-Haken nur bei Betrug/Spam mit Folgen-Satz (Nr. 14, Abwägung 3); Wiederherstellungscodes nur bei der App (Nr. 13, Abwägung 5); bekannte Geräte 12 Monate in konto_geraete (Nr. 22)K2a 1.1 Nr. 3, 1.2, 1.9 Nr. 4; K3 1.4 Nr. 7, 1.15, D44
2

Prüfliste

Punkt für Punkt, wie Bululu die bekannten Angriffsarten abwehrt, von fremden Formularen über Dateien bis zur Zahlung.

Je Punkt eine Zeile: Maßstab, wie Bululu es regelt, wo es steht, Stand (geregelt = stand schon so im Plan; ergänzt = am 26.10. eingetragen; bewusst offen = Abwägung, siehe Abschnitt 3).

NrPunkt (OWASP / ASVS)So macht es BululuFundstelleStand
1Zugriffskontrolle, Firmen-Trennung (A01, IDOR)Jede Seite und Aktion prüft Rolle und Firma selbst (darf()), dazu Zeilensperre in der Datenbank (RLS) je Sitzung; Kennungen UUID v7, nie fortlaufend; je Endpunkt ein Test mit fremder Firmen-Sitzung; Verwaltung schreibt nur mit Protokoll-Zeile (Trigger D18); Support-Modus mit Sperrliste der HandlungenP Sicherheit „Firmen-Trennung doppelt“; K2a 2.2 fachlich/rechte.ts, dienste/db/rls.ts, Teil 4; K3 Teil 3 RLS, 1.5; _planung/rolle-ding-handlung.mdgeregelt
2Einschleusen in Datenbank (A03, SQL)Drizzle mit gebundenen Parametern, kein Zusammenbau von SQL aus Text; Suche über Volltext-Funktionen mit Parametern; jsonb-Felder nie gefiltertK1a Teil 3 (Regeln), K1a 2.2 fachlich/suche/geregelt
3Einschleusen in Seiten (A03, XSS)React gibt Text immer entschärft aus; einziges HTML aus Nutzerhand sind Ratgeber, Rechtstexte, Seitentexte und Firmen-Beschreibung: inhalt/bereinigen.ts mit Erlaubnisliste (keine Skripte, keine Ereignis-Attribute, nur http/https/mailto-Links, rel="noopener"), beim Einspielen und beim Speichern aus der Verwaltung; Mailvorlagen der Verwaltung sind reiner Text mit Platzhaltern (kein HTML), der Rahmen bleibt Code; Nutzertexte in Mails (Einladung, Absage, Nachricht) stehen als Text, nicht als HTML; PDF-Mappe (2b) entschärft Text vor dem EinsetzenK1b 2.1 inhalt/, H40 (Test: Script-Tag entfernt); K3 1.10 Nr. 2, 3; P Sicherheit „Bewerbungsmappe“geregelt (Mailvorlagen-Regel am 26.10. hier klargestellt: K3 1.10 Nr. 2 sagt „Text als einfaches Textfeld“)
4Fremde Formulare (CSRF, Herkunft)Server-Aktionen von Next.js prüfen Origin gegen Host; serverActions.allowedOrigins nur die eigene Adresse je Umgebung; Formulare ohne JS laufen über dieselben Aktionen; Sitzungs-Cookie SameSite=Lax, darum sendet ein fremdes Formular kein Cookie mit; Beacon /api/z nimmt nur JSON mit passendem OriginP Sicherheit „Herkunftsprüfung der Formulare“; K1a app/api/z/route.ts (Bot-Filter)geregelt, Einzelheiten hier
5Umleitungen (A01, offene Weiterleitung)weiter= nur auf eigene Pfade (beginnt mit /, kein //, kein Schema), sonst Standardziel; Verwaltung nur /verwaltung-Pfade; Werbe-Klick /api/w/[id] leitet nur auf die gespeicherte zielseite (https, von der Verwaltung eingetragen)K2a H55 (Test „fremde Adresse in weiter → /mein-bereich“); K3 1.12, 1.6 Nr. 4geregelt
6Klick-Fallen (Clickjacking)Content-Security-Policy: frame-ancestors 'none' (Bululu wird nirgends eingebettet; die COPLA-Stellen laufen über Abruf, nicht über iframe); zusätzlich X-Frame-Options: DENY für alte Browserhier; P Sicherheit „Webschutz“ergänzt
7Sicherheits-Kopfzeilen (A05)proxy.ts (Middleware) erzeugt je Anfrage einen Nonce und setzt für alle Antworten: CSP (default-src 'self'; script-src 'nonce-…' 'strict-dynamic' plus 'self' als Rückfall für alte Browser, kein unsafe-inline und kein unsafe-eval für Skripte; style-src 'self' 'unsafe-inline' nur, solange Next.js Inline-Styles für Schriften braucht, sonst Nonce; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'; upgrade-insecure-requests), Strict-Transport-Security: max-age=31536000; includeSubDomains, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), X-Content-Type-Options: nosniff, Cross-Origin-Opener-Policy: same-origin. Nonce auf jeder Seite, auch öffentlich (Entscheidung 26.10., Bewertung): Next.js (App Router) bettet auf jeder Seite eigene Inline-Skripte ein (Hydrations-Daten, je Seite und Bau verschieden), darum ist eine Hash-Liste nicht pflegbar und „Nonce nur mit Sitzung“ nicht haltbar. Folge: öffentliche Seiten werden nicht als ganze Seite zwischengespeichert (kein Full-Route-Cache, HTML mit Cache-Control: private, no-store), sondern ihre Daten (Suchen, Stellenlisten, Fachbereichs-Zahlen über den Daten-Zwischenspeicher von Next.js, 60 Sekunden, Karte 1a 2.2 fachlich/); feste Dateien (_next/static, Bilder, Schriften) bleiben ein Jahr zwischengespeichert. Eigene Skripte laden nur über Next.js-Bundles (nie <script> von Hand), damit strict-dynamic sie erfasst. Annahme, bestätigt in Paket 1a.1: next build zeigt alle Seiten als dynamisch, pruefung/kopfzeilen.test.ts liest die Kopfzeilen einer öffentlichen und einer Konto-Seite (Nonce vorhanden, je Anfrage verschieden, in jedem <script> der Antwort, kein unsafe-inline im script-src); der Nachtlauf (Karte 4 Teil 2b) zählt CSP-Verstöße in der Browser-Konsole als Fehler. Trägt die Annahme nicht (z. B. weil Next.js 16 die Nonce-Weitergabe an alle Inline-Skripte nicht leistet), ist der Rückfall script-src 'self' 'sha256-…' für die festen Next.js-Skripte plus 'unsafe-inline'-Verbot für alles andere, und der Weg dorthin wird in STAND.md notierthier; P Sicherheit „Webschutz“; K1a 1a.1 proxy.ts, next.config; Baureihenfolge 1a.1ergänzt (26.10. entschieden: Nonce überall, Daten statt Seiten zwischenspeichern)
8Cookies (A07)Sitzungs-Cookie HttpOnly; Secure; SameSite=Lax; Path=/, Name mit Präfix __Host- (erzwingt Secure und keine Domain), Laufzeit nach Rolle (30/14 Tage, Verwaltung 8 Stunden); Sprach-Cookie sprache ohne HttpOnly (liest nur proxy.ts, kein Geheimnis); Cookie-Hinweis nennt nur diese beiden (notwendige)K2a Teil 3 sitzungen, 7.2 C13; K3 D2; K1a 1.1 Cookie-Hinweisgeregelt, Attribute hier festgelegt
9Sitzungen (A07)Sitzungen in der Datenbank (Token nur gehasht), Wechsel des Tokens nach Anmeldung und nach Faktor-Bestätigung (gegen Sitzungs-Fixierung), Ende aller anderen Sitzungen bei Kennwortwechsel, Faktor-Rücksetzung, E-Mail-Wechsel und „Das war ich nicht“; Geräte-Liste mit „Überall abmelden“; „halbe Sitzung“ bis der Faktor bestätigt ist; Verwaltung fest 8 Stunden ohne „angemeldet bleiben“; zwischengespeicherte Sitzungsprüfung abgeschaltetP Sicherheit „Sitzungen“, „Zweiter Faktor“; K2a 1.2, 1.9 Nr. 5, Teil 3 sitzungen; K3 D2, 1.4ageregelt
10Kennwörter (A07, ASVS 2.1)Argon2id über Better Auth; mindestens 10 Zeichen, höchstens 128, keine Vorgabe für Sonderzeichen oder Wechsel; Prüfung gegen eine lokale Liste der 100.000 häufigsten Kennwörter (Datei im Projekt, kein Dienst eines Dritten) und gegen E-Mail-Adresse und Namen der Person; Kennwortänderung meldet A02; Kennwort vergessen: Link 60 Minuten, einmalig, immer dieselbe Antwort; neues Kennwort beendet alle anderen SitzungenK2a 1.1, 1.2, 1.9 Nr. 3, 2.2 dienste/auth/kennwort.ts, H59, H140ergänzt (Liste war „optional nein“, jetzt ja; Namens-Prüfung neu)
11Fehlversuche, Bremsen (A07, ASVS 2.2)Anmelden: wachsende Wartezeit aus anmeldungen, kein dauerhaftes Aussperren. Je Konto ab dem 5. Fehlversuch in 15 Minuten 1 Minute, dann je weiterem Fehlversuch verdoppelt bis 15 Minuten (Satz fehler.versuche.anmelden). Je IP erst ab 50 Fehlversuchen in 15 Minuten, dann dieselbe Staffel; der Satz dafür ist fehler.versuche.netz ohne Schuldzuweisung („Aus Ihrem Netz kamen gerade viele Anmeldeversuche …“); ab dem 3. Fehlversuch je Konto steht „Kennwort vergessen? Neues Kennwort anfordern“ hervorgehoben unter dem Feld; Faktor-Code 5 Fehlversuche → Code verfällt (App: 15 Minuten warten); E-Mail-Code höchstens 3 je 15 Minuten; Formulare (Kontakt, Job-Alarm, Melden, Werbeanfrage, Registrierung): Honigfeld und Bremse je Mail und IP (Zähler flüchtig, IP gehasht); Kennwort vergessen und Bestätigungs-Mails höchstens 3 je Adresse und StundeK2a 1.1 Nr. 3, H55, H60, H137, H140; K1b 2.1 bremse/, honigfeld/; K3 1.8 (Melden 3/Stunde); P Sicherheit „Bremsen“ergänzt (Wartezeit statt „10 Fehlversuche → Sperre“)
12Bot-Schutz, ZusatzprüfungHonigfeld und Bremse zuerst (kein Rätsel für ehrliche Nutzer). Rückfall nur, wenn eine IP oder Adresse die Bremse mehrfach reißt: selbst gehostete Rechenaufgabe im Browser (Proof-of-Work, z. B. ALTCHA-Baustein ohne Dienst eines Dritten), die der Server prüft; kein Google reCAPTCHA, kein hCaptcha, keine Anfrage an Dritte. Bau erst, wenn der Betrieb Spam zeigt (2b, Idee), bis dahin Sperrliste von Handhier; K1b 2.1; K3 1.15Vorschlag, bewusst 2b
13Zweiter Faktor (A07, ASVS 2.8)TOTP-App (±1 Schritt, Wiederverwendung gesperrt) oder E-Mail-Code (6 Ziffern, 10 Minuten, gehasht gespeichert, einmalig); 10 Wiederherstellungscodes je Hash nur bei der App; bei der App kein eigener Pflichtschritt, sondern Rand-Knopf „Codes als Datei speichern“ im Einrichtungs-Fenster, die Codes bleiben in den Einstellungen abrufbar; Verwaltung nur App (Begründung K3 1.12); nie zwei E-Mail-Codes hintereinander: nach einer Anmeldung per E-Mail-Code ist bei Art „Code per E-Mail“ das Kennwort der zweite Schritt, und Konten ohne Kennwort können diese Art erst nach dem Festlegen eines Kennworts wählen; Firmen-Pflicht akzeptiert beides; Rücksetzung durch die Verwaltung nur per Link, bei der App nach 24 Stunden mit Abbrechen-Link, beim E-Mail-Code sofortK2a 1.2, 1.9 Nr. 4, Teil 3 faktor, faktor_mail_codes, H60, H137, H138; K3 1.4 Nr. 4, 1.12ergänzt (E-Mail-Code)
14Sperren von Personen, Firmen, Adressen (A07, Missbrauch)Konto sperren (B18/B19), Firma sperren (F14/F15), Stelle sperren (F22) mit Grund und Protokoll; Sperrliste nach IP, Mail, Domain (IP-Bereich erst 2b.9) mit Frist (IP 7 Tage Standard, nur Registrieren und Formulare ohne Konto; Mail und Domain 30 Tage, ebenfalls nur Registrieren und Formulare ohne Konto); Haken „auch die zuletzt genutzten IP-Adressen sperren“ nur beim Grund Betrug/Spam, mit Folgen-Satz; gesperrte Besucher erfahren nicht, dass sie gesperrt sindK3 1.4 Nr. 7, 8, 1.15, H105 ff., H136ergänzt
15Konto-Aufzählung (ASVS 2.2.2)Anmelden: ein Satz für falsche Mail und falsches Kennwort; Kennwort vergessen: immer dieselbe Antwort; Bestätigungs-Mail erneut: immer dieselbe Antwort; Registrieren mit vergebener Adresse zeigt „schon vergeben“ (bewusst, Abschnitt 3 Nr. 1), aber nur gebremst (höchstens 10 Versuche je IP und Stunde); kontoStatus(mail) im Bewerben-Fenster ebenso gebremst (10 je IP und Stunde) und hinter dem Honigfeld; liefert nur „neu“ oder „vorhanden“, nie ob ein zweiter Faktor oder ein Kennwort besteht; darum sehen alle vorhandenen Konten im Bewerben-Fenster denselben Aufbau (Code-Knopf vorn, Kennwort aufklappbar), und die Anmeldeseite zeigt den Code-Weg „Ohne Kennwort anmelden: Code per E-Mail“ für jede Adresse mit derselben Antwort, auch ohne KontoK2a 1.1, 1.2, H56, H59, 3.2 kontoStatusgeregelt, Abwägung dokumentiert
16Datei-Hochladen (A04, ASVS 12)Typ am Inhalt erkannt (Magic Bytes), erlaubte Typen je Zweck (Lebenslauf: PDF, JPG, PNG, HEIC bis 10 MB, kein Word zum Livegang (K2a 1.3 Nr. 3); Logo: PNG, JPG, WebP bis 2 MB; Werbemotiv: JPG, PNG, WebP, GIF bis 2 MB; kein SVG, kein HTML, keine Skripte); ClamAV vor der Freigabe, gestörte Prüfung sperrt die Datei; Bilder verkleinert und kodiert der Browser vor dem Hochladen neu, der Server verlässt sich darauf nicht: er prüft Typ, Größe und Pixelmaße je Zweck, entfernt bei JPG die Metadaten-Segmente ohne Neukodierung und lehnt zu große Bilder ab (fehler.datei.bildZuGross); verkleinert wird auf dem Server nur im begrenzten Rückfall HEIC → JPG (Lebenslauf-Fotos, 3 je Konto und Tag); Dateien außerhalb des Web-Roots auf verschlüsseltem Datenträger, Name = Kennung, nie der Nutzername; Auslieferung nur über /api/datei/[id] mit Rechte-Prüfung, Content-Disposition: attachment für PDF und DOCX, X-Content-Type-Options: nosniff, Content-Type aus der geprüften ErkennungP Sicherheit „Dateien“; K2a 2.2 fachlich/datei/, C16; K3 2.2 fachlich/datei/ Fall „öffentlich“, D28geregelt
17Server-seitige Abrufe (A10, SSRF)Einziger Abruf fremder Adressen ist der COPLA-Stellen-Abruf: Adresse nur https, Host muss in einstellungen.schnittstelle.erlaubte_hosts stehen (der Super Admin trägt ihn ein), keine privaten oder lokalen Netze (127/8, 10/8, 172.16/12, 192.168/16, ::1, fc00::/7), Weiterleitungen werden nicht gefolgt, Zeitgrenze 10 Sekunden, Antwort höchstens 5 MB; Karriere-Links an Stellen und Zielseiten der Werbung werden nie vom Server abgerufen, nur als Link ausgegeben (und über /api/w weitergeleitet); Mail-Klick /api/mail-klick leitet nur auf eigene PfadeK3 1.13 (ergänzt), 1.6 Nr. 4; K1a karriere_klicksergänzt
18Abhängigkeiten (A06)feste Fassungen in package.json (keine Bereiche), npm audit bei jedem Bau, monatliches Update-Paket, kritische Lücken innerhalb von 2 Werktagen; Container-Images mit fester Kennung; kein CDN, Schriften lokalP Bauordnung „Automatische Prüfungen“; K0 Teil 6; P Sicherheit „Keine Anfragen an Dritte“geregelt
19Geheimnisse (A02, A05)nur als Umgebungsvariablen der Plattform, nie im Repo (gitleaks vor Commit und Bau), .env.beispiel nur mit Namen; Verschlüsselung in der Datenbank (Faktor-Geheimnis, Schnittstellen-Schlüssel, Zertifikat) mit BULULU_GEHEIM_SCHLUESSEL; Wechsel-Rhythmus und Anlässe; echte IP nur aus VERTRAUTE_WEITERLEITERK0 Teil 4; K3 2.2 fachlich/einstellungen/geheim.tsgeregelt (Variable für Weiterleiter am 26.10. in K0 Teil 4 eingetragen)
20Fehlermeldungen ohne Interna (A05, ASVS 7.4)Fehlerseiten zeigen nur Texte aus fehler. und eine Fehlernummer (Kennung der Anfrage), nie Stacktrace, Pfad oder SQL; Server-Aktionen geben nur bekannte Fehlerschlüssel zurück, Unbekanntes wird zu fehler.netz. plus Eintrag im Fehlerprotokoll mit derselben Kennung; Datenbank-Fehlertexte verlassen den Server nie; NODE_ENV=production auf test und liveK1a 1.3 Fehler 500, 2.1 sonderseiten/; K0 Teil 9geregelt, Regel hier festgehalten
21Protokollierung sicherheitsrelevanter Ereignisse mit Alarm (A09)protokoll (12 Monate, anonymisiert nach Kontolöschung): Faktor ein/aus/umgestellt, Kennwort geändert, Sperren, Rücksetzungen, Unterlagen geöffnet, Support, Einstellungen; anmeldungen (30 Tage, mit IP): Erfolg, Fehlversuch, Faktor falsch, gebremst, gesperrt, neues Gerät; Alarm an den Super Admin (Betriebsmail D24 + Zeile „Heute zu tun“): über 100 Fehlversuche je Stunde von einer IP, Fehlversuche gegen über 20 Konten je Stunde, Faktor eines Verwaltungskontos abgeschaltet oder zurückgesetzt (Anmeldung eines Verwaltungskontos von neuem Gerät nur als Zeile „Heute zu tun“, keine Mail); unbehandelte Fehler ins Fehlerprotokoll (GlitchTip)K2a Teil 3 protokoll, anmeldungen; K3 1.16, 1.14, H142; K0 Teil 9ergänzt (Alarm-Schwellen und anmeldungen)
22Datenschutz-Sparsamkeit (ASVS 8)Zählung ohne IP und Cookies; IP nur im Sicherheitsprotokoll (30 Tage) und in der Sperrliste (mit Frist); bekannte Geräte je Konto (konto_geraete, nur Browser- und Systemname, 12 Monate) ohne IP; Geräte-Liste zeigt die IP der letzten Anmeldung gekürzt; Protokoll ohne Bewerbungsinhalte; Export und Löschung je Konto; Fristen als Nachtlauf; Datenschutzerklärung nennt Sicherheitsprotokoll und Sperrliste (Abschnitte 2.7, 2.14 in RECHTSTEXTE P-5050-RT; K0 E15)K1a 2.2 dienste/zaehler/; K2a 1.9 Nr. 5, 7, 8; K3 1.14; K0 E15ergänzt (E15)
23Sicherung und Wiederherstellung (A08, Verfügbarkeit)zweimal täglich (03:00, 15:00) restic verschlüsselt auf Speicher in Deutschland außerhalb des Servers, 30 Tage, restic check wöchentlich, Wiederherstellungsprobe Do 26.11. mit gemessener Dauer, Ziel 4 Stunden; Rückweg beim Ausrollen mit fester Image-Kennung; Migrationen „erst erweitern, später aufräumen“K0 Teil 8, Teil 6; P Betriebgeregelt
24Integrität von Bau und Ausrollen (A08)Bau nur aus dem Repo mit package-lock.json, Images mit fester Kennung, keine Skripte aus dem Netz zur Laufzeit, Gesundheitsprüfung /api/gesund vor FreigabeK0 Teil 6, Teil 9geregelt
25Schnittstelle COPLA (A07, A08)je Firma ein Schlüssel, verschlüsselt gespeichert, Wechsel mit 7 Tagen Überlappung, Signatur je Sendung (sobald COPLA sie liefert), höchstens 60 Abrufe je Minute, Protokoll je Lauf, Schalter zum sofortigen AbschaltenP „Sicherheit der Schnittstelle“; K3 1.13, D16geregelt
26Rechte der Datenbank-Rollen (A01, A05)Rollenliste, Rolle × Tabelle × Recht, Schreiben ohne Anmeldung nur über Datenbank-Funktionen, ein Pool je Rolle, Rollen je Umgebung getrennt (bululu_test_* darf bululu_live nicht öffnen): alles an EINER Stelle, Karte 0 Teil 7; keine Superuser-Rolle für die App; RLS auf allen Tabellen mit PersonendatenK0 Teil 7 (einzige Quelle); K2a Teil 3 und K3 Teil 3 nur Policies je Tabellegeregelt
27Wartung und NotausWartungsseite per Schalter (Super Admin), „Alle Verwaltungssitzungen abmelden“, Schnittstelle abschaltbar, Funktionen per Schalter abschaltbarK3 1.11 Nr. 1, 6geregelt
30Zahlungkeine Kartendaten bei uns (Bezahlseite und Kundenportal von Stripe, PCI SAQ A); Stripe-Schlüssel nur als Plattform-Variablen; Ereignisse nur mit gültiger Signatur, jede Ereignis-Kennung genau einmal (stripe_ereignisse); Betriebsumgebung nimmt nur Ereignisse mit livemode wahr an, Testumgebung nur Test-Ereignisse; Freischaltung nur aus dem Ereignis, nie aus dem Rücksprung der Bezahlseite;geplant
29Links in Mails wirken erst nach einem KnopfFirmen-Postfächer (Microsoft 365 Safe Links, Virenscanner, Vorschau) rufen jeden Link vorab per GET auf. Darum ändert kein Mail-Link beim bloßen Abruf etwas: Jede Zielseite aus einer Mail zeigt nur an, was passieren wird (GET /…?t= prüft den Token lesend und rendert eine Box mit genau einem Knopf), und erst der Knopf (POST derselben Adresse mit dem Token im Formular, Origin-Prüfung wie jede Server-Aktion) wirkt. Gilt für: E-Mail bestätigen, Konto sichern (A13: „Ja, alle Geräte abmelden“), Faktor-Rücksetzung ausführen und abbrechen (A04, A08), neue Adresse bestätigen (A10), Stelle beenden aus F09 („Stelle beenden“, nach Anmeldung), Stelle verlängern aus F08 und F09, Job-Alarm behalten (B17), Job-Alarm abbestellen (B15-Fuß: Seite „Abbestellen?“ mit Knopf). Einzige Ausnahme: der Kopf List-Unsubscribe-Post: List-Unsubscribe=One-Click (RFC 8058), den nur Mailprogramme per POST auslösen; er bleibt Ein-Klick. Links, die nur anzeigen (Stelle, Bewerbung, Trefferliste, Anmelden), bleiben normale Links. Export-Links (A05, F18) führen nur zur Einstellungs-Seite; der Download verlangt die Anmeldung des Kontoinhabers bzw. eines Firmen-Admins. Test je Zielseite: „GET ändert nichts“ (Tabelle vorher = nachher), „POST ohne Token oder mit fremdem Origin ändert nichts“K2a 1.2, 1.11 Nr. 4, 1.9 Nr. 7, 1.13 Nr. 6; K3 1.4 Nr. 4, 1.4a, 1.12; K1b 1.11; Mail-Liste Kopf; Knopfliste H47, H49, H58, H139, H203, H109, H135; T 6.0ergänzt (26.10.)
28Betreiber-Stufen und Super AdminBetreiber in zwei Stufen: Verwaltung (3BS) und Super Admin (Nomad, Enum super_admin); Super-Admin-Konten für die Verwaltung unsichtbar (Zeilensperre auf verwaltung_konten, Protokollname „Betreiber“), technische Karten fehlen im Markup, Server antwortet 403/404; Stufe ändern, Kennwort-Link und Faktor-Rücksetzung für Verwaltungskonten nur Super Admin mit Betriebsmail; Faktor-App Pflicht für beide Stufen; Notfallweg nur über den Server (db/verwaltung/notfall.ts, --bestaetigen, Protokoll, Betriebsmail); SMTP-Zugang bleibt Plattform-VariableK3 1.11, Teil 3, H145 bis H150, D38 bis D42; K0 Teil 4, Teil 11 Kapitel 11geregelt
3

Bewusste Abwägungen (nichts zu strikt)

Wo Bululu absichtlich weniger streng ist, damit ehrliche Nutzer nicht ausgesperrt oder verwirrt werden, und warum.

  1. Registrieren sagt „schon vergeben“. Streng wäre: immer „E-Mail gesendet“ und eine Hinweis-Mail an die bestehende Adresse. Das verwirrt Menschen, die ihr Konto vergessen haben, und kostet eine weitere Mail. Bululu zeigt den Hinweis mit Link „Anmelden“ und bremst stattdessen (10 Versuche je IP und Stunde). Kennwort vergessen und Bestätigungs-Mail bleiben neutral.
  2. Keine Kontosperre nach Fehlversuchen. Eine dauerhafte Sperre lässt sich als Waffe gegen fremde Konten nutzen (jemand sperrt den Admin einer Firma aus). Darum wachsende Wartezeit je Konto (ab 5 Fehlversuchen) und je IP erst ab 50 Fehlversuchen in 15 Minuten, höchstens 15 Minuten, mit eigenem Satz für die IP-Wartezeit, und früh der Weg „Kennwort vergessen?“. Die hohe IP-Schwelle schützt Unbeteiligte im selben Netz (Mobilfunk, Firmennetz); das Massen-Raten über viele Konten fängt zusätzlich der Alarm (Nr. 21) ab.
  3. IP-Sperren treffen nur Fremde ohne Konto und laufen schnell aus. Hinter einer IP-Adresse stecken oft viele Menschen (Firmen-Netz, Mobilfunk). Darum wirkt ein IP-Eintrag nur auf das Registrieren und auf die Formulare ohne Konto (Kontakt, Job-Alarm, Melden, Werbeanfrage), nie auf Anmelden und Bewerben mit vorhandenem Konto (Personen sperrt man über das Konto, Firmen über die Firma); Standard 7 Tage, Auswahl 7, 30, 90 Tage; E-Mail- und Domain-Einträge wirken auf dieselben Aufrufer mit Standard 30 Tagen, nie auf Anmelden und Bewerben mit Konto; „unbefristet“ legt nur der Super Admin an; IP-Bereiche (CIDR) und einstellbare Alarm-Schwellen kommen erst nach dem Livegang (2b.9). Der Haken „auch die IP-Adressen sperren“ in den Sperren-Fenstern erscheint nur beim Grund Betrug/Spam und sagt, dass er andere Menschen trifft.
  4. Kennwort: Länge statt Sonderzeichen. 10 Zeichen und die Liste häufiger Kennwörter reichen; Zwangs-Sonderzeichen und Ablaufdaten machen Kennwörter schlechter, nicht besser (NIST, OWASP).
  5. Zweiter Faktor freiwillig für Bewerber, wählbar per E-Mail. Der E-Mail-Code ist schwächer als die App (wer das Postfach hat, hat beides), aber weit besser als kein Faktor und für die meisten Bewerber die einzige Form, die sie nutzen. Firmen dürfen ihn verlangen (beides zählt); die Verwaltung bleibt bei der App, weil dort Bewerberdaten aller Firmen liegen. Wiederherstellungscodes gibt es nur bei der App: Beim E-Mail-Code schützt ein Code nur gegen den Verlust des Postfachs, und dann greift ohnehin die Verwaltung (Faktor zurücksetzen per Link, beim E-Mail-Code ohne Wartezeit; Adresse ändern). Zehn Codes „speichern oder drucken“ als Pflichtschritt würden die meisten wegklicken; bei der App liegen sie hinter einem Knopf und in den Einstellungen.
  6. Zusatz-Rätsel erst bei Bedarf. Honigfeld und Bremse zuerst; eine Rechenaufgabe im Browser nur als Rückfall und selbst gehostet (Nr. 12). Kein reCAPTCHA.
  7. CSP mit Nonce auf jeder Seite statt Seiten-Zwischenspeicher. Streng und zugleich schnell wäre beides nur mit einer Hash-Liste der Next.js-Skripte, die sich mit jedem Bau ändert. Bululu nimmt den Nonce überall (Nr. 7) und speichert Daten statt ganzer Seiten zwischen; die Seiten sind klein, die Abfragen laufen aus dem Daten-Zwischenspeicher. Für Styles bleibt unsafe-inline, solange Next.js es braucht; die Prüfung am Abschnittsende 1a misst, ob die Ausnahme fallen kann.
4

Prüfungen (ohne Browser, Teil der Prüfsammlung)

Welche automatischen Tests prüfen, ob die Schutzmaßnahmen wirklich greifen.

Dieser Abschnitt beschreibt nur die technische Umsetzung. Mit dem Schalter „Technische Angaben“ blenden Sie ihn ein.

  • pruefung/kopfzeilen.test.ts: alle Kopfzeilen aus Nr. 7 und Cookie-Attribute aus Nr. 8 auf einer öffentlichen Seite und einer Konto-Seite; dazu die CSP-Annahme (Nonce je Anfrage verschieden, in jedem <script> der Antwort, kein unsafe-inline im script-src, HTML Cache-Control: private, no-store, _next/static ein Jahr).
  • pruefung/rechte/fremde-firma.test.ts (2a), verwaltung.test.ts (3): je Endpunkt mit fremder Sitzung.
  • pruefung/fachlich/konto/anmelden-bremse.test.ts: Wartezeit wächst je Konto ab 5 und je IP erst ab 50 Fehlversuchen (Wert aus einstellungen.sicherheit), läuft aus, Satz je Ursache verschieden (fehler.versuche.anmelden / fehler.versuche.netz), „Kennwort vergessen?“ ab dem 3. Fehlversuch.
  • pruefung/fachlich/konto/kennwort.test.ts: häufiges Kennwort abgelehnt, Name und Mail im Kennwort abgelehnt, 10 Zeichen Grenze.
  • pruefung/fachlich/datei/hochladen.test.ts: zu große Pixelmaße abgelehnt (Server vertraut der Browser-Verkleinerung nicht), JPG-Metadaten entfernt, Typ am Inhalt, HEIC-Rückfall nur für Lebenslauf und nur 3 je Tag; pruefung/fachlich/konto/kennwort-link.test.ts: weiter nur auf eigene Adressen (N-1).
  • pruefung/fachlich/konto/faktor-mail.test.ts: Code 10 Minuten, einmalig, 5 Fehlversuche, höchstens 3 Codes je 15 Minuten, Verwaltung lehnt Art mail ab.
  • pruefung/fachlich/konto/neues-geraet.test.ts: A13 nur bei unbekannter Gerätekennung (konto_geraete, 12 Monate; eine Anmeldung nach 4 Monaten vom bekannten Gerät löst keine A13 aus), „Das war ich nicht“ beendet alle Sitzungen und verlangt neues Kennwort.
  • pruefung/fachlich/sperrliste/*.test.ts: Treffer je Art (IP, Mail, Domain; CIDR mit 2b.9), Frist (IP 7, Mail 30 Standard), Rechte (verwaltung ohne unbefristet), neutrale Antwort je Aufrufer, IP-Eintrag greift bei Registrieren und Formularen ohne Konto, nicht bei Anmelden und Bewerben mit Konto, Haken „auch IP-Adressen“ nur bei Grund Betrug/Spam, Freemail-Domain abgelehnt.
  • pruefung/fachlich/schnittstelle/abruf.test.ts: Host außerhalb der Liste, private Adresse, http statt https und Weiterleitung werden abgelehnt.
  • pruefung/inhalte/bereinigen.test.ts (1b): Script, Ereignis-Attribute, javascript:-Links entfernt.
  • pruefung/api/datei.test.ts (2a): falscher Typ trotz Endung, SVG, zu groß, Auslieferung ohne Rechte 403, Content-Disposition.
  • gitleaks, npm audit bei jedem Bau (Plan Bauordnung).