diff --git a/package.json b/package.json index 578f37a..f3ca19c 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "test:pwa": "node scripts/validate-pwa.mjs", "test:modules": "node scripts/validate-modules.mjs", "test:health": "node scripts/validate-health.mjs", - "test": "npm run test:pwa && npm run test:modules && npm run test:health" + "test": "npm run test:pwa && npm run test:modules && npm run test:health && node scripts/validate-escape-html.mjs" }, "dependencies": { "express": "^4.22.2", diff --git a/scripts/validate-escape-html.mjs b/scripts/validate-escape-html.mjs new file mode 100644 index 0000000..fad5253 --- /dev/null +++ b/scripts/validate-escape-html.mjs @@ -0,0 +1,17 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { escapeHTML } from '../src/html-utils.js'; + +const source = readFileSync('src/app.js', 'utf8'); + +assert.match(source, /import\s+\{\s*escapeHTML\s*\}\s+from\s+['"]\.\/html-utils\.js['"]/); +assert.equal( + escapeHTML(` & waste`), + '<img src=x onerror="alert('xss')"> & waste', +); +assert.equal(escapeHTML(null), ''); +assert.equal(escapeHTML(undefined), ''); +assert.equal(escapeHTML(42), '42'); +assert.equal(escapeHTML('Safe Provider'), 'Safe Provider'); + +console.log('escapeHTML regression check passed'); diff --git a/src/app.js b/src/app.js index 5883ebf..d7a010a 100644 --- a/src/app.js +++ b/src/app.js @@ -12,6 +12,7 @@ import { ReGenXRealtime } from './realtime-sync.js'; import { CloudSync } from './cloud-sync.js'; import { ESGReporter } from './esg-reporter.js'; import { AccessibilityManager } from './accessibility.js'; +import { escapeHTML } from './html-utils.js'; const STORAGE_KEY_PREFIX = "regenx-v3:"; const TRUST_LEDGER_KEY = STORAGE_KEY_PREFIX + "trust-ledger"; const ESG_ALERTS_KEY = STORAGE_KEY_PREFIX + "esg-alerts"; @@ -5351,4 +5352,4 @@ if ('serviceWorker' in navigator) { console.error('Service Worker registration failed:', error); } }); -} \ No newline at end of file +} diff --git a/src/html-utils.js b/src/html-utils.js new file mode 100644 index 0000000..36d250b --- /dev/null +++ b/src/html-utils.js @@ -0,0 +1,16 @@ +const HTML_ESCAPE_MAP = Object.freeze({ + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', +}); + +/** + * Escapes user-provided values before interpolating them into HTML strings. + * @param {unknown} value + * @returns {string} + */ +export function escapeHTML(value) { + return String(value ?? '').replace(/[&<>"']/g, (char) => HTML_ESCAPE_MAP[char]); +}