| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- // src/middleware/secure-headers/secure-headers.ts
- import { encodeBase64 } from "../../utils/encode.js";
- var HEADERS_MAP = {
- crossOriginEmbedderPolicy: ["Cross-Origin-Embedder-Policy", "require-corp"],
- crossOriginResourcePolicy: ["Cross-Origin-Resource-Policy", "same-origin"],
- crossOriginOpenerPolicy: ["Cross-Origin-Opener-Policy", "same-origin"],
- originAgentCluster: ["Origin-Agent-Cluster", "?1"],
- referrerPolicy: ["Referrer-Policy", "no-referrer"],
- strictTransportSecurity: ["Strict-Transport-Security", "max-age=15552000; includeSubDomains"],
- xContentTypeOptions: ["X-Content-Type-Options", "nosniff"],
- xDnsPrefetchControl: ["X-DNS-Prefetch-Control", "off"],
- xDownloadOptions: ["X-Download-Options", "noopen"],
- xFrameOptions: ["X-Frame-Options", "SAMEORIGIN"],
- xPermittedCrossDomainPolicies: ["X-Permitted-Cross-Domain-Policies", "none"],
- xXssProtection: ["X-XSS-Protection", "0"]
- };
- var DEFAULT_OPTIONS = {
- crossOriginEmbedderPolicy: false,
- crossOriginResourcePolicy: true,
- crossOriginOpenerPolicy: true,
- originAgentCluster: true,
- referrerPolicy: true,
- strictTransportSecurity: true,
- xContentTypeOptions: true,
- xDnsPrefetchControl: true,
- xDownloadOptions: true,
- xFrameOptions: true,
- xPermittedCrossDomainPolicies: true,
- xXssProtection: true,
- removePoweredBy: true,
- permissionsPolicy: {}
- };
- var generateNonce = () => {
- const arrayBuffer = new Uint8Array(16);
- crypto.getRandomValues(arrayBuffer);
- return encodeBase64(arrayBuffer.buffer);
- };
- var NONCE = (ctx) => {
- const key = "secureHeadersNonce";
- const init = ctx.get(key);
- const nonce = init || generateNonce();
- if (init == null) {
- ctx.set(key, nonce);
- }
- return `'nonce-${nonce}'`;
- };
- var secureHeaders = (customOptions) => {
- const options = { ...DEFAULT_OPTIONS, ...customOptions };
- const headersToSet = getFilteredHeaders(options);
- const callbacks = [];
- if (options.contentSecurityPolicy) {
- const [callback, value] = getCSPDirectives(options.contentSecurityPolicy);
- if (callback) {
- callbacks.push(callback);
- }
- headersToSet.push(["Content-Security-Policy", value]);
- }
- if (options.contentSecurityPolicyReportOnly) {
- const [callback, value] = getCSPDirectives(options.contentSecurityPolicyReportOnly);
- if (callback) {
- callbacks.push(callback);
- }
- headersToSet.push(["Content-Security-Policy-Report-Only", value]);
- }
- if (options.permissionsPolicy && Object.keys(options.permissionsPolicy).length > 0) {
- headersToSet.push([
- "Permissions-Policy",
- getPermissionsPolicyDirectives(options.permissionsPolicy)
- ]);
- }
- if (options.reportingEndpoints) {
- headersToSet.push(["Reporting-Endpoints", getReportingEndpoints(options.reportingEndpoints)]);
- }
- if (options.reportTo) {
- headersToSet.push(["Report-To", getReportToOptions(options.reportTo)]);
- }
- return async function secureHeaders2(ctx, next) {
- const headersToSetForReq = callbacks.length === 0 ? headersToSet : callbacks.reduce((acc, cb) => cb(ctx, acc), headersToSet);
- await next();
- setHeaders(ctx, headersToSetForReq);
- if (options?.removePoweredBy) {
- ctx.res.headers.delete("X-Powered-By");
- }
- };
- };
- function getFilteredHeaders(options) {
- return Object.entries(HEADERS_MAP).filter(([key]) => options[key]).map(([key, defaultValue]) => {
- const overrideValue = options[key];
- return typeof overrideValue === "string" ? [defaultValue[0], overrideValue] : defaultValue;
- });
- }
- function getCSPDirectives(contentSecurityPolicy) {
- const callbacks = [];
- const resultValues = [];
- for (const [directive, value] of Object.entries(contentSecurityPolicy)) {
- const valueArray = Array.isArray(value) ? value : [value];
- valueArray.forEach((value2, i) => {
- if (typeof value2 === "function") {
- const index = i * 2 + 2 + resultValues.length;
- callbacks.push((ctx, values) => {
- values[index] = value2(ctx, directive);
- });
- }
- });
- resultValues.push(
- directive.replace(
- /[A-Z]+(?![a-z])|[A-Z]/g,
- (match, offset) => offset ? "-" + match.toLowerCase() : match.toLowerCase()
- ),
- ...valueArray.flatMap((value2) => [" ", value2]),
- "; "
- );
- }
- resultValues.pop();
- return callbacks.length === 0 ? [void 0, resultValues.join("")] : [
- (ctx, headersToSet) => headersToSet.map((values) => {
- if (values[0] === "Content-Security-Policy" || values[0] === "Content-Security-Policy-Report-Only") {
- const clone = values[1].slice();
- callbacks.forEach((cb) => {
- cb(ctx, clone);
- });
- return [values[0], clone.join("")];
- } else {
- return values;
- }
- }),
- resultValues
- ];
- }
- function getPermissionsPolicyDirectives(policy) {
- return Object.entries(policy).map(([directive, value]) => {
- const kebabDirective = camelToKebab(directive);
- if (typeof value === "boolean") {
- return `${kebabDirective}=${value ? "*" : "none"}`;
- }
- if (Array.isArray(value)) {
- if (value.length === 0) {
- return `${kebabDirective}=()`;
- }
- if (value.length === 1 && (value[0] === "*" || value[0] === "none")) {
- return `${kebabDirective}=${value[0]}`;
- }
- const allowlist = value.map((item) => ["self", "src"].includes(item) ? item : `"${item}"`);
- return `${kebabDirective}=(${allowlist.join(" ")})`;
- }
- return "";
- }).filter(Boolean).join(", ");
- }
- function camelToKebab(str) {
- return str.replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
- }
- function getReportingEndpoints(reportingEndpoints = []) {
- return reportingEndpoints.map((endpoint) => `${endpoint.name}="${endpoint.url}"`).join(", ");
- }
- function getReportToOptions(reportTo = []) {
- return reportTo.map((option) => JSON.stringify(option)).join(", ");
- }
- function setHeaders(ctx, headersToSet) {
- headersToSet.forEach(([header, value]) => {
- ctx.res.headers.set(header, value);
- });
- }
- export {
- NONCE,
- secureHeaders
- };
|