Skip to content

Security Audit: Input Validation & Privacy Gaps in v2.1.0 #2

Description

@CheswickDEV

Security Audit: Input Validation & Privacy Gaps in v2.1.0

Hi @xtditom,

I ran a full security review of YourDynamicDashboard v2.1.0 and wanted to share my findings. Overall the codebase is well-built — consistent use of textContent over innerHTML, minimal permissions, and a solid GPS consent flow. That said, I found a few issues worth addressing.


Medium Severity

1. Backup Restore Writes Arbitrary Data to localStorage Without Validation

File: src/modules/settings.js — Lines 1840–1856

The restore() function parses a user-provided JSON file and writes every key-value pair directly into localStorage without any validation:

const data = JSON.parse(event.target.result);
Object.keys(data).forEach((key) =>
  localStorage.setItem(key, data[key]),
);
location.reload();

A crafted backup file can inject arbitrary state — including malicious URLs in userShortcuts, oversized values that exhaust the storage quota, or manipulated settings that alter extension behavior.

Suggested fix:

const ALLOWED_KEYS = new Set([
  'todos', 'userShortcuts', 'searchProvider', 'darkMode', 'clockFormat',
  'clockType', 'tempUnit', 'quotePosition', 'showTodo', 'showApps',
  'showShortcuts', 'showAiTools', 'hiddenTools', 'yd_city', 'yd_lat',
  'yd_lon', 'backgroundImage', 'savedBgUrl', 'randomBgMode', 'keyMap',
  'linkTargets', 'normalThemeId', 'gradientThemeId', 'gradientModeActive',
  'transparencyActive', 'userSavedThemes', 'userName', 'welcomeText',
  'autoTheme', 'glowEffect', 'tempDisplayMode', 'showDate',
  'aiToolsOrder', 'socialToolsOrder', 'googleAppsOrder',
  // ... add any other legitimate keys
]);

restore(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const data = JSON.parse(event.target.result);
Object.keys(data).forEach((key) => {
if (ALLOWED_KEYS.has(key)) {
localStorage.setItem(key, data[key]);
}
});
location.reload();
} catch (err) {
showCustomModal("Invalid Backup File");
}
};
reader.readAsText(file);
}

Additionally, after restoring, any URL fields (especially in userShortcuts) should be validated against an allowlist of safe schemes (see next finding).


2. javascript: URI Scheme Not Blocked in Shortcuts

Files:

  • src/modules/keyboard.js — Line 90
  • src/modules/shortcuts.js — Line 52
  • src/modules/settings.js — Lines 1789–1809

The keyboard shortcut launcher sets window.location.href directly from the stored URL:

// keyboard.js
window.location.href = shortcuts[index].url;

While addShortcut() prepends https:// when no protocol is present, this check is bypassed when URLs are injected via a manipulated backup file (see finding #1) or direct localStorage manipulation. A javascript: URL would execute script in the extension's origin.

Suggested fix — centralized URL sanitizer:

// Add to utils.js
export function sanitizeUrl(url) {
  try {
    const parsed = new URL(url);
    if (!['http:', 'https:'].includes(parsed.protocol)) {
      return 'https://example.com'; // fallback
    }
    return url;
  } catch {
    return 'https://example.com';
  }
}

Then use it in:

  • addShortcut() and updateShortcut() in settings.js
  • launchShortcut() in keyboard.js
  • render() in shortcuts.js when setting link.href
  • The restore() function for all URL fields in userShortcuts

3. CSS Injection via Unsanitized Background URL

File: src/modules/settings.js — Lines 406, 419, 1650, 1869

Background URLs from state are interpolated directly into style.backgroundImage:

document.body.style.backgroundImage = `url(${state.get("savedBgUrl")})`;

A crafted value like x); color: red; background-image: url(y could inject arbitrary CSS properties via a manipulated backup or direct localStorage edit.

Suggested fix:

function applyBackgroundUrl(url) {
  if (!url) return;
  // Allow data:image/* URLs (for uploaded backgrounds) and valid https URLs
  const isDataUrl = /^data:image\/(jpeg|png|gif|webp|svg\+xml);base64,/.test(url);
  const isHttpsUrl = /^https:\/\//.test(url);
  if (isDataUrl || isHttpsUrl) {
    document.body.style.backgroundImage = `url("${CSS.escape ? url : encodeURI(url)}")`;
  }
}

Alternatively, wrapping the URL in quotes inside the template literal already mitigates most injection vectors:

document.body.style.backgroundImage = `url("${url}")`;

Low Severity / Informational

4. web_accessible_resources Matches <all_urls> — Enables Extension Fingerprinting

Files: manifest.json (Lines 17–28), firefox-manifest.json (Lines 26–37)

All image assets are accessible to every website via "matches": ["<all_urls>"]. Any site can probe for these resources to detect whether this extension is installed (browser fingerprinting).

Since the extension only runs on the new tab page, these resources don't need to be web-accessible at all. Consider removing the web_accessible_resources block entirely, or restricting matches to the extension's own origin.

5. Incomplete Privacy Policy — Missing Third-Party Disclosures

Files: privacy-policy.html, src/modules/settings.js, src/utils.js, index.html

The extension communicates with several external services not mentioned in the privacy policy:

Service Data Sent Disclosed?
Open-Meteo GPS coordinates ✅ Yes
BigDataCloud GPS coordinates ✅ Yes
Google Fonts IP address, User-Agent ❌ No
picsum.photos IP address, screen resolution (screen.width * devicePixelRatio) ❌ No
Google Favicons Domains of user shortcuts ❌ No

Findings 1–3 can all be addressed with a single sanitizeUrl() utility function plus a key whitelist in the restore logic. No remote exploit is possible without user interaction (social engineering via a crafted backup file), but hardening these paths would be good defense-in-depth.

Great work on the extension overall — the consistent DOM API usage over innerHTML, minimal permissions, and the GPS consent modal are all really solid security practices. 👍

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions