Cross-tab identity leak protection (tor-browser#41071)
This commit is contained in:
parent
c052ea2a18
commit
dd4042f10f
|
@ -706,5 +706,33 @@
|
|||
},
|
||||
"DonateLong": {
|
||||
"message": "NoScript is Free Software and can't exist without your help. Please donate now!"
|
||||
},
|
||||
|
||||
"TabGuard_label": {
|
||||
"message": "Cross-tab identity leak protection"
|
||||
},
|
||||
"TabGuard_optEnabled": {
|
||||
"message": "Enabled everywhere"
|
||||
},
|
||||
"TabGuard_optIncognito": {
|
||||
"message": "Enabled in Private Browsing only"
|
||||
},
|
||||
"TabGuard_optDisabled": {
|
||||
"message": "Disabled"
|
||||
},
|
||||
"TabGuard_forget": {
|
||||
"message": "Forget decisions."
|
||||
},
|
||||
"TabGuard_title": {
|
||||
"message": "Potential Identity Leak"
|
||||
},
|
||||
"TabGuard_message": {
|
||||
"message": "You are about to load a page from $1.\nIf you are a $1 logged-in user, information about your identity might be acquired by $2."
|
||||
},
|
||||
"TabGuard_optAnonymize": {
|
||||
"message": "Load anonymously"
|
||||
},
|
||||
"TabGuard_optAllow": {
|
||||
"message": "Load normally"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ var Defaults = {
|
|||
sync: {
|
||||
global: false,
|
||||
xss: true,
|
||||
TabGuardMode: "incognito",
|
||||
cascadeRestrictions : false,
|
||||
overrideTorBrowserPolicy: false, // note: Settings.update() on reset will flip this to true
|
||||
}
|
||||
|
|
|
@ -550,10 +550,16 @@ var RequestGuard = (() => {
|
|||
}
|
||||
return ALLOW;
|
||||
},
|
||||
|
||||
onBeforeSendHeaders(request) {
|
||||
normalizeRequest(request);
|
||||
return checkLANRequest(request);
|
||||
let lanRes = checkLANRequest(request);
|
||||
if (!UA.isMozilla) return lanRes; // Chromium doesn't support async blocking suspension, stop here
|
||||
if (lanRes === ABORT) return ABORT;
|
||||
let chainNext = r => r === ABORT ? r : TabGuard.check(request);
|
||||
return lanRes instanceof Promise ? lanRes.then(chainNext) : chainNext(lanRes);
|
||||
},
|
||||
|
||||
onHeadersReceived(request) {
|
||||
// called for main_frame, sub_frame and object
|
||||
|
||||
|
@ -754,7 +760,7 @@ var RequestGuard = (() => {
|
|||
let filterDocs = {urls: allUrls, types: docTypes};
|
||||
let filterAll = {urls: allUrls};
|
||||
listen("onBeforeRequest", filterAll, ["blocking"]);
|
||||
listen("onBeforeSendHeaders", filterAll, ["blocking"]);
|
||||
listen("onBeforeSendHeaders", filterAll, ["blocking", "requestHeaders"]);
|
||||
|
||||
let mergingCSP = "getBrowserInfo" in browser.runtime;
|
||||
if (mergingCSP) {
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* NoScript - a Firefox extension for whitelist driven safe JavaScript execution
|
||||
*
|
||||
* Copyright (C) 2005-2022 Giorgio Maone <https://maone.net>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
var TabGuard = (() => {
|
||||
(async () => { await include(["/nscl/service/TabCache.js", "/nscl/service/TabTies.js"]); })();
|
||||
|
||||
let allowedGroups, filteredGroups;
|
||||
let forget = () => {
|
||||
allowedGroups = {};
|
||||
filteredGroups = {};
|
||||
};
|
||||
forget();
|
||||
|
||||
const AUTH_HEADERS_RX = /^(?:authorization|cookie)/i;
|
||||
|
||||
function getDomain(u) {
|
||||
let {url} = Sites.parse(u);
|
||||
return url && url.protocol.startsWith("http") && tld.getDomain(url.hostname);
|
||||
}
|
||||
|
||||
return {
|
||||
forget,
|
||||
check(request) {
|
||||
const mode = ns.sync.TabGuardMode;
|
||||
if (mode === "off" || !request.incognito && mode!== "global") return;
|
||||
|
||||
const {tabId, type, url} = request;
|
||||
|
||||
if (tabId < 0) return; // no tab, no party
|
||||
|
||||
let targetDomain = getDomain(url);
|
||||
if (!targetDomain) return; // no domain, no cookies
|
||||
|
||||
const mainFrame = type === "main_frame";
|
||||
let tabDomain = getDomain(mainFrame ? url : TabCache.get(tabId).url);
|
||||
if (!tabDomain) return; // no domain, no cookies
|
||||
|
||||
let ties = TabTies.get(tabId);
|
||||
if (ties.size === 0) return; // no ties, no party
|
||||
|
||||
let legitDomains = allowedGroups[tabDomain] || new Set([tabDomain]);
|
||||
|
||||
let otherDomains = new Set([...ties].map(id => getDomain(TabCache.get(id).url)).filter(d => !legitDomains.has(d)));
|
||||
if (otherDomains.size === 0) return; // no cross-site ties, no party
|
||||
|
||||
let {requestHeaders} = request;
|
||||
|
||||
if (!requestHeaders.some(h => AUTH_HEADERS_RX.test(h.name))) return; // no auth, no party
|
||||
|
||||
// danger zone
|
||||
|
||||
let filterAuth = () => {
|
||||
requestHeaders = requestHeaders.filter(h => !AUTH_HEADERS_RX.test(h.name));
|
||||
debug("TabGuard removing auth headers from %o (%o)", request, requestHeaders);
|
||||
return {requestHeaders};
|
||||
};
|
||||
|
||||
if (mainFrame) {
|
||||
let quietDomains = filteredGroups[tabDomain];
|
||||
let mustPrompt = (!quietDomains || [...otherDomains].some(d => !quietDomains.has(d)));
|
||||
if (mustPrompt) {
|
||||
return (async () => {
|
||||
let options = [
|
||||
{label: _("TabGuard_optAnonymize"), checked: true},
|
||||
{label: _("TabGuard_optAllow")},
|
||||
];
|
||||
let ret = await Prompts.prompt({
|
||||
title: _("TabGuard_title"),
|
||||
message: _("TabGuard_message", [tabDomain, [...otherDomains].join(", ")]),
|
||||
options});
|
||||
if (ret.button === 1) return {cancel: true};
|
||||
let list = ret.option === 0 ? filteredGroups : allowedGroups;
|
||||
otherDomains.add(tabDomain);
|
||||
for (let d of otherDomains) list[d] = otherDomains;
|
||||
return list === filteredGroups ? filterAuth() : null;
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
return filterAuth();
|
||||
|
||||
}
|
||||
}
|
||||
})();
|
|
@ -156,6 +156,10 @@
|
|||
|
||||
let messageHandler = {
|
||||
async updateSettings(settings, sender) {
|
||||
if (settings.command === "tg-forget") {
|
||||
TabGuard.forget();
|
||||
delete settings.tabGuardCommand;
|
||||
}
|
||||
await Settings.update(settings);
|
||||
toggleCtxMenuItem();
|
||||
},
|
||||
|
|
2
src/nscl
2
src/nscl
|
@ -1 +1 @@
|
|||
Subproject commit 1a222e51f8ea529e611ff6e51d5f135feebb54e3
|
||||
Subproject commit 763457e53e56a4116dd7f37c7ca0da9572fc1dc1
|
|
@ -167,6 +167,22 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<section id="sect-TabGuard">
|
||||
<fieldset id="TabGuard">
|
||||
<legend>__MSG_TabGuard_label__</legend>
|
||||
<div class="opt-group">
|
||||
<span id="tgMode">
|
||||
<input id="tgm-global" type="radio" name="TabGuardMode" value="global" /><label for="tgm-global">__MSG_TabGuard_optEnabled__</label>
|
||||
<input id="tgm-incognito" type="radio" name="TabGuardMode" value="incognito" checked="checked"/><label for="tgm-incognito">__MSG_TabGuard_optIncognito__</label>
|
||||
<input id="tgm-off" type="radio" name="TabGuardMode" value="off"/><label for="tgm-off">__MSG_TabGuard_optDisabled__</label>
|
||||
</span>
|
||||
<div id="tgForget"><button id="tgForgetButton">__MSG_TabGuard_forget__</button></div>
|
||||
</div>
|
||||
|
||||
|
||||
</fieldset>
|
||||
</section>
|
||||
|
||||
<section id="debug" class="browser-style">
|
||||
<div class="opt-group">
|
||||
<span><input type="checkbox" id="opt-debug"><label id="label-debug" for="opt-debug">Debug</label></span>
|
||||
|
|
|
@ -141,6 +141,13 @@ document.querySelector("#version").textContent = _("Version",
|
|||
if (checked) updateRawPolicyEditor();
|
||||
});
|
||||
|
||||
UI.wireChoice("TabGuardMode");
|
||||
|
||||
document.querySelector("#tgForgetButton").onclick = e => {
|
||||
e.target.disabled = true;
|
||||
UI.updateSettings({command: "tg-forget"});
|
||||
};
|
||||
|
||||
// Appearance
|
||||
|
||||
opt("showCountBadge", "local");
|
||||
|
|
|
@ -92,7 +92,7 @@ var UI = (() => {
|
|||
async pullSettings() {
|
||||
Messages.send("broadcastSettings", {tabId: UI.tabId});
|
||||
},
|
||||
async updateSettings({policy, xssUserChoices, unrestrictedTab, local, sync, reloadAffected}) {
|
||||
async updateSettings({policy, xssUserChoices, unrestrictedTab, local, sync, reloadAffected, command}) {
|
||||
if (policy) policy = policy.dry(true);
|
||||
return await Messages.send("updateSettings", {
|
||||
policy,
|
||||
|
@ -102,6 +102,7 @@ var UI = (() => {
|
|||
sync,
|
||||
reloadAffected,
|
||||
tabId: UI.tabId,
|
||||
command
|
||||
});
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue