nuxt-security-module 0.0.0-stage → 4.1.1
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/LICENSE +201 -0
- package/README.md +21 -2
- package/dist/index.d.ts +17 -0
- package/dist/index.js +105 -0
- package/dist/models/AllowedMethodsRestricterOptions.d.ts +5 -0
- package/dist/models/BasicAuthOptions.d.ts +9 -0
- package/dist/models/ContentSecurityPolicy.d.ts +3 -0
- package/dist/models/CorsHandlerOptions.d.ts +13 -0
- package/dist/models/CrossOriginEmbedderPolicy.d.ts +8 -0
- package/dist/models/CrossOriginEmbedderPolicy.js +8 -0
- package/dist/models/CsrfOptions.d.ts +6 -0
- package/dist/models/ModuleOptions.d.ts +27 -0
- package/dist/models/PermissionsPolicy.d.ts +3 -0
- package/dist/models/RateLimitWindow.d.ts +6 -0
- package/dist/models/RateLimiterOptions.d.ts +6 -0
- package/dist/models/RateLimiterStore.d.ts +6 -0
- package/dist/models/RequestSizeLimiterOptions.d.ts +6 -0
- package/dist/models/SecurityHeaders.d.ts +22 -0
- package/dist/models/SecurityRouteRules.d.ts +4 -0
- package/dist/models/SecurityRules.d.ts +4 -0
- package/dist/models/SsgOptions.d.ts +9 -0
- package/dist/models/StrictTransportSecurity.d.ts +7 -0
- package/dist/models/XFrameOptions.d.ts +7 -0
- package/dist/models/XFrameOptions.js +7 -0
- package/dist/runtime/constants.d.ts +24 -0
- package/dist/runtime/constants.js +52 -0
- package/dist/runtime/nitro/plugins/hidePoweredBy.d.ts +5 -0
- package/dist/runtime/nitro/plugins/hidePoweredBy.js +13 -0
- package/dist/runtime/nitro/plugins/nonce.d.ts +5 -0
- package/dist/runtime/nitro/plugins/nonce.js +27 -0
- package/dist/runtime/nitro/plugins/routeRules.d.ts +5 -0
- package/dist/runtime/nitro/plugins/routeRules.js +17 -0
- package/dist/runtime/nitro/plugins/securityHeaders.d.ts +5 -0
- package/dist/runtime/nitro/plugins/securityHeaders.js +25 -0
- package/dist/runtime/nitro/plugins/ssgHashes.d.ts +5 -0
- package/dist/runtime/nitro/plugins/ssgHashes.js +27 -0
- package/dist/runtime/nitro/plugins/subresourceIntegrity.d.ts +4 -0
- package/dist/runtime/nitro/plugins/subresourceIntegrity.js +19 -0
- package/dist/runtime/server/middleware/allowedMethodsRestricter.d.ts +5 -0
- package/dist/runtime/server/middleware/allowedMethodsRestricter.js +15 -0
- package/dist/runtime/server/middleware/basicAuth.d.ts +5 -0
- package/dist/runtime/server/middleware/basicAuth.js +23 -0
- package/dist/runtime/server/middleware/corsHandler.d.ts +5 -0
- package/dist/runtime/server/middleware/corsHandler.js +20 -0
- package/dist/runtime/server/middleware/csrf.d.ts +5 -0
- package/dist/runtime/server/middleware/csrf.js +28 -0
- package/dist/runtime/server/middleware/rateLimiter.d.ts +5 -0
- package/dist/runtime/server/middleware/rateLimiter.js +22 -0
- package/dist/runtime/server/middleware/requestSizeLimiter.d.ts +5 -0
- package/dist/runtime/server/middleware/requestSizeLimiter.js +17 -0
- package/dist/runtime/services/checkIsBasicAuthorized.d.ts +4 -0
- package/dist/runtime/services/checkIsBasicAuthorized.js +12 -0
- package/dist/runtime/services/checkIsChallenged.d.ts +4 -0
- package/dist/runtime/services/checkIsChallenged.js +8 -0
- package/dist/runtime/services/checkIsCsrfTokenValid.d.ts +3 -0
- package/dist/runtime/services/checkIsCsrfTokenValid.js +8 -0
- package/dist/runtime/services/checkIsEqualInConstantTime.d.ts +3 -0
- package/dist/runtime/services/checkIsEqualInConstantTime.js +11 -0
- package/dist/runtime/services/checkIsMethodAllowed.d.ts +3 -0
- package/dist/runtime/services/checkIsMethodAllowed.js +4 -0
- package/dist/runtime/services/checkIsRecord.d.ts +3 -0
- package/dist/runtime/services/checkIsRecord.js +4 -0
- package/dist/runtime/services/checkIsRequestTooLarge.d.ts +4 -0
- package/dist/runtime/services/checkIsRequestTooLarge.js +9 -0
- package/dist/runtime/services/collectSsgHashes.d.ts +5 -0
- package/dist/runtime/services/collectSsgHashes.js +41 -0
- package/dist/runtime/services/createInMemoryRateLimiterStore.d.ts +4 -0
- package/dist/runtime/services/createInMemoryRateLimiterStore.js +22 -0
- package/dist/runtime/services/generateHash.d.ts +3 -0
- package/dist/runtime/services/generateHash.js +9 -0
- package/dist/runtime/services/generateNonce.d.ts +3 -0
- package/dist/runtime/services/generateNonce.js +6 -0
- package/dist/runtime/services/generateRandomToken.d.ts +3 -0
- package/dist/runtime/services/generateRandomToken.js +8 -0
- package/dist/runtime/services/getBasicAuthCredentials.d.ts +6 -0
- package/dist/runtime/services/getBasicAuthCredentials.js +19 -0
- package/dist/runtime/services/getBasicAuthOptions.d.ts +4 -0
- package/dist/runtime/services/getBasicAuthOptions.js +17 -0
- package/dist/runtime/services/getRetryAfterSeconds.d.ts +5 -0
- package/dist/runtime/services/getRetryAfterSeconds.js +9 -0
- package/dist/runtime/services/getSecurityRules.d.ts +5 -0
- package/dist/runtime/services/getSecurityRules.js +15 -0
- package/dist/runtime/services/getSecurityState.d.ts +11 -0
- package/dist/runtime/services/getSecurityState.js +14 -0
- package/dist/runtime/services/injectNonce.d.ts +3 -0
- package/dist/runtime/services/injectNonce.js +7 -0
- package/dist/runtime/services/injectSubresourceIntegrity.d.ts +3 -0
- package/dist/runtime/services/injectSubresourceIntegrity.js +22 -0
- package/dist/runtime/services/insertContentSecurityPolicyMeta.d.ts +4 -0
- package/dist/runtime/services/insertContentSecurityPolicyMeta.js +17 -0
- package/dist/runtime/services/mergeSecurityRules.d.ts +4 -0
- package/dist/runtime/services/mergeSecurityRules.js +11 -0
- package/dist/runtime/services/readHtmlTagAttribute.d.ts +3 -0
- package/dist/runtime/services/readHtmlTagAttribute.js +8 -0
- package/dist/runtime/services/rewriteHtmlTags.d.ts +3 -0
- package/dist/runtime/services/rewriteHtmlTags.js +9 -0
- package/dist/runtime/services/securityRouteMatcher.d.ts +5 -0
- package/dist/runtime/services/securityRouteMatcher.js +11 -0
- package/dist/runtime/services/serializeBasicAuthChallenge.d.ts +3 -0
- package/dist/runtime/services/serializeBasicAuthChallenge.js +4 -0
- package/dist/runtime/services/serializeContentSecurityPolicy.d.ts +4 -0
- package/dist/runtime/services/serializeContentSecurityPolicy.js +7 -0
- package/dist/runtime/services/serializePermissionsPolicy.d.ts +4 -0
- package/dist/runtime/services/serializePermissionsPolicy.js +4 -0
- package/dist/runtime/services/serializeSecurityHeaders.d.ts +4 -0
- package/dist/runtime/services/serializeSecurityHeaders.js +19 -0
- package/dist/runtime/services/serializeStrictTransportSecurity.d.ts +4 -0
- package/dist/runtime/services/serializeStrictTransportSecurity.js +8 -0
- package/dist/runtime/services/stripHtmlTagAttribute.d.ts +3 -0
- package/dist/runtime/services/stripHtmlTagAttribute.js +5 -0
- package/dist/runtime/services/updateContentSecurityPolicy.d.ts +5 -0
- package/dist/runtime/services/updateContentSecurityPolicy.js +15 -0
- package/dist/schemas/allowedMethodsRestricterOptionsSchema.js +5 -0
- package/dist/schemas/basicAuthOptionsSchema.js +12 -0
- package/dist/schemas/contentSecurityPolicySchema.js +5 -0
- package/dist/schemas/corsHandlerOptionsSchema.js +14 -0
- package/dist/schemas/csrfOptionsSchema.js +9 -0
- package/dist/schemas/moduleOptionsSchema.js +29 -0
- package/dist/schemas/permissionsPolicySchema.js +5 -0
- package/dist/schemas/rateLimiterOptionsSchema.js +8 -0
- package/dist/schemas/requestSizeLimiterOptionsSchema.js +8 -0
- package/dist/schemas/securityHeadersSchema.js +25 -0
- package/dist/schemas/ssgOptionsSchema.js +11 -0
- package/dist/schemas/strictTransportSecurityOptionsSchema.js +9 -0
- package/dist/services/computeSubresourceIntegrityHashes.js +29 -0
- package/dist/services/formatZodIssues.js +16 -0
- package/dist/services/getDefaultModuleOptions.js +41 -0
- package/dist/services/getDefaultSecurityHeaders.js +85 -0
- package/dist/services/getPluginTemplate.js +10 -0
- package/dist/services/getRemoveLoggersViteConfig.js +4 -0
- package/dist/services/getRouteRulesTemplate.js +4 -0
- package/dist/services/getRouteSecurityRules.js +17 -0
- package/dist/services/getStaticResourceHeaders.js +12 -0
- package/dist/services/getTypeTemplate.js +25 -0
- package/dist/services/mergeModuleOptions.js +39 -0
- package/dist/services/parseModuleOptions.js +10 -0
- package/package.json +70 -4
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { checkIsRecord } from "./checkIsRecord.js";
|
|
2
|
+
//#region src/runtime/services/getBasicAuthOptions.ts
|
|
3
|
+
const checkIsStringList = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
4
|
+
const getBasicAuthOptions = (privateRuntimeConfig) => {
|
|
5
|
+
if (!checkIsRecord(privateRuntimeConfig) || !checkIsRecord(privateRuntimeConfig.basicAuth)) return void 0;
|
|
6
|
+
const { exclude, include, message, name, pass } = privateRuntimeConfig.basicAuth;
|
|
7
|
+
if (typeof name !== "string" || typeof pass !== "string") return void 0;
|
|
8
|
+
return {
|
|
9
|
+
...checkIsStringList(exclude) && { exclude },
|
|
10
|
+
...checkIsStringList(include) && { include },
|
|
11
|
+
...typeof message === "string" && { message },
|
|
12
|
+
name,
|
|
13
|
+
pass
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { getBasicAuthOptions };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { RateLimiterOptions } from "../../models/RateLimiterOptions.js";
|
|
2
|
+
import { RateLimiterStore } from "../../models/RateLimiterStore.js";
|
|
3
|
+
//#region src/runtime/services/getRetryAfterSeconds.d.ts
|
|
4
|
+
export declare const getRetryAfterSeconds: (store: RateLimiterStore, key: string, { intervalInMilliseconds, tokensPerInterval }: RateLimiterOptions) => Promise<number | undefined>;
|
|
5
|
+
//#endregion
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/runtime/services/getRetryAfterSeconds.ts
|
|
2
|
+
const MILLISECONDS_PER_SECOND = 1e3;
|
|
3
|
+
const getRetryAfterSeconds = async (store, key, { intervalInMilliseconds, tokensPerInterval }) => {
|
|
4
|
+
const { count, resetAt } = await store.increment(key, intervalInMilliseconds);
|
|
5
|
+
if (count <= tokensPerInterval) return void 0;
|
|
6
|
+
return Math.max(1, Math.ceil((resetAt - Date.now()) / MILLISECONDS_PER_SECOND));
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { getRetryAfterSeconds };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mergeSecurityRules } from "./mergeSecurityRules.js";
|
|
2
|
+
import { findSecurityRules } from "./securityRouteMatcher.js";
|
|
3
|
+
//#region src/runtime/services/getSecurityRules.ts
|
|
4
|
+
const securityRulesByEvent = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
const getSecurityRules = (event) => {
|
|
6
|
+
const cachedRules = securityRulesByEvent.get(event);
|
|
7
|
+
if (cachedRules) return cachedRules;
|
|
8
|
+
const matchedRules = findSecurityRules(event.url.pathname);
|
|
9
|
+
if (!matchedRules) return void 0;
|
|
10
|
+
const rules = mergeSecurityRules(...matchedRules.toReversed());
|
|
11
|
+
securityRulesByEvent.set(event, rules);
|
|
12
|
+
return rules;
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { getSecurityRules };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { H3Event } from "nitro/h3";
|
|
2
|
+
//#region src/runtime/services/getSecurityState.d.ts
|
|
3
|
+
export interface SecurityState {
|
|
4
|
+
hashes: {
|
|
5
|
+
script: string[];
|
|
6
|
+
style: string[];
|
|
7
|
+
};
|
|
8
|
+
nonce?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const getSecurityState: (event: H3Event) => SecurityState;
|
|
11
|
+
//#endregion
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/runtime/services/getSecurityState.ts
|
|
2
|
+
const securityStateByEvent = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
const getSecurityState = (event) => {
|
|
4
|
+
const cachedState = securityStateByEvent.get(event);
|
|
5
|
+
if (cachedState) return cachedState;
|
|
6
|
+
const state = { hashes: {
|
|
7
|
+
script: [],
|
|
8
|
+
style: []
|
|
9
|
+
} };
|
|
10
|
+
securityStateByEvent.set(event, state);
|
|
11
|
+
return state;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
export { getSecurityState };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { NONCE_TAG_NAMES } from "../constants.js";
|
|
2
|
+
import { rewriteHtmlTags } from "./rewriteHtmlTags.js";
|
|
3
|
+
import { stripHtmlTagAttribute } from "./stripHtmlTagAttribute.js";
|
|
4
|
+
//#region src/runtime/services/injectNonce.ts
|
|
5
|
+
const injectNonce = (html, nonce) => rewriteHtmlTags(html, (tagName, attributes) => NONCE_TAG_NAMES.includes(tagName.toLowerCase()) ? ` nonce="${nonce}"${stripHtmlTagAttribute(attributes, "nonce")}` : void 0);
|
|
6
|
+
//#endregion
|
|
7
|
+
export { injectNonce };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readHtmlTagAttribute } from "./readHtmlTagAttribute.js";
|
|
2
|
+
import { rewriteHtmlTags } from "./rewriteHtmlTags.js";
|
|
3
|
+
//#region src/runtime/services/injectSubresourceIntegrity.ts
|
|
4
|
+
const RESOURCE_ATTRIBUTE_NAMES = {
|
|
5
|
+
link: "href",
|
|
6
|
+
script: "src"
|
|
7
|
+
};
|
|
8
|
+
const INTEGRITY_LINK_RELS = /* @__PURE__ */ new Set([
|
|
9
|
+
"modulepreload",
|
|
10
|
+
"preload",
|
|
11
|
+
"stylesheet"
|
|
12
|
+
]);
|
|
13
|
+
const injectSubresourceIntegrity = (html, hashes) => rewriteHtmlTags(html, (tagName, attributes) => {
|
|
14
|
+
const resourceAttributeName = RESOURCE_ATTRIBUTE_NAMES[tagName.toLowerCase()];
|
|
15
|
+
if (!resourceAttributeName || readHtmlTagAttribute(attributes, "integrity") !== void 0) return void 0;
|
|
16
|
+
const resource = readHtmlTagAttribute(attributes, resourceAttributeName);
|
|
17
|
+
if (resource === void 0 || hashes[resource] === void 0) return void 0;
|
|
18
|
+
if (tagName.toLowerCase() === "link" && !INTEGRITY_LINK_RELS.has(readHtmlTagAttribute(attributes, "rel") ?? "")) return void 0;
|
|
19
|
+
return ` integrity="${hashes[resource]}"${attributes}`;
|
|
20
|
+
});
|
|
21
|
+
//#endregion
|
|
22
|
+
export { injectSubresourceIntegrity };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ContentSecurityPolicy } from "../../models/ContentSecurityPolicy.js";
|
|
2
|
+
//#region src/runtime/services/insertContentSecurityPolicyMeta.d.ts
|
|
3
|
+
export declare const insertContentSecurityPolicyMeta: (html: string, policy: ContentSecurityPolicy) => string;
|
|
4
|
+
//#endregion
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { serializeContentSecurityPolicy } from "./serializeContentSecurityPolicy.js";
|
|
2
|
+
//#region src/runtime/services/insertContentSecurityPolicyMeta.ts
|
|
3
|
+
const META_CONTENT_SECURITY_POLICY_REGEX = /<meta\s+http-equiv=["']Content-Security-Policy["'][^>]*>/iu;
|
|
4
|
+
const HEAD_CHARSET_REGEX = /<meta\s+charset=["'][^"']*["'][^>]*>/iu;
|
|
5
|
+
const HEAD_OPEN_REGEX = /<head\b[^>]*>/iu;
|
|
6
|
+
const insertContentSecurityPolicyMeta = (html, policy) => {
|
|
7
|
+
const metaTag = `<meta http-equiv="Content-Security-Policy" content="${serializeContentSecurityPolicy({
|
|
8
|
+
...policy,
|
|
9
|
+
"frame-ancestors": false
|
|
10
|
+
})}">`;
|
|
11
|
+
if (META_CONTENT_SECURITY_POLICY_REGEX.test(html)) return html.replace(META_CONTENT_SECURITY_POLICY_REGEX, metaTag);
|
|
12
|
+
const insertion = HEAD_CHARSET_REGEX.exec(html) ?? HEAD_OPEN_REGEX.exec(html);
|
|
13
|
+
const insertAt = insertion ? insertion.index + insertion[0].length : 0;
|
|
14
|
+
return html.slice(0, insertAt) + metaTag + html.slice(insertAt);
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { insertContentSecurityPolicyMeta };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createDefu } from "defu";
|
|
2
|
+
//#region src/runtime/services/mergeSecurityRules.ts
|
|
3
|
+
const mergeRulesWithReplacedArrays = createDefu((object, key, value) => {
|
|
4
|
+
if (Array.isArray(object[key]) || Array.isArray(value)) {
|
|
5
|
+
object[key] = value;
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
});
|
|
9
|
+
const mergeSecurityRules = (...rules) => mergeRulesWithReplacedArrays({}, ...rules);
|
|
10
|
+
//#endregion
|
|
11
|
+
export { mergeSecurityRules };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { HTML_ATTRIBUTE_REGEX } from "../constants.js";
|
|
2
|
+
//#region src/runtime/services/readHtmlTagAttribute.ts
|
|
3
|
+
const readHtmlTagAttribute = (attributes, attributeName) => {
|
|
4
|
+
for (const [, name, , value] of attributes.matchAll(HTML_ATTRIBUTE_REGEX)) if (name?.toLowerCase() === attributeName) return unquote(value ?? "");
|
|
5
|
+
};
|
|
6
|
+
const unquote = (value) => value.replace(/^(?<quote>["'])(?<inner>.*)\k<quote>$/su, "$<inner>");
|
|
7
|
+
//#endregion
|
|
8
|
+
export { readHtmlTagAttribute };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { HTML_TAG_REGEX } from "../constants.js";
|
|
2
|
+
//#region src/runtime/services/rewriteHtmlTags.ts
|
|
3
|
+
const rewriteHtmlTags = (html, rewriteAttributes) => html.replace(HTML_TAG_REGEX, (match, elementName, elementAttributes, elementTrailer, body, tagName, tagAttributes, tagTrailer) => {
|
|
4
|
+
if (elementName) return `<${elementName}${rewriteAttributes(elementName, elementAttributes ?? "") ?? elementAttributes}${elementTrailer}>${body}</${elementName}>`;
|
|
5
|
+
const attributes = rewriteAttributes(tagName ?? "", tagAttributes ?? "");
|
|
6
|
+
return attributes === void 0 ? match : `<${tagName}${attributes}${tagTrailer}>`;
|
|
7
|
+
});
|
|
8
|
+
//#endregion
|
|
9
|
+
export { rewriteHtmlTags };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { SecurityRules } from "../../models/SecurityRules.js";
|
|
2
|
+
//#region src/runtime/services/securityRouteMatcher.d.ts
|
|
3
|
+
export declare const setSecurityRouteMatcher: (routes: Record<string, SecurityRules>) => void;
|
|
4
|
+
export declare const findSecurityRules: (pathname: string) => SecurityRules[] | undefined;
|
|
5
|
+
//#endregion
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { addRoute, createRouter, findAllRoutes } from "rou3";
|
|
2
|
+
//#region src/runtime/services/securityRouteMatcher.ts
|
|
3
|
+
let securityRouter;
|
|
4
|
+
const setSecurityRouteMatcher = (routes) => {
|
|
5
|
+
const router = createRouter();
|
|
6
|
+
for (const [pattern, rules] of Object.entries(routes)) addRoute(router, "", pattern, rules);
|
|
7
|
+
securityRouter = router;
|
|
8
|
+
};
|
|
9
|
+
const findSecurityRules = (pathname) => securityRouter && findAllRoutes(securityRouter, "", pathname).flatMap(({ data }) => data ? [data] : []);
|
|
10
|
+
//#endregion
|
|
11
|
+
export { findSecurityRules, setSecurityRouteMatcher };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/runtime/services/serializeContentSecurityPolicy.ts
|
|
2
|
+
const serializeContentSecurityPolicy = (policy) => Object.entries(policy).filter(([, value]) => value !== false).map(([directive, value]) => {
|
|
3
|
+
if (directive === "upgrade-insecure-requests") return "upgrade-insecure-requests;";
|
|
4
|
+
return `${directive} ${Array.isArray(value) ? value.map((source) => source.trim()).join(" ") : ""};`;
|
|
5
|
+
}).join(" ");
|
|
6
|
+
//#endregion
|
|
7
|
+
export { serializeContentSecurityPolicy };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
//#region src/runtime/services/serializePermissionsPolicy.ts
|
|
2
|
+
const serializePermissionsPolicy = (policy) => Object.entries(policy).flatMap(([feature, sources]) => sources === false ? [] : [`${feature}=(${(sources ?? []).join(" ")})`]).join(", ");
|
|
3
|
+
//#endregion
|
|
4
|
+
export { serializePermissionsPolicy };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SECURITY_HEADER_NAMES } from "../constants.js";
|
|
2
|
+
import { serializeContentSecurityPolicy } from "./serializeContentSecurityPolicy.js";
|
|
3
|
+
import { serializePermissionsPolicy } from "./serializePermissionsPolicy.js";
|
|
4
|
+
import { serializeStrictTransportSecurity } from "./serializeStrictTransportSecurity.js";
|
|
5
|
+
//#region src/runtime/services/serializeSecurityHeaders.ts
|
|
6
|
+
const checkIsSecurityHeaderKey = (key) => Object.hasOwn(SECURITY_HEADER_NAMES, key);
|
|
7
|
+
const serializeSecurityHeaders = (headers) => Object.fromEntries(Object.keys(headers).flatMap((key) => {
|
|
8
|
+
if (!checkIsSecurityHeaderKey(key)) return [];
|
|
9
|
+
const value = headers[key];
|
|
10
|
+
return value === false || value === void 0 ? [] : [[SECURITY_HEADER_NAMES[key], serializeValue(key, value)]];
|
|
11
|
+
}));
|
|
12
|
+
const serializeValue = (key, value) => {
|
|
13
|
+
if (key === "contentSecurityPolicy") return serializeContentSecurityPolicy(value);
|
|
14
|
+
if (key === "permissionsPolicy") return serializePermissionsPolicy(value);
|
|
15
|
+
if (key === "strictTransportSecurity") return serializeStrictTransportSecurity(value);
|
|
16
|
+
return String(value);
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { serializeSecurityHeaders };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region 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
|
+
//#endregion
|
|
8
|
+
export { serializeStrictTransportSecurity };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { HTML_ATTRIBUTE_REGEX } from "../constants.js";
|
|
2
|
+
//#region src/runtime/services/stripHtmlTagAttribute.ts
|
|
3
|
+
const stripHtmlTagAttribute = (attributes, attributeName) => attributes.replace(HTML_ATTRIBUTE_REGEX, (token, name) => name.toLowerCase() === attributeName ? "" : token);
|
|
4
|
+
//#endregion
|
|
5
|
+
export { stripHtmlTagAttribute };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ContentSecurityPolicy } from "../../models/ContentSecurityPolicy.js";
|
|
2
|
+
import { SecurityState } from "./getSecurityState.js";
|
|
3
|
+
//#region src/runtime/services/updateContentSecurityPolicy.d.ts
|
|
4
|
+
export declare const updateContentSecurityPolicy: (policy: ContentSecurityPolicy, { hashes, nonce }: SecurityState) => ContentSecurityPolicy;
|
|
5
|
+
//#endregion
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NONCE_PLACEHOLDER } from "../constants.js";
|
|
2
|
+
//#region src/runtime/services/updateContentSecurityPolicy.ts
|
|
3
|
+
const SCRIPT_DIRECTIVES = /* @__PURE__ */ new Set(["script-src", "script-src-elem"]);
|
|
4
|
+
const STYLE_DIRECTIVES = /* @__PURE__ */ new Set(["style-src", "style-src-elem"]);
|
|
5
|
+
const updateContentSecurityPolicy = (policy, { hashes, nonce }) => Object.fromEntries(Object.keys(policy).map((directive) => {
|
|
6
|
+
const value = policy[directive];
|
|
7
|
+
if (!Array.isArray(value)) return [directive, value];
|
|
8
|
+
const sources = value.filter((source) => !checkIsStaticNonce(source)).map((source) => source === "'nonce-{{nonce}}'" && nonce ? `'nonce-${nonce}'` : source).filter((source) => source !== NONCE_PLACEHOLDER);
|
|
9
|
+
if (SCRIPT_DIRECTIVES.has(directive)) sources.push(...hashes.script);
|
|
10
|
+
if (STYLE_DIRECTIVES.has(directive)) sources.push(...hashes.style);
|
|
11
|
+
return [directive, sources];
|
|
12
|
+
}));
|
|
13
|
+
const checkIsStaticNonce = (source) => source.startsWith("'nonce-") && source !== "'nonce-{{nonce}}'";
|
|
14
|
+
//#endregion
|
|
15
|
+
export { updateContentSecurityPolicy };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region 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
|
+
//#endregion
|
|
12
|
+
export { basicAuthOptionsSchema };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region 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
|
+
//#endregion
|
|
14
|
+
export { corsHandlerOptionsSchema };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region 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
|
+
//#endregion
|
|
9
|
+
export { csrfOptionsSchema };
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
//#region 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
|
+
//#endregion
|
|
29
|
+
export { moduleOptionsSchema };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region 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
|
+
//#endregion
|
|
8
|
+
export { rateLimiterOptionsSchema };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/schemas/requestSizeLimiterOptionsSchema.ts
|
|
3
|
+
const requestSizeLimiterOptionsSchema = z.strictObject({
|
|
4
|
+
maxRequestSizeInBytes: z.int("must be a whole number of bytes").positive("must be more than zero bytes"),
|
|
5
|
+
maxUploadFileRequestInBytes: z.int("must be a whole number of bytes").positive("must be more than zero bytes")
|
|
6
|
+
});
|
|
7
|
+
//#endregion
|
|
8
|
+
export { requestSizeLimiterOptionsSchema };
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
//#region 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
|
+
xDownloadOptions: z.union([headerValueSchema, z.literal(false)]).optional(),
|
|
21
|
+
xFrameOptions: z.union([z.enum(XFrameOptions), z.literal(false)]).optional(),
|
|
22
|
+
xPermittedCrossDomainPolicies: z.union([headerValueSchema, z.literal(false)]).optional()
|
|
23
|
+
});
|
|
24
|
+
//#endregion
|
|
25
|
+
export { securityHeadersSchema };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/schemas/ssgOptionsSchema.ts
|
|
3
|
+
const ssgOptionsSchema = z.strictObject({
|
|
4
|
+
exportToPresets: z.boolean(),
|
|
5
|
+
hashScripts: z.boolean(),
|
|
6
|
+
hashStyles: z.boolean(),
|
|
7
|
+
meta: z.boolean(),
|
|
8
|
+
nitroHeaders: z.boolean()
|
|
9
|
+
});
|
|
10
|
+
//#endregion
|
|
11
|
+
export { ssgOptionsSchema };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/schemas/strictTransportSecurityOptionsSchema.ts
|
|
3
|
+
const strictTransportSecurityOptionsSchema = z.strictObject({
|
|
4
|
+
includeSubdomains: z.boolean(),
|
|
5
|
+
maxAge: z.int().nonnegative(),
|
|
6
|
+
preload: z.boolean()
|
|
7
|
+
});
|
|
8
|
+
//#endregion
|
|
9
|
+
export { strictTransportSecurityOptionsSchema };
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
//#region src/services/computeSubresourceIntegrityHashes.ts
|
|
7
|
+
const computeSubresourceIntegrityHashes = async (nitro) => {
|
|
8
|
+
const { baseURL: appBaseUrl, cdnURL: appCdnUrl } = nitro.options.runtimeConfig.app;
|
|
9
|
+
const existingPublicAssets = nitro.options.publicAssets.filter(({ dir: directory }) => existsSync(directory));
|
|
10
|
+
const filesByPublicAsset = await Promise.all(existingPublicAssets.map(async ({ baseURL = "", dir: directory }) => {
|
|
11
|
+
return (await readdir(directory, {
|
|
12
|
+
recursive: true,
|
|
13
|
+
withFileTypes: true
|
|
14
|
+
})).filter((entry) => entry.isFile()).map((entry) => {
|
|
15
|
+
const filePath = join(entry.parentPath, entry.name);
|
|
16
|
+
return {
|
|
17
|
+
filePath,
|
|
18
|
+
fullPath: posix.join(baseURL, ...relative(directory, filePath).split(sep))
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
}));
|
|
22
|
+
const hashEntries = await Promise.all(filesByPublicAsset.flat().map(async ({ filePath, fullPath }) => {
|
|
23
|
+
const hash = await generateHash(await readFile(filePath), SRI_HASH_ALGORITHM);
|
|
24
|
+
return [appCdnUrl ? new URL(posix.isAbsolute(fullPath) ? fullPath.slice(1) : fullPath, appCdnUrl.endsWith("/") ? appCdnUrl : `${appCdnUrl}/`).href : posix.join("/", appBaseUrl, fullPath), hash];
|
|
25
|
+
}));
|
|
26
|
+
return Object.fromEntries(hashEntries);
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { computeSubresourceIntegrityHashes };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/services/formatZodIssues.ts
|
|
2
|
+
const getDeepestPathLength = (leafIssues) => Math.max(-1, ...leafIssues.map(({ path }) => path.length));
|
|
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
|
+
const formatLeafIssue = ({ issue, path }) => {
|
|
12
|
+
return `${path.map(String).join(".") || "(options)"}: ${issue.message}`;
|
|
13
|
+
};
|
|
14
|
+
const formatZodIssues = (issues) => issues.flatMap((issue) => getLeafIssues(issue, [])).map((leafIssue) => formatLeafIssue(leafIssue)).join("; ");
|
|
15
|
+
//#endregion
|
|
16
|
+
export { formatZodIssues };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getDefaultSecurityHeaders } from "./getDefaultSecurityHeaders.js";
|
|
2
|
+
//#region 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: {
|
|
27
|
+
maxRequestSizeInBytes: 2e6,
|
|
28
|
+
maxUploadFileRequestInBytes: 8e6
|
|
29
|
+
},
|
|
30
|
+
sri: true,
|
|
31
|
+
ssg: {
|
|
32
|
+
exportToPresets: true,
|
|
33
|
+
hashScripts: true,
|
|
34
|
+
hashStyles: false,
|
|
35
|
+
meta: true,
|
|
36
|
+
nitroHeaders: true
|
|
37
|
+
},
|
|
38
|
+
strict: isStrict
|
|
39
|
+
});
|
|
40
|
+
//#endregion
|
|
41
|
+
export { getDefaultModuleOptions };
|