@nodefony/security 10.0.0-beta.3 → 10.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/index.js +6 -8
  3. package/dist/nodefony/command/{security-token.js → security-mcp-token.js} +18 -11
  4. package/dist/nodefony/command/security-oauth-doctor.js +5 -3
  5. package/dist/nodefony/command/security-secrets.js +10 -4
  6. package/dist/nodefony/command/security-user-add.js +9 -3
  7. package/dist/nodefony/command/security-user-delete.js +5 -3
  8. package/dist/nodefony/command/security-user-list.js +5 -3
  9. package/dist/nodefony/command/security-user-password.js +5 -3
  10. package/dist/nodefony/config/config.js +20 -4
  11. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  12. package/dist/nodefony/contracts/IAccessVoter.js +1 -2
  13. package/dist/nodefony/errors/AccessDeniedError.js +1 -2
  14. package/dist/nodefony/errors/ApiKeyError.js +3 -2
  15. package/dist/nodefony/errors/AuthenticationError.js +1 -2
  16. package/dist/nodefony/errors/CsrfError.js +1 -2
  17. package/dist/nodefony/errors/InsecureTransportError.js +1 -2
  18. package/dist/nodefony/errors/InvalidTargetError.js +1 -2
  19. package/dist/nodefony/errors/SsrfError.js +1 -2
  20. package/dist/nodefony/errors/ThrottledError.js +1 -2
  21. package/dist/nodefony/errors/UnverifiableTokenError.js +1 -2
  22. package/dist/nodefony/errors/WebAuthnError.js +1 -2
  23. package/dist/nodefony/keycloak/keycloakAdmin.js +1 -2
  24. package/dist/nodefony/keycloak/keycloakRealm.js +1 -2
  25. package/dist/nodefony/keycloak/keycloakRealmInput.js +1 -2
  26. package/dist/nodefony/keycloak/scaffold.js +3 -3
  27. package/dist/nodefony/keycloak/security-keycloak-realm.js +5 -3
  28. package/dist/nodefony/scaffold/contribute.js +1 -2
  29. package/dist/nodefony/service/accessTokenVerifier.js +101 -34
  30. package/dist/nodefony/service/apiKeys.js +45 -5
  31. package/dist/nodefony/service/auditService.js +4 -5
  32. package/dist/nodefony/service/authFlow.js +10 -8
  33. package/dist/nodefony/service/authorization.js +1 -2
  34. package/dist/nodefony/service/cors.js +1 -2
  35. package/dist/nodefony/service/csrf.js +1 -2
  36. package/dist/nodefony/service/firewall.js +5 -4
  37. package/dist/nodefony/service/keycloak.js +1 -2
  38. package/dist/nodefony/service/oauth2.js +29 -7
  39. package/dist/nodefony/service/securityHeaders.js +1 -2
  40. package/dist/nodefony/service/tokenService.js +38 -22
  41. package/dist/nodefony/service/totp.js +4 -5
  42. package/dist/nodefony/service/webAuthn.js +4 -5
  43. package/dist/nodefony/service/webhooks.js +4 -5
  44. package/dist/nodefony/src/RoleHierarchyWalker.js +1 -2
  45. package/dist/nodefony/src/SecuredArea.js +1 -2
  46. package/dist/nodefony/src/accountBinding.js +96 -0
  47. package/dist/nodefony/src/admin/SecurityAdminApi.js +1 -2
  48. package/dist/nodefony/src/admin/WebhookAdminApi.js +23 -7
  49. package/dist/nodefony/src/admin/adminAudit.js +1 -2
  50. package/dist/nodefony/src/admin/userRevocationCascade.js +79 -15
  51. package/dist/nodefony/src/apikey/apiKeyFormat.js +1 -2
  52. package/dist/nodefony/src/audit/MemoryAuditStore.js +1 -2
  53. package/dist/nodefony/src/audit/auditBridge.js +1 -2
  54. package/dist/nodefony/src/audit/auditFilters.js +1 -2
  55. package/dist/nodefony/src/audit/auditStoreRegistry.js +1 -2
  56. package/dist/nodefony/src/audit/readAuditContext.js +1 -2
  57. package/dist/nodefony/src/audit/recordAudit.js +1 -2
  58. package/dist/nodefony/src/authenticator/AnonymousAuthenticator.js +1 -2
  59. package/dist/nodefony/src/authenticator/ApiKeyAuthenticator.js +3 -2
  60. package/dist/nodefony/src/authenticator/ExternalJwtAuthenticator.js +8 -113
  61. package/dist/nodefony/src/authenticator/ExternalSubjectResolver.js +180 -0
  62. package/dist/nodefony/src/authenticator/FirewallRealtimeAuthenticator.js +42 -4
  63. package/dist/nodefony/src/authenticator/JwtAuthenticator.js +91 -16
  64. package/dist/nodefony/src/authenticator/SessionAuthenticator.js +9 -5
  65. package/dist/nodefony/src/authenticator/UserPasswordAuthenticator.js +1 -2
  66. package/dist/nodefony/src/authenticator/authenticatorRegistry.js +3 -17
  67. package/dist/nodefony/src/authenticator/externalSubject.js +1 -2
  68. package/dist/nodefony/src/authenticator/peekIssuer.js +1 -2
  69. package/dist/nodefony/src/boot/publicRoutesNotice.js +1 -2
  70. package/dist/nodefony/src/crypto/secretCipher.js +1 -2
  71. package/dist/nodefony/src/csp.js +1 -2
  72. package/dist/nodefony/src/csrfToken.js +1 -2
  73. package/dist/nodefony/src/entryPoint.js +1 -2
  74. package/dist/nodefony/src/net/ssrfGuard.js +1 -2
  75. package/dist/nodefony/src/net/transportFailure.js +57 -0
  76. package/dist/nodefony/src/oauth/clientCredentials.js +1 -2
  77. package/dist/nodefony/src/oauth/httpJson.js +3 -3
  78. package/dist/nodefony/src/oauth/metadata.js +5 -16
  79. package/dist/nodefony/src/oauth/oauth2Client.js +1 -2
  80. package/dist/nodefony/src/oauth/oauthProviderRegistry.js +1 -2
  81. package/dist/nodefony/src/oauth/providerDiagnosis.js +5 -6
  82. package/dist/nodefony/src/oauth/providerRoles.js +1 -2
  83. package/dist/nodefony/src/oauth/providers/github.js +16 -6
  84. package/dist/nodefony/src/oauth/providers/oidc.js +9 -6
  85. package/dist/nodefony/src/openToSignup.js +1 -2
  86. package/dist/nodefony/src/realtime/UserRealtimeToken.js +17 -3
  87. package/dist/nodefony/src/realtime/frameAuthorizer.js +1 -2
  88. package/dist/nodefony/src/rolesWithin.js +57 -0
  89. package/dist/nodefony/src/sessionAuthentication.js +1 -2
  90. package/dist/nodefony/src/sessionIdentity.js +29 -3
  91. package/dist/nodefony/src/throttle/LoginThrottler.js +1 -2
  92. package/dist/nodefony/src/token/AnonymousToken.js +1 -2
  93. package/dist/nodefony/src/token/JwtKeystore.js +14 -5
  94. package/dist/nodefony/src/token/MemoryTokenStore.js +1 -2
  95. package/dist/nodefony/src/token/RemoteJwtVerifier.js +6 -3
  96. package/dist/nodefony/src/token/UserToken.js +1 -2
  97. package/dist/nodefony/src/token/jwtRuntime.js +1 -2
  98. package/dist/nodefony/src/token/scopeGrants.js +109 -0
  99. package/dist/nodefony/src/token/secretFile.js +1 -2
  100. package/dist/nodefony/src/token/tokenCriteria.js +1 -2
  101. package/dist/nodefony/src/token/tokenFilters.js +1 -2
  102. package/dist/nodefony/src/token/tokenSort.js +1 -2
  103. package/dist/nodefony/src/token/tokenStatus.js +1 -2
  104. package/dist/nodefony/src/token/tokenStoreRegistry.js +1 -2
  105. package/dist/nodefony/src/totp/MemoryTotpSecretStore.js +1 -2
  106. package/dist/nodefony/src/totp/totpCipher.js +1 -2
  107. package/dist/nodefony/src/totp/totpCrypto.js +1 -2
  108. package/dist/nodefony/src/totp/totpOperations.js +1 -2
  109. package/dist/nodefony/src/totp/totpSecretStoreRegistry.js +1 -2
  110. package/dist/nodefony/src/transport/CredentialTransportPolicy.js +1 -2
  111. package/dist/nodefony/src/voter/RoleVoter.js +1 -2
  112. package/dist/nodefony/src/voter/ScopeVoter.js +1 -2
  113. package/dist/nodefony/src/voter/voterRegistry.js +1 -2
  114. package/dist/nodefony/src/webauthn/MemoryWebAuthnCredentialStore.js +1 -2
  115. package/dist/nodefony/src/webauthn/webAuthnCredentialStoreRegistry.js +1 -2
  116. package/dist/nodefony/src/webhook/MemoryWebhookStore.js +1 -2
  117. package/dist/nodefony/src/webhook/WebhookDispatcher.js +1 -2
  118. package/dist/nodefony/src/webhook/webhookCipher.js +1 -2
  119. package/dist/nodefony/src/webhook/webhookDelivery.js +1 -2
  120. package/dist/nodefony/src/webhook/webhookFilters.js +1 -2
  121. package/dist/nodefony/src/webhook/webhookSignature.js +1 -2
  122. package/dist/nodefony/src/webhook/webhookSort.js +1 -2
  123. package/dist/nodefony/src/webhook/webhookStoreRegistry.js +1 -2
  124. package/dist/types/nodefony/command/{security-token.d.ts → security-mcp-token.d.ts} +7 -6
  125. package/dist/types/nodefony/config/config.d.ts +1 -0
  126. package/dist/types/nodefony/errors/ApiKeyError.d.ts +3 -1
  127. package/dist/types/nodefony/service/accessTokenVerifier.d.ts +35 -14
  128. package/dist/types/nodefony/service/apiKeys.d.ts +13 -2
  129. package/dist/types/nodefony/service/oauth2.d.ts +20 -0
  130. package/dist/types/nodefony/src/accountBinding.d.ts +82 -0
  131. package/dist/types/nodefony/src/admin/userRevocationCascade.d.ts +23 -5
  132. package/dist/types/nodefony/src/authenticator/ExternalJwtAuthenticator.d.ts +2 -44
  133. package/dist/types/nodefony/src/authenticator/ExternalSubjectResolver.d.ts +99 -0
  134. package/dist/types/nodefony/src/authenticator/FirewallRealtimeAuthenticator.d.ts +4 -1
  135. package/dist/types/nodefony/src/authenticator/JwtAuthenticator.d.ts +37 -1
  136. package/dist/types/nodefony/src/net/transportFailure.d.ts +22 -0
  137. package/dist/types/nodefony/src/oauth/httpJson.d.ts +0 -8
  138. package/dist/types/nodefony/src/realtime/UserRealtimeToken.d.ts +7 -1
  139. package/dist/types/nodefony/src/realtime/realtimeContracts.d.ts +6 -0
  140. package/dist/types/nodefony/src/rolesWithin.d.ts +27 -0
  141. package/dist/types/nodefony/src/sessionIdentity.d.ts +22 -0
  142. package/dist/types/nodefony/src/token/scopeGrants.d.ts +77 -0
  143. package/docs/api-keys.md +181 -174
  144. package/docs/audit.md +140 -140
  145. package/docs/authenticators.md +101 -101
  146. package/docs/authorization.md +116 -69
  147. package/docs/cors.md +64 -64
  148. package/docs/csrf.md +74 -71
  149. package/docs/external-jwt.md +22 -12
  150. package/docs/firewall.md +85 -82
  151. package/docs/headers.md +81 -81
  152. package/docs/index.md +4 -4
  153. package/docs/keycloak.md +95 -78
  154. package/docs/login-page.md +23 -23
  155. package/docs/oauth2.md +157 -146
  156. package/docs/obtenir-un-jeton.md +116 -26
  157. package/docs/tokens.md +128 -122
  158. package/docs/totp.md +136 -136
  159. package/docs/webauthn.md +153 -153
  160. package/docs/webhooks.md +127 -124
  161. package/package.json +13 -13
  162. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
package/docs/headers.md CHANGED
@@ -38,8 +38,8 @@ source: "src/packages/@nodefony/security/docs/headers.md"
38
38
  > les pose en **deux couches, une seule autorité par en-tête** : le socle **transport**
39
39
  > (`@nodefony/http`, dès l'entrée brute — couvre aussi les fichiers statiques et les erreurs) et la
40
40
  > couche **applicative** (`@nodefony/security`, dans le pipeline — CSP, Referrer-Policy, isolation
41
- > cross-origin). Ancré sur `SecurityHeaders` (`securityHeaders.ts:42`) et
42
- > `Firewall.applySecurityHeaders()` (`firewall.ts:1152`).
41
+ > cross-origin). Ancré sur `SecurityHeaders` (`securityHeaders.ts#SecurityHeaders`) et
42
+ > `Firewall.applySecurityHeaders()` (`firewall.ts#Firewall.applySecurityHeaders`).
43
43
 
44
44
  📍 [Documentation](../../../../../docs/index.md) › [Sécurité](index.md) › **En-têtes de sécurité**
45
45
 
@@ -67,7 +67,7 @@ flowchart TD
67
67
 
68
68
  **Une seule source par en-tête** : `@nodefony/security` ne ré-émet **jamais** les trois en-têtes
69
69
  transport — c'est écrit noir sur blanc dans le contrat de la couche applicative
70
- (`ISecurityHeadersOptions`, `securityHeaders.ts:12`). Pas de double émission, donc pas de valeurs
70
+ (`ISecurityHeadersOptions`, `securityHeaders.ts#ISecurityHeadersOptions`). Pas de double émission, donc pas de valeurs
71
71
  contradictoires sur la même réponse.
72
72
 
73
73
  ## 📖 Lexique
@@ -117,20 +117,20 @@ Le détail de chaque en-tête — menace, valeur par défaut, compromis — est
117
117
  Un framework qui recalcule ses en-têtes à chaque requête paie ce confort en allocations. Nodefony
118
118
  fait l'inverse : **tout ce qui est constant est calculé une fois au démarrage**.
119
119
 
120
- - `SecurityHeaders` (`securityHeaders.ts:42`) construit **au boot** la table des en-têtes constants
120
+ - `SecurityHeaders` (`securityHeaders.ts#SecurityHeaders`) construit **au boot** la table des en-têtes constants
121
121
  (Referrer-Policy, COOP/COEP/CORP, Origin-Agent-Cluster, Permissions-Policy, et le CSP quand il est
122
- statique) et la **gèle** avec `Object.freeze` (`securityHeaders.ts:77`). Par requête, le firewall
122
+ statique) et la **gèle** avec `Object.freeze` (`securityHeaders.ts#SecurityHeaders.constructor`). Par requête, le firewall
123
123
  se contente de la parcourir et de la poser : zéro concaténation, zéro objet créé.
124
124
  - Côté transport, même principe : `HttpKernel.computeSecurityHeaderCaches()`
125
- (`http-kernel.ts:411`) précalcule la chaîne HSTS (`max-age`, `includeSubDomains`, `preload`) au
126
- boot ; `onHttpRequest` (`http-kernel.ts:1093`) ne fait plus que trois `setHeader`.
125
+ (`http-kernel.ts#HttpKernel.computeSecurityHeaderCaches`) précalcule la chaîne HSTS (`max-age`, `includeSubDomains`, `preload`) au
126
+ boot ; `onHttpRequest` (`http-kernel.ts#HttpKernel.onHttpRequest`) ne fait plus que trois `setHeader`.
127
127
  - Le seul coût variable est le **nonce CSP**, et il est **paresseux** : `Context.cspNonce`
128
- (`Context.ts:253`) ne tire ses 128 bits (16 octets CSPRNG, en base64) d'un pool amorti (`randomFillSync`, 4 Ko pour 256 nonces) qu'à la première lecture,
128
+ (`Context.ts#Context.#cspNonce`) ne tire ses 128 bits (16 octets CSPRNG, en base64) d'un pool amorti (`randomFillSync`, 4 Ko pour 256 nonces) qu'à la première lecture,
129
129
  puis mémoïse. Une réponse qui n'a aucun script inline à signer ne paie aucun appel crypto.
130
130
 
131
131
  Le second parti pris est la **séparation d'autorité** décrite plus haut : un seul émetteur par
132
132
  en-tête, donc un comportement prévisible et testable — le banc live vérifie les deux couches sur la
133
- même réponse (`security-headers.test.ts:30`).
133
+ même réponse (`security-headers.test.ts`).
134
134
 
135
135
  > [!IMPORTANT]
136
136
  > `@nodefony/security` est **optionnel**, pas le socle. Une app Nodefony sans module security émet
@@ -173,7 +173,7 @@ export default defineConfig(() => ({
173
173
  ```
174
174
 
175
175
  Les clés sont **typées et auto-complétées** : le module augmente le registre `NodefonyModuleConfig`
176
- du core (`index.ts:37`), donc `use("@nodefony/security", …)` propose les clés **et** les valeurs
176
+ du core (`index.ts#nodefony`), donc `use("@nodefony/security", …)` propose les clés **et** les valeurs
177
177
  d'enum (`referrerPolicy`, `coop`, `corp`…). Une valeur hors enum casse le boot, pas la production.
178
178
 
179
179
  ### 2. Ce qu'on observe
@@ -230,7 +230,7 @@ export default EmbedController;
230
230
  curl -sI http://localhost:5151/embed/video | grep -i content-security-policy
231
231
  # … ; frame-src https://www.youtube.com ← ajouté ICI seulement
232
232
  curl -sI http://localhost:5151/nodefony/test/index | grep -c youtube
233
- # 0 ← isolation prouvée (security-headers.test.ts:83)
233
+ # 0 ← isolation prouvée (security-headers.test.ts)
234
234
  ```
235
235
 
236
236
  ## 🛡️ Le catalogue des en-têtes
@@ -255,7 +255,7 @@ Choisir en cinq secondes — puis le détail dans les cartes.
255
255
  **La menace** : n'importe quelle entrée non échappée (commentaire, nom d'utilisateur, paramètre
256
256
  réfléchi) devient un vecteur d'exécution de code. Le CSP est le filet quand l'échappement a raté.
257
257
 
258
- **Le défaut Nodefony** est délibérément « secure-but-usable » (`config.ts:276`) : seul `script-src`
258
+ **Le défaut Nodefony** est délibérément « secure-but-usable » (`config.ts#headersSchema`) : seul `script-src`
259
259
  est **strict** — `'self'` plus le nonce de la requête, ce qui est la vraie défense XSS. Le reste
260
260
  couvre les besoins réels d'une app moderne (CSS-in-JS via `style-src 'unsafe-inline'`, images
261
261
  `data:`/`blob:`, workers, fetch/WS same-origin) et ajoute les durcissements gratuits :
@@ -275,8 +275,8 @@ privilégient `frame-ancestors`, `X-Frame-Options` reste le filet pour les ancie
275
275
  victime est capté par ton interface.
276
276
 
277
277
  Posé par le **transport** depuis un cache calculé au boot — `secFrameOptions`
278
- (`http-kernel.ts:1144`) — et configuré côté `@nodefony/http` avec `frameOptions`
279
- (`http/nodefony/config/config.ts:136`), qui vaut `DENY` par défaut. `SAMEORIGIN` si ton propre site
278
+ (`http-kernel.ts#HttpKernel.secFrameOptions`) — et configuré côté `@nodefony/http` avec `frameOptions`
279
+ (`http/nodefony/config/config.ts#securityHeadersSchema`), qui vaut `DENY` par défaut. `SAMEORIGIN` si ton propre site
280
280
  s'auto-encadre. C'est un des trois en-têtes que security **ne ré-émet pas** : il doit valoir aussi
281
281
  pour un HTML statique servi directement depuis `public/`.
282
282
 
@@ -287,9 +287,9 @@ pour un HTML statique servi directement depuis `public/`.
287
287
  avec tes cookies.
288
288
 
289
289
  Valeur unique reconnue : `nosniff`, posée depuis le cache `secContentTypeOptions`
290
- (`http-kernel.ts:1141`). C'est **l'en-tête qui justifie le mieux la couche transport** : le danger
290
+ (`http-kernel.ts#HttpKernel.secContentTypeOptions`). C'est **l'en-tête qui justifie le mieux la couche transport** : le danger
291
291
  vient précisément des fichiers servis hors pipeline applicatif — un banc live le prouve sur une 404
292
- (`security-headers.test.ts:38`).
292
+ (`security-headers.test.ts`).
293
293
 
294
294
  ### `Strict-Transport-Security` — HTTPS, et rien d'autre
295
295
 
@@ -297,15 +297,15 @@ vient précisément des fichiers servis hors pipeline applicatif — un banc liv
297
297
  installer un intercepteur.
298
298
 
299
299
  La chaîne est assemblée au boot par `HttpKernel.computeSecurityHeaderCaches()`
300
- (`http-kernel.ts:411`) : `max-age`, puis `includeSubDomains` et `preload` selon la config.
300
+ (`http-kernel.ts#HttpKernel.computeSecurityHeaderCaches`) : `max-age`, puis `includeSubDomains` et `preload` selon la config.
301
301
 
302
302
  Elle n'est posée que **sur une réponse HTTPS ou HTTP/2** — le cache `secHsts` est conditionné au type
303
- de serveur (`http-kernel.ts:1147`). C'est conforme à la RFC 6797, qui veut qu'un HSTS reçu en clair
303
+ de serveur (`http-kernel.ts#HttpKernel.secHsts`). C'est conforme à la RFC 6797, qui veut qu'un HSTS reçu en clair
304
304
  soit ignoré : l'émettre sur du HTTP simple ne ferait que polluer. Défaut : un an, sous-domaines
305
305
  inclus.
306
306
 
307
307
  > [!CAUTION]
308
- > `preload: true` (`http/nodefony/config/config.ts:107`) inscrit ton domaine dans la liste
308
+ > `preload: true` (`http/nodefony/config/config.ts#strictTransportSecuritySchema`) inscrit ton domaine dans la liste
309
309
  > pré-chargée des navigateurs. C'est un **engagement quasi irréversible** : tout sous-domaine
310
310
  > incapable de servir en HTTPS devient inaccessible, et la sortie de liste prend des mois. À ne
311
311
  > jamais activer « pour voir ».
@@ -315,7 +315,7 @@ inclus.
315
315
  **La menace** : la fuite d'URL. Chemins parlants, identifiants de session dans une query, jetons de
316
316
  réinitialisation — tout part chez le site suivant via le `Referer`.
317
317
 
318
- Défaut Nodefony : `no-referrer` (`security/nodefony/config/config.ts:316`), la valeur la plus stricte. La valeur est un
318
+ Défaut Nodefony : `no-referrer` (`security/nodefony/config/config.ts#headersSchema`), la valeur la plus stricte. La valeur est un
319
319
  **enum W3C fermé** — huit valeurs validées au boot, donc pas de faute de frappe qui passerait en
320
320
  silence (l'écriture libre `no-refferer` casserait la protection sans prévenir).
321
321
 
@@ -328,7 +328,7 @@ interne, origine seule vers l'extérieur, rien du tout vers du HTTP en clair.
328
328
  `window.opener` et partage un groupe de contexte de navigation — surface d'attaque pour du
329
329
  _tabnabbing_ et pour les canaux auxiliaires type Spectre.
330
330
 
331
- `same-origin` (`securityHeaders.ts:71`) rompt ce lien. C'est aussi, avec COEP, l'une des deux
331
+ `same-origin` (`securityHeaders.ts#SecurityHeaders.constructor`) rompt ce lien. C'est aussi, avec COEP, l'une des deux
332
332
  conditions de l'**isolation cross-origin**, indispensable si tu veux `SharedArrayBuffer` ou des
333
333
  timers haute résolution.
334
334
 
@@ -337,13 +337,13 @@ timers haute résolution.
337
337
  **La menace** : ta page embarque des ressources tierces qui n'ont jamais donné leur accord, et les
338
338
  place dans ton processus.
339
339
 
340
- `require-corp` (`securityHeaders.ts:72`) exige que **chaque** ressource tierce s'annonce comme
340
+ `require-corp` (`securityHeaders.ts#SecurityHeaders.constructor`) exige que **chaque** ressource tierce s'annonce comme
341
341
  partageable (CORP ou CORS). C'est le complément de COOP pour l'isolation complète.
342
342
 
343
343
  > [!WARNING]
344
344
  > `coep: "require-corp"` **casse toutes les ressources tierces non conformes** — polices Google,
345
345
  > images de CDN, iframes de paiement. C'est pour cette raison qu'il est absent des défauts, et
346
- > volontairement exclu du banc de test (`security-headers.test.ts:62`). À activer seulement si tu
346
+ > volontairement exclu du banc de test (`security-headers.test.ts`). À activer seulement si tu
347
347
  > as besoin de l'isolation cross-origin, et après audit de tes assets.
348
348
 
349
349
  ### `Cross-Origin-Resource-Policy` — mes assets ne s'incluent pas ailleurs
@@ -351,7 +351,7 @@ partageable (CORP ou CORS). C'est le complément de COOP pour l'isolation compl
351
351
  **La menace** : symétrique du précédent. Un site tiers inclut tes images ou tes scripts pour les
352
352
  mesurer, les mettre en cache, ou monter une attaque par inclusion.
353
353
 
354
- `same-origin` (`securityHeaders.ts:73`) interdit toute inclusion externe ; `same-site` autorise tes
354
+ `same-origin` (`securityHeaders.ts#SecurityHeaders.constructor`) interdit toute inclusion externe ; `same-site` autorise tes
355
355
  propres sous-domaines ; `cross-origin` ouvre — c'est ce qu'il faut sur une CDN publique assumée.
356
356
 
357
357
  ### `Origin-Agent-Cluster` — un bac à sable par origine
@@ -360,14 +360,14 @@ propres sous-domaines ; `cross-origin` ouvre — c'est ce qu'il faut sur une CDN
360
360
  mesurables.
361
361
 
362
362
  Nodefony l'émet comme un **booléen de champ structuré** RFC 9651 : la valeur est littéralement `?1`
363
- (`securityHeaders.ts:75`). C'est une **demande**, pas une garantie — le navigateur décide.
363
+ (`securityHeaders.ts#SecurityHeaders.constructor`). C'est une **demande**, pas une garantie — le navigateur décide.
364
364
 
365
365
  ### `Permissions-Policy` — coupe le micro par défaut
366
366
 
367
367
  **La menace** : une iframe tierce (widget, publicité) demande la caméra, le micro ou la position, et
368
368
  la boîte de dialogue s'affiche sous **ton** nom de domaine.
369
369
 
370
- Valeur libre — le champ `permissionsPolicy` est recopié tel quel (`securityHeaders.ts:76`),
370
+ Valeur libre — le champ `permissionsPolicy` est recopié tel quel (`securityHeaders.ts#ISecurityHeadersOptions.permissionsPolicy`),
371
371
  typiquement `camera=(), microphone=(), geolocation=()` : la
372
372
  liste vide signifie « personne, pas même moi ». Absent par défaut car la liste des fonctionnalités
373
373
  dépend entièrement de l'application.
@@ -379,7 +379,7 @@ config security est la première source de confusion sur ce sujet.
379
379
 
380
380
  ### Couche applicative — `use("@nodefony/security", { headers })`
381
381
 
382
- Dérivé du schéma Zod `headersSchema` (`config.ts:256`).
382
+ Dérivé du schéma Zod `headersSchema` (`config.ts#headersSchema`).
383
383
 
384
384
  <!-- prettier-ignore -->
385
385
  | Option | Type | Défaut | Effet |
@@ -396,8 +396,8 @@ Dérivé du schéma Zod `headersSchema` (`config.ts:256`).
396
396
 
397
397
  ### Socle transport — `use("@nodefony/http", { securityHeaders })`
398
398
 
399
- Dérivé de `securityHeadersSchema` (`http/nodefony/config/config.ts:123`). Ces trois réglages sont
400
- **éditables à chaud** (`runtimeMutable`) : `HttpKernel.onConfigChanged()` (`http-kernel.ts:461`)
399
+ Dérivé de `securityHeadersSchema` (`http/nodefony/config/config.ts#securityHeadersSchema`). Ces trois réglages sont
400
+ **éditables à chaud** (`runtimeMutable`) : `HttpKernel.onConfigChanged()` (`http-kernel.ts#HttpKernel.onConfigChanged`)
401
401
  recalcule les caches, donc la valeur suivante s'applique sans redémarrage.
402
402
 
403
403
  | Option | Type | Défaut | Effet |
@@ -411,10 +411,10 @@ recalcule les caches, donc la valeur suivante s'applique sans redémarrage.
411
411
 
412
412
  > [!WARNING]
413
413
  > Les clés `hsts`, `hstsMaxAgeS`, `frameguard` et `noSniff` **existent** dans la config security
414
- > (`config.ts:263`, `config.ts:263`, `config.ts:263`, `config.ts:263`) mais **ne pilotent rien** : la couche
415
- > applicative ne les lit pas (`securityHeaders.ts:6`), elles ne servent qu'à l'introspection
414
+ > (`config.ts#headersSchema`) mais **ne pilotent rien** : la couche
415
+ > applicative ne les lit pas (`securityHeaders.ts#ISecurityHeadersOptions`), elles ne servent qu'à l'introspection
416
416
  > affichée dans Studio. Pour changer réellement `X-Frame-Options`, c'est `securityHeaders.frameOptions`
417
- > **du module http**. Même remarque pour `hidePoweredBy` (`config.ts:288`) : Nodefony n'émet aucun
417
+ > **du module http**. Même remarque pour `hidePoweredBy` (`config.ts#headersSchema`) : Nodefony n'émet aucun
418
418
  > `X-Powered-By`, l'option est un no-op documenté.
419
419
 
420
420
  ## 🏗️ Le CSP en détail — deux régimes, trois façons de l'étendre
@@ -422,11 +422,11 @@ recalcule les caches, donc la valeur suivante s'applique sans redémarrage.
422
422
  ### Régime 1 — CSP statique (0 allocation par requête)
423
423
 
424
424
  Si le CSP ne contient pas `{{nonce}}`, ou si `cspNonces` est à `false`, la chaîne est rangée telle
425
- quelle dans la table gelée du boot (`securityHeaders.ts:66`). Par requête : une lecture, un
425
+ quelle dans la table gelée du boot (`securityHeaders.ts#SecurityHeaders.constructor`). Par requête : une lecture, un
426
426
  `setHeader`. Rien d'autre.
427
427
 
428
428
  Détail de robustesse : si tu désactives `cspNonces` en laissant le placeholder dans la chaîne, le
429
- token résiduel `'nonce-{{nonce}}'` est **purgé** (`securityHeaders.ts:63`). Sans ça, tu servirais un
429
+ token résiduel `'nonce-{{nonce}}'` est **purgé** (`securityHeaders.ts#SecurityHeaders.constructor`). Sans ça, tu servirais un
430
430
  CSP contenant un nonce littéral jamais émis — donc un `script-src` qui bloque **tout**, y compris tes
431
431
  propres scripts. Le code refuse de produire un CSP cassé.
432
432
 
@@ -437,22 +437,22 @@ attaquant ne peut pas deviner la valeur : il est refusé même s'il est syntaxiq
437
437
 
438
438
  Le chemin complet, sans surprise :
439
439
 
440
- 1. **Au boot**, la chaîne CSP est **pré-découpée** autour de `{{nonce}}` (`securityHeaders.ts:58`).
440
+ 1. **Au boot**, la chaîne CSP est **pré-découpée** autour de `{{nonce}}` (`securityHeaders.ts#SecurityHeaders.constructor`).
441
441
  Aucun parsing ni regex n'aura lieu pendant une requête.
442
- 2. **Par requête**, `Firewall.applySecurityHeaders()` (`firewall.ts:1152`) lit `context.cspNonce` —
443
- ce qui **génère** le jeton à cet instant (`Context.ts:253`) — puis appelle
444
- `SecurityHeaders.cspFor()` (`securityHeaders.ts:100`) : un seul `join`.
442
+ 2. **Par requête**, `Firewall.applySecurityHeaders()` (`firewall.ts#Firewall.applySecurityHeaders`) lit `context.cspNonce` —
443
+ ce qui **génère** le jeton à cet instant (`Context.ts#Context.#cspNonce`) — puis appelle
444
+ `SecurityHeaders.cspFor()` (`securityHeaders.ts#SecurityHeaders.cspFor`) : un seul `join`.
445
445
  3. **Dans la vue**, le contrôleur relit `context.cspNonce`, qui est **mémoïsé** : l'en-tête et le
446
446
  `<script nonce="…">` portent forcément la même valeur. C'est le motif employé par le contrôleur
447
- de Studio (`StudioController.ts:62`).
447
+ de Studio (`StudioController.ts#StudioController.renderStudio`).
448
448
 
449
- `SecurityHeaders.hasNonce` (`securityHeaders.ts:100`) est le drapeau qui décide du régime : à `false`,
449
+ `SecurityHeaders.hasNonce` (`securityHeaders.ts#SecurityHeaders.hasNonce`) est le drapeau qui décide du régime : à `false`,
450
450
  pas une seule opération crypto. Il n'y a **aucun setter** pour `cspNonce` : un jeton serveur doit
451
451
  rester imprévisible, jamais pilotable par le client — contrairement au `requestId`, qui, lui, accepte
452
452
  une corrélation entrante.
453
453
 
454
454
  **Placement dans le pipeline** : `applySecurityHeaders` est appelé **après le resolve** et **avant**
455
- le repli statique et le `writeHead` (`http-kernel.ts:1666`). Cet ordre n'est pas cosmétique : il
455
+ le repli statique et le `writeHead` (`http-kernel.ts#HttpKernel.runHttpPipeline`). Cet ordre n'est pas cosmétique : il
456
456
  faut que le routeur ait posé les directives `@Csp` de la route pour pouvoir les fusionner, et il faut
457
457
  être avant l'écriture des en-têtes pour pouvoir en poser.
458
458
 
@@ -464,21 +464,21 @@ faut que le routeur ait posé les directives `@Csp` de la route pour pouvoir les
464
464
  | Module | `Firewall.registerCspOrigins()` | besoin **permanent** d'un module (ex. Vite) | à l'enregistrement |
465
465
  | Route / contrôleur | `@Csp({ … })` | besoin **ponctuel** d'une réponse (iframe, CDN) | par requête décorée |
466
466
 
467
- Les trois convergent vers `mergeCspFragments()` (`csp.ts:56`), et c'est un **merge structuré**, pas
467
+ Les trois convergent vers `mergeCspFragments()` (`csp.ts#mergeCspFragments`), et c'est un **merge structuré**, pas
468
468
  une concaténation. Pourquoi c'est vital : en CSP, une directive **répétée est ignorée** après sa
469
- première occurrence (W3C CSP Level 3 §3, rationnel documenté `csp.ts:8`). Concaténer
469
+ première occurrence (W3C CSP Level 3 §3, rationnel documenté `csp.ts#CspFragment`). Concaténer
470
470
  `"script-src 'self'"` et `"script-src https://cdn"` produirait un en-tête où la seconde est purement
471
471
  et simplement jetée — une extension silencieusement sans effet, le pire des deux mondes.
472
472
 
473
473
  Le merge fusionne donc les sources **dans une seule directive**, dédoublonnées, base d'abord ; une
474
474
  directive absente est ajoutée en fin. La fonction est **pure et déterministe** (`parseCsp()`
475
- `csp.ts:25` → `serializeCsp()` `csp.ts:34`), ce qui rend l'en-tête stable d'une requête à l'autre et
475
+ `csp.ts#parseCsp` → `serializeCsp()` `csp.ts#serializeCsp`), ce qui rend l'en-tête stable d'une requête à l'autre et
476
476
  les tests fiables.
477
477
 
478
478
  **Coût** : le merge d'un module est payé **une fois**, au (dés)enregistrement
479
- (`Firewall.#rebuildSecurityHeaders()`, `firewall.ts:1209`), jamais par requête. Le merge d'une route
479
+ (`Firewall.#rebuildSecurityHeaders()`, `firewall.ts#Firewall.#rebuildSecurityHeaders`), jamais par requête. Le merge d'une route
480
480
  `@Csp` est payé **uniquement sur les routes décorées** (`SecurityHeaders.cspForExtra()`,
481
- `securityHeaders.ts:131`) ; le cas courant reste le simple `join`.
481
+ `securityHeaders.ts#SecurityHeaders.cspForExtra`) ; le cas courant reste le simple `join`.
482
482
 
483
483
  ## 🧩 Extension — déclarer un fragment CSP depuis son module
484
484
 
@@ -502,44 +502,44 @@ Trois propriétés à retenir :
502
502
 
503
503
  - **Aucun couplage** : la résolution par nom de service évite un cycle de dépendances, et
504
504
  `registerCspOrigins` est optionnel — un module fonctionne dans une app **sans** security.
505
- - **Réversible** : `Firewall.unregisterCspOrigins()` (`firewall.ts:1202`) retire le fragment et
505
+ - **Réversible** : `Firewall.unregisterCspOrigins()` (`firewall.ts#Firewall.unregisterCspOrigins`) retire le fragment et
506
506
  reconstruit le CSP de base. C'est ce que fait `@nodefony/frontend` à l'arrêt du serveur Vite.
507
507
  - **Idempotent** : la reconstruction repart **toujours** du `headers.csp` d'origine
508
- (`firewall.ts:1212`), jamais d'un CSP déjà fusionné — pas d'accumulation entre deux
508
+ (`firewall.ts#Firewall.#rebuildSecurityHeaders`), jamais d'un CSP déjà fusionné — pas d'accumulation entre deux
509
509
  enregistrements.
510
510
 
511
511
  L'exemple de référence vit dans le framework : en développement, `@nodefony/frontend` déclare les
512
512
  origines du serveur Vite et `'unsafe-eval'` (exigé par le Fast Refresh de React) via
513
- `FrontendService.#viteCspFragment()` (`FrontendService.ts:804`) — ce qui explique qu'un CSP observé
513
+ `FrontendService.#viteCspFragment()` (`FrontendService.ts#FrontendService.#viteCspFragment`) — ce qui explique qu'un CSP observé
514
514
  en dev soit plus large qu'en production, où ce fragment n'existe pas.
515
515
 
516
516
  ## 📜 Normes appliquées
517
517
 
518
- | Domaine | Norme | Ancrage dans le code |
519
- | ------------------------------------ | -------------------------------- | ------------------------------------------------------------ |
520
- | Politique de sécurité du contenu | W3C CSP Level 3 | `mergeCspFragments()` (`csp.ts:56`), directive non dupliquée |
521
- | Nonce CSP (unicité, imprévisibilité) | W3C CSP Level 3 §6.7.4 | `Context.cspNonce` — 128 bits CSPRNG (`Context.ts:253`) |
522
- | HSTS | RFC 6797 | posé sur TLS uniquement (`http-kernel.ts:981`) |
523
- | Champ structuré booléen | RFC 9651 | `Origin-Agent-Cluster: ?1` (`securityHeaders.ts:75`) |
524
- | Referrer-Policy | W3C Referrer Policy (enum fermé) | 8 valeurs validées au boot (`config.ts:273`) |
525
- | Isolation cross-origin | WHATWG HTML (COOP/COEP/CORP) | `securityHeaders.ts:71` |
526
- | Anti-MIME-sniffing | WHATWG Fetch (`nosniff`) | `secContentTypeOptions` (`http-kernel.ts:1141`) |
527
- | Durcissement en-têtes | OWASP Secure Headers | `computeSecurityHeaderCaches()` (`http-kernel.ts:411`) |
518
+ | Domaine | Norme | Ancrage dans le code |
519
+ | ------------------------------------ | -------------------------------- | ----------------------------------------------------------------------------------------- |
520
+ | Politique de sécurité du contenu | W3C CSP Level 3 | `mergeCspFragments()` (`csp.ts#mergeCspFragments`), directive non dupliquée |
521
+ | Nonce CSP (unicité, imprévisibilité) | W3C CSP Level 3 §6.7.4 | `Context.cspNonce` — 128 bits CSPRNG (`Context.ts#Context.#cspNonce`) |
522
+ | HSTS | RFC 6797 | posé sur TLS uniquement (`http-kernel.ts#HttpKernel.getErrorRenderer`) |
523
+ | Champ structuré booléen | RFC 9651 | `Origin-Agent-Cluster: ?1` (`securityHeaders.ts#SecurityHeaders.constructor`) |
524
+ | Referrer-Policy | W3C Referrer Policy (enum fermé) | 8 valeurs validées au boot (`config.ts#headersSchema`) |
525
+ | Isolation cross-origin | WHATWG HTML (COOP/COEP/CORP) | `securityHeaders.ts#SecurityHeaders.constructor` |
526
+ | Anti-MIME-sniffing | WHATWG Fetch (`nosniff`) | `secContentTypeOptions` (`http-kernel.ts#HttpKernel.secContentTypeOptions`) |
527
+ | Durcissement en-têtes | OWASP Secure Headers | `computeSecurityHeaderCaches()` (`http-kernel.ts#HttpKernel.computeSecurityHeaderCaches`) |
528
528
 
529
529
  ## ⚡ Performance & mémoire
530
530
 
531
531
  Le coût est concentré au boot, par construction :
532
532
 
533
- - **En-têtes constants** : une seule table, gelée (`securityHeaders.ts:77`). Par requête, une boucle
533
+ - **En-têtes constants** : une seule table, gelée (`securityHeaders.ts#SecurityHeaders.constructor`). Par requête, une boucle
534
534
  `for…in` sur un objet de 1 à 6 entrées et autant de `setHeader`. Aucune allocation.
535
535
  - **CSP statique** : rien de plus — la chaîne est dans la table.
536
536
  - **CSP à nonce** : un tirage dans le pool plus une concaténation `préfixe + nonce + suffixe` par requête. C'est le seul coût variable, et il
537
- n'existe **que** si le CSP porte un placeholder : `hasNonce` (`securityHeaders.ts:100`)
538
- court-circuite entièrement ce chemin sinon. La paresse de `Context.cspNonce` (`Context.ts:253`)
537
+ n'existe **que** si le CSP porte un placeholder : `hasNonce` (`securityHeaders.ts#SecurityHeaders.hasNonce`)
538
+ court-circuite entièrement ce chemin sinon. La paresse de `Context.cspNonce` (`Context.ts#Context.#cspNonce`)
539
539
  protège en plus les chemins internes qui n'atteignent jamais le firewall.
540
540
  - **Merge CSP** : jamais dans le chemin chaud. Le fragment d'un module est fusionné à
541
- l'enregistrement (`firewall.ts:1142`) ; celui d'une route ne coûte que sur les routes `@Csp`.
542
- - **Socle transport** : trois `setHeader` sur des chaînes précalculées (`http-kernel.ts:1131-1137`), avec
541
+ l'enregistrement (`firewall.ts#Firewall.currentRevalidator`) ; celui d'une route ne coûte que sur les routes `@Csp`.
542
+ - **Socle transport** : trois `setHeader` sur des chaînes précalculées (`http-kernel.ts#HttpKernel.dispatchHttpRequest`), avec
543
543
  un test `!== null` qui annule le coût des en-têtes désactivés.
544
544
 
545
545
  Le module n'attache aucun écouteur d'événement et ne conserve aucun état par requête : il n'entre pas
@@ -548,9 +548,9 @@ dans le périmètre du gate mémoire, qu'il ne peut structurellement pas dégrad
548
548
  ## 📡 Observabilité — Studio
549
549
 
550
550
  L'écran **Firewall** de Studio affiche la section « En-têtes de sécurité » — pilotée par
551
- `headers.enabled` (`FirewallDefenses.tsx:219`) — avec le CSP effectif, l'état du nonce par requête, la
551
+ `headers.enabled` (`FirewallDefenses.tsx#FirewallDefenses`) — avec le CSP effectif, l'état du nonce par requête, la
552
552
  Referrer-Policy et les valeurs d'isolation. Les données
553
- viennent de `Firewall.describe()` (`firewall.ts:588`), qui projette la config **sans aucun secret**,
553
+ viennent de `Firewall.describe()` (`firewall.ts#Firewall.describe`), qui projette la config **sans aucun secret**,
554
554
  exposée par `GET /nodefony/security/api/firewall`.
555
555
 
556
556
  L'onglet **Configuration** de Studio rend les mêmes options depuis le schéma Zod — chaque champ y
@@ -563,17 +563,17 @@ porte sa description, ce qui en fait la référence toujours à jour des défaut
563
563
 
564
564
  ## ⚠️ Pièges (symptôme → cause → correction)
565
565
 
566
- | Symptôme | Cause dans le code | Correction |
567
- | -------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
568
- | `frameguard: "sameorigin"` en config security sans effet | Clé **inerte** — non lue par la couche applicative (`securityHeaders.ts:6`) | Régler `securityHeaders.frameOptions` du module **http** |
569
- | `<script>` inline bloqué | Le template ne reprend pas le nonce | Rendre `<script nonce="…">` depuis `context.cspNonce` |
570
- | CSP contenant `'nonce-{{nonce}}'` littéral | `cspNonces` désactivé, placeholder laissé | Nodefony purge le résiduel (`securityHeaders.ts:63`) ; retirer le placeholder |
571
- | Une extension `script-src` de module sans effet | Second en-tête / directive dupliquée (ignorée, W3C CSP3 §3) | Déclarer un fragment via `registerCspOrigins()`, jamais un `setHeader` |
572
- | Assets tiers cassés après activation de l'isolation | `coep: "require-corp"` exige CORP/CORS sur **chaque** ressource | Retirer `coep` ou faire annoncer les ressources |
573
- | HSTS absent en développement | Posé sur TLS uniquement (`http-kernel.ts:981`) | Comportement conforme RFC 6797 — vérifier sur le port HTTPS |
574
- | Le CSP est plus large en dev qu'en prod | `@nodefony/frontend` déclare les origines Vite (`FrontendService.ts:695`) | Attendu : le fragment n'existe pas en production |
575
- | Aucun en-tête applicatif | Module security absent ou `headers.enabled: false` | Le socle transport reste actif ; réactiver security pour CSP/Referrer/isolation |
576
- | Domaine injoignable après activation de `preload` | Inscription à la liste pré-chargée, sortie très lente | Ne jamais activer sans plan HTTPS sur **tous** les sous-domaines |
566
+ | Symptôme | Cause dans le code | Correction |
567
+ | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
568
+ | `frameguard: "sameorigin"` en config security sans effet | Clé **inerte** — non lue par la couche applicative (`securityHeaders.ts#ISecurityHeadersOptions`) | Régler `securityHeaders.frameOptions` du module **http** |
569
+ | `<script>` inline bloqué | Le template ne reprend pas le nonce | Rendre `<script nonce="…">` depuis `context.cspNonce` |
570
+ | CSP contenant `'nonce-{{nonce}}'` littéral | `cspNonces` désactivé, placeholder laissé | Nodefony purge le résiduel (`securityHeaders.ts#ISecurityHeadersOptions.cspNonces`) ; retirer le placeholder |
571
+ | Une extension `script-src` de module sans effet | Second en-tête / directive dupliquée (ignorée, W3C CSP3 §3) | Déclarer un fragment via `registerCspOrigins()`, jamais un `setHeader` |
572
+ | Assets tiers cassés après activation de l'isolation | `coep: "require-corp"` exige CORP/CORS sur **chaque** ressource | Retirer `coep` ou faire annoncer les ressources |
573
+ | HSTS absent en développement | Posé sur TLS uniquement (`http-kernel.ts#HttpKernel.getErrorRenderer`) | Comportement conforme RFC 6797 — vérifier sur le port HTTPS |
574
+ | Le CSP est plus large en dev qu'en prod | `@nodefony/frontend` déclare les origines Vite (`FrontendService.ts#FrontendService.#buildEntries`) | Attendu : le fragment n'existe pas en production |
575
+ | Aucun en-tête applicatif | Module security absent ou `headers.enabled: false` | Le socle transport reste actif ; réactiver security pour CSP/Referrer/isolation |
576
+ | Domaine injoignable après activation de `preload` | Inscription à la liste pré-chargée, sortie très lente | Ne jamais activer sans plan HTTPS sur **tous** les sous-domaines |
577
577
 
578
578
  ## 🧪 Tests & couverture
579
579
 
@@ -587,12 +587,12 @@ depuis vitest :
587
587
  - **Intégration sur serveur réel** (`@nodefony/http`, port 5151) — `security-headers.test` vérifie les
588
588
  deux couches sur une **vraie** réponse. Quatre invariants y sont prouvés :
589
589
  - le socle transport survit à une 404, avec `x-content-type-options` sur une route inexistante
590
- (`security-headers.test.ts:38`) ;
590
+ (`security-headers.test.ts`) ;
591
591
  - le CSP applicatif complète cette réponse avec `content-security-policy`
592
- (`security-headers.test.ts:43`) ;
592
+ (`security-headers.test.ts`) ;
593
593
  - une route décorée voit sa directive `frame-src` fusionnée, sans dupliquer `img-src`
594
- (`security-headers.test.ts:73`) ;
595
- - deux requêtes concurrentes reçoivent deux nonces différents (`security-headers.test.ts:100`).
594
+ (`security-headers.test.ts`) ;
595
+ - deux requêtes concurrentes reçoivent deux nonces différents (`security-headers.test.ts`).
596
596
 
597
597
  `headers.test` et `security.test` couvrent le reste du contrat d'en-têtes.
598
598
 
package/docs/index.md CHANGED
@@ -133,7 +133,7 @@ flowchart LR
133
133
  ```
134
134
 
135
135
  Le module consomme `@nodefony/user` (jamais l'inverse) et n'importe `@nodefony/http` / `framework`
136
- qu'en **type-only**, à une exception près : `schemaMismatchOf` (`webhooks.ts:49`), importé de
136
+ qu'en **type-only**, à une exception près : `schemaMismatchOf` (`webhooks.ts`), importé de
137
137
  `@nodefony/http` pour reconnaître une base au schéma en retard avec la même règle que le cœur HTTP,
138
138
  plutôt qu'avec une copie.
139
139
 
@@ -161,15 +161,15 @@ sans ouvrir de port (profil console) :
161
161
  | `nodefony security:user:password <id>` | change le mot de passe — **et révoque sessions et jetons** |
162
162
  | `nodefony security:user:delete <id>` | supprime, après confirmation ; refuse le dernier administrateur |
163
163
  | `nodefony security:secrets [--write]` | engendre les clés attendues et guide leur câblage |
164
- | `nodefony security:token` | émet un jeton d'accès pour la porte MCP |
164
+ | `nodefony security:mcp:token` | émet un jeton d'accès pour la porte MCP |
165
165
  | `nodefony security:keycloak:realm [--write\|--check]` | écrit le realm Keycloak déduit de la configuration ([Keycloak](keycloak.md)) |
166
166
 
167
167
  En terminal, le mot de passe est **demandé masqué** et confirmé ; pour un script, `--password <pwd>`
168
168
  l'accepte en argument — au prix de l'historique du shell, que la commande rappelle.
169
169
 
170
170
  Deux garde-fous qui se constatent plutôt qu'ils ne se supposent : le **dernier administrateur actif
171
- ne se supprime pas** (`security-user-delete.ts:101`), et un changement de mot de passe **éjecte les
172
- accès en cours** par la même cascade que la suppression (`userRevocationCascade.ts:37`) — on change
171
+ ne se supprime pas** (`security-user-delete.ts#SecurityUserDelete.generate`), et un changement de mot de passe **éjecte les
172
+ accès en cours** par la même cascade que la suppression (`userRevocationCascade.ts#KernelListenerLike`) — on change
173
173
  un mot de passe parce qu'il est perdu ou compromis.
174
174
 
175
175
  ## ⚙️ Configuration