From f717529bc3f24b733bb80e4a95845dc090dc91e4 Mon Sep 17 00:00:00 2001 From: Chris Zuber Date: Tue, 9 Dec 2025 16:13:15 -0800 Subject: [PATCH] Refactor and enhance CSP configuration Refactored csp.js to use named constants for CSP directives, added support for additional directives (base-uri, form-action, frame-ancestors), and improved flexibility for trusted types. Updated http.config.js to dynamically add script sources and trusted type policies, and to use the new CSP configuration in response postprocessors. --- CHANGELOG.md | 6 +++++ csp.js | 66 ++++++++++++++++++++++++++++++++++------------- http.config.js | 6 ++++- package-lock.json | 4 +-- package.json | 2 +- 5 files changed, 62 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da79c77..955ddbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v1.0.4] - 2025-12-09 + +### Added +- Add more CSP directives +- Add CSP value constants (eg `'self'`) + ## [v1.0.3] - 2025-12-09 ### Added diff --git a/csp.js b/csp.js index 1488f38..7aa57f3 100644 --- a/csp.js +++ b/csp.js @@ -1,22 +1,40 @@ -import { Importmap } from '@shgysk8zer0/importmap'; +import { Importmap, imports, scopes } from '@shgysk8zer0/importmap'; -export const importmap = new Importmap(); +export const importmap = new Importmap({ imports, scopes }); await importmap.importLocalPackage(); export const integrity = await importmap.getIntegrity(); -const DEFAULT_SRC = ['\'self\'']; -const SCRIPT_SRC = ['\'self\'', 'https://unpkg.com/@shgysk8zer0/', 'https://unpkg.com/@kernvalley/', 'https://unpkg.com/@aegisjsproject/', `'${integrity}'`]; -const STYLE_SRC = ['\'self\'', 'https://unpkg.com/@agisjsproject/', 'blob:']; -const IMAGE_SRC = ['\'self\'', 'https://i.imgur.com/', 'https://secure.gravatar.com/avatar/', 'blob:', 'data:']; -const MEDIA_SRC = ['\'self\'', 'blob:']; -const CONNECT_SRC = ['\'self\'']; -const FONT_SRC = ['\'self\'']; -const FRAME_SRC = ['\'self\'', 'https://www.youtube-nocookie.com']; -const MANIFEST_SRC = ['\'self\'']; -const PREFETCH_SRC = ['\'self\'']; -const WORKER_SRC = ['\'selfs\'']; +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy +export const SELF = '\'self\''; +export const NONE = '\'none\''; +export const UNSAFE_EVAL = '\'unsafe-eval\''; +export const UNSAFE_INLINE = '\'unsafe-inline\''; +export const TRUSTED_TYPES_EVAL = '\'trusted-types-eval\''; +export const UNSAFE_HASHES = '\'unsafe-hashes\''; +export const WASM_UNSAFE_EVAL = '\'wasm-unsafe-eval\''; +export const INLINE_SPECULATION_RULES = '\'inline-speculation-rules\''; +export const STRICT_DYNAMIC = '\'strict-dynamic\''; +export const REPORT_SAMPLE = '\'report-sample\''; +export const SCRIPT = '\'script\''; + +let TRUSTED_TYPES_ENABLED = true; + +const BASE_URIS = [SELF]; +const FORM_ACTIONS = [SELF]; +const FRAME_ANCESTORS = []; +const DEFAULT_SRC = []; +const SCRIPT_SRC = [SELF, `'${integrity}'`]; +const STYLE_SRC = [SELF]; +const IMAGE_SRC = [SELF]; +const MEDIA_SRC = [SELF]; +const CONNECT_SRC = [SELF]; +const FONT_SRC = [SELF]; +const FRAME_SRC = [SELF]; +const MANIFEST_SRC = [SELF]; +const PREFETCH_SRC = [SELF]; +const WORKER_SRC = [SELF]; const OBJECT_SRC = []; -const TRUSTED_TYPES = ['aegis-sanitizer#html']; +const TRUSTED_TYPES = []; export const lockCSP = () => { Object.freeze(DEFAULT_SRC); @@ -32,6 +50,9 @@ export const lockCSP = () => { Object.freeze(WORKER_SRC); Object.freeze(OBJECT_SRC); Object.freeze(TRUSTED_TYPES); + Object.freeze(BASE_URIS); + Object.freeze(FORM_ACTIONS); + Object.freeze(FRAME_ANCESTORS); }; export const addDefaultSrc = (...srcs) => DEFAULT_SRC.push(...srcs); @@ -47,10 +68,16 @@ export const addObjectSrc = (...srcs) => OBJECT_SRC.push(...srcs); export const addPrefetchSrc = (...srcs) => PREFETCH_SRC.push(...srcs); export const addWorkerSrc = (...srcs) => WORKER_SRC.push(...srcs); export const addTrustedTypePolicy = (...policies) => TRUSTED_TYPES.push(...policies); +export const addBaseURI = (...bases) => BASE_URIS.push(...bases); +export const addFormAction = (...actions) => FORM_ACTIONS.push(...actions); +export const disabledTrustedTypes = () => TRUSTED_TYPES_ENABLED = false; +export const enableTrustedTypes = () => TRUSTED_TYPES_ENABLED = true; -export function useCSP(policy = { 'default-src': ['\'self\''] }) { +export function useCSP(policy = { 'default-src': [SELF] }) { const policyStr = Object.entries(policy).map(([name, values]) => { - return `${name} ${Array.isArray(values) ? values.length === 0 ? '\'none\'' : values.join(' ') : values}`; + return `${name} ${Array.isArray(values) + ? values.length === 0 ? NONE : values.join(' ') + : values ?? NONE}`; }).join('; '); /** @@ -79,11 +106,14 @@ export const useDefaultCSP = ({ ...rest } = {}) => useCSP({ 'frame-src': FRAME_SRC, 'connect-src': CONNECT_SRC, 'manifest-src': MANIFEST_SRC, - 'obejct-src': OBJECT_SRC, + 'object-src': OBJECT_SRC, 'prefetch-src': PREFETCH_SRC, 'worker-src': WORKER_SRC, 'trusted-types': TRUSTED_TYPES, - 'require-trusted-types-for': '\'script\'', + 'base-uri': BASE_URIS, + 'form-action': FORM_ACTIONS, + 'frame-ancestors': FRAME_ANCESTORS, + 'require-trusted-types-for': TRUSTED_TYPES_ENABLED ? SCRIPT : NONE, ...rest }); diff --git a/http.config.js b/http.config.js index 12b0bec..7b21f45 100644 --- a/http.config.js +++ b/http.config.js @@ -1,6 +1,10 @@ import { useRateLimit } from './rate-limit.js'; import { checkCacheItem, setCacheItem } from './cache.js'; import { imports } from '@shgysk8zer0/importmap'; +import { addTrustedTypePolicy, addScriptSrc, useDefaultCSP } from './csp.js'; + +addScriptSrc(imports['@shgysk8zer0/polyfills']); +addTrustedTypePolicy('aegis-sanitizer#html'); const visits = new Map(); @@ -24,7 +28,7 @@ export default { responsePostprocessors: [ '@aegisjsproject/http-utils/compression.js', '@aegisjsproject/http-utils/cors.js', - '@aegisjsproject/http-utils/csp.js', + useDefaultCSP(), (response, { request }) => { if (request.destination === 'document') { response.headers.append('Link', `<${imports['@shgysk8zer0/polyfills']}>; rel="preload"; as="script"; fetchpriority="high"; crossorigin="anonymous"; referrerpolicy="no-referrer"`); diff --git a/package-lock.json b/package-lock.json index 4e872b5..8d5de68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@aegisjsproject/http-utils", - "version": "1.0.3", + "version": "1.0.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@aegisjsproject/http-utils", - "version": "1.0.3", + "version": "1.0.4", "funding": [ { "type": "librepay", diff --git a/package.json b/package.json index cd7e7bf..45063f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aegisjsproject/http-utils", - "version": "1.0.3", + "version": "1.0.4", "description": "HTTP Utilities for @shgysk8zer0/http-server ", "keywords": [], "type": "module",