From 832a89c4b4be43599c7a4cb53777561a0fb26df0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:48:34 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[HIGH]=20Fi?= =?UTF-8?q?x=20XSS=20vulnerability=20via=20javascript:=20URI=20in=20href?= =?UTF-8?q?=20attributes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added `sanitizeUrl` function to enforce `http://` or `https://` protocols for dynamic URLs injected into `href` attributes, specifically `ex.mediaUrl` and `info.url`, preventing potential XSS attacks via `javascript:` URIs. Co-authored-by: longestmt <1509654+longestmt@users.noreply.github.com> --- .Jules/sentinel.md | 5 +++++ src/pages/exercises.js | 3 ++- src/pages/settings.js | 3 ++- src/utils/sanitize.js | 10 ++++++++++ 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 .Jules/sentinel.md diff --git a/.Jules/sentinel.md b/.Jules/sentinel.md new file mode 100644 index 0000000..05e7c7f --- /dev/null +++ b/.Jules/sentinel.md @@ -0,0 +1,5 @@ + +## 2024-05-01 - Prevent javascript: URI XSS in href Attributes +**Vulnerability:** XSS vulnerability through `javascript:` URI injection in `href` attributes for `ex.mediaUrl` and `info.url` (e.g. ``). +**Learning:** `escapeHTML` mitigates standard XSS but not URI-based XSS when user-controlled URLs are injected directly into `href` without protocol validation, allowing `javascript:alert(1)` to be executed upon clicking. +**Prevention:** Always validate and enforce safe protocols (`http://` or `https://`) for URLs dynamically injected into attributes like `href` or `src` using a function like `sanitizeUrl()`. diff --git a/src/pages/exercises.js b/src/pages/exercises.js index cedd6d0..9da47a6 100644 --- a/src/pages/exercises.js +++ b/src/pages/exercises.js @@ -10,6 +10,7 @@ import { createLineChart } from '../components/charts.js'; import { getExerciseHistory } from '../engine/progression.js'; import { createRMCalculator } from '../components/rm-calculator.js'; import { getSetting } from '../data/db.js'; +import { sanitizeUrl } from '../utils/sanitize.js'; export async function renderExercisesPage(container) { const exercises = await getAll('exercises'); @@ -130,7 +131,7 @@ export async function renderExercisesPage(container) { ${ex.mediaUrl ? ` - + diff --git a/src/pages/settings.js b/src/pages/settings.js index 15390d0..9ae385c 100644 --- a/src/pages/settings.js +++ b/src/pages/settings.js @@ -8,6 +8,7 @@ import { showToast } from '../components/toast.js'; import { STANDARD_PLATES_LB, STANDARD_PLATES_KG } from '../engine/progression.js'; import { getGistToken, setGistToken, validateToken, pushBackup, pullBackup, restoreFromGist, getBackupInfo, disconnectGist } from '../data/gist-backup.js'; import { getWebDavConfig, setWebDavConfig, pushToWebDav, pullFromWebDav, disconnectWebDav } from '../data/webdav.js'; +import { sanitizeUrl } from '../utils/sanitize.js'; export async function renderSettingsPage(container) { const unit = await getSetting('unit', 'lb'); @@ -259,7 +260,7 @@ export async function renderSettingsPage(container) {
● Connected
Last backup: ${lastBackup}
- ${info ? `
View Gist ↗` : ''} + ${info ? `View Gist ↗` : ''}
diff --git a/src/utils/sanitize.js b/src/utils/sanitize.js index 7a6bf91..1de0ad2 100644 --- a/src/utils/sanitize.js +++ b/src/utils/sanitize.js @@ -7,3 +7,13 @@ export function escapeHTML(str) { .replace(/"/g, """) .replace(/'/g, "'"); } + +export function sanitizeUrl(url) { + if (typeof url !== 'string') return '#'; + const trimmed = url.trim(); + if (!trimmed) return '#'; + if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) { + return escapeHTML(trimmed); + } + return '#'; +}