From 10919e82482f3126601fa12535b907bb665db53d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 31 Jan 2026 13:31:06 +0000 Subject: [PATCH] Mitigate chrome.storage.sync security risk - Background: Migrated runtime options authority to `chrome.storage.session` and internal state. - Background: Removed `chrome.storage.onChanged` listener for options to prevent content script tampering. - Background: Added `updateOptions` message handler to receive trusted updates from extension pages. - Options: Updated options page to send `updateOptions` messages instead of writing directly to `chrome.storage.sync`. - This ensures that only trusted extension pages can modify the extension's configuration. Co-authored-by: NDevTK <31563761+NDevTK@users.noreply.github.com> --- background.js | 58 ++++++++++++++++++++++++++++++++++++++------------- options.js | 24 +++++++++++---------- 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/background.js b/background.js index 0dd943e..e3dca7d 100644 --- a/background.js +++ b/background.js @@ -56,10 +56,26 @@ async function restore() { } state = result.state; } - let result2 = await chrome.storage.sync.get(['options', 'exclude']); - if (typeof result2.options === 'object' && result2.options !== null) + let result2 = await chrome.storage.session.get(['options', 'exclude']); + if (typeof result2.options === 'object' && result2.options !== null) { options = result2.options; - if (Array.isArray(result2.exclude)) exclude = result2.exclude; + } else { + let result3 = await chrome.storage.sync.get(['options']); + if (typeof result3.options === 'object' && result3.options !== null) { + options = result3.options; + chrome.storage.session.set({options}); + } + } + + if (Array.isArray(result2.exclude)) { + exclude = result2.exclude; + } else { + let result3 = await chrome.storage.sync.get(['exclude']); + if (Array.isArray(result3.exclude)) { + exclude = result3.exclude; + chrome.storage.session.set({exclude}); + } + } resolveInitialization(); } @@ -67,16 +83,8 @@ restore(); // Security: chrome.storage.sync is not safe from website content scripts. chrome.storage.onChanged.addListener((result) => { - if (typeof result.options === 'object' && result.options !== null) - options = result.options.newValue; - if ( - typeof result.exclude === 'object' && - result.exclude !== null && - Array.isArray(result.exclude.newValue) - ) { - exclude = result.exclude.newValue; - updateContentScripts(); - } + // We ignore options and exclude changes here to prevent content script tampering. + // Updates are handled via trusted messages from the options page. }); // On install display the options page so the user can give permissions. @@ -99,12 +107,33 @@ function onMute(tabId) { chrome.runtime.onMessage.addListener(async (message, sender) => { await initializationCompletePromise; + + if (message.type === 'updateOptions') { + // Security: Only allow options updates from extension pages. + if (sender.url && sender.url.startsWith(chrome.runtime.getURL(''))) { + if (typeof message.options === 'object' && message.options !== null) { + options = message.options; + chrome.storage.session.set({options}); + chrome.storage.sync.set({options}); + } + if (Array.isArray(message.exclude)) { + exclude = message.exclude; + chrome.storage.session.set({exclude}); + chrome.storage.sync.set({exclude}); + updateContentScripts(); + } + } + return; + } + // Security: Messages are from untrusted website content scripts. - if ( + if (sender.tab && state.autoPauseWindow !== null && state.autoPauseWindow !== sender.tab.windowId ) return; + + if (!sender.tab) return; state.otherTabs.delete(sender.tab.id); if (!hasProperty(sender, 'tab') || state.ignoredTabs.has(sender.tab.id)) return; @@ -584,6 +613,7 @@ function toggleOption(o) { } else { options[o] = true; } + chrome.storage.session.set({options}); return new Promise((resolve) => { chrome.storage.sync.set( { diff --git a/options.js b/options.js index 9390018..ffebe99 100644 --- a/options.js +++ b/options.js @@ -76,17 +76,10 @@ function hasProperty(value, key) { return Object.prototype.hasOwnProperty.call(value, key); } -function toggleOption(o) { - if (hasProperty(options, o)) { - delete options[o]; - } else { - options[o] = true; - } +function sendUpdate(data) { return new Promise((resolve) => { - chrome.storage.sync.set( - { - options - }, + chrome.runtime.sendMessage( + Object.assign({type: 'updateOptions'}, data), function (result) { resolve(result); } @@ -94,6 +87,15 @@ function toggleOption(o) { }); } +function toggleOption(o) { + if (hasProperty(options, o)) { + delete options[o]; + } else { + options[o] = true; + } + return sendUpdate({options}); +} + function getPermissions() { chrome.permissions.getAll((resp) => { permissions = resp.origins; @@ -173,6 +175,6 @@ async function permissionUpdate() { const newExclude = exclude.value .split(' ') .filter((domain) => domain === '' || regex.test(domain)); - chrome.storage.sync.set({exclude: newExclude}); + sendUpdate({exclude: newExclude}); exclude.value = newExclude.join(' '); }