CI / pruefen (push) Canceled after 0s
Geprueft gegen ASVS und die Cheat Sheets zu Authentication, Session Management und Password Storage. Das meiste stand schon gut da – Token mit 256 Bit und nur als Hash in der Datenbank, serverseitige Pruefung bei jeder Anfrage, Sitzungen weg beim Passwortwechsel, TOTP mit Replay-Schutz. Fuenf Punkte blieben. **Aufwand beim Hashen.** N war 2^15 bei p=1. Das Password Storage Cheat Sheet nennt 2^17/p=1 als Minimum und listet als gleichwertig unter anderem 2^16/p=2 und 2^15/p=3 – mit p=1 lag 2^15 unter *allen* zulaessigen Kombinationen und war rund dreimal billiger zu knacken als vorgesehen. Jetzt 2^17/p=1, gemessen 0,5 s und 128 MiB je Pruefung. Der Speicher ist gedeckelt: Node rechnet scrypt im libuv-Threadpool, also hoechstens vier parallel. **Neuhashen bei der Anmeldung.** Ohne das blieben bestehende Passwoerter fuer immer auf den alten Parametern – anheben laesst er sich nur dort, wo das Klartextpasswort vorliegt. needsRehash vergleicht den Gesamtaufwand N*r*p, nicht Feld fuer Feld, damit gleichwertige Kombinationen nicht grundlos neu gehasht werden. **Aufzaehlbarkeit.** Drei Zweige beantworteten sich *vor* der Passwort- pruefung und verrieten damit die Existenz eines Kontos: gesperrt (423), deaktiviert (403) und "kein Passwort hinterlegt, nimm einen Passkey" (401). Der einheitliche Text GENERIC war damit wirkungslos. Jetzt wird zuerst das Passwort geprueft; Klartext gibt es erst danach. Reine Passkey-Konten verbrennen dieselbe Rechenzeit, damit sich der Fall auch nicht an der Dauer zeigt. Der Preis ist der verlorene Hinweis "nimm den Passkey" – bewusst hingenommen. **Leerlauffrist.** lastSeenAt wurde gefuehrt, aber nie ausgewertet: ein abgegriffener Cookie galt bis zu 30 Tage, egal ob das Konto benutzt wurde. ASVS 3.3.2 will beide Grenzen. Acht Stunden, ueber SESSION_IDLE_HOURS einstellbar. Das Schreibintervall fuer lastSeenAt haengt jetzt an der Frist – bei fester Stunde haette eine kuerzere Frist sich selbst ausgehebelt. **Cookies.** SameSite Strict statt Lax, und bei https der Praefix "__Host-". Der laesst den Browser erzwingen, dass kein Domain-Attribut gesetzt ist – eine andere Subdomain kann den Cookie dann nicht ueberschreiben. Beides kostet einmalig eine neue Anmeldung. Dazu die Passwortpruefung: Wiederholungen, durchlaufende Zeichenfolgen und Passwoerter, die den Benutzernamen enthalten, werden abgelehnt. Zusammensetzungsregeln bleiben bewusst aus – ASVS 2.1.9 raet davon ab. Geprueft: 232 Unit-Tests (davon 12 neue zu Aufwand, Neuhashen und Mustern) und 30 E2E-Tests, darunter einer, der die Antwort auf ein stillgelegtes Konto gegen die auf ein erfundenes haelt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
115 lines
5.8 KiB
Bash
115 lines
5.8 KiB
Bash
# ─────────────────────────────────────────────────────────────
|
||
# Nebula – Beispielkonfiguration
|
||
# Kopieren nach ".env" und Werte anpassen.
|
||
# (Die .env wird sowohl von Next.js als auch von docker compose gelesen.)
|
||
# ─────────────────────────────────────────────────────────────
|
||
|
||
# ── Anwendung ────────────────────────────────────────────────
|
||
# Anzeigename: Seitentitel, Kopfzeile und Name im Passkey-Dialog.
|
||
APP_NAME="Nebula"
|
||
# Kleine Zeile unter dem Namen in der Kopfzeile.
|
||
APP_TAGLINE="Vibe Coding"
|
||
|
||
# Oeffentliche Adresse der Instanz – genau so, wie du sie im Browser aufrufst.
|
||
#
|
||
# WICHTIG fuer Passkeys: Daraus werden die WebAuthn-Relying-Party-ID (der
|
||
# Hostname) und der erwartete Origin abgeleitet. Passt der Wert nicht zur
|
||
# aufgerufenen Adresse, lehnt der Browser die Anmeldung ab. Passkeys brauchen
|
||
# ausserdem einen sicheren Kontext: https:// – oder http://localhost.
|
||
#
|
||
# Wird die rpID spaeter geaendert (anderer Hostname), sind bestehende Passkeys
|
||
# ungueltig und muessen neu angelegt werden. Passwoerter bleiben davon unberuehrt.
|
||
APP_URL="http://localhost:3000"
|
||
|
||
# Port innerhalb des Containers bzw. im Standalone-Betrieb
|
||
PORT="3000"
|
||
# Host-Port-Mapping fuer den App-Container (--profile full). Nur auf dem
|
||
# Loopback – nach draussen geht es ueber einen Reverse Proxy. Soll die
|
||
# Instanz im LAN erreichbar sein, hier "3000" eintragen.
|
||
APP_PORT="127.0.0.1:3000"
|
||
|
||
# ── Datenbank ────────────────────────────────────────────────
|
||
# Standalone-Betrieb: App laeuft lokal, Postgres via "npm run docker:db"
|
||
# oder als nativ installierter Dienst.
|
||
DATABASE_URL="postgresql://nebula:nebula@localhost:5432/nebula?schema=public"
|
||
|
||
# Im Docker-Vollbetrieb (--profile full) setzt compose DATABASE_URL selbst
|
||
# auf den Service-Hostnamen "db" – der Wert oben wird dann nicht verwendet.
|
||
|
||
# Zugangsdaten des Postgres-Containers aus docker-compose.yml
|
||
POSTGRES_USER="nebula"
|
||
POSTGRES_PASSWORD="nebula"
|
||
POSTGRES_DB="nebula"
|
||
# Host-Port der Datenbank. Wirkt NUR zusammen mit docker-compose.dev.yml
|
||
# (npm run docker:db) – die Basisdatei veroeffentlicht keinen DB-Port, weil
|
||
# die Anwendung im Container "db:5432" anspricht und nichts davon nach
|
||
# aussen muss. Auf einem Server bleibt dieser Wert also folgenlos.
|
||
POSTGRES_PORT="127.0.0.1:5432"
|
||
# Host-Port fuer Adminer (--profile tools)
|
||
ADMINER_PORT="127.0.0.1:8080"
|
||
|
||
# ── Betrieb ohne veroeffentlichte Ports ──────────────────────
|
||
# Liegen mehrere Stacks auf demselben Docker-Host und regelt ein gemeinsamer
|
||
# Reverse Proxy den Zugang, veroeffentlicht Nebula gar keinen Port. Dann diese
|
||
# beiden Zeilen aktivieren – danach genuegt ueberall "docker compose up -d":
|
||
# COMPOSE_FILE="docker-compose.yml:docker-compose.server.yml"
|
||
# COMPOSE_PROFILES="full"
|
||
#
|
||
# Name des geteilten Netzes und der Alias, unter dem der Proxy Nebula findet.
|
||
# PROXY_NETWORK="proxy"
|
||
# PROXY_ALIAS="nebula"
|
||
|
||
# ── Sicherheit ───────────────────────────────────────────────
|
||
# Schluessel zur Verschluesselung der Git-Access-Tokens (AES-256-GCM).
|
||
# Erzeugen mit: openssl rand -base64 32
|
||
# oder: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
|
||
# oder: npm run setup (legt die .env inkl. Schluessel an)
|
||
# WICHTIG: Wird der Wert nachtraeglich geaendert, sind gespeicherte Git-Tokens
|
||
# nicht mehr entschluesselbar und muessen neu hinterlegt werden. Anmeldungen
|
||
# und Passkeys sind davon nicht betroffen.
|
||
APP_SECRET="bitte-aendern-mindestens-32-zeichen-langer-zufallswert"
|
||
|
||
# Absolute Lebensdauer einer Anmeldung in Tagen – ab dem Anmelden gerechnet
|
||
# und nicht verlaengerbar.
|
||
SESSION_TTL_DAYS="30"
|
||
|
||
# Leerlauffrist in Stunden: so lange darf eine Sitzung unbenutzt bleiben.
|
||
# Wird bei jeder Nutzung neu angestossen. 0 schaltet die Pruefung ab.
|
||
SESSION_IDLE_HOURS="8"
|
||
|
||
# ── Benachrichtigungen ───────────────────────────────────────
|
||
# ntfy und Webhook brauchen nichts weiter – die Ziele werden in der
|
||
# Anwendung unter "Mein Konto" eingerichtet.
|
||
#
|
||
# E-Mail steht erst zur Verfuegung, wenn SMTP_HOST gesetzt ist:
|
||
# SMTP_HOST="smtp.example.com"
|
||
# SMTP_PORT="587"
|
||
# SMTP_SECURE="false" # true bei Port 465, sonst STARTTLS
|
||
# SMTP_USER="nebula@example.com"
|
||
# SMTP_PASSWORD="geheim"
|
||
# SMTP_FROM="Nebula <nebula@example.com>"
|
||
|
||
# Timeout fuer das Zustellen in Millisekunden
|
||
NOTIFY_TIMEOUT_MS="10000"
|
||
|
||
# ── Ausgehende Git-Zugriffe ──────────────────────────────────
|
||
# Timeout fuer Git-API-Aufrufe in Millisekunden
|
||
GIT_API_TIMEOUT_MS="15000"
|
||
|
||
# Schutz vor Server Side Request Forgery: die Basis-URL einer Git-Verbindung
|
||
# ist eine Benutzereingabe und wird vom Server abgerufen.
|
||
#
|
||
# Voreinstellung: Loopback, Link-Local (169.254.x, Cloud-Metadaten), Multicast
|
||
# und CGNAT sind gesperrt; das private LAN ist erlaubt, weil dort ueblicherweise
|
||
# die eigene Gitea-Instanz steht.
|
||
|
||
# Gitea laeuft auf demselben Rechner wie Nebula? Dann auf true setzen.
|
||
# GIT_ALLOW_LOOPBACK="false"
|
||
|
||
# Kein Zugriff ins private Netz (10/8, 172.16/12, 192.168/16, fc00::/7)?
|
||
# GIT_BLOCK_PRIVATE="false"
|
||
|
||
# Strengster Modus: nur diese Hosts sind erlaubt, alles andere wird geblockt.
|
||
# Eine Angabe hier hebt die beiden Schalter darueber auf.
|
||
# GIT_ALLOWED_HOSTS="github.com,gitlab.com,git.meine-firma.de"
|