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.
- package/README.md +13 -1
- package/dist/constants.js +7 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +101 -0
- package/dist/models/AllowedMethodsRestricterOptions.d.ts +3 -0
- package/dist/models/BasicAuthOptions.d.ts +7 -0
- package/dist/models/ContentSecurityPolicy.d.ts +1 -0
- package/dist/models/CorsHandlerOptions.d.ts +11 -0
- package/dist/models/CrossOriginEmbedderPolicy.d.ts +6 -0
- package/dist/models/CrossOriginEmbedderPolicy.js +7 -0
- package/dist/models/CsrfOptions.d.ts +4 -0
- package/dist/models/ModuleOptions.d.ts +25 -0
- package/dist/models/PermissionsPolicy.d.ts +1 -0
- package/dist/models/RateLimitWindow.d.ts +4 -0
- package/dist/models/RateLimiterOptions.d.ts +4 -0
- package/dist/models/RateLimiterStore.d.ts +4 -0
- package/dist/models/RequestSizeLimiterOptions.d.ts +3 -0
- package/dist/models/SecurityHeaders.d.ts +19 -0
- package/dist/models/SecurityRouteRules.d.ts +2 -0
- package/dist/models/SecurityRules.d.ts +2 -0
- package/dist/models/SsgElement.d.ts +1 -0
- package/dist/models/SsgHashSource.d.ts +2 -0
- package/dist/models/SsgOptions.d.ts +5 -0
- package/dist/models/StrictTransportSecurity.d.ts +5 -0
- package/dist/models/XFrameOptions.d.ts +5 -0
- package/dist/models/XFrameOptions.js +6 -0
- package/dist/runtime/constants.d.ts +20 -0
- package/dist/runtime/constants.js +50 -0
- package/dist/runtime/nitro/plugins/hidePoweredBy.d.ts +3 -0
- package/dist/runtime/nitro/plugins/hidePoweredBy.js +12 -0
- package/dist/runtime/nitro/plugins/nonce.d.ts +3 -0
- package/dist/runtime/nitro/plugins/nonce.js +21 -0
- package/dist/runtime/nitro/plugins/routeRules.d.ts +3 -0
- package/dist/runtime/nitro/plugins/routeRules.js +20 -0
- package/dist/runtime/nitro/plugins/securityHeaders.d.ts +3 -0
- package/dist/runtime/nitro/plugins/securityHeaders.js +24 -0
- package/dist/runtime/nitro/plugins/ssgHashes.d.ts +3 -0
- package/dist/runtime/nitro/plugins/ssgHashes.js +21 -0
- package/dist/runtime/nitro/plugins/subresourceIntegrity.d.ts +2 -0
- package/dist/runtime/nitro/plugins/subresourceIntegrity.js +13 -0
- package/dist/runtime/server/middleware/allowedMethodsRestricter.d.ts +3 -0
- package/dist/runtime/server/middleware/allowedMethodsRestricter.js +14 -0
- package/dist/runtime/server/middleware/basicAuth.d.ts +3 -0
- package/dist/runtime/server/middleware/basicAuth.js +22 -0
- package/dist/runtime/server/middleware/corsHandler.d.ts +3 -0
- package/dist/runtime/server/middleware/corsHandler.js +20 -0
- package/dist/runtime/server/middleware/csrf.d.ts +3 -0
- package/dist/runtime/server/middleware/csrf.js +27 -0
- package/dist/runtime/server/middleware/rateLimiter.d.ts +3 -0
- package/dist/runtime/server/middleware/rateLimiter.js +21 -0
- package/dist/runtime/server/middleware/requestSizeLimiter.d.ts +3 -0
- package/dist/runtime/server/middleware/requestSizeLimiter.js +18 -0
- package/dist/runtime/services/checkIsBase64.d.ts +1 -0
- package/dist/runtime/services/checkIsBase64.js +5 -0
- package/dist/runtime/services/checkIsBasicAuthorized.d.ts +2 -0
- package/dist/runtime/services/checkIsBasicAuthorized.js +12 -0
- package/dist/runtime/services/checkIsBodyCounted.d.ts +1 -0
- package/dist/runtime/services/checkIsBodyCounted.js +4 -0
- package/dist/runtime/services/checkIsChallenged.d.ts +2 -0
- package/dist/runtime/services/checkIsChallenged.js +7 -0
- package/dist/runtime/services/checkIsCsrfTokenValid.d.ts +1 -0
- package/dist/runtime/services/checkIsCsrfTokenValid.js +7 -0
- package/dist/runtime/services/checkIsEqualInConstantTime.d.ts +1 -0
- package/dist/runtime/services/checkIsEqualInConstantTime.js +10 -0
- package/dist/runtime/services/checkIsMethodAllowed.d.ts +1 -0
- package/dist/runtime/services/checkIsMethodAllowed.js +3 -0
- package/dist/runtime/services/checkIsRecord.d.ts +1 -0
- package/dist/runtime/services/checkIsRecord.js +3 -0
- package/dist/runtime/services/checkIsRequestTooLarge.d.ts +2 -0
- package/dist/runtime/services/checkIsRequestTooLarge.js +7 -0
- package/dist/runtime/services/checkIsSecurityHeaderKey.d.ts +2 -0
- package/dist/runtime/services/checkIsSecurityHeaderKey.js +4 -0
- package/dist/runtime/services/checkIsStaticNonce.d.ts +1 -0
- package/dist/runtime/services/checkIsStaticNonce.js +4 -0
- package/dist/runtime/services/checkIsStringList.d.ts +1 -0
- package/dist/runtime/services/checkIsStringList.js +3 -0
- package/dist/runtime/services/collectSsgHashes.d.ts +3 -0
- package/dist/runtime/services/collectSsgHashes.js +18 -0
- package/dist/runtime/services/createInMemoryRateLimiterStore.d.ts +2 -0
- package/dist/runtime/services/createInMemoryRateLimiterStore.js +21 -0
- package/dist/runtime/services/generateHash.d.ts +1 -0
- package/dist/runtime/services/generateHash.js +8 -0
- package/dist/runtime/services/generateNonce.d.ts +1 -0
- package/dist/runtime/services/generateNonce.js +5 -0
- package/dist/runtime/services/generateRandomToken.d.ts +1 -0
- package/dist/runtime/services/generateRandomToken.js +7 -0
- package/dist/runtime/services/getBasicAuthCredentials.d.ts +4 -0
- package/dist/runtime/services/getBasicAuthCredentials.js +16 -0
- package/dist/runtime/services/getBasicAuthOptions.d.ts +2 -0
- package/dist/runtime/services/getBasicAuthOptions.js +16 -0
- package/dist/runtime/services/getRetryAfterSeconds.d.ts +3 -0
- package/dist/runtime/services/getRetryAfterSeconds.js +8 -0
- package/dist/runtime/services/getSecurityRules.d.ts +3 -0
- package/dist/runtime/services/getSecurityRules.js +14 -0
- package/dist/runtime/services/getSecurityState.d.ts +9 -0
- package/dist/runtime/services/getSecurityState.js +13 -0
- package/dist/runtime/services/getSsgHashSource.d.ts +4 -0
- package/dist/runtime/services/getSsgHashSource.js +26 -0
- package/dist/runtime/services/injectNonce.d.ts +1 -0
- package/dist/runtime/services/injectNonce.js +6 -0
- package/dist/runtime/services/injectSubresourceIntegrity.d.ts +1 -0
- package/dist/runtime/services/injectSubresourceIntegrity.js +21 -0
- package/dist/runtime/services/insertContentSecurityPolicyMeta.d.ts +2 -0
- package/dist/runtime/services/insertContentSecurityPolicyMeta.js +16 -0
- package/dist/runtime/services/mergeSecurityRules.d.ts +2 -0
- package/dist/runtime/services/mergeSecurityRules.js +10 -0
- package/dist/runtime/services/readHtmlTagAttribute.d.ts +1 -0
- package/dist/runtime/services/readHtmlTagAttribute.js +7 -0
- package/dist/runtime/services/rewriteHtmlTags.d.ts +1 -0
- package/dist/runtime/services/rewriteHtmlTags.js +8 -0
- package/dist/runtime/services/securityRouteMatcher.d.ts +4 -0
- package/dist/runtime/services/securityRouteMatcher.js +17 -0
- package/dist/runtime/services/serializeBasicAuthChallenge.d.ts +1 -0
- package/dist/runtime/services/serializeBasicAuthChallenge.js +3 -0
- package/dist/runtime/services/serializeContentSecurityPolicy.d.ts +2 -0
- package/dist/runtime/services/serializeContentSecurityPolicy.js +6 -0
- package/dist/runtime/services/serializePermissionsPolicy.d.ts +2 -0
- package/dist/runtime/services/serializePermissionsPolicy.js +3 -0
- package/dist/runtime/services/serializeSecurityHeaders.d.ts +2 -0
- package/dist/runtime/services/serializeSecurityHeaders.js +10 -0
- package/dist/runtime/services/serializeStrictTransportSecurity.d.ts +2 -0
- package/dist/runtime/services/serializeStrictTransportSecurity.js +7 -0
- package/dist/runtime/services/serializeValue.d.ts +1 -0
- package/dist/runtime/services/serializeValue.js +11 -0
- package/dist/runtime/services/stripHtmlTagAttribute.d.ts +1 -0
- package/dist/runtime/services/stripHtmlTagAttribute.js +4 -0
- package/dist/runtime/services/unquote.d.ts +1 -0
- package/dist/runtime/services/unquote.js +3 -0
- package/dist/runtime/services/updateContentSecurityPolicy.d.ts +3 -0
- package/dist/runtime/services/updateContentSecurityPolicy.js +14 -0
- package/dist/schemas/allowedMethodsRestricterOptionsSchema.js +4 -0
- package/dist/schemas/basicAuthOptionsSchema.js +11 -0
- package/dist/schemas/contentSecurityPolicySchema.js +4 -0
- package/dist/schemas/corsHandlerOptionsSchema.js +13 -0
- package/dist/schemas/csrfOptionsSchema.js +8 -0
- package/dist/schemas/moduleOptionsSchema.js +28 -0
- package/dist/schemas/permissionsPolicySchema.js +4 -0
- package/dist/schemas/rateLimiterOptionsSchema.js +7 -0
- package/dist/schemas/requestSizeLimiterOptionsSchema.js +4 -0
- package/dist/schemas/securityHeadersSchema.js +23 -0
- package/dist/schemas/ssgOptionsSchema.js +8 -0
- package/dist/schemas/strictTransportSecurityOptionsSchema.js +8 -0
- package/dist/services/computeSubresourceIntegrityHashes.js +23 -0
- package/dist/services/constants.js +3 -0
- package/dist/services/formatLeafIssue.js +5 -0
- package/dist/services/formatZodIssues.js +5 -0
- package/dist/services/getDeepestPathLength.js +3 -0
- package/dist/services/getDefaultModuleOptions.js +35 -0
- package/dist/services/getDefaultSecurityHeaders.js +83 -0
- package/dist/services/getLeafIssues.js +11 -0
- package/dist/services/getPluginTemplate.js +9 -0
- package/dist/services/getRemoveLoggersViteConfig.js +3 -0
- package/dist/services/getRouteRulesTemplate.js +3 -0
- package/dist/services/getRouteSecurityRules.js +16 -0
- package/dist/services/getStaticResourceHeaders.js +10 -0
- package/dist/services/getTypeTemplate.js +24 -0
- package/dist/services/mergeDirectives.js +12 -0
- package/dist/services/mergeFeatures.js +10 -0
- package/dist/services/mergeHeaders.js +14 -0
- package/dist/services/mergeModuleOptions.js +12 -0
- package/dist/services/mergeSources.js +9 -0
- package/dist/services/parseModuleOptions.js +9 -0
- package/package.json +5 -5
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SECURITY_HEADER_NAMES } from "../constants.js";
|
|
2
|
+
import { checkIsSecurityHeaderKey } from "./checkIsSecurityHeaderKey.js";
|
|
3
|
+
import { serializeValue } from "./serializeValue.js";
|
|
4
|
+
// src/runtime/services/serializeSecurityHeaders.ts
|
|
5
|
+
const serializeSecurityHeaders = (headers) => Object.fromEntries(Object.keys(headers).flatMap((key) => {
|
|
6
|
+
if (!checkIsSecurityHeaderKey(key)) return [];
|
|
7
|
+
const value = headers[key];
|
|
8
|
+
return value === false || value === void 0 ? [] : [[SECURITY_HEADER_NAMES[key], serializeValue(key, value)]];
|
|
9
|
+
}));
|
|
10
|
+
export { serializeSecurityHeaders };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// src/runtime/services/serializeStrictTransportSecurity.ts
|
|
2
|
+
const serializeStrictTransportSecurity = (policy) => [
|
|
3
|
+
`max-age=${policy.maxAge}`,
|
|
4
|
+
policy.includeSubdomains && "includeSubDomains",
|
|
5
|
+
policy.preload && "preload"
|
|
6
|
+
].filter(Boolean).join("; ");
|
|
7
|
+
export { serializeStrictTransportSecurity };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const serializeValue: (key: string, value: unknown) => string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { serializeContentSecurityPolicy } from "./serializeContentSecurityPolicy.js";
|
|
2
|
+
import { serializePermissionsPolicy } from "./serializePermissionsPolicy.js";
|
|
3
|
+
import { serializeStrictTransportSecurity } from "./serializeStrictTransportSecurity.js";
|
|
4
|
+
// src/runtime/services/serializeValue.ts
|
|
5
|
+
const serializeValue = (key, value) => {
|
|
6
|
+
if (key === "contentSecurityPolicy") return serializeContentSecurityPolicy(value);
|
|
7
|
+
if (key === "permissionsPolicy") return serializePermissionsPolicy(value);
|
|
8
|
+
if (key === "strictTransportSecurity") return serializeStrictTransportSecurity(value);
|
|
9
|
+
return String(value);
|
|
10
|
+
};
|
|
11
|
+
export { serializeValue };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const stripHtmlTagAttribute: (attributes: string, attributeName: string) => string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { HTML_ATTRIBUTE_REGEX } from "../constants.js";
|
|
2
|
+
// src/runtime/services/stripHtmlTagAttribute.ts
|
|
3
|
+
const stripHtmlTagAttribute = (attributes, attributeName) => attributes.replace(HTML_ATTRIBUTE_REGEX, (token, name) => name.toLowerCase() === attributeName ? "" : token);
|
|
4
|
+
export { stripHtmlTagAttribute };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const unquote: (value: string) => string;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ContentSecurityPolicy } from "../../models/ContentSecurityPolicy.js";
|
|
2
|
+
import { SecurityState } from "./getSecurityState.js";
|
|
3
|
+
export declare const updateContentSecurityPolicy: (policy: ContentSecurityPolicy, { hashes, nonce }: SecurityState) => ContentSecurityPolicy;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NONCE_PLACEHOLDER } from "../constants.js";
|
|
2
|
+
import { checkIsStaticNonce } from "./checkIsStaticNonce.js";
|
|
3
|
+
// src/runtime/services/updateContentSecurityPolicy.ts
|
|
4
|
+
const SCRIPT_DIRECTIVES = /* @__PURE__ */ new Set(["script-src", "script-src-elem"]);
|
|
5
|
+
const STYLE_DIRECTIVES = /* @__PURE__ */ new Set(["style-src", "style-src-elem"]);
|
|
6
|
+
const updateContentSecurityPolicy = (policy, { hashes, nonce }) => Object.fromEntries(Object.keys(policy).map((directive) => {
|
|
7
|
+
const value = policy[directive];
|
|
8
|
+
if (!Array.isArray(value)) return [directive, value];
|
|
9
|
+
const sources = value.filter((source) => !checkIsStaticNonce(source)).map((source) => source === "'nonce-{{nonce}}'" && nonce ? `'nonce-${nonce}'` : source).filter((source) => source !== NONCE_PLACEHOLDER);
|
|
10
|
+
if (SCRIPT_DIRECTIVES.has(directive)) sources.push(...hashes.script);
|
|
11
|
+
if (STYLE_DIRECTIVES.has(directive)) sources.push(...hashes.style);
|
|
12
|
+
return [directive, sources];
|
|
13
|
+
}));
|
|
14
|
+
export { updateContentSecurityPolicy };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// src/schemas/basicAuthOptionsSchema.ts
|
|
3
|
+
const pathSchema = z.string().startsWith("/");
|
|
4
|
+
const basicAuthOptionsSchema = z.strictObject({
|
|
5
|
+
exclude: z.array(pathSchema).optional(),
|
|
6
|
+
include: z.array(pathSchema).optional(),
|
|
7
|
+
message: z.string().optional(),
|
|
8
|
+
name: z.string().min(1),
|
|
9
|
+
pass: z.string().min(1)
|
|
10
|
+
});
|
|
11
|
+
export { basicAuthOptionsSchema };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// src/schemas/corsHandlerOptionsSchema.ts
|
|
3
|
+
const headerNameSchema = z.string().regex(/^[\w-]+$/u);
|
|
4
|
+
const corsHandlerOptionsSchema = z.strictObject({
|
|
5
|
+
allowHeaders: z.array(headerNameSchema).optional(),
|
|
6
|
+
credentials: z.boolean().optional(),
|
|
7
|
+
exposeHeaders: z.array(headerNameSchema).optional(),
|
|
8
|
+
maxAge: z.string().regex(/^\d+$/u).optional(),
|
|
9
|
+
methods: z.array(z.string().min(1)).min(1),
|
|
10
|
+
origin: z.union([z.url(), z.array(z.url())]),
|
|
11
|
+
preflight: z.strictObject({ statusCode: z.int().min(200).max(299) }).optional()
|
|
12
|
+
});
|
|
13
|
+
export { corsHandlerOptionsSchema };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// src/schemas/csrfOptionsSchema.ts
|
|
3
|
+
const tokenNameSchema = z.string().regex(/^[\w-]+$/u, "must be letters, digits, underscores or hyphens");
|
|
4
|
+
const csrfOptionsSchema = z.strictObject({
|
|
5
|
+
cookieName: tokenNameSchema,
|
|
6
|
+
headerName: tokenNameSchema
|
|
7
|
+
});
|
|
8
|
+
export { csrfOptionsSchema };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { allowedMethodsRestricterOptionsSchema } from "./allowedMethodsRestricterOptionsSchema.js";
|
|
2
|
+
import { basicAuthOptionsSchema } from "./basicAuthOptionsSchema.js";
|
|
3
|
+
import { corsHandlerOptionsSchema } from "./corsHandlerOptionsSchema.js";
|
|
4
|
+
import { csrfOptionsSchema } from "./csrfOptionsSchema.js";
|
|
5
|
+
import { rateLimiterOptionsSchema } from "./rateLimiterOptionsSchema.js";
|
|
6
|
+
import { requestSizeLimiterOptionsSchema } from "./requestSizeLimiterOptionsSchema.js";
|
|
7
|
+
import { securityHeadersSchema } from "./securityHeadersSchema.js";
|
|
8
|
+
import { ssgOptionsSchema } from "./ssgOptionsSchema.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
// src/schemas/moduleOptionsSchema.ts
|
|
11
|
+
const moduleOptionsSchema = z.strictObject({
|
|
12
|
+
allowedMethodsRestricter: z.union([allowedMethodsRestricterOptionsSchema, z.literal(false)]),
|
|
13
|
+
basicAuth: z.union([basicAuthOptionsSchema, z.literal(false)]),
|
|
14
|
+
contentSecurityPolicyReportOnly: z.boolean(),
|
|
15
|
+
corsHandler: z.union([corsHandlerOptionsSchema, z.literal(false)]),
|
|
16
|
+
csrf: z.union([csrfOptionsSchema, z.literal(false)]),
|
|
17
|
+
enabled: z.boolean(),
|
|
18
|
+
headers: z.union([securityHeadersSchema, z.literal(false)]),
|
|
19
|
+
hidePoweredBy: z.boolean(),
|
|
20
|
+
nonce: z.boolean(),
|
|
21
|
+
rateLimiter: z.union([rateLimiterOptionsSchema, z.literal(false)]),
|
|
22
|
+
removeLoggers: z.boolean(),
|
|
23
|
+
requestSizeLimiter: z.union([requestSizeLimiterOptionsSchema, z.literal(false)]),
|
|
24
|
+
sri: z.boolean(),
|
|
25
|
+
ssg: z.union([ssgOptionsSchema, z.literal(false)]),
|
|
26
|
+
strict: z.boolean()
|
|
27
|
+
});
|
|
28
|
+
export { moduleOptionsSchema };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// src/schemas/rateLimiterOptionsSchema.ts
|
|
3
|
+
const rateLimiterOptionsSchema = z.strictObject({
|
|
4
|
+
intervalInMilliseconds: z.int("must be a whole number of milliseconds").positive("must be more than zero milliseconds"),
|
|
5
|
+
tokensPerInterval: z.int("must be a whole number of requests").positive("must allow at least one request")
|
|
6
|
+
});
|
|
7
|
+
export { rateLimiterOptionsSchema };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// src/schemas/requestSizeLimiterOptionsSchema.ts
|
|
3
|
+
const requestSizeLimiterOptionsSchema = z.strictObject({ maxRequestSizeInBytes: z.int("must be a whole number of bytes").positive("must be more than zero bytes") });
|
|
4
|
+
export { requestSizeLimiterOptionsSchema };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CrossOriginEmbedderPolicy } from "../models/CrossOriginEmbedderPolicy.js";
|
|
2
|
+
import { XFrameOptions } from "../models/XFrameOptions.js";
|
|
3
|
+
import { contentSecurityPolicySchema } from "./contentSecurityPolicySchema.js";
|
|
4
|
+
import { permissionsPolicySchema } from "./permissionsPolicySchema.js";
|
|
5
|
+
import { strictTransportSecurityOptionsSchema } from "./strictTransportSecurityOptionsSchema.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
// src/schemas/securityHeadersSchema.ts
|
|
8
|
+
const headerValueSchema = z.string().min(1);
|
|
9
|
+
const securityHeadersSchema = z.strictObject({
|
|
10
|
+
contentSecurityPolicy: z.union([contentSecurityPolicySchema, z.literal(false)]).optional(),
|
|
11
|
+
crossOriginEmbedderPolicy: z.union([z.enum(CrossOriginEmbedderPolicy), z.literal(false)]).optional(),
|
|
12
|
+
crossOriginOpenerPolicy: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
13
|
+
crossOriginResourcePolicy: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
14
|
+
originAgentCluster: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
15
|
+
permissionsPolicy: z.union([permissionsPolicySchema, z.literal(false)]).optional(),
|
|
16
|
+
referrerPolicy: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
17
|
+
strictTransportSecurity: z.union([strictTransportSecurityOptionsSchema, z.literal(false)]).optional(),
|
|
18
|
+
xContentTypeOptions: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
19
|
+
xDNSPrefetchControl: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
20
|
+
xFrameOptions: z.union([z.enum(XFrameOptions), z.literal(false)]).optional(),
|
|
21
|
+
xPermittedCrossDomainPolicies: z.union([headerValueSchema, z.literal(false)]).optional()
|
|
22
|
+
});
|
|
23
|
+
export { securityHeadersSchema };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// src/schemas/strictTransportSecurityOptionsSchema.ts
|
|
3
|
+
const strictTransportSecurityOptionsSchema = z.strictObject({
|
|
4
|
+
includeSubdomains: z.boolean(),
|
|
5
|
+
maxAge: z.int().nonnegative(),
|
|
6
|
+
preload: z.boolean()
|
|
7
|
+
});
|
|
8
|
+
export { strictTransportSecurityOptionsSchema };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SRI_HASH_ALGORITHM } from "../runtime/constants.js";
|
|
2
|
+
import { generateHash } from "../runtime/services/generateHash.js";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
5
|
+
import { join, posix, relative, sep } from "node:path";
|
|
6
|
+
// src/services/computeSubresourceIntegrityHashes.ts
|
|
7
|
+
const computeSubresourceIntegrityHashes = async (nitro) => {
|
|
8
|
+
const { baseURL: appBaseUrl, cdnURL: appCdnUrl } = nitro.options.runtimeConfig.app;
|
|
9
|
+
const { publicDir } = nitro.options.output;
|
|
10
|
+
if (!existsSync(publicDir)) return {};
|
|
11
|
+
const entries = await readdir(publicDir, {
|
|
12
|
+
recursive: true,
|
|
13
|
+
withFileTypes: true
|
|
14
|
+
});
|
|
15
|
+
const hashEntries = await Promise.all(entries.filter((entry) => entry.isFile()).map(async (entry) => {
|
|
16
|
+
const filePath = join(entry.parentPath, entry.name);
|
|
17
|
+
const hash = await generateHash(await readFile(filePath), SRI_HASH_ALGORITHM);
|
|
18
|
+
const fullPath = posix.join(...relative(publicDir, filePath).split(sep));
|
|
19
|
+
return [appCdnUrl ? new URL(fullPath, appCdnUrl.endsWith("/") ? appCdnUrl : `${appCdnUrl}/`).href : posix.join("/", appBaseUrl, fullPath), hash];
|
|
20
|
+
}));
|
|
21
|
+
return Object.fromEntries(hashEntries);
|
|
22
|
+
};
|
|
23
|
+
export { computeSubresourceIntegrityHashes };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { formatLeafIssue } from "./formatLeafIssue.js";
|
|
2
|
+
import { getLeafIssues } from "./getLeafIssues.js";
|
|
3
|
+
// src/services/formatZodIssues.ts
|
|
4
|
+
const formatZodIssues = (issues) => issues.flatMap((issue) => getLeafIssues(issue, [])).map((leafIssue) => formatLeafIssue(leafIssue)).join("; ");
|
|
5
|
+
export { formatZodIssues };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getDefaultSecurityHeaders } from "./getDefaultSecurityHeaders.js";
|
|
2
|
+
// src/services/getDefaultModuleOptions.ts
|
|
3
|
+
const getDefaultModuleOptions = (isDevelopment, devServerUrl, isStrict) => ({
|
|
4
|
+
allowedMethodsRestricter: false,
|
|
5
|
+
basicAuth: false,
|
|
6
|
+
contentSecurityPolicyReportOnly: false,
|
|
7
|
+
corsHandler: {
|
|
8
|
+
methods: [
|
|
9
|
+
"GET",
|
|
10
|
+
"HEAD",
|
|
11
|
+
"PUT",
|
|
12
|
+
"PATCH",
|
|
13
|
+
"POST",
|
|
14
|
+
"DELETE"
|
|
15
|
+
],
|
|
16
|
+
origin: devServerUrl,
|
|
17
|
+
preflight: { statusCode: 204 }
|
|
18
|
+
},
|
|
19
|
+
csrf: false,
|
|
20
|
+
enabled: true,
|
|
21
|
+
headers: getDefaultSecurityHeaders(isDevelopment, isStrict),
|
|
22
|
+
hidePoweredBy: true,
|
|
23
|
+
nonce: true,
|
|
24
|
+
rateLimiter: false,
|
|
25
|
+
removeLoggers: false,
|
|
26
|
+
requestSizeLimiter: { maxRequestSizeInBytes: 2e6 },
|
|
27
|
+
sri: true,
|
|
28
|
+
ssg: {
|
|
29
|
+
hashScripts: true,
|
|
30
|
+
hashStyles: false,
|
|
31
|
+
meta: true
|
|
32
|
+
},
|
|
33
|
+
strict: isStrict
|
|
34
|
+
});
|
|
35
|
+
export { getDefaultModuleOptions };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { CrossOriginEmbedderPolicy } from "../models/CrossOriginEmbedderPolicy.js";
|
|
2
|
+
import { XFrameOptions } from "../models/XFrameOptions.js";
|
|
3
|
+
import { NONCE_PLACEHOLDER } from "../runtime/constants.js";
|
|
4
|
+
// src/services/getDefaultSecurityHeaders.ts
|
|
5
|
+
const getDefaultSecurityHeaders = (isDevelopment, isStrict) => ({
|
|
6
|
+
contentSecurityPolicy: {
|
|
7
|
+
"base-uri": ["'none'"],
|
|
8
|
+
"default-src": ["'self'"],
|
|
9
|
+
"font-src": [
|
|
10
|
+
"'self'",
|
|
11
|
+
"https:",
|
|
12
|
+
"data:"
|
|
13
|
+
],
|
|
14
|
+
"form-action": ["'self'"],
|
|
15
|
+
"frame-ancestors": ["'self'"],
|
|
16
|
+
"img-src": ["'self'", "data:"],
|
|
17
|
+
"object-src": ["'none'"],
|
|
18
|
+
"script-src": isStrict ? [
|
|
19
|
+
"'self'",
|
|
20
|
+
"'strict-dynamic'",
|
|
21
|
+
NONCE_PLACEHOLDER
|
|
22
|
+
] : [
|
|
23
|
+
"'self'",
|
|
24
|
+
"https:",
|
|
25
|
+
"'unsafe-inline'",
|
|
26
|
+
"'strict-dynamic'",
|
|
27
|
+
NONCE_PLACEHOLDER
|
|
28
|
+
],
|
|
29
|
+
"script-src-attr": ["'none'"],
|
|
30
|
+
"style-src": [
|
|
31
|
+
"'self'",
|
|
32
|
+
"https:",
|
|
33
|
+
"'unsafe-inline'"
|
|
34
|
+
],
|
|
35
|
+
"upgrade-insecure-requests": true
|
|
36
|
+
},
|
|
37
|
+
crossOriginEmbedderPolicy: isDevelopment ? CrossOriginEmbedderPolicy.UnsafeNone : CrossOriginEmbedderPolicy.Credentialless,
|
|
38
|
+
crossOriginOpenerPolicy: "same-origin",
|
|
39
|
+
crossOriginResourcePolicy: "same-origin",
|
|
40
|
+
originAgentCluster: "?1",
|
|
41
|
+
permissionsPolicy: {
|
|
42
|
+
accelerometer: [],
|
|
43
|
+
autoplay: [],
|
|
44
|
+
camera: [],
|
|
45
|
+
"clipboard-read": [],
|
|
46
|
+
"clipboard-write": [],
|
|
47
|
+
"cross-origin-isolated": [],
|
|
48
|
+
"display-capture": [],
|
|
49
|
+
"encrypted-media": [],
|
|
50
|
+
fullscreen: [],
|
|
51
|
+
gamepad: [],
|
|
52
|
+
geolocation: [],
|
|
53
|
+
gyroscope: [],
|
|
54
|
+
hid: [],
|
|
55
|
+
"idle-detection": [],
|
|
56
|
+
"interest-cohort": [],
|
|
57
|
+
"keyboard-map": [],
|
|
58
|
+
magnetometer: [],
|
|
59
|
+
microphone: [],
|
|
60
|
+
midi: [],
|
|
61
|
+
payment: [],
|
|
62
|
+
"picture-in-picture": [],
|
|
63
|
+
"publickey-credentials-get": [],
|
|
64
|
+
"screen-wake-lock": [],
|
|
65
|
+
serial: [],
|
|
66
|
+
"sync-xhr": ["self"],
|
|
67
|
+
unload: [],
|
|
68
|
+
usb: [],
|
|
69
|
+
"web-share": [],
|
|
70
|
+
"xr-spatial-tracking": []
|
|
71
|
+
},
|
|
72
|
+
referrerPolicy: "no-referrer",
|
|
73
|
+
strictTransportSecurity: {
|
|
74
|
+
includeSubdomains: true,
|
|
75
|
+
maxAge: 63072e3,
|
|
76
|
+
preload: false
|
|
77
|
+
},
|
|
78
|
+
xContentTypeOptions: "nosniff",
|
|
79
|
+
xDNSPrefetchControl: "off",
|
|
80
|
+
xFrameOptions: XFrameOptions.SameOrigin,
|
|
81
|
+
xPermittedCrossDomainPolicies: "none"
|
|
82
|
+
});
|
|
83
|
+
export { getDefaultSecurityHeaders };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { getDeepestPathLength } from "./getDeepestPathLength.js";
|
|
2
|
+
// src/services/getLeafIssues.ts
|
|
3
|
+
const getLeafIssues = (issue, parentPath) => {
|
|
4
|
+
const path = [...parentPath, ...issue.path];
|
|
5
|
+
if (issue.code !== "invalid_union") return [{
|
|
6
|
+
issue,
|
|
7
|
+
path
|
|
8
|
+
}];
|
|
9
|
+
return issue.errors.map((branch) => branch.flatMap((branchIssue) => getLeafIssues(branchIssue, path))).reduce((deepest, branch) => getDeepestPathLength(branch) > getDeepestPathLength(deepest) ? branch : deepest);
|
|
10
|
+
};
|
|
11
|
+
export { getLeafIssues };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// src/services/getPluginTemplate.ts
|
|
2
|
+
const getPluginTemplate = (factoryPath, factoryName, dataPath) => [
|
|
3
|
+
`import { ${factoryName} } from ${JSON.stringify(factoryPath)};`,
|
|
4
|
+
`import data from ${JSON.stringify(dataPath)};`,
|
|
5
|
+
"",
|
|
6
|
+
`export default ${factoryName}(data);`,
|
|
7
|
+
""
|
|
8
|
+
].join("\n");
|
|
9
|
+
export { getPluginTemplate };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { moduleOptionsSchema } from "../schemas/moduleOptionsSchema.js";
|
|
2
|
+
import { formatZodIssues } from "./formatZodIssues.js";
|
|
3
|
+
import { mergeModuleOptions } from "./mergeModuleOptions.js";
|
|
4
|
+
// src/services/getRouteSecurityRules.ts
|
|
5
|
+
const getRouteSecurityRules = (routeRules, options) => {
|
|
6
|
+
const routeSecurityRules = {};
|
|
7
|
+
for (const [route, { security }] of Object.entries(routeRules)) {
|
|
8
|
+
if (security === void 0) continue;
|
|
9
|
+
if (Object.hasOwn(security, "basicAuth")) throw new TypeError(`nuxt-security-module: route ${route}: basicAuth cannot be set in a route rule`);
|
|
10
|
+
const result = moduleOptionsSchema.safeParse(mergeModuleOptions(options, security));
|
|
11
|
+
if (!result.success) throw new TypeError(`nuxt-security-module: route ${route}: ${formatZodIssues(result.error.issues)}`);
|
|
12
|
+
routeSecurityRules[route] = security;
|
|
13
|
+
}
|
|
14
|
+
return routeSecurityRules;
|
|
15
|
+
};
|
|
16
|
+
export { getRouteSecurityRules };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { serializeSecurityHeaders } from "../runtime/services/serializeSecurityHeaders.js";
|
|
2
|
+
// src/services/getStaticResourceHeaders.ts
|
|
3
|
+
const getStaticResourceHeaders = (headers) => serializeSecurityHeaders({
|
|
4
|
+
referrerPolicy: headers.referrerPolicy,
|
|
5
|
+
strictTransportSecurity: headers.strictTransportSecurity,
|
|
6
|
+
xContentTypeOptions: headers.xContentTypeOptions,
|
|
7
|
+
xFrameOptions: headers.xFrameOptions,
|
|
8
|
+
xPermittedCrossDomainPolicies: headers.xPermittedCrossDomainPolicies
|
|
9
|
+
});
|
|
10
|
+
export { getStaticResourceHeaders };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MODULE_NAME, ROUTE_RULES_HOOK } from "../runtime/constants.js";
|
|
2
|
+
// src/services/getTypeTemplate.ts
|
|
3
|
+
const getTypeTemplate = () => `// Generated by ${MODULE_NAME}
|
|
4
|
+
import type { ModuleOptions } from '${MODULE_NAME}'
|
|
5
|
+
|
|
6
|
+
declare module '#${MODULE_NAME}/route-rules.mjs' {
|
|
7
|
+
const routeRules: Record<string, Partial<ModuleOptions>>
|
|
8
|
+
export default routeRules
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare module '#${MODULE_NAME}/sri-hashes.mjs' {
|
|
12
|
+
const sriHashes: Record<string, string>
|
|
13
|
+
export default sriHashes
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare module 'nitro/types' {
|
|
17
|
+
interface NitroRuntimeHooks {
|
|
18
|
+
'${ROUTE_RULES_HOOK}': (routeRules: Record<string, Partial<ModuleOptions>>) => void | Promise<void>
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {}
|
|
23
|
+
`;
|
|
24
|
+
export { getTypeTemplate };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { mergeSources } from "./mergeSources.js";
|
|
2
|
+
// src/services/mergeDirectives.ts
|
|
3
|
+
const mergeDirectives = (defaults, overrides) => {
|
|
4
|
+
if (overrides === false || defaults === false) return overrides ?? defaults;
|
|
5
|
+
if (overrides === void 0) return defaults;
|
|
6
|
+
const merged = { ...defaults };
|
|
7
|
+
for (const [directive, value] of Object.entries(overrides)) if (value === false) delete merged[directive];
|
|
8
|
+
else if (Array.isArray(value)) merged[directive] = mergeSources(merged[directive], value);
|
|
9
|
+
else merged[directive] = value;
|
|
10
|
+
return merged;
|
|
11
|
+
};
|
|
12
|
+
export { mergeDirectives };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/services/mergeFeatures.ts
|
|
2
|
+
const mergeFeatures = (defaults, overrides) => {
|
|
3
|
+
if (overrides === false || defaults === false) return overrides ?? defaults;
|
|
4
|
+
if (overrides === void 0) return defaults;
|
|
5
|
+
const merged = { ...defaults };
|
|
6
|
+
for (const [feature, sources] of Object.entries(overrides)) if (sources === false) delete merged[feature];
|
|
7
|
+
else merged[feature] = [.../* @__PURE__ */ new Set([...merged[feature] || [], ...sources || []])];
|
|
8
|
+
return merged;
|
|
9
|
+
};
|
|
10
|
+
export { mergeFeatures };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { mergeDirectives } from "./mergeDirectives.js";
|
|
2
|
+
import { mergeFeatures } from "./mergeFeatures.js";
|
|
3
|
+
// src/services/mergeHeaders.ts
|
|
4
|
+
const mergeHeaders = (defaults, overrides) => {
|
|
5
|
+
if (overrides === false || defaults === false) return overrides ?? defaults;
|
|
6
|
+
if (overrides === void 0) return defaults;
|
|
7
|
+
return {
|
|
8
|
+
...defaults,
|
|
9
|
+
...overrides,
|
|
10
|
+
contentSecurityPolicy: mergeDirectives(defaults.contentSecurityPolicy, overrides.contentSecurityPolicy),
|
|
11
|
+
permissionsPolicy: mergeFeatures(defaults.permissionsPolicy, overrides.permissionsPolicy)
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export { mergeHeaders };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { mergeHeaders } from "./mergeHeaders.js";
|
|
2
|
+
// src/services/mergeModuleOptions.ts
|
|
3
|
+
const mergeModuleOptions = (defaults, overrides) => ({
|
|
4
|
+
...defaults,
|
|
5
|
+
...overrides,
|
|
6
|
+
headers: mergeHeaders(defaults.headers, overrides.headers),
|
|
7
|
+
ssg: overrides.ssg === false ? false : {
|
|
8
|
+
...defaults.ssg,
|
|
9
|
+
...overrides.ssg
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
export { mergeModuleOptions };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NONE_SOURCE } from "./constants.js";
|
|
2
|
+
// src/services/mergeSources.ts
|
|
3
|
+
const mergeSources = (defaultSources, sources) => {
|
|
4
|
+
if (sources.length === 1 && sources[0] === "'none'") return sources;
|
|
5
|
+
const mergedSources = /* @__PURE__ */ new Set([...Array.isArray(defaultSources) ? defaultSources : [], ...sources]);
|
|
6
|
+
if (mergedSources.size > 1) mergedSources.delete(NONE_SOURCE);
|
|
7
|
+
return [...mergedSources];
|
|
8
|
+
};
|
|
9
|
+
export { mergeSources };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { moduleOptionsSchema } from "../schemas/moduleOptionsSchema.js";
|
|
2
|
+
import { formatZodIssues } from "./formatZodIssues.js";
|
|
3
|
+
// src/services/parseModuleOptions.ts
|
|
4
|
+
const parseModuleOptions = (options) => {
|
|
5
|
+
const result = moduleOptionsSchema.safeParse(options);
|
|
6
|
+
if (!result.success) throw new TypeError(`nuxt-security-module: ${formatZodIssues(result.error.issues)}`);
|
|
7
|
+
return result.data;
|
|
8
|
+
};
|
|
9
|
+
export { parseModuleOptions };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-security-module",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"description": "Nuxt security module for Nitro 3 and h3 2 — security headers, a per-request nonce Content Security Policy, subresource integrity, CORS, allowed methods, basic auth and a request size limit.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Jimmy Chen",
|
|
@@ -39,16 +39,16 @@
|
|
|
39
39
|
"zod": "^4.6.7"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@esposter/configuration": "4.
|
|
43
|
-
"@esposter/shared-node": "4.
|
|
42
|
+
"@esposter/configuration": "4.6.0",
|
|
43
|
+
"@esposter/shared-node": "4.6.0",
|
|
44
44
|
"@nuxt/nitro-server": "npm:@nuxt/nitro-server-nightly@5x",
|
|
45
45
|
"@types/node": "^26.6.6",
|
|
46
46
|
"h3": "^2.0.8",
|
|
47
|
-
"tsdown": "0.23.
|
|
47
|
+
"tsdown": "^0.23.7"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"nitro": "npm:nitro-nightly@latest",
|
|
51
51
|
"nuxt": "npm:nuxt-nightly@5x"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "0ceea3ffbe12ca69413ea5db2d3cfad197a63cba"
|
|
54
54
|
}
|