nuxt-security
Version:
🛡️ Security Module for Nuxt based on HTTP Headers and Middleware
50 lines (49 loc) • 1.9 kB
JavaScript
import { defineNitroPlugin } from "#imports";
import { resolveSecurityRules } from "../context/index.js";
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook("render:html", (response, { event }) => {
if (response.island) {
return;
}
const rules = resolveSecurityRules(event);
if (rules.enabled && rules.headers) {
const headers = rules.headers;
if (headers.contentSecurityPolicy) {
const csp = headers.contentSecurityPolicy;
const nonce = event.context.security?.nonce;
const scriptHashes = event.context.security?.hashes?.script;
const styleHashes = event.context.security?.hashes?.style;
headers.contentSecurityPolicy = updateCspVariables(csp, nonce, scriptHashes, styleHashes);
}
}
});
});
function updateCspVariables(csp, nonce, scriptHashes, styleHashes) {
const generatedCsp = Object.fromEntries(Object.entries(csp).map(([directive, value]) => {
if (typeof value === "boolean") {
return [directive, value];
}
const sources = typeof value === "string" ? value.split(" ").map((token) => token.trim()).filter((token) => token) : value;
const modifiedSources = sources.filter((source) => {
if (source.startsWith("'nonce-") && source !== "'nonce-{{nonce}}'") {
console.warn("[nuxt-security] removing static nonce from CSP header");
return false;
}
return true;
}).map((source) => {
if (source === "'nonce-{{nonce}}'") {
return nonce ? `'nonce-${nonce}'` : "";
} else {
return source;
}
}).filter((source) => source);
if (directive === "script-src" && scriptHashes) {
modifiedSources.push(...scriptHashes);
}
if (directive === "style-src" && styleHashes) {
modifiedSources.push(...styleHashes);
}
return [directive, modifiedSources];
}));
return generatedCsp;
}