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) {