nuxt-security-module 4.4.0 → 4.6.0

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 (163) hide show
  1. package/README.md +13 -1
  2. package/dist/constants.js +7 -0
  3. package/dist/index.d.ts +15 -0
  4. package/dist/index.js +101 -0
  5. package/dist/models/AllowedMethodsRestricterOptions.d.ts +3 -0
  6. package/dist/models/BasicAuthOptions.d.ts +7 -0
  7. package/dist/models/ContentSecurityPolicy.d.ts +1 -0
  8. package/dist/models/CorsHandlerOptions.d.ts +11 -0
  9. package/dist/models/CrossOriginEmbedderPolicy.d.ts +6 -0
  10. package/dist/models/CrossOriginEmbedderPolicy.js +7 -0
  11. package/dist/models/CsrfOptions.d.ts +4 -0
  12. package/dist/models/ModuleOptions.d.ts +25 -0
  13. package/dist/models/PermissionsPolicy.d.ts +1 -0
  14. package/dist/models/RateLimitWindow.d.ts +4 -0
  15. package/dist/models/RateLimiterOptions.d.ts +4 -0
  16. package/dist/models/RateLimiterStore.d.ts +4 -0
  17. package/dist/models/RequestSizeLimiterOptions.d.ts +3 -0
  18. package/dist/models/SecurityHeaders.d.ts +19 -0
  19. package/dist/models/SecurityRouteRules.d.ts +2 -0
  20. package/dist/models/SecurityRules.d.ts +2 -0
  21. package/dist/models/SsgElement.d.ts +1 -0
  22. package/dist/models/SsgHashSource.d.ts +2 -0
  23. package/dist/models/SsgOptions.d.ts +5 -0
  24. package/dist/models/StrictTransportSecurity.d.ts +5 -0
  25. package/dist/models/XFrameOptions.d.ts +5 -0
  26. package/dist/models/XFrameOptions.js +6 -0
  27. package/dist/runtime/constants.d.ts +20 -0
  28. package/dist/runtime/constants.js +50 -0
  29. package/dist/runtime/nitro/plugins/hidePoweredBy.d.ts +3 -0
  30. package/dist/runtime/nitro/plugins/hidePoweredBy.js +12 -0
  31. package/dist/runtime/nitro/plugins/nonce.d.ts +3 -0
  32. package/dist/runtime/nitro/plugins/nonce.js +21 -0
  33. package/dist/runtime/nitro/plugins/routeRules.d.ts +3 -0
  34. package/dist/runtime/nitro/plugins/routeRules.js +20 -0
  35. package/dist/runtime/nitro/plugins/securityHeaders.d.ts +3 -0
  36. package/dist/runtime/nitro/plugins/securityHeaders.js +24 -0
  37. package/dist/runtime/nitro/plugins/ssgHashes.d.ts +3 -0
  38. package/dist/runtime/nitro/plugins/ssgHashes.js +21 -0
  39. package/dist/runtime/nitro/plugins/subresourceIntegrity.d.ts +2 -0
  40. package/dist/runtime/nitro/plugins/subresourceIntegrity.js +13 -0
  41. package/dist/runtime/server/middleware/allowedMethodsRestricter.d.ts +3 -0
  42. package/dist/runtime/server/middleware/allowedMethodsRestricter.js +14 -0
  43. package/dist/runtime/server/middleware/basicAuth.d.ts +3 -0
  44. package/dist/runtime/server/middleware/basicAuth.js +22 -0
  45. package/dist/runtime/server/middleware/corsHandler.d.ts +3 -0
  46. package/dist/runtime/server/middleware/corsHandler.js +20 -0
  47. package/dist/runtime/server/middleware/csrf.d.ts +3 -0
  48. package/dist/runtime/server/middleware/csrf.js +27 -0
  49. package/dist/runtime/server/middleware/rateLimiter.d.ts +3 -0
  50. package/dist/runtime/server/middleware/rateLimiter.js +21 -0
  51. package/dist/runtime/server/middleware/requestSizeLimiter.d.ts +3 -0
  52. package/dist/runtime/server/middleware/requestSizeLimiter.js +18 -0
  53. package/dist/runtime/services/checkIsBase64.d.ts +1 -0
  54. package/dist/runtime/services/checkIsBase64.js +5 -0
  55. package/dist/runtime/services/checkIsBasicAuthorized.d.ts +2 -0
  56. package/dist/runtime/services/checkIsBasicAuthorized.js +12 -0
  57. package/dist/runtime/services/checkIsBodyCounted.d.ts +1 -0
  58. package/dist/runtime/services/checkIsBodyCounted.js +4 -0
  59. package/dist/runtime/services/checkIsChallenged.d.ts +2 -0
  60. package/dist/runtime/services/checkIsChallenged.js +7 -0
  61. package/dist/runtime/services/checkIsCsrfTokenValid.d.ts +1 -0
  62. package/dist/runtime/services/checkIsCsrfTokenValid.js +7 -0
  63. package/dist/runtime/services/checkIsEqualInConstantTime.d.ts +1 -0
  64. package/dist/runtime/services/checkIsEqualInConstantTime.js +10 -0
  65. package/dist/runtime/services/checkIsMethodAllowed.d.ts +1 -0
  66. package/dist/runtime/services/checkIsMethodAllowed.js +3 -0
  67. package/dist/runtime/services/checkIsRecord.d.ts +1 -0
  68. package/dist/runtime/services/checkIsRecord.js +3 -0
  69. package/dist/runtime/services/checkIsRequestTooLarge.d.ts +2 -0
  70. package/dist/runtime/services/checkIsRequestTooLarge.js +7 -0
  71. package/dist/runtime/services/checkIsSecurityHeaderKey.d.ts +2 -0
  72. package/dist/runtime/services/checkIsSecurityHeaderKey.js +4 -0
  73. package/dist/runtime/services/checkIsStaticNonce.d.ts +1 -0
  74. package/dist/runtime/services/checkIsStaticNonce.js +4 -0
  75. package/dist/runtime/services/checkIsStringList.d.ts +1 -0
  76. package/dist/runtime/services/checkIsStringList.js +3 -0
  77. package/dist/runtime/services/collectSsgHashes.d.ts +3 -0
  78. package/dist/runtime/services/collectSsgHashes.js +18 -0
  79. package/dist/runtime/services/createInMemoryRateLimiterStore.d.ts +2 -0
  80. package/dist/runtime/services/createInMemoryRateLimiterStore.js +21 -0
  81. package/dist/runtime/services/generateHash.d.ts +1 -0
  82. package/dist/runtime/services/generateHash.js +8 -0
  83. package/dist/runtime/services/generateNonce.d.ts +1 -0
  84. package/dist/runtime/services/generateNonce.js +5 -0
  85. package/dist/runtime/services/generateRandomToken.d.ts +1 -0
  86. package/dist/runtime/services/generateRandomToken.js +7 -0
  87. package/dist/runtime/services/getBasicAuthCredentials.d.ts +4 -0
  88. package/dist/runtime/services/getBasicAuthCredentials.js +16 -0
  89. package/dist/runtime/services/getBasicAuthOptions.d.ts +2 -0
  90. package/dist/runtime/services/getBasicAuthOptions.js +16 -0
  91. package/dist/runtime/services/getRetryAfterSeconds.d.ts +3 -0
  92. package/dist/runtime/services/getRetryAfterSeconds.js +8 -0
  93. package/dist/runtime/services/getSecurityRules.d.ts +3 -0
  94. package/dist/runtime/services/getSecurityRules.js +14 -0
  95. package/dist/runtime/services/getSecurityState.d.ts +9 -0
  96. package/dist/runtime/services/getSecurityState.js +13 -0
  97. package/dist/runtime/services/getSsgHashSource.d.ts +4 -0
  98. package/dist/runtime/services/getSsgHashSource.js +26 -0
  99. package/dist/runtime/services/injectNonce.d.ts +1 -0
  100. package/dist/runtime/services/injectNonce.js +6 -0
  101. package/dist/runtime/services/injectSubresourceIntegrity.d.ts +1 -0
  102. package/dist/runtime/services/injectSubresourceIntegrity.js +21 -0
  103. package/dist/runtime/services/insertContentSecurityPolicyMeta.d.ts +2 -0
  104. package/dist/runtime/services/insertContentSecurityPolicyMeta.js +16 -0
  105. package/dist/runtime/services/mergeSecurityRules.d.ts +2 -0
  106. package/dist/runtime/services/mergeSecurityRules.js +10 -0
  107. package/dist/runtime/services/readHtmlTagAttribute.d.ts +1 -0
  108. package/dist/runtime/services/readHtmlTagAttribute.js +7 -0
  109. package/dist/runtime/services/rewriteHtmlTags.d.ts +1 -0
  110. package/dist/runtime/services/rewriteHtmlTags.js +8 -0
  111. package/dist/runtime/services/securityRouteMatcher.d.ts +4 -0
  112. package/dist/runtime/services/securityRouteMatcher.js +17 -0
  113. package/dist/runtime/services/serializeBasicAuthChallenge.d.ts +1 -0
  114. package/dist/runtime/services/serializeBasicAuthChallenge.js +3 -0
  115. package/dist/runtime/services/serializeContentSecurityPolicy.d.ts +2 -0
  116. package/dist/runtime/services/serializeContentSecurityPolicy.js +6 -0
  117. package/dist/runtime/services/serializePermissionsPolicy.d.ts +2 -0
  118. package/dist/runtime/services/serializePermissionsPolicy.js +3 -0
  119. package/dist/runtime/services/serializeSecurityHeaders.d.ts +2 -0
  120. package/dist/runtime/services/serializeSecurityHeaders.js +10 -0
  121. package/dist/runtime/services/serializeStrictTransportSecurity.d.ts +2 -0
  122. package/dist/runtime/services/serializeStrictTransportSecurity.js +7 -0
  123. package/dist/runtime/services/serializeValue.d.ts +1 -0
  124. package/dist/runtime/services/serializeValue.js +11 -0
  125. package/dist/runtime/services/stripHtmlTagAttribute.d.ts +1 -0
  126. package/dist/runtime/services/stripHtmlTagAttribute.js +4 -0
  127. package/dist/runtime/services/unquote.d.ts +1 -0
  128. package/dist/runtime/services/unquote.js +3 -0
  129. package/dist/runtime/services/updateContentSecurityPolicy.d.ts +3 -0
  130. package/dist/runtime/services/updateContentSecurityPolicy.js +14 -0
  131. package/dist/schemas/allowedMethodsRestricterOptionsSchema.js +4 -0
  132. package/dist/schemas/basicAuthOptionsSchema.js +11 -0
  133. package/dist/schemas/contentSecurityPolicySchema.js +4 -0
  134. package/dist/schemas/corsHandlerOptionsSchema.js +13 -0
  135. package/dist/schemas/csrfOptionsSchema.js +8 -0
  136. package/dist/schemas/moduleOptionsSchema.js +28 -0
  137. package/dist/schemas/permissionsPolicySchema.js +4 -0
  138. package/dist/schemas/rateLimiterOptionsSchema.js +7 -0
  139. package/dist/schemas/requestSizeLimiterOptionsSchema.js +4 -0
  140. package/dist/schemas/securityHeadersSchema.js +23 -0
  141. package/dist/schemas/ssgOptionsSchema.js +8 -0
  142. package/dist/schemas/strictTransportSecurityOptionsSchema.js +8 -0
  143. package/dist/services/computeSubresourceIntegrityHashes.js +23 -0
  144. package/dist/services/constants.js +3 -0
  145. package/dist/services/formatLeafIssue.js +5 -0
  146. package/dist/services/formatZodIssues.js +5 -0
  147. package/dist/services/getDeepestPathLength.js +3 -0
  148. package/dist/services/getDefaultModuleOptions.js +35 -0
  149. package/dist/services/getDefaultSecurityHeaders.js +83 -0
  150. package/dist/services/getLeafIssues.js +11 -0
  151. package/dist/services/getPluginTemplate.js +9 -0
  152. package/dist/services/getRemoveLoggersViteConfig.js +3 -0
  153. package/dist/services/getRouteRulesTemplate.js +3 -0
  154. package/dist/services/getRouteSecurityRules.js +16 -0
  155. package/dist/services/getStaticResourceHeaders.js +10 -0
  156. package/dist/services/getTypeTemplate.js +24 -0
  157. package/dist/services/mergeDirectives.js +12 -0
  158. package/dist/services/mergeFeatures.js +10 -0
  159. package/dist/services/mergeHeaders.js +14 -0
  160. package/dist/services/mergeModuleOptions.js +12 -0
  161. package/dist/services/mergeSources.js +9 -0
  162. package/dist/services/parseModuleOptions.js +9 -0
  163. package/package.json +5 -5
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![NPM downloads][badge-npm-downloads]][url-npm]
6
6
  [![NPM Unpacked Size (with version)][badge-npm-unpacked-size]][url-npm]
7
7
 
8
- A Nuxt security module for [Nitro 3](https://nitro.build) and h3 2 that sets security headers on rendered pages, a per-request nonce Content Security Policy, subresource integrity, CORS, allowed methods, basic auth and a request size limit.
8
+ This package is a Nuxt security module for [Nitro 3](https://nitro.build) and h3 2 that sets security headers on rendered pages. It also provides a per-request nonce Content Security Policy, subresource integrity, CORS, allowed methods, basic auth and a request size limit.
9
9
 
10
10
  Design notes live in [the specs](https://github.com/Esposter/Esposter/tree/main/openspec/specs/nuxt-security-module).
11
11
 
@@ -51,6 +51,18 @@ export default defineNuxtConfig({
51
51
  });
52
52
  ```
53
53
 
54
+ A request's body limit is the one its route sets, whatever its `Content-Type` claims, since a client writes that header. A route that takes a larger body names its own limit in its route rule:
55
+
56
+ ```ts
57
+ // nuxt.config.ts
58
+ export default defineNuxtConfig({
59
+ routeRules: {
60
+ // Lets a request to this route carry a 10 MB body, where the rest of the app takes the 2 MB default
61
+ "/api/import/**": { security: { requestSizeLimiter: { maxRequestSizeInBytes: 10_000_000 } } },
62
+ },
63
+ });
64
+ ```
65
+
54
66
  ## <a name="license">⚖️ License</a>
55
67
 
56
68
  Licensed under [Apache-2.0][url-license].
@@ -0,0 +1,7 @@
1
+ import { MODULE_NAME } from "./runtime/constants.js";
2
+ // src/constants.ts
3
+ const ROUTE_RULES_TEMPLATE_FILENAME = `#${MODULE_NAME}/route-rules.mjs`;
4
+ const SRI_HASHES_TEMPLATE_FILENAME = `#${MODULE_NAME}/sri-hashes.mjs`;
5
+ const ROUTE_RULES_PLUGIN_FILENAME = `#${MODULE_NAME}/route-rules-plugin.mjs`;
6
+ const SRI_PLUGIN_FILENAME = `#${MODULE_NAME}/sri-plugin.mjs`;
7
+ export { ROUTE_RULES_PLUGIN_FILENAME, ROUTE_RULES_TEMPLATE_FILENAME, SRI_HASHES_TEMPLATE_FILENAME, SRI_PLUGIN_FILENAME };
@@ -0,0 +1,15 @@
1
+ import { ModuleOptions } from "./models/ModuleOptions.js";
2
+ import { SecurityRouteRules } from "./models/SecurityRouteRules.js";
3
+ import { NuxtModule } from "@nuxt/schema";
4
+ declare module "@nuxt/schema" {
5
+ interface NuxtConfig {
6
+ security?: Partial<ModuleOptions>;
7
+ }
8
+ }
9
+ declare module "h3/rules" {
10
+ interface RouteRuleConfig {
11
+ security?: SecurityRouteRules;
12
+ }
13
+ }
14
+ declare const securityModule: NuxtModule<Partial<ModuleOptions>>;
15
+ export { type ModuleOptions, securityModule as default };
package/dist/index.js ADDED
@@ -0,0 +1,101 @@
1
+ import { ROUTE_RULES_PLUGIN_FILENAME, ROUTE_RULES_TEMPLATE_FILENAME, SRI_HASHES_TEMPLATE_FILENAME, SRI_PLUGIN_FILENAME } from "./constants.js";
2
+ import { MODULE_NAME } from "./runtime/constants.js";
3
+ import { checkIsRecord } from "./runtime/services/checkIsRecord.js";
4
+ import { computeSubresourceIntegrityHashes } from "./services/computeSubresourceIntegrityHashes.js";
5
+ import { getDefaultModuleOptions } from "./services/getDefaultModuleOptions.js";
6
+ import { getPluginTemplate } from "./services/getPluginTemplate.js";
7
+ import { getRemoveLoggersViteConfig } from "./services/getRemoveLoggersViteConfig.js";
8
+ import { getRouteRulesTemplate } from "./services/getRouteRulesTemplate.js";
9
+ import { getRouteSecurityRules } from "./services/getRouteSecurityRules.js";
10
+ import { getStaticResourceHeaders } from "./services/getStaticResourceHeaders.js";
11
+ import { getTypeTemplate } from "./services/getTypeTemplate.js";
12
+ import { mergeModuleOptions } from "./services/mergeModuleOptions.js";
13
+ import { parseModuleOptions } from "./services/parseModuleOptions.js";
14
+ import { defu } from "defu";
15
+ import { addNitroPlugin, addServerHandler, addServerTemplate, addTypeTemplate, createResolver, defineNuxtModule } from "@nuxt/kit";
16
+ // src/module.ts
17
+ const securityModule = defineNuxtModule({
18
+ meta: {
19
+ compatibility: { nuxt: ">=5.0.0-0" },
20
+ configKey: "security",
21
+ name: MODULE_NAME
22
+ },
23
+ setup(userOptions, nuxt) {
24
+ const options = parseModuleOptions(mergeModuleOptions(getDefaultModuleOptions(nuxt.options.dev, nuxt.options.devServer.url, userOptions.strict === true), userOptions));
25
+ if (!options.enabled) return;
26
+ const resolver = createResolver(import.meta.url);
27
+ const { basicAuth, ...publicOptions } = options;
28
+ if (basicAuth) {
29
+ const { private: privateRuntimeConfig } = nuxt.options.runtimeConfig;
30
+ nuxt.options.runtimeConfig.private = defu(checkIsRecord(privateRuntimeConfig) ? privateRuntimeConfig : {}, { basicAuth });
31
+ }
32
+ const routeSecurityRules = getRouteSecurityRules(nuxt.options.routeRules ?? {}, options);
33
+ addServerTemplate({
34
+ filename: ROUTE_RULES_TEMPLATE_FILENAME,
35
+ getContents: () => getRouteRulesTemplate({
36
+ "/**": publicOptions,
37
+ ...routeSecurityRules
38
+ })
39
+ });
40
+ addServerTemplate({
41
+ filename: ROUTE_RULES_PLUGIN_FILENAME,
42
+ getContents: () => getPluginTemplate(resolver.resolve("./runtime/nitro/plugins/routeRules"), "createRouteRulesPlugin", ROUTE_RULES_TEMPLATE_FILENAME)
43
+ });
44
+ addNitroPlugin(ROUTE_RULES_PLUGIN_FILENAME);
45
+ if (options.removeLoggers) nuxt.options.vite = defu(getRemoveLoggersViteConfig(), nuxt.options.vite);
46
+ if (options.headers) nuxt.options.routeRules = defu({ "/**": { headers: getStaticResourceHeaders(options.headers) } }, nuxt.options.routeRules);
47
+ if (options.nonce) addNitroPlugin(resolver.resolve("./runtime/nitro/plugins/nonce"));
48
+ if (options.sri) {
49
+ addServerTemplate({
50
+ filename: SRI_PLUGIN_FILENAME,
51
+ getContents: () => getPluginTemplate(resolver.resolve("./runtime/nitro/plugins/subresourceIntegrity"), "createSubresourceIntegrityPlugin", SRI_HASHES_TEMPLATE_FILENAME)
52
+ });
53
+ addNitroPlugin(SRI_PLUGIN_FILENAME);
54
+ }
55
+ if (options.ssg) addNitroPlugin(resolver.resolve("./runtime/nitro/plugins/ssgHashes"));
56
+ if (options.headers) addNitroPlugin(resolver.resolve("./runtime/nitro/plugins/securityHeaders"));
57
+ if (options.hidePoweredBy) addNitroPlugin(resolver.resolve("./runtime/nitro/plugins/hidePoweredBy"));
58
+ if (options.csrf) addServerHandler({
59
+ handler: resolver.resolve("./runtime/server/middleware/csrf"),
60
+ middleware: true
61
+ });
62
+ if (options.rateLimiter) addServerHandler({
63
+ handler: resolver.resolve("./runtime/server/middleware/rateLimiter"),
64
+ middleware: true
65
+ });
66
+ if (options.requestSizeLimiter) addServerHandler({
67
+ handler: resolver.resolve("./runtime/server/middleware/requestSizeLimiter"),
68
+ middleware: true
69
+ });
70
+ if (options.corsHandler) addServerHandler({
71
+ handler: resolver.resolve("./runtime/server/middleware/corsHandler"),
72
+ middleware: true
73
+ });
74
+ if (options.allowedMethodsRestricter) addServerHandler({
75
+ handler: resolver.resolve("./runtime/server/middleware/allowedMethodsRestricter"),
76
+ middleware: true
77
+ });
78
+ if (basicAuth) addServerHandler({
79
+ handler: resolver.resolve("./runtime/server/middleware/basicAuth"),
80
+ middleware: true
81
+ });
82
+ let sriHashes = {};
83
+ if (options.sri) {
84
+ addServerTemplate({
85
+ filename: SRI_HASHES_TEMPLATE_FILENAME,
86
+ getContents: () => `export default ${JSON.stringify(sriHashes)}\n`
87
+ });
88
+ nuxt.hook("nitro:build:public-assets", async (nitro) => {
89
+ sriHashes = await computeSubresourceIntegrityHashes(nitro);
90
+ });
91
+ }
92
+ addTypeTemplate({
93
+ filename: `types/${MODULE_NAME}.d.ts`,
94
+ getContents: getTypeTemplate
95
+ }, {
96
+ nitro: true,
97
+ nuxt: true
98
+ });
99
+ }
100
+ });
101
+ export { securityModule as default };
@@ -0,0 +1,3 @@
1
+ export interface AllowedMethodsRestricterOptions {
2
+ methods: string[];
3
+ }
@@ -0,0 +1,7 @@
1
+ export interface BasicAuthOptions {
2
+ exclude?: string[];
3
+ include?: string[];
4
+ message?: string;
5
+ name: string;
6
+ pass: string;
7
+ }
@@ -0,0 +1 @@
1
+ export type ContentSecurityPolicy = Record<string, boolean | string[] | undefined>;
@@ -0,0 +1,11 @@
1
+ export interface CorsHandlerOptions {
2
+ allowHeaders?: string[];
3
+ credentials?: boolean;
4
+ exposeHeaders?: string[];
5
+ maxAge?: string;
6
+ methods: string[];
7
+ origin: string | string[];
8
+ preflight?: {
9
+ statusCode: number;
10
+ };
11
+ }
@@ -0,0 +1,6 @@
1
+ export declare const CrossOriginEmbedderPolicy: {
2
+ readonly Credentialless: "credentialless";
3
+ readonly RequireCorp: "require-corp";
4
+ readonly UnsafeNone: "unsafe-none";
5
+ };
6
+ export type CrossOriginEmbedderPolicy = (typeof CrossOriginEmbedderPolicy)[keyof typeof CrossOriginEmbedderPolicy];
@@ -0,0 +1,7 @@
1
+ // src/models/CrossOriginEmbedderPolicy.ts
2
+ const CrossOriginEmbedderPolicy = {
3
+ Credentialless: "credentialless",
4
+ RequireCorp: "require-corp",
5
+ UnsafeNone: "unsafe-none"
6
+ };
7
+ export { CrossOriginEmbedderPolicy };
@@ -0,0 +1,4 @@
1
+ export interface CsrfOptions {
2
+ cookieName: string;
3
+ headerName: string;
4
+ }
@@ -0,0 +1,25 @@
1
+ import { AllowedMethodsRestricterOptions } from "./AllowedMethodsRestricterOptions.js";
2
+ import { BasicAuthOptions } from "./BasicAuthOptions.js";
3
+ import { CorsHandlerOptions } from "./CorsHandlerOptions.js";
4
+ import { CsrfOptions } from "./CsrfOptions.js";
5
+ import { RateLimiterOptions } from "./RateLimiterOptions.js";
6
+ import { RequestSizeLimiterOptions } from "./RequestSizeLimiterOptions.js";
7
+ import { SecurityHeaders } from "./SecurityHeaders.js";
8
+ import { SsgOptions } from "./SsgOptions.js";
9
+ export interface ModuleOptions {
10
+ allowedMethodsRestricter: AllowedMethodsRestricterOptions | false;
11
+ basicAuth: BasicAuthOptions | false;
12
+ contentSecurityPolicyReportOnly: boolean;
13
+ corsHandler: CorsHandlerOptions | false;
14
+ csrf: CsrfOptions | false;
15
+ enabled: boolean;
16
+ headers: false | SecurityHeaders;
17
+ hidePoweredBy: boolean;
18
+ nonce: boolean;
19
+ rateLimiter: false | RateLimiterOptions;
20
+ removeLoggers: boolean;
21
+ requestSizeLimiter: false | RequestSizeLimiterOptions;
22
+ sri: boolean;
23
+ ssg: false | SsgOptions;
24
+ strict: boolean;
25
+ }
@@ -0,0 +1 @@
1
+ export type PermissionsPolicy = Record<string, false | string[] | undefined>;
@@ -0,0 +1,4 @@
1
+ export interface RateLimitWindow {
2
+ count: number;
3
+ resetAt: number;
4
+ }
@@ -0,0 +1,4 @@
1
+ export interface RateLimiterOptions {
2
+ intervalInMilliseconds: number;
3
+ tokensPerInterval: number;
4
+ }
@@ -0,0 +1,4 @@
1
+ import { RateLimitWindow } from "./RateLimitWindow.js";
2
+ export interface RateLimiterStore {
3
+ increment: (key: string, intervalInMilliseconds: number) => Promise<RateLimitWindow>;
4
+ }
@@ -0,0 +1,3 @@
1
+ export interface RequestSizeLimiterOptions {
2
+ maxRequestSizeInBytes: number;
3
+ }
@@ -0,0 +1,19 @@
1
+ import { ContentSecurityPolicy } from "./ContentSecurityPolicy.js";
2
+ import { CrossOriginEmbedderPolicy } from "./CrossOriginEmbedderPolicy.js";
3
+ import { PermissionsPolicy } from "./PermissionsPolicy.js";
4
+ import { StrictTransportSecurity } from "./StrictTransportSecurity.js";
5
+ import { XFrameOptions } from "./XFrameOptions.js";
6
+ export interface SecurityHeaders {
7
+ contentSecurityPolicy?: ContentSecurityPolicy | false;
8
+ crossOriginEmbedderPolicy?: CrossOriginEmbedderPolicy | false;
9
+ crossOriginOpenerPolicy?: false | string;
10
+ crossOriginResourcePolicy?: false | string;
11
+ originAgentCluster?: false | string;
12
+ permissionsPolicy?: false | PermissionsPolicy;
13
+ referrerPolicy?: false | string;
14
+ strictTransportSecurity?: false | StrictTransportSecurity;
15
+ xContentTypeOptions?: false | string;
16
+ xDNSPrefetchControl?: false | string;
17
+ xFrameOptions?: false | XFrameOptions;
18
+ xPermittedCrossDomainPolicies?: false | string;
19
+ }
@@ -0,0 +1,2 @@
1
+ import { ModuleOptions } from "./ModuleOptions.js";
2
+ export type SecurityRouteRules = Partial<ModuleOptions>;
@@ -0,0 +1,2 @@
1
+ import { ModuleOptions } from "./ModuleOptions.js";
2
+ export type SecurityRules = Partial<ModuleOptions>;
@@ -0,0 +1 @@
1
+ export type SsgElement = [name: string, attributes: string, body: string | undefined];
@@ -0,0 +1,2 @@
1
+ import { SecurityState } from "../runtime/services/getSecurityState.js";
2
+ export type SsgHashSource = [kind: keyof SecurityState["hashes"], source: string];
@@ -0,0 +1,5 @@
1
+ export interface SsgOptions {
2
+ hashScripts: boolean;
3
+ hashStyles: boolean;
4
+ meta: boolean;
5
+ }
@@ -0,0 +1,5 @@
1
+ export interface StrictTransportSecurity {
2
+ includeSubdomains: boolean;
3
+ maxAge: number;
4
+ preload: boolean;
5
+ }
@@ -0,0 +1,5 @@
1
+ export declare const XFrameOptions: {
2
+ readonly Deny: "DENY";
3
+ readonly SameOrigin: "SAMEORIGIN";
4
+ };
5
+ export type XFrameOptions = (typeof XFrameOptions)[keyof typeof XFrameOptions];
@@ -0,0 +1,6 @@
1
+ // src/models/XFrameOptions.ts
2
+ const XFrameOptions = {
3
+ Deny: "DENY",
4
+ SameOrigin: "SAMEORIGIN"
5
+ };
6
+ export { XFrameOptions };
@@ -0,0 +1,20 @@
1
+ import { SecurityHeaders } from "../models/SecurityHeaders.js";
2
+ export declare const MODULE_NAME = "nuxt-security-module";
3
+ export declare const ROUTE_RULES_HOOK: `${typeof MODULE_NAME}:routeRules`;
4
+ export declare const SECURITY_HEADER_NAMES: Readonly<Record<keyof SecurityHeaders, string>>;
5
+ export declare const CONTENT_SECURITY_POLICY_REPORT_ONLY_HEADER_NAME = "Content-Security-Policy-Report-Only";
6
+ export declare const MAX_RATE_LIMIT_ENTRIES = 1e4;
7
+ export declare const UNKNOWN_CLIENT_KEY = "unknown";
8
+ export declare const CSRF_UNSAFE_METHODS: readonly string[];
9
+ export declare const CSRF_TOKEN_BYTE_LENGTH = 32;
10
+ export declare const NONCE_BYTE_LENGTH = 18;
11
+ export declare const NONCE_PLACEHOLDER = "'nonce-{{nonce}}'";
12
+ export declare const SRI_HASH_ALGORITHM = "SHA-384";
13
+ export declare const CSP_HASH_ALGORITHM = "SHA-256";
14
+ export declare const HTML_ATTRIBUTE_REGEX: RegExp;
15
+ export declare const HTML_TAG_REGEX: RegExp;
16
+ export declare const NONCE_TAG_NAMES: readonly string[];
17
+ export declare const HTML_SECTIONS: readonly ["head", "bodyPrepend", "body", "bodyAppend"];
18
+ export declare const SIZE_LIMITED_METHODS: readonly string[];
19
+ export declare const DEFAULT_BASIC_AUTH_REALM = "Please enter username and password";
20
+ export declare const BASIC_AUTHORIZATION_SCHEME = "Basic";
@@ -0,0 +1,50 @@
1
+ // src/runtime/constants.ts
2
+ const MODULE_NAME = "nuxt-security-module";
3
+ const ROUTE_RULES_HOOK = `${MODULE_NAME}:routeRules`;
4
+ const SECURITY_HEADER_NAMES = {
5
+ contentSecurityPolicy: "Content-Security-Policy",
6
+ crossOriginEmbedderPolicy: "Cross-Origin-Embedder-Policy",
7
+ crossOriginOpenerPolicy: "Cross-Origin-Opener-Policy",
8
+ crossOriginResourcePolicy: "Cross-Origin-Resource-Policy",
9
+ originAgentCluster: "Origin-Agent-Cluster",
10
+ permissionsPolicy: "Permissions-Policy",
11
+ referrerPolicy: "Referrer-Policy",
12
+ strictTransportSecurity: "Strict-Transport-Security",
13
+ xContentTypeOptions: "X-Content-Type-Options",
14
+ xDNSPrefetchControl: "X-DNS-Prefetch-Control",
15
+ xFrameOptions: "X-Frame-Options",
16
+ xPermittedCrossDomainPolicies: "X-Permitted-Cross-Domain-Policies"
17
+ };
18
+ const CONTENT_SECURITY_POLICY_REPORT_ONLY_HEADER_NAME = "Content-Security-Policy-Report-Only";
19
+ const MAX_RATE_LIMIT_ENTRIES = 1e4;
20
+ const UNKNOWN_CLIENT_KEY = "unknown";
21
+ const STATE_CHANGING_METHODS = [
22
+ "DELETE",
23
+ "PATCH",
24
+ "POST",
25
+ "PUT"
26
+ ];
27
+ const CSRF_UNSAFE_METHODS = STATE_CHANGING_METHODS;
28
+ const CSRF_TOKEN_BYTE_LENGTH = 32;
29
+ const NONCE_BYTE_LENGTH = 18;
30
+ const NONCE_PLACEHOLDER = "'nonce-{{nonce}}'";
31
+ const SRI_HASH_ALGORITHM = "SHA-384";
32
+ const CSP_HASH_ALGORITHM = "SHA-256";
33
+ const HTML_ATTRIBUTE_PATTERN = String.raw`\s+[^\s"'<>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'<>=]+))?`;
34
+ const HTML_ATTRIBUTE_REGEX = /\s+(?<name>[^\s"'<>/=]+)(?<assignment>\s*=\s*(?<value>"[^"]*"|'[^']*'|[^\s"'<>=]+))?/gu;
35
+ const HTML_TAG_REGEX = new RegExp(String.raw`<(script|style)\b((?:${HTML_ATTRIBUTE_PATTERN})*)(\s*\/?)>([\s\S]*?)<\/\1\s*>|<([a-z][\w:-]*)((?:${HTML_ATTRIBUTE_PATTERN})*)(\s*\/?)>`, "giu");
36
+ const NONCE_TAG_NAMES = [
37
+ "link",
38
+ "script",
39
+ "style"
40
+ ];
41
+ const HTML_SECTIONS = [
42
+ "head",
43
+ "bodyPrepend",
44
+ "body",
45
+ "bodyAppend"
46
+ ];
47
+ const SIZE_LIMITED_METHODS = STATE_CHANGING_METHODS;
48
+ const DEFAULT_BASIC_AUTH_REALM = "Please enter username and password";
49
+ const BASIC_AUTHORIZATION_SCHEME = "Basic";
50
+ export { BASIC_AUTHORIZATION_SCHEME, CONTENT_SECURITY_POLICY_REPORT_ONLY_HEADER_NAME, CSP_HASH_ALGORITHM, CSRF_TOKEN_BYTE_LENGTH, CSRF_UNSAFE_METHODS, DEFAULT_BASIC_AUTH_REALM, HTML_ATTRIBUTE_REGEX, HTML_SECTIONS, HTML_TAG_REGEX, MAX_RATE_LIMIT_ENTRIES, MODULE_NAME, NONCE_BYTE_LENGTH, NONCE_PLACEHOLDER, NONCE_TAG_NAMES, ROUTE_RULES_HOOK, SECURITY_HEADER_NAMES, SIZE_LIMITED_METHODS, SRI_HASH_ALGORITHM, UNKNOWN_CLIENT_KEY };
@@ -0,0 +1,3 @@
1
+ import { NitroAppPlugin } from "nitro/types";
2
+ declare const hidePoweredBy: NitroAppPlugin;
3
+ export { hidePoweredBy as default };
@@ -0,0 +1,12 @@
1
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
2
+ import { definePlugin } from "nitro";
3
+ import { isEvent } from "nitro/h3";
4
+ // src/runtime/nitro/plugins/hidePoweredBy.ts
5
+ const hidePoweredBy = definePlugin((nitroApp) => {
6
+ nitroApp.hooks.hook("response", (response, event) => {
7
+ if (!isEvent(event)) return;
8
+ const rules = getSecurityRules(event);
9
+ if (rules?.enabled && rules.hidePoweredBy) response.headers.delete("x-powered-by");
10
+ });
11
+ });
12
+ export { hidePoweredBy as default };
@@ -0,0 +1,3 @@
1
+ import { NitroAppPlugin } from "nitro/types";
2
+ declare const noncePlugin: NitroAppPlugin;
3
+ export { noncePlugin as default };
@@ -0,0 +1,21 @@
1
+ import { HTML_SECTIONS } from "../../constants.js";
2
+ import { generateNonce } from "../../services/generateNonce.js";
3
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
4
+ import { getSecurityState } from "../../services/getSecurityState.js";
5
+ import { injectNonce } from "../../services/injectNonce.js";
6
+ import { definePlugin } from "nitro";
7
+ // src/runtime/nitro/plugins/nonce.ts
8
+ const noncePlugin = definePlugin((nitroApp) => {
9
+ if (import.meta.prerender) return;
10
+ nitroApp.hooks.hook("render:route", (_context, { event }) => {
11
+ const rules = getSecurityRules(event);
12
+ if (rules?.enabled && rules.nonce) getSecurityState(event).nonce = generateNonce();
13
+ });
14
+ nitroApp.hooks.hook("render:html", (html, { event }) => {
15
+ const rules = getSecurityRules(event);
16
+ const nonce = getSecurityState(event).nonce;
17
+ if (!rules?.enabled || !rules.nonce || !rules.headers || !rules.headers.contentSecurityPolicy || !nonce) return;
18
+ for (const section of HTML_SECTIONS) html[section] = html[section].map((element) => injectNonce(element, nonce));
19
+ });
20
+ });
21
+ export { noncePlugin as default };
@@ -0,0 +1,3 @@
1
+ import { SecurityRules } from "../../../models/SecurityRules.js";
2
+ import { NitroAppPlugin } from "nitro/types";
3
+ export declare const createRouteRulesPlugin: (routeRules: Record<string, SecurityRules>) => NitroAppPlugin;
@@ -0,0 +1,20 @@
1
+ import { ROUTE_RULES_HOOK } from "../../constants.js";
2
+ import { setSecurityRouteMatcher, setSecurityRouteMatcherFailure } from "../../services/securityRouteMatcher.js";
3
+ import { definePlugin } from "nitro";
4
+ // src/runtime/nitro/plugins/routeRules.ts
5
+ const createRouteRulesPlugin = (routeRules) => definePlugin((nitroApp) => {
6
+ const resolveRouteRules = async () => {
7
+ const routes = structuredClone(routeRules);
8
+ await nitroApp.hooks.callHook(ROUTE_RULES_HOOK, routes);
9
+ setSecurityRouteMatcher(routes);
10
+ };
11
+ let routesResolved;
12
+ nitroApp.hooks.hook("request", () => {
13
+ routesResolved ??= (async () => {
14
+ const [resolution] = await Promise.allSettled([resolveRouteRules()]);
15
+ if (resolution.status === "rejected") setSecurityRouteMatcherFailure(resolution.reason);
16
+ })();
17
+ return routesResolved;
18
+ });
19
+ });
20
+ export { createRouteRulesPlugin };
@@ -0,0 +1,3 @@
1
+ import { NitroAppPlugin } from "nitro/types";
2
+ declare const securityHeaders: NitroAppPlugin;
3
+ export { securityHeaders as default };
@@ -0,0 +1,24 @@
1
+ import { CONTENT_SECURITY_POLICY_REPORT_ONLY_HEADER_NAME, SECURITY_HEADER_NAMES } from "../../constants.js";
2
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
3
+ import { getSecurityState } from "../../services/getSecurityState.js";
4
+ import { serializeSecurityHeaders } from "../../services/serializeSecurityHeaders.js";
5
+ import { updateContentSecurityPolicy } from "../../services/updateContentSecurityPolicy.js";
6
+ import { definePlugin } from "nitro";
7
+ // src/runtime/nitro/plugins/securityHeaders.ts
8
+ const CONTENT_SECURITY_POLICY_HEADER_NAME = SECURITY_HEADER_NAMES.contentSecurityPolicy;
9
+ const securityHeaders = definePlugin((nitroApp) => {
10
+ nitroApp.hooks.hook("render:route", (_context, { event }) => {
11
+ const rules = getSecurityRules(event);
12
+ if (!rules?.enabled || !rules.headers) return;
13
+ const { contentSecurityPolicy } = rules.headers;
14
+ const serializedHeaders = serializeSecurityHeaders({
15
+ ...rules.headers,
16
+ contentSecurityPolicy: contentSecurityPolicy ? updateContentSecurityPolicy(contentSecurityPolicy, getSecurityState(event)) : false
17
+ });
18
+ for (const [name, value] of Object.entries(serializedHeaders)) {
19
+ const isPolicy = name === CONTENT_SECURITY_POLICY_HEADER_NAME;
20
+ event.res.headers.set(isPolicy && rules.contentSecurityPolicyReportOnly ? CONTENT_SECURITY_POLICY_REPORT_ONLY_HEADER_NAME : name, value);
21
+ }
22
+ });
23
+ });
24
+ export { securityHeaders as default };
@@ -0,0 +1,3 @@
1
+ import { NitroAppPlugin } from "nitro/types";
2
+ declare const ssgHashes: NitroAppPlugin;
3
+ export { ssgHashes as default };
@@ -0,0 +1,21 @@
1
+ import { HTML_SECTIONS } from "../../constants.js";
2
+ import { collectSsgHashes } from "../../services/collectSsgHashes.js";
3
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
4
+ import { getSecurityState } from "../../services/getSecurityState.js";
5
+ import { insertContentSecurityPolicyMeta } from "../../services/insertContentSecurityPolicyMeta.js";
6
+ import { updateContentSecurityPolicy } from "../../services/updateContentSecurityPolicy.js";
7
+ import { definePlugin } from "nitro";
8
+ // src/runtime/nitro/plugins/ssgHashes.ts
9
+ const ssgHashes = definePlugin((nitroApp) => {
10
+ if (!import.meta.prerender) return;
11
+ nitroApp.hooks.hook("render:html", async (html, { event }) => {
12
+ const rules = getSecurityRules(event);
13
+ if (!rules?.enabled || !rules.ssg || !rules.headers || !rules.headers.contentSecurityPolicy) return;
14
+ const state = getSecurityState(event);
15
+ state.hashes = await collectSsgHashes(HTML_SECTIONS.map((section) => html[section].join("")).join(""), rules.ssg);
16
+ if (!rules.ssg.meta || rules.contentSecurityPolicyReportOnly) return;
17
+ const policy = updateContentSecurityPolicy(rules.headers.contentSecurityPolicy, state);
18
+ html.head = [insertContentSecurityPolicyMeta(html.head.join(""), policy)];
19
+ });
20
+ });
21
+ export { ssgHashes as default };
@@ -0,0 +1,2 @@
1
+ import { NitroAppPlugin } from "nitro/types";
2
+ export declare const createSubresourceIntegrityPlugin: (sriHashes: Record<string, string>) => NitroAppPlugin;
@@ -0,0 +1,13 @@
1
+ import { HTML_SECTIONS } from "../../constants.js";
2
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
3
+ import { injectSubresourceIntegrity } from "../../services/injectSubresourceIntegrity.js";
4
+ import { definePlugin } from "nitro";
5
+ // src/runtime/nitro/plugins/subresourceIntegrity.ts
6
+ const createSubresourceIntegrityPlugin = (sriHashes) => definePlugin((nitroApp) => {
7
+ nitroApp.hooks.hook("render:html", (html, { event }) => {
8
+ const rules = getSecurityRules(event);
9
+ if (!rules?.enabled || !rules.sri) return;
10
+ for (const section of HTML_SECTIONS) html[section] = html[section].map((element) => injectSubresourceIntegrity(element, sriHashes));
11
+ });
12
+ });
13
+ export { createSubresourceIntegrityPlugin };
@@ -0,0 +1,3 @@
1
+ import { Middleware } from "nitro/h3";
2
+ declare const allowedMethodsRestricter: Middleware;
3
+ export { allowedMethodsRestricter as default };
@@ -0,0 +1,14 @@
1
+ import { checkIsMethodAllowed } from "../../services/checkIsMethodAllowed.js";
2
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
3
+ import { defineMiddleware } from "nitro";
4
+ import { HTTPError } from "nitro/h3";
5
+ // src/runtime/server/middleware/allowedMethodsRestricter.ts
6
+ const allowedMethodsRestricter = defineMiddleware((event) => {
7
+ const rules = getSecurityRules(event);
8
+ if (!rules?.enabled || !rules.allowedMethodsRestricter) return;
9
+ if (!checkIsMethodAllowed(event.req.method, rules.allowedMethodsRestricter.methods)) throw new HTTPError({
10
+ status: 405,
11
+ statusText: "Method not allowed"
12
+ });
13
+ });
14
+ export { allowedMethodsRestricter as default };
@@ -0,0 +1,3 @@
1
+ import { Middleware } from "nitro/h3";
2
+ declare const basicAuthMiddleware: Middleware;
3
+ export { basicAuthMiddleware as default };
@@ -0,0 +1,22 @@
1
+ import "../../constants.js";
2
+ import { checkIsBasicAuthorized } from "../../services/checkIsBasicAuthorized.js";
3
+ import { checkIsChallenged } from "../../services/checkIsChallenged.js";
4
+ import { getBasicAuthOptions } from "../../services/getBasicAuthOptions.js";
5
+ import { serializeBasicAuthChallenge } from "../../services/serializeBasicAuthChallenge.js";
6
+ import { defineMiddleware } from "nitro";
7
+ import { HTTPError } from "nitro/h3";
8
+ import { useRuntimeConfig } from "nuxt/server";
9
+ // src/runtime/server/middleware/basicAuth.ts
10
+ const basicAuthMiddleware = defineMiddleware((event) => {
11
+ const basicAuth = getBasicAuthOptions(useRuntimeConfig().private);
12
+ if (!basicAuth) return;
13
+ const { pathname } = event.url;
14
+ if (!checkIsChallenged(pathname, basicAuth)) return;
15
+ if (checkIsBasicAuthorized(event.req.headers.get("authorization"), basicAuth)) return;
16
+ throw new HTTPError({
17
+ headers: { "WWW-Authenticate": serializeBasicAuthChallenge(basicAuth.message || "Please enter username and password") },
18
+ status: 401,
19
+ statusText: "Access denied"
20
+ });
21
+ });
22
+ export { basicAuthMiddleware as default };
@@ -0,0 +1,3 @@
1
+ import { Middleware } from "nitro/h3";
2
+ declare const corsHandler: Middleware;
3
+ export { corsHandler as default };
@@ -0,0 +1,20 @@
1
+ import { getSecurityRules } from "../../services/getSecurityRules.js";
2
+ import { defineMiddleware } from "nitro";
3
+ import { handleCors } from "nitro/h3";
4
+ // src/runtime/server/middleware/corsHandler.ts
5
+ const corsHandler = defineMiddleware((event) => {
6
+ const rules = getSecurityRules(event);
7
+ if (!rules?.enabled || !rules.corsHandler) return void 0;
8
+ const { allowHeaders, credentials, exposeHeaders, maxAge, methods, origin, preflight } = rules.corsHandler;
9
+ const response = handleCors(event, {
10
+ allowHeaders,
11
+ credentials,
12
+ exposeHeaders,
13
+ maxAge,
14
+ methods,
15
+ origin: [origin].flat(),
16
+ preflight
17
+ });
18
+ return response === false ? void 0 : response;
19
+ });
20
+ export { corsHandler as default };
@@ -0,0 +1,3 @@
1
+ import { Middleware } from "nitro/h3";
2
+ declare const csrf: Middleware;
3
+ export { csrf as default };