Traduction de la page originale en anglais. En cas de divergence, la version anglaise fait foi.
Sécurité
Les mauvaises valeurs par défaut sont des bugs. DaloyJS sépare les garde-fous imposés par le cœur des middlewares de sécurité officiels : ce qui est dangereux est bloqué par défaut, et ce qui dépend du déploiement reste explicite.
Les garde-fous du cœur protègent chaque application par défaut. Les middlewares officiels ajoutent la politique qui dépend de vos routes, de vos utilisateurs et de votre déploiement.
Ce que le cœur impose
Ces contrôles ont lieu dans App ou dans l’adaptateur de runtime lui-même. Les applications en bénéficient sans appeler aucun middleware.
- 01entréeRequête entranteméthode, chemin, en-têtes, corps
- 02plafond de tailleLimite de taille du corpsContent-Length au-dessus du plafond : 413
- 03analyseAnalyse JSON renforcéeretire __proto__ / constructor
- 04routeRouteur + contrôle de méthode.. résolu en chemin canonique. Mauvaise méthode : 405
- 05handlerVotre handler typés’exécute sous requestTimeoutMs
| Menace | Comportement intégré |
|---|---|
| DoS par taille de corps | Lecture en flux, plafond strict (1 Mio par défaut), Content-Length vérifié en premier -> 413. Depuis la 1.5.4, le plafond couvre toutes les routes, y compris les handlers qui lisent directement ctx.request ; avant, seulement les routes avec un schéma de corps. Le plafond s’applique à la lecture du corps : une route qui ignore son corps n’est donc pas refusée à cause de lui. |
| Pollution de prototype | safeJsonParse retire __proto__, constructor, prototype via un reviver. |
| UTF-8 malformé dans les corps | Depuis la 1.16.0, les analyseurs de corps JSON, urlencoded et texte décodent strictement : un corps contenant une séquence UTF-8 invalide ou trop longue reçoit 400 au lieu d’être validé avec U+FFFD substitué (RFC 8259 §8.1). Le non-ASCII bien formé et un BOM initial s’analysent toujours. Un handler qui lit lui-même ctx.request.text() conserve le décodage permissif de la plateforme. |
| Découpage d’en-têtes / de réponse | sanitizeHeaderName / sanitizeHeaderValue rejettent CRLF + NUL. |
| Traversée de chemin | Deux couches. D’abord, l’URL de la requête est canonicalisée : l’analyse d’URL WHATWG résout . / .. (et leurs formes %2e), et l’adaptateur Node applique la même étape aux cibles de requête brutes. Ensuite, le routeur du cœur rejette tout segment point ou segment vide // restant, et ne lie jamais une traversée encodée (%2e%2e, ..%2F) dans les paramètres. Les routes correspondent à des chaînes exactes : il n’y a donc aucun répertoire dont s’échapper. |
| Slow-loris / handlers bloqués | requestTimeoutMs (30 s par défaut) renvoie 408 et déclenche ctx.request.signal pour un arrêt coopératif. Depuis la 1.5.4, il couvre les hooks, la lecture du corps et le handler (avant : seulement le handler). L’adaptateur Node définit aussi des délais d’expiration de socket. |
| Limites désactivées par une mauvaise valeur | Depuis la 1.5.4, bodyLimitBytes, requestTimeoutMs, maxHeaderCount, jsonMaxKeys et jsonMaxDepth doivent être des entiers finis non négatifs ; NaN (par exemple Number(process.env.UNSET)) et les autres valeurs invalides lèvent une erreur à la construction, dans tous les environnements. |
| Types de contenu non pris en charge | Les routes avec schéma de corps rejettent les content-types non autorisés -> 415. |
| Confusion de méthode | Un vrai 405 avec en-tête Allow, jamais un 404 trompeur. |
| Divulgation d’informations (5xx) | Le detail des problem+json 5xx est masqué sauf si l’environnement est explicitement development ou test ; un NODE_ENV non défini est aussi masqué. |
Middlewares de sécurité officiels
Ils font partie de DaloyJS et sont documentés ensemble, mais restent explicites, car la CSP, CORS, les clés de limitation de débit, les secrets de session et le déploiement de CSRF sont des décisions de déploiement.
Placez rateLimit() ou loginThrottle() avant l’authentification bearer/basic/JWK/mTLS lorsqu’il doit compter les identifiants refusés. Le limiteur décompte quand même la tentative et renvoie 429 une fois le plafond atteint, même si l’authentification rejette dans preBody. DaloyJS ne lit pas pour cela un corps de requête déclaré. Un générateur de clé personnalisé sur ce chemin doit utiliser des données brutes de la requête ou un état rempli par un hook preBody antérieur.
Les modèles de démarrage officiels les branchent pour vous : Node, Bun et Deno activent secureHeaders(), requestId() et rateLimit(). Cloudflare Worker et Vercel activent secureHeaders() et requestId(), avec des limites de corps et de délai plus serrées, adaptées à l’edge.
Recommandations par cible de déploiement
Commencez par les middlewares ci-dessous, sauf raison concrète d’en changer. Cela garde les choix risqués explicites et cohérents.
| Cible | Base recommandée |
|---|---|
| API Node / Bun / Deno | requestId(), secureHeaders(), rateLimit(), et cors() lorsque l’API est cross-origin. |
| Cloudflare Workers | requestId() et secureHeaders() par défaut. N’utilisez cors() qu’en cas de besoin, et préférez un limiteur externe ou partagé à la valeur par défaut en mémoire lorsque le trafic se répartit sur de nombreux isolats. |
| Vercel | requestId() et secureHeaders() par défaut. N’ajoutez cors() qu’en cas de besoin, et utilisez un limiteur partagé si vous avez besoin de compteurs durables entre régions. |
| Application authentifiée par cookie | Ajoutez session() et csrf() à la base, pour que les routes de modification soient protégées contre les attaques cross-site par formulaire et par fetch. |
| Derrière un reverse proxy de confiance | Gardez la base, puis configurez rateLimit() avec un keyGenerator explicite, ou réglez trustProxyHeaders: true seulement une fois que le proxy retire et réécrit les en-têtes de transfert. |
csrf() pour les routes qui modifient l’état
Utilisez csrf() (en anglais) pour protéger les endpoints de modification. Deux stratégies sont prises en charge :
- Cookie double-submit (par défaut) : définit un cookie de jeton sur les requêtes sûres, exige la même valeur dans l’en-tête
x-csrf-tokenpour les méthodes non sûres, et rejette les différences par un 403 à temps constant. - Fetch Metadata (
strategy: "fetch-metadata") : une protection sans jeton qui s’appuie sur l’en-tête moderneSec-Fetch-Site. Pas d’aller-retour de cookie. Pas de couplage avec le rendu HTML. Recommandée pour les nouvelles applications exposées au navigateur.
Valeurs par défaut de secureHeaders()
Si vous avez besoin d’une autre CSP, voulez désactiver HSTS en développement local ou avez besoin d’une politique de permissions plus souple, passez explicitement des options à secureHeaders(). L’ancien en-tête X-XSS-Protection: 0 s’active via xssProtection: true pour les déploiements qui veulent désactiver explicitement les anciens filtres XSS des navigateurs.
CSP avec nonces par requête et Trusted Types
secureHeaders() peut construire la CSP à partir d’une table de directives et injecter un nonce par requête neuf dans script-src, script-src-elem, style-src et style-src-elem, puis émettre require-trusted-types-for 'script' pour durcir le DOM contre le XSS à l’exécution. Le nonce est exposé dans ctx.state.cspNonce pour que les handlers puissent l’insérer dans des balises <script nonce="...">.
N’affichez pas cette page avec htmlResponse() de @daloyjs/core/docs : ce helper livre sa propre Content-Security-Policy (réglée pour les interfaces de documentation Swagger / Scalar, avec 'unsafe-inline') et remplacerait la CSP stricte à nonce ci-dessus, si bien que le nonce ne serait plus ce qui contrôle les scripts en ligne. Gardez htmlResponse() pour votre route de documentation d’API, et renvoyez votre propre corps de Response pour les pages protégées par nonce.
Authentification
Injection SQL
Daloy ne fournit pas de pilote de base de données, mais la frontière HTTP dont il est responsable (schémas Zod stricts, analyseur JSON renforcé, plafonds de taille du corps) réduit la surface qui atteint votre couche d’accès aux données. Voir SQL injection (en anglais) pour les motifs sûrs et dangereux par ORM (Prisma, Drizzle, Kysely, pilotes bruts), une recette de liste d’autorisation pour un ORDER BY dynamique et les règles grep qu’utilisent les mainteneurs pour détecter les régressions.
Injection de commandes
Le runtime de DaloyJS est exempt de child_process, ce qu’impose une vérification de CI : le framework lui-même ne peut donc pas lancer de shell. Voir Command injection (en anglais) pour la forme sûre d’un handler qui doit réellement invoquer un programme externe (execFile + tableau argv, jamais exec(`cmd $${input}`)), le piège BatBadBut de Windows et les règles grep qui empêchent les nouveaux bugs d’entrer au moment de la PR.
Panneaux d’administration
Vous construisez une interface d’administration ou de support client sur DaloyJS ? Voir Secure admin panels (en anglais) pour le modèle recommandé : routes internal: true, ipRestriction(), CSP stricte avec nonces par requête, authentification par administrateur, groupes rateLimit() de limitation des connexions et journalisation d’audit structurée, en correspondance un à un avec la liste de contrôle publique « secure admin panel » d’Aikido.
Chaîne d’approvisionnement
DaloyJS est distribué via pnpm pour un modèle d’installation plus strict. Les applications pnpm générées héritent des contrôles à l’installation, tandis que le dépôt de DaloyJS et le paquet GitHub Actions facultatif ajoutent des contrôles CI/CD contre l’empoisonnement de cache, l’hameçonnage de mainteneurs et l’abus de jetons OIDC observés lors d’incidents npm récents.
- Isolation stricte : les paquets ne peuvent pas atteindre de dépendances fantômes.
- Stockage adressable par contenu : chaque octet est haché et vérifié.
- Lockfile figé en CI avec
--ignore-scripts: installations reproductibles sans exécution des scripts de cycle de vie transitifs. verifyStoreIntegrity, des lectures qui détectent la corruption.strictPeerDependencies, aucune incompatibilité silencieuse de dépendances pair.minimumReleaseAge: 1440danspnpm-workspace.yaml, attendre 24 h avant d’installer des versions fraîchement publiées (pnpm 11 ignore la forme.npmrc; voir Durcissement de l’installation).ignoreScripts: truedans les projets générés, etstrictDepBuildsavec une liste d’autorisationallowBuildsdans le dépôt de DaloyJS : aucun script d’installation de dépendance, sauf ceux qui ont été revus. Cela arrête les vers à script d’installation, pas les charges utiles qui s’exécutent à l’import.- GitHub Actions épinglées par SHA : les workflows GitHub générés en option épinglent les actions tierces sur des commits immuables, pas sur des tags modifiables.
- Publication npm protégée de DaloyJS : les paquets du framework utilisent un workflow de release déclenché uniquement par tag, l’approbation d’un environnement protégé, la publication de confiance OIDC et
--provenance.
Si votre application générée vit hors de GitHub, reprenez directement les parties portables et transposez les règles des workflows GitHub vers votre hôte de CI. Le framework ne peut pas imposer la protection des branches ni le contrôle des sorties réseau des runners dans une installation GitLab, Bitbucket, Azure DevOps privée ou sur site.
Proxies de confiance et limitation de débit
DaloyJS ne fait plus confiance par défaut à X-Forwarded-For ni à X-Real-IP pour dériver une clé de limitation de débit. Ces en-têtes peuvent être falsifiés par le client, sauf si votre reverse proxy les retire et les réécrit. Le limiteur par défaut utilise donc comme clé le pair TCP, impossible à falsifier (depuis la 1.3.7 ; un seul compartiment partagé uniquement sur les runtimes qui n’exposent pas de pair), jusqu’à ce que vous fournissiez un keyGenerator explicite ou activiez trustProxyHeaders: true / trustedProxies derrière un proxy de confiance. Lorsque les en-têtes de proxy sont dignes de confiance, la clé est l’entrée X-Forwarded-For la plus à droite (celle qu’a ajoutée votre proxy), jamais une entrée de gauche préfixée par un attaquant. Les chaînes à plusieurs sauts déclarent leur nombre de sauts avec trustedHops. Lorsque l’origine elle-même est joignable, définissez trustedProxies pour que le socket du pair soit vérifié par rapport à une liste d’autorisation CIDR avant de croire le moindre en-tête transféré (voir la note sur autoBan, en anglais).
Si vous avez déjà déclaré votre topologie sur l’App avec behindProxy, inutile de la répéter. Un rateLimit() ou un loginThrottle() qui ne définit ni keyGenerator, ni trustProxyHeaders, ni trustedHops, ni trustedProxies suit le behindProxy de l’App : chaque client derrière votre proxy (ou sur Vercel et Cloudflare Workers, où il n’y a pas de socket de pair) obtient son propre compartiment au lieu que tout le monde en partage un seul. Les options explicites du limiteur l’emportent toujours, et trustProxyHeaders: false désactive ce comportement. Faites correspondre behindProxy à la réalité : préférez { cidrs } lorsque vous connaissez les adresses du proxy, car { hops } fait confiance aux entrées transférées les plus à droite de quiconque peut joindre directement l’application.
Pour les routes de saisie d’identifiants, utilisez loginThrottle() (en anglais) sur les routes /login, OTP et de réinitialisation du mot de passe, et wsRateLimit() sur les upgrades WebSocket associés. Les deux helpers peuvent puiser dans le même compartiment groupId.
Ressources de documentation auto-hébergées
Les helpers de documentation intégrés n’imposent plus une CSP taillée pour jsDelivr. Vous pouvez héberger vous-même les ressources de Swagger UI ou de Scalar, ajouter un nonce au script d’amorçage et émettre une CSP same-origin pour votre route de documentation.
Durcissement de l’installation
Avec pnpm 11+, les réglages d’installation se trouvent dans pnpm-workspace.yaml, avec des clés en camelCase. pnpm 11 ignore la forme .npmrc de ces réglages : une configuration limitée à .npmrc perd donc silencieusement le délai de carence et la politique de scripts. Ne gardez un miroir .npmrc que pour npm et les outils plus anciens.
Ces réglages protègent les installations qui les utilisent. ignoreScripts arrête les vers à script d’installation (du type Shai-Hulud) ; une charge utile qui s’exécute lorsque votre code importe le paquet n’est atténuée que par le délai de carence et un lockfile commité. create-daloy écrit ce fichier pour les projets pnpm générés.
Pour le guide complet CI/CD et mainteneurs, lisez Supply-chain security (en anglais). Exécutez pnpm audit --prod en CI et avant chaque release.
Correspondance avec l’OWASP API Security Top 10
Pour un parcours point par point de la façon dont Daloy traite chaque entrée de l’OWASP API Security Top 10 (2023), ainsi que des bonnes pratiques transversales (chiffrement, validation, limitation de débit, journalisation, inventaire, sûreté des API tierces), lisez OWASP API Top 10 mapping (en anglais).
Signaler une vulnérabilité
Utilisez le signalement privé de vulnérabilités de GitHub sur github.com/daloyjs/daloy/security/advisories/new en indiquant les étapes de reproduction. N’ouvrez pas d’issue publique contenant des détails d’exploitation.
Dernière mise à jour :