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. 👍
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
textContentoverinnerHTML, 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–1856The
restore()function parses a user-provided JSON file and writes every key-value pair directly intolocalStoragewithout any validation: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:
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 ShortcutsFiles:
src/modules/keyboard.js— Line 90src/modules/shortcuts.js— Line 52src/modules/settings.js— Lines 1789–1809The keyboard shortcut launcher sets
window.location.hrefdirectly from the stored URL:While
addShortcut()prependshttps://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. Ajavascript:URL would execute script in the extension's origin.Suggested fix — centralized URL sanitizer:
Then use it in:
addShortcut()andupdateShortcut()insettings.jslaunchShortcut()inkeyboard.jsrender()inshortcuts.jswhen settinglink.hrefrestore()function for all URL fields inuserShortcuts3. CSS Injection via Unsanitized Background URL
File:
src/modules/settings.js— Lines 406, 419, 1650, 1869Background URLs from state are interpolated directly into
style.backgroundImage:A crafted value like
x); color: red; background-image: url(ycould inject arbitrary CSS properties via a manipulated backup or direct localStorage edit.Suggested fix:
Alternatively, wrapping the URL in quotes inside the template literal already mitigates most injection vectors:
Low Severity / Informational
4.
web_accessible_resourcesMatches<all_urls>— Enables Extension FingerprintingFiles:
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_resourcesblock entirely, or restrictingmatchesto 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.htmlThe extension communicates with several external services not mentioned in the privacy policy:
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. 👍