From b6ad7d52b4dc6a51ccdf19b17de35f8db7111a6e Mon Sep 17 00:00:00 2001 From: Chris Zuber Date: Mon, 5 Jan 2026 09:41:00 -0800 Subject: [PATCH] Improve HTML and attribute escaping, update CSP config Refactored dom.js to enhance HTML and attribute escaping, added deprecation warning for escapeAttrVal(), and introduced escapeAttrName(). Updated http.config.js to add script sources and modify Trusted Types policy. Minor HTML entity fix in index.html. --- CHANGELOG.md | 11 +++++++++++ dom.js | 41 +++++++++++++++++++++++++++-------------- http.config.js | 6 +++--- index.html | 2 +- package-lock.json | 12 +++++++++--- package.json | 2 +- 6 files changed, 52 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dce505..a83e4df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v0.2.30] - 2026-01-05 + +### Added +- Add `escapeAttrName()` + +### Changed +- Refactor HTML escaping + +### Deprecated +- Mark `escapeAttrValue()` as deprecated with warning to use `escape()` + ## [v0.2.29] - 2025-12-23 ### Changed diff --git a/dom.js b/dom.js index 17070cf..fa00154 100644 --- a/dom.js +++ b/dom.js @@ -1,11 +1,30 @@ import { attachListeners } from '@aegisjsproject/callback-registry/events.js'; - -const ESCAPED_PATTERN = /&(?![a-zA-Z\d]{2,5};|#\d{1,3};)/g; - -export const escapeAttrVal = str => str.toString() - // Do not double-escape - .replaceAll(ESCAPED_PATTERN, '&') - .replaceAll('"', '"'); +export const HTML_UNSAFE_PATTERN = /[<>"']|&(?![a-zA-Z\d]{2,5};|#\d{1,3};)/g; +/*eslint no-control-regex: "off"*/ +export const ATTR_NAME_UNSAFE_PATTERN = /[\u0000-\u001f\u007f-\u009f\s"'\\/=><&]/g; +export const HTML_REPLACEMENTS = Object.freeze({ + '&': '&', + '"': '"', + '\'': ''', + '<': '<', + '>': '>', +}); + +export const escape = str => (str?.toString?.() ?? '') + .replaceAll(HTML_UNSAFE_PATTERN, char => HTML_REPLACEMENTS[char]); + +/** + * + * @param {@deprecated} str + * @returns {string} + */ +export const escapeAttrVal = str => { + console.warn('`escapeAttrVal()` is deprecated. Please use `escape()` instead.'); + return escape(str); +}; + +export const escapeAttrName = str => (str?.toString?.() ?? '') + .replace(ATTR_NAME_UNSAFE_PATTERN, char => '_' + char.charCodeAt(0).toString(16).padStart(4, '0') + '_'); export function createAttribute(name, value = '', namespace) { const attr = typeof namespace === 'string' @@ -17,13 +36,7 @@ export function createAttribute(name, value = '', namespace) { return attr; } -export const stringifyAttr = attr => `${attr.name}="${escapeAttrVal(attr.value)}"`; - -export const escape = str => str.toString() - .replaceAll(ESCAPED_PATTERN, '&') - .replaceAll('<', '<') - .replaceAll('>', '>') - .replaceAll('"', '"'); +export const stringifyAttr = attr => `${escapeAttrName(attr?.name)}="${escape(attr?.value)}"`; export const getUniqueSelector = (prefix = '_aegis-scope') => `${prefix}-${crypto.randomUUID()}`; diff --git a/http.config.js b/http.config.js index 25ae412..cf85a6f 100644 --- a/http.config.js +++ b/http.config.js @@ -1,7 +1,7 @@ -import { useDefaultCSP, addConnectSrc, addTrustedTypePolicy, lockCSP } from '@aegisjsproject/http-utils/csp.js'; - +import { useDefaultCSP, addScriptSrc, addConnectSrc, addTrustedTypePolicy, lockCSP } from '@aegisjsproject/http-utils/csp.js'; +addScriptSrc('https://unpkg.com/@shgysk8zer0/', 'https://unpkg.com/@aegisjsproject/'); addConnectSrc('https://icanhazdadjoke.com/'); -addTrustedTypePolicy('aegis-router#html', 'default'); +addTrustedTypePolicy('aegis-router#html', 'aegis-sanitizer#html', 'default'); lockCSP(); export default { diff --git a/index.html b/index.html index abb65ee..7b5f65e 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@ diff --git a/package-lock.json b/package-lock.json index 36dadeb..9bd423a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@aegisjsproject/core", - "version": "0.2.29", + "version": "0.2.30", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@aegisjsproject/core", - "version": "0.2.29", + "version": "0.2.30", "funding": [ { "type": "librepay", @@ -1206,6 +1206,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1403,6 +1404,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2053,6 +2055,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -2871,7 +2874,8 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -3007,6 +3011,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3465,6 +3470,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", "dev": true, + "peer": true, "requires": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", diff --git a/package.json b/package.json index 5d67663..2c2981f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aegisjsproject/core", - "version": "0.2.29", + "version": "0.2.30", "description": "A fast, secure, modern, light-weight, and simple JS library for creating web components and more!", "keywords": [ "aegis",