UNPKG

nuxt-security

Version:

🛡️ Security Module for Nuxt based on HTTP Headers and Middleware

73 lines (72 loc) 3.04 kB
import { defineNitroPlugin } from "#imports"; import { resolveSecurityRules } from "../context/index.js"; import { generateHash } from "../../../utils/crypto"; const INLINE_SCRIPT_RE = /<script(?![^>]*?\bsrc="[\w:.\-\\/]+")[^>]*>([\s\S]*?)<\/script>/gi; const STYLE_RE = /<style[^>]*>([\s\S]*?)<\/style>/gi; const SCRIPT_RE = /<script(?=[^>]+\bsrc="[^"]+")(?=[^>]+\bintegrity="([\w\-+/=]+)")[^>]+(?:\/>|><\/script[^>]*?>)/gi; const LINK_RE = /<link(?=[^>]+\brel="(stylesheet|preload|modulepreload)")(?=[^>]+\bintegrity="([\w\-+/=]+)")(?=(?:[^>]+\bas="(\w+)")?)[^>]+>/gi; export default defineNitroPlugin((nitroApp) => { if (!import.meta.prerender) { return; } nitroApp.hooks.hook("render:html", async (html, { event }) => { const rules = resolveSecurityRules(event); if (!rules.enabled || !rules.headers || !rules.headers.contentSecurityPolicy) { return; } event.context.security.hashes = { script: /* @__PURE__ */ new Set(), style: /* @__PURE__ */ new Set() }; const scriptHashes = event.context.security.hashes.script; const styleHashes = event.context.security.hashes.style; const hashAlgorithm = "SHA-256"; if (rules.ssg) { const { hashScripts, hashStyles } = rules.ssg; const sections = ["body", "bodyAppend", "bodyPrepend", "head"]; for (const section of sections) { for (const element of html[section]) { if (hashScripts) { const inlineScriptMatches = element.matchAll(INLINE_SCRIPT_RE); for (const [, scriptText] of inlineScriptMatches) { const hash = await generateHash(scriptText, hashAlgorithm); scriptHashes.add(`'${hash}'`); } const externalScriptMatches = element.matchAll(SCRIPT_RE); for (const [, integrity] of externalScriptMatches) { scriptHashes.add(`'${integrity}'`); } } if (hashStyles) { const styleMatches = element.matchAll(STYLE_RE); for (const [, styleText] of styleMatches) { const hash = await generateHash(styleText, hashAlgorithm); styleHashes.add(`'${hash}'`); } } const linkMatches = element.matchAll(LINK_RE); for (const [, rel, integrity, as] of linkMatches) { if (integrity) { if (rel === "stylesheet" && hashStyles) { styleHashes.add(`'${integrity}'`); } else if (rel === "preload" && hashScripts) { switch (as) { case "script": case "audioworklet": case "paintworklet": case "xlst": scriptHashes.add(`'${integrity}'`); break; default: break; } } else if (rel === "modulepreload" && hashScripts) { scriptHashes.add(`'${integrity}'`); } } } } } } }); });