Zum Inhalt springen
Deutsch

Sicherheit

Das Framework liefert die Bausteine; diese Seite zeigt, wie man sie zusammensetzt. Das lauffähige Gegenstück ist examples/http/secure-service.ts.

Die Reihenfolge zählt — von außen nach innen:

import {
concat, cors, csrfProtection, fallback, handleErrors, path,
requestId, requestTimeout, securityHeaders, withMiddleware,
CorsOptions, CsrfOptions,
} from 'actor-ts/http';
const corsOptions = CorsOptions.create().withOrigins('https://app.example').withCredentials();
const csrf = csrfProtection(CsrfOptions.create().withSecret(process.env.CSRF_SECRET!));
const routes =
withMiddleware(requestId(),
withMiddleware(securityHeaders(),
cors(corsOptions,
withMiddleware(requestTimeout(15_000),
withMiddleware(csrf,
handleErrors(errorMapper,
concat(
appRoutes,
fallback(notFound),
)))))));
SchichtWarum hier
requestIdganz außen, damit jede Log-Zeile (auch Fehler) eine ID hat
securityHeadersHeader auf jeder Response setzen, auch bei Short-Circuits
corsaußerhalb der Auth — Preflights sind laut Spec anonym
requestTimeoutLatenz begrenzen, bevor echte Arbeit läuft
csrfnur Browser-Apps; nach CORS, vor den Handlern
handleErrorsgeworfene Fehler nahe den Routen auf Responses mappen
fallbackan der Wurzel — beantwortet alles Unpassende

X-Content-Type-Options: nosniff braucht keine Einrichtung. Geschrieben wird der Header vom Backend, nicht von einer Middleware — er erreicht damit auch die Responses, die keine Middleware sieht: das backend-eigene Fehler-Mapping, den fallback-404 und den 413 bei zu großem Body. Ein Header aus dem Handler gewinnt weiterhin.

Er ist bewusst der einzige, der per Default an ist — es ist der einzige Header des Bündels, der nichts daran ändern kann, wie eine bestehende Anwendung eingebettet, geframed oder referenziert wird. X-Frame-Options und Cross-Origin-Resource-Policy würden iframes, Cross-Origin-Einbettung und OAuth-Popups brechen und bleiben deshalb Opt-in.

Um das ganze Bündel auf jede Response zu legen — inklusive derselben middleware-unsichtbaren Pfade — konfiguriere es am Server statt am Routen-Baum:

import { SecurityHeadersOptions } from 'actor-ts/http';
const securityHeadersOptions = SecurityHeadersOptions.create()
.withFrameOptions('SAMEORIGIN')
.withReferrerPolicy('strict-origin-when-cross-origin');
await http.newServerAt('0.0.0.0', 8080)
.withSecurityHeaders(securityHeadersOptions)
.bind(routes);

Optionen zu übergeben ist ein Opt-in in das vollständige Bündel, samt dessen eigener Defaults — die beiden Header oben kommen also zusammen mit Cross-Origin-Resource-Policy: same-origin und dem Rest. Ein einfaches Objekt geht genauso (withSecurityHeaders({ frameOptions: 'SAMEORIGIN' })), und withSecurityHeaders(false) schaltet den Mechanismus komplett ab.

Serverweit oder als securityHeaders()-Middleware — beide setzen dieselbe Menge. Zur Middleware greifst du, wenn nur ein Routen-Teilbaum sie braucht; zum Builder, wenn der ganze Server sie braucht — denn die Middleware wird überall dort übersprungen, wo eine Response nie durch sie zurückläuft.

  • TLS terminiert am Proxy. Setze HSTS trotzdem — Browser ignorieren es über reines HTTP. Gate Security-Header nicht an einem Scheme, das die App nicht sieht.
  • Client-IPs vertrauen. req.remoteAddress ist der Socket-Peer, kein fälschbares X-Forwarded-For. Vertraue einem Forwarded-Header nur, wenn dein Proxy ihn überschreibt (getClientIp-Option von IpAllowlist).
  • Secrets aus der Umgebung, nicht aus HOCON-Dateien — das CSRF-Secret, Basic-Zugangsdaten und Bearer-Tokens nehmen alle Werte, die du beim Start lädst.

Bevorzuge das __Host--Präfix (erfordert Secure, Path=/, kein Domain) und SameSite=Lax oder Strict. Das CSRF-Cookie ist bewusst nicht HttpOnly (Double-Submit braucht JS zum Lesen); dein Session-/Auth-Cookie sollte HttpOnly sein. serializeCookie verweigert Werte, die einen zweiten Header injizieren könnten.

Interna nicht leaken. Nutze withErrorHandler (oder ein Top-Level handleErrors), um einen generischen Body zurückzugeben, und logge das Detail serverseitig, verschlüsselt über die Request-ID. Halte 403/404 unspezifisch — die Static-File-Schicht liefert für jeden abgelehnten Pfad einen einheitlichen 404, verrät also nie, was existiert.

redirect(target) nimmt ausschließlich Same-Origin-Ziele — eine relative Referenz. Eine absolute URL, ein protokollrelatives //host oder ein Steuerzeichen wirft HttpError(400); einen ?next=-Parameter direkt in einen Redirect zu reichen, scheitert damit geschlossen, statt zum Open-Redirect zu werden. Wenn das Verlassen des Origins Absicht ist, sag es: redirectExternal(...) ist derselbe Helper ohne Origin-Regel, und der eigene Name macht jeden bewussten Off-Origin-Sprung mit einem Befehl auffindbar. Übergib eine Konstante oder ein Ziel aus deiner Allowlist — nie einen rohen Request-Parameter. Siehe Route DSL.

  • Request-Bodies werden vom Backend gekappt (Fastify 1 MiB Default; Express/Hono ~10 MiB) — pro Backend an deine Payloads anpassen.
  • WebSocket-Frames werden durch maxFrameBytes gekappt (1 MiB Default).
  • Untrusted HTML — mit dem html-Template escapen; für Rich-Markup vor rawHtml mit einer dedizierten Bibliothek sanitisieren.

Behalte die Defaults dotfiles: 'deny' und symlinks: 'within-root'. Aktiviere browse nur für interne Tools — ein öffentliches Listing legt deine Verzeichnisstruktur offen. Siehe Statische Dateien.

Wickle eine websocket()-Route in cors(...), um Cross-Origin-Upgrades abzuweisen, und lege Auth-Middleware darum — withMiddleware läuft zur Upgrade-Zeit und gated damit den Handshake.

MaßnahmeWomit
MIME-Sniffing auf jeder Response abgeschaltetnichts — per Default an
Security-Header auf jeder ResponsesecurityHeaders(), oder serverweit withSecurityHeaders(...)
HTTPS vom Browser erzwungenstrictTransportSecurity() / withHsts
Cross-Origin kontrolliertcors(...)
CSRF (Browser-Formulare)csrfProtection(...)
Keine internen Fehler-LeakswithErrorHandler + generische Bodies
Keine Open Redirectsredirect(...) — same-origin per Default
Request-KorrelationrequestId()
Begrenzte LatenzrequestTimeout(...)
Sichere DateiauslieferunggetFromDirectory-Defaults (dotfiles deny, Symlink-Confinement)