Zum Inhalt springen
Dokumentation auf Deutsch

Übersetzung der englischen Originalseite. Bei Abweichungen gilt die englische Fassung.

Sicherheit

Schlechte Standardwerte sind Bugs. DaloyJS trennt vom Kern erzwungene Schutzmechanismen von eigener Sicherheits-Middleware, damit die gefährlichen Dinge standardmäßig blockiert sind und die einsatzspezifischen Dinge explizit bleiben.

Die Schutzmechanismen des Kerns schützen jede Anwendung standardmäßig. Die eigene Middleware ergänzt die Richtlinien, die von Ihren Routen, Nutzern und Ihrem Deployment abhängen.
Zwei Verteidigungsebenen
Vom Kern erzwungene Schutzmechanismenstandardmäßig scharf, kein Middleware-Aufruf nötig
bodyLimitBytessafeJsonParsesanitizeHeaderValuePath-Traversal-AblehnungrequestTimeoutMs405 + Allow5xx-Schwärzung in Produktion
Eigene Sicherheits-Middlewareexplizit, weil Richtlinien eine Deployment-Entscheidung sind
secureHeaders()cors()csrf()rateLimit()session()bearerAuth()
Die gefährlichen Dinge blockiert der Kern ohne jede Einrichtung. Die einsatzspezifischen Dinge (CSP, CORS-Origins, Session-Secrets, CSRF-Einführung) bleiben explizite Middleware, die Sie bewusst aktivieren.

Was der Kern erzwingt

Diese Prüfungen finden in App oder im Laufzeit-Adapter selbst statt. Anwendungen erhalten sie, ohne eine Middleware aufzurufen.

Jeder Request muss alle Kontrollen passieren
  1. 01EingangEingehender RequestMethode, Pfad, Header, Body
  2. 02GrößengrenzeBody-GrößenlimitContent-Length über der Grenze führt zu 413
  3. 03ParsenGehärtetes JSON-Parsenentfernt __proto__ / constructor
  4. 04RouteRouter + Methodenprüfung.. zum kanonischen Pfad aufgelöst. Falsche Methode 405
  5. 05HandlerIhr typisierter Handlerläuft unter requestTimeoutMs
Ein Request erreicht Ihren Handler erst, nachdem er die Body-Grenze, den gegen Prototype Pollution abgesicherten Parser und die Pfad- und Methoden-Guards passiert hat. Alles, was an einem Guard scheitert, lehnt der Kern ab, bevor Ihr Code läuft.
BedrohungEingebautes Verhalten
DoS über Body-GrößeGestreamtes Lesen, harte Grenze (Standard 1 MiB), Content-Length wird zuerst geprüft -> 413. Ab 1.5.4 gilt die Grenze für jede Route, auch für Handler, die ctx.request direkt lesen; vorher nur für Routen mit Body-Schema. Die Grenze greift, wenn der Body gelesen wird, daher wird eine Route, die ihren Body ignoriert, deswegen nicht abgelehnt.
Prototype PollutionsafeJsonParse entfernt __proto__, constructor und prototype per Reviver.
Fehlerhaftes UTF-8 in BodysAb 1.16.0 dekodieren die Body-Parser für JSON, urlencoded und Text strikt: Ein Body mit einer ungültigen oder überlangen UTF-8-Sequenz wird mit 400 beantwortet, statt mit ersetztem U+FFFD validiert zu werden (RFC 8259 §8.1). Wohlgeformtes Nicht-ASCII und ein führendes BOM werden weiterhin geparst. Ein Handler, der ctx.request.text() selbst liest, behält die nachsichtige Dekodierung der Plattform.
Header- / Response-SplittingsanitizeHeaderName / sanitizeHeaderValue lehnen CRLF + NUL ab.
Path TraversalZwei Ebenen. Zuerst wird die Request-URL kanonisiert: Das WHATWG-URL-Parsing löst . / .. (und ihre %2e-Schreibweisen) auf, und der Node-Adapter wendet denselben Schritt auf rohe Request-Targets an. Dann lehnt der Router des Kerns jedes verbleibende Punktsegment oder leere //-Segment ab und bindet niemals kodiertes Traversal (%2e%2e, ..%2F) in Parameter. Routen passen auf exakte Strings, daher gibt es kein Verzeichnis, aus dem man ausbrechen könnte.
Slow-Loris / hängende HandlerrequestTimeoutMs (Standard 30 s) liefert 408 und löst ctx.request.signal für kooperatives Aufräumen aus. Ab 1.5.4 deckt es die Hooks, das Lesen des Bodys und den Handler ab (vorher nur den Handler). Der Node-Adapter setzt außerdem Socket-Timeouts.
Durch einen falschen Wert deaktivierte LimitsAb 1.5.4 müssen bodyLimitBytes, requestTimeoutMs, maxHeaderCount, jsonMaxKeys und jsonMaxDepth endliche, nicht negative Ganzzahlen sein; NaN (zum Beispiel Number(process.env.UNSET)) und andere ungültige Werte lösen in jeder Umgebung bei der Konstruktion einen Fehler aus.
Nicht unterstützte Content-TypesRouten mit Body-Schemas lehnen nicht erlaubte Content-Types ab -> 415.
MethodenverwechslungEchtes 405 mit Allow-Header, niemals ein irreführendes 404.
Offenlegung von Informationen (5xx)Das detail in 5xx-problem+json wird geschwärzt, sofern die Umgebung nicht ausdrücklich development oder test ist; auch ein nicht gesetztes NODE_ENV führt zur Schwärzung.

Eigene Sicherheits-Middleware

Diese Bausteine sind Teil von DaloyJS und gemeinsam dokumentiert, bleiben aber explizit, weil CSP, CORS, Schlüssel für Rate Limits, Session-Secrets und die Einführung von CSRF Deployment-Entscheidungen sind.

ts
import {
  requestId,
  secureHeaders,
  cors,
  rateLimit,
  bearerAuth,
  timing,
} from "@daloyjs/core";

app.use(requestId());           // Weitergabe von x-request-id
app.use(secureHeaders());       // CSP, HSTS, X-Frame-Options, COOP, CORP, no-sniff ...
app.use(cors({                  // explizite Allowlist; niemals * mit Credentials
  origin: ["https://app.example.com"],
  credentials: true,
  methods: ["GET", "POST"],
}));
app.use(rateLimit({             // standardmäßig pro TCP-Peer; hinter einem Proxy trustedProxies konfigurieren
  windowMs: 60_000,
  max: 120,
}));
app.use(timing());              // Server-Timing-Header für die Beobachtbarkeit

Setzen Sie rateLimit() oder loginThrottle() vor die Authentifizierung per Bearer, Basic, JWK oder mTLS, wenn fehlgeschlagene Zugangsdaten mitgezählt werden müssen. Der Limiter verbraucht den Versuch trotzdem und liefert nach Erreichen der Grenze 429, auch wenn die Authentifizierung in preBody ablehnt. DaloyJS liest dafür keinen deklarierten Request-Body. Ein eigener Schlüsselgenerator auf diesem Pfad sollte rohe Request-Daten oder einen Zustand verwenden, den ein früherer preBody-Hook befüllt hat.

Die offiziellen Starter binden das für Sie ein: Node, Bun und Deno aktivieren secureHeaders(), requestId() und rateLimit(). Cloudflare Worker und Vercel aktivieren secureHeaders() und requestId() sowie engere, Edge-taugliche Limits für Body und Timeout.

Beginnen Sie mit der Middleware unten, sofern Sie keinen konkreten Grund für eine Änderung haben. So bleiben riskante Entscheidungen explizit und einheitlich.

ZielEmpfohlene Grundausstattung
API auf Node / Bun / DenorequestId(), secureHeaders(), rateLimit() und cors(), wenn die API Cross-Origin genutzt wird.
Cloudflare WorkersStandardmäßig requestId() und secureHeaders(). Verwenden Sie cors() nur bei Bedarf und ziehen Sie einen externen oder gemeinsamen Limiter dem Standard im Arbeitsspeicher vor, wenn sich der Verkehr auf viele Isolates verteilt.
VercelStandardmäßig requestId() und secureHeaders(). Ergänzen Sie cors() nur bei Bedarf und verwenden Sie einen gemeinsamen Limiter, wenn Sie dauerhafte Zähler über Regionen hinweg brauchen.
Anwendung mit Cookie-AuthentifizierungErgänzen Sie session() und csrf() zur Grundausstattung, damit verändernde Routen gegen Cross-Site-Angriffe per Formular und fetch geschützt sind.
Hinter einem vertrauenswürdigen Reverse ProxyBehalten Sie die Grundausstattung und konfigurieren Sie dann rateLimit() mit einem expliziten keyGenerator, oder setzen Sie trustProxyHeaders: true erst, wenn der Proxy die Forwarding-Header entfernt und neu schreibt.

csrf() für zustandsändernde Routen

Verwenden Sie csrf() (auf Englisch), um verändernde Endpunkte zu schützen. Zwei Strategien werden unterstützt:

  • Double-Submit-Cookie (Standard): setzt bei sicheren Requests ein Token-Cookie, verlangt bei unsicheren Methoden denselben Wert im Header x-csrf-token und lehnt Abweichungen mit einem timing-sicheren 403 ab.
  • Fetch Metadata (strategy: "fetch-metadata"): tokenloser Schutz, der sich auf den modernen Header Sec-Fetch-Site stützt. Kein Cookie-Hin-und-Her. Keine Kopplung an das HTML-Rendering. Empfohlen für neue Anwendungen mit Browser-Frontend.
ts
import { csrf } from "@daloyjs/core";

// Klassisches Double-Submit-Cookie (Standard).
app.use(csrf());

// Tokenloser Schutz per Fetch Metadata (empfohlen für Anwendungen mit Browser-Frontend).
app.use(csrf({
  strategy: "fetch-metadata",
  allowedOrigins: ["https://app.example.com"],
}));

Standardwerte von secureHeaders()

text
content-security-policy: default-src 'self'; frame-ancestors 'none'
strict-transport-security: max-age=31536000; includeSubDomains
x-content-type-options: nosniff
x-frame-options: DENY
referrer-policy: no-referrer
permissions-policy: camera=(), microphone=(), geolocation=(), clipboard-write=()
cross-origin-opener-policy: same-origin
cross-origin-resource-policy: same-origin

Brauchen Sie eine andere CSP, wollen HSTS in der lokalen Entwicklung abschalten oder benötigen eine lockerere Permissions-Policy, übergeben Sie secureHeaders() explizit Optionen. Der veraltete Header X-XSS-Protection: 0 lässt sich über xssProtection: true aktivieren, für Deployments, die alte XSS-Filter von Browsern ausdrücklich abschalten wollen.

CSP mit Nonces pro Request und Trusted Types

secureHeaders() kann die CSP aus einer Map von Direktiven bauen und eine frische Nonce pro Request in script-src, script-src-elem, style-src und style-src-elem einfügen sowie require-trusted-types-for 'script' für die Härtung gegen DOM-XSS zur Laufzeit ausgeben. Die Nonce steht unter ctx.state.cspNonce bereit, damit Handler sie in <script nonce="...">-Tags rendern können.

ts
import { secureHeaders } from "@daloyjs/core";

app.use(secureHeaders({
  contentSecurityPolicy: {
    directives: {
      "default-src": "'self'",
      "script-src": "'self'",
      "style-src": "'self'",
      "img-src": ["'self'", "data:"],
    },
    nonce: true,
    trustedTypes: { policies: ["default"] },
  },
}));

app.get(
  "/page",
  {
    operationId: "page",
    responses: { 200: { description: "ok" } },
  },
  // Das HTML selbst zurückgeben, damit die Nonce-CSP von secureHeaders ausgeliefert wird.
  async ({ state }) => ({
    status: 200,
    body: `<!doctype html>
<script nonce="${state.cspNonce}">
  // inline bootstrap is allowed only via this fresh nonce
</script>`,
    headers: { "content-type": "text/html; charset=utf-8" },
  }),
);

Rendern Sie diese Seite nicht mit htmlResponse() aus @daloyjs/core/docs: Dieser Helfer liefert seine eigene Content-Security-Policy (abgestimmt auf die Doku-Oberflächen von Swagger / Scalar, mit 'unsafe-inline') und würde die strikte Nonce-CSP oben überschreiben, sodass die Nonce nicht mehr das wäre, was Inline-Skripte steuert. Behalten Sie htmlResponse() für Ihre API-Doku-Route und geben Sie für Nonce-geschützte Seiten Ihren eigenen Response-Body zurück.

Authentifizierung

ts
import { bearerAuth, basicAuth, timingSafeEqual } from "@daloyjs/core";

// Bearer (opake Tokens, JWT über Ihr eigenes `validate` geprüft).
app.post(
  "/admin/purge",
  {
    operationId: "adminPurge",
    hooks: bearerAuth({
      validate: (token) => timingSafeEqual(token, process.env.ADMIN_TOKEN!),
      realm: "admin",
    }),
    responses: { 204: { description: "ok" }, 401: { description: "denied" } },
  },
  async () => ({ status: 204 as const, body: undefined }),
);

// Basic Auth (RFC 7617).
app.use(basicAuth({
  realm: "books-api",
  verify: (user, pass) =>
    timingSafeEqual(user, "admin") &&
    timingSafeEqual(pass, process.env.ADMIN_PASSWORD ?? ""),
}));

SQL-Injection

Daloy liefert keinen Datenbanktreiber mit, aber die HTTP-Grenze, die es sehr wohl kontrolliert (strikte Zod-Schemas, gehärteter JSON-Parser, Body-Größengrenzen), verkleinert die Angriffsfläche, die Ihre Repository-Schicht erreicht. Unter SQL injection (auf Englisch) finden Sie sichere und unsichere Muster pro ORM (Prisma, Drizzle, Kysely, rohe Treiber), ein Allowlist-Rezept für dynamisches ORDER BY und die grep-Regeln, mit denen die Maintainer Regressionen aufspüren.

Command-Injection

Die Laufzeit von DaloyJS ist per CI-Gate frei von child_process, daher kann das Framework selbst keine Shell aufrufen. Unter Command injection (auf Englisch) finden Sie die sichere Form eines Handlers, der doch ein externes Programm aufrufen muss (execFile + argv-Array, niemals exec(`cmd $${input}`)), die Windows-Falle BatBadBut und die grep-Regeln, mit denen neue Bugs schon im PR draußen bleiben.

Admin-Panels

Sie bauen eine Admin- oder Customer-Success-Oberfläche auf DaloyJS? Unter Secure admin panels (auf Englisch) finden Sie das empfohlene Muster: Routen mit internal: true, ipRestriction(), strikte CSP mit Nonces pro Request, Authentifizierung pro Admin, rateLimit()-Gruppen zur Drosselung von Anmeldungen und strukturiertes Audit-Logging, eins zu eins abgebildet auf die öffentliche Checkliste „secure admin panel“ von Aikido.

Lieferkette

DaloyJS wird über pnpm verteilt, für ein strengeres Installationsmodell. Generierte pnpm-Anwendungen erben die Kontrollen zur Installationszeit, während das Repository von DaloyJS selbst und das optionale GitHub-Actions-Paket CI/CD-Kontrollen gegen Cache Poisoning, Phishing von Maintainern und den Missbrauch von OIDC-Tokens ergänzen, wie sie in jüngsten npm-Vorfällen zu sehen waren.

  • Strikte Isolation: Pakete erreichen keine Phantom-Abhängigkeiten.
  • Inhaltsadressierter Store: Jedes Byte wird gehasht und geprüft.
  • Eingefrorene Lockfile in CI mit --ignore-scripts: reproduzierbare Installationen ohne Ausführung transitiver Lifecycle-Skripte.
  • verifyStoreIntegrity: Lesezugriffe, die Beschädigungen erkennen.
  • strictPeerDependencies: keine stillen Peer-Abweichungen.
  • minimumReleaseAge: 1440 in pnpm-workspace.yaml: 24 Stunden warten, bevor frische Releases installiert werden (pnpm 11 ignoriert die Schreibweise in .npmrc; siehe Installation härten).
  • ignoreScripts: true in generierten Projekten und strictDepBuilds mit einer allowBuilds-Allowlist im DaloyJS-Repo: keine Installationsskripte von Abhängigkeiten außer geprüften. Das stoppt Würmer in Installationsskripten, nicht aber Nutzlasten, die beim Import laufen.
  • Per SHA fixierte GitHub Actions: Die optional generierten GitHub-Workflows fixieren Actions von Drittanbietern auf unveränderliche Commits statt auf veränderliche Tags.
  • Geschütztes npm-Publishing von DaloyJS: Die eigenen Pakete des Frameworks nutzen einen Release-Workflow nur über Tags, eine Freigabe über eine geschützte Umgebung, OIDC Trusted Publishing und --provenance.

Liegt Ihre generierte Anwendung außerhalb von GitHub, übernehmen Sie die portablen Teile direkt und übertragen die Regeln des GitHub-Workflows auf Ihren CI-Host. Das Framework kann Branch-Schutz oder ausgehenden Verkehr von Runnern in einem privaten GitLab, Bitbucket, Azure DevOps oder einer On-Premises-Installation nicht erzwingen.

Vertrauenswürdige Proxys und Rate Limiting

DaloyJS vertraut X-Forwarded-For oder X-Real-IP bei der Ableitung eines Rate-Limit-Schlüssels standardmäßig nicht mehr. Diese Header können Clients fälschen, sofern Ihr Reverse Proxy sie nicht entfernt und neu schreibt. Der Standard-Limiter verwendet daher den nicht fälschbaren TCP-Peer als Schlüssel (seit 1.3.7; ein einziger gemeinsamer Bucket nur auf Laufzeiten, die keinen Peer bereitstellen), bis Sie einen expliziten keyGenerator angeben oder hinter einem vertrauenswürdigen Proxy trustProxyHeaders: true / trustedProxies aktivieren. Wird Proxy-Headern vertraut, ist der Schlüssel der rechteste Eintrag in X-Forwarded-For (der, den Ihr Proxy angehängt hat), niemals ein von Angreifern vorangestellter linker Eintrag. Ketten über mehrere Hops geben ihre Anzahl mit trustedHops an. Ist der Origin selbst erreichbar, setzen Sie trustedProxies, damit der Peer-Socket gegen eine CIDR-Allowlist geprüft wird, bevor irgendeinem weitergeleiteten Header geglaubt wird (siehe den Hinweis zu autoBan, auf Englisch).

Haben Sie Ihre Topologie bereits mit behindProxy an der App deklariert, müssen Sie sie nicht wiederholen. Ein rateLimit() oder loginThrottle(), das weder keyGenerator noch trustProxyHeaders, trustedHops oder trustedProxies setzt, folgt dem behindProxy der App, sodass jeder Client hinter Ihrem Proxy (oder auf Vercel und Cloudflare Workers, wo es keinen Peer-Socket gibt) seinen eigenen Bucket erhält, statt dass sich alle einen teilen. Explizite Optionen des Limiters haben immer Vorrang, und trustProxyHeaders: false schaltet das ab. Lassen Sie behindProxy der Realität entsprechen: Bevorzugen Sie { cidrs }, wenn Sie die Proxy-Adressen kennen, denn { hops } vertraut den rechtesten weitergeleiteten Einträgen von jedem, der die Anwendung direkt erreichen kann.

ts
const app = new App({ behindProxy: { hops: 1 } }); // ein vertrauenswürdiger Proxy davor
app.use(rateLimit({ windowMs: 60_000, max: 120 })); // Schlüssel ist die echte Client-IP

Für Routen, auf denen Zugangsdaten eingegeben werden, verwenden Sie loginThrottle() (auf Englisch) über /login, OTP und Passwort-Zurücksetzung hinweg, und wsRateLimit() für zugehörige WebSocket-Upgrades. Beide Helfer können aus demselben groupId-Bucket verbrauchen.

Selbst gehostete Doku-Assets

Die eingebauten Doku-Helfer erzwingen keine auf jsDelivr zugeschnittene CSP mehr. Sie können die Assets von Swagger UI oder Scalar selbst hosten, dem Bootstrap-Skript eine Nonce hinzufügen und für Ihre Doku-Route eine CSP nur für den eigenen Origin ausgeben.

ts
import {
  swaggerUiHtml,
  htmlResponse,
} from "@daloyjs/core/docs";

const nonce = crypto.randomUUID();
const html = swaggerUiHtml({
  specUrl: "/openapi.json",
  scriptNonce: nonce,
  assets: {
    swaggerUiCssUrl: "/docs-assets/swagger-ui.css",
    swaggerUiBundleUrl: "/docs-assets/swagger-ui.js",
  },
});

return htmlResponse(html, {
  assetOrigins: [],
  scriptNonce: nonce,
  allowInlineStyles: false,
});

Installation härten

Ab pnpm 11 liegen die Einstellungen zur Installationszeit in pnpm-workspace.yaml, mit Schlüsseln in camelCase. pnpm 11 ignoriert die .npmrc-Schreibweise dieser Einstellungen, daher verliert ein Setup nur mit .npmrc stillschweigend die Wartezeit und die Skript-Richtlinie. Behalten Sie eine gespiegelte .npmrc nur für npm und ältere Werkzeuge.

pnpm-workspace.yamlyaml
minimumReleaseAge: 1440    # skip versions published in the last 24 hours
blockExoticSubdeps: true   # transitive deps must come from the registry
ignoreScripts: true        # no dependency lifecycle scripts
strictPeerDependencies: true
verifyStoreIntegrity: true
preferFrozenLockfile: true

Diese Einstellungen schützen die Installationen, die sie verwenden. ignoreScripts stoppt Würmer in Installationsskripten (von der Art Shai-Hulud); eine Nutzlast, die läuft, wenn Ihr Code das Paket importiert, wird nur durch die Wartezeit und eine eingecheckte Lockfile abgemildert. create-daloy schreibt diese Datei für pnpm-Projekte.

Das vollständige Handbuch für CI/CD und Maintainer finden Sie unter Supply-chain security (auf Englisch). Führen Sie pnpm audit --prod in CI und vor jedem Release aus.

Zuordnung zu den OWASP API Security Top 10

Eine Durchsicht Punkt für Punkt, wie Daloy jeden Eintrag der OWASP API Security Top 10 (2023) sowie die übergreifenden Best Practices (Verschlüsselung, Validierung, Rate Limiting, Logging, Inventar, sicherer Umgang mit APIs von Drittanbietern) adressiert, finden Sie unter OWASP API Top 10 mapping (auf Englisch).

Eine Sicherheitslücke melden

Nutzen Sie die private Meldung von Sicherheitslücken auf GitHub unter github.com/daloyjs/daloy/security/advisories/new und geben Sie Schritte zur Reproduktion an. Eröffnen Sie kein öffentliches Issue mit Details zum Exploit.

Zuletzt aktualisiert: