Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,30 @@
"TabGuard_optAllow": {
"message": "Load normally"
},
"DefaultContainerName": {
"message": "Default"
},
"enable_container_tabs_label": {
"message": "Enable support for container tabs"
},
"select_container_label": {
"message": "Choose a container:"
},
"copy_container_label": {
"message": "Copy permissions from container:"
},
"clear_container_label": {
"message": "Clear permissions for this container"
},
"forbid_replace_default_policy": {
"message": "Cannot replace the default policy."
},
"container_copy_warning": {
"message": "Copying permissions from \"$1\".\nAll site permissions for this container will be removed.\nThis action cannot be reverted.\nDo you want to continue?"
},
"container_clear_warning": {
"message": "All site permissions for this container will be removed.\nThis action cannot be reverted.\nDo you want to continue?"
},
"LearnMoreLink": {
"message": "Learn more…"
},
Expand Down
16 changes: 15 additions & 1 deletion src/bg/LifeCycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ var LifeCycle = (() => {
));

const policy = ns.policy.dry(true);
const contextStore = ns.contextStore.dry(true);
const unrestrictedTabs = [...ns.unrestrictedTabs];

if (policy.sites.temp.length == 0 &&
Expand Down Expand Up @@ -118,6 +119,7 @@ var LifeCycle = (() => {
let {url} = tab;
let {cypherText, key, iv} = await encrypt(JSON.stringify({
policy,
contextStore,
allSeen,
unrestrictedTabs,
}));
Expand Down Expand Up @@ -225,14 +227,15 @@ var LifeCycle = (() => {
iv
}, key, cypherText
);
let {policy, allSeen, unrestrictedTabs} = JSON.parse(new TextDecoder().decode(encoded));
let {policy, contextStore, allSeen, unrestrictedTabs} = JSON.parse(new TextDecoder().decode(encoded));
if (!policy) {
throw new error("Ephemeral policy not found in survival tab %s!", tabId);
}
ns.unrestrictedTabs = new Set(unrestrictedTabs);
destroyIfNeeded();
if (ns.initializing) await ns.initializing;
ns.policy = new Policy(policy);
ns.contextStore = new ContextStore(contextStore);
await Promise.allSettled(
Object.entries(allSeen).map(
async ([tabId, seen]) => {
Expand Down Expand Up @@ -336,6 +339,17 @@ var LifeCycle = (() => {
if (changed) {
await ns.savePolicy();
}
if (ns.contextStore) {
changed = false;
for (let k of Object.keys(ns.contextStore.policies)){
for (let p of ns.contextStore.policies[k].getPresets(presetNames)) {
if (callback(p)) changed = true;
}
}
if (changed) {
await ns.saveContextStore();
}
}
};

const configureNewCap = async (cap, presetNames, capsFilter) => {
Expand Down
30 changes: 18 additions & 12 deletions src/bg/RequestGuard.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,24 +381,27 @@

const wantsContext = checked.includes("ctx");

let { siteMatch, contextMatch, perms } = ns.policy.get(key, contextUrl);
let cookieStoreId = sender.tab && sender.tab.cookieStoreId;
let policy = ns.getPolicy(cookieStoreId);
let { contextMatch, perms } = policy.get(key, contextUrl);

if (!perms.capabilities.has(policyType) ||
!contextMatch && wantsContext && ctxKey) {

const wantsTemp = forcedTemp || checked.includes("temp");
if (!contextMatch) {
const isDefault = perms === ns.policy.DEFAULT;
const isDefault = perms === policy.DEFAULT;
perms = perms.clone();
if (isDefault) perms.temp = wantsTemp;
ns.policy.set(key, perms);
policy.set(key, perms);
if (ctxKey && wantsContext) {
perms.contextual.set(ctxKey, perms = perms.clone(/* noContext = */ true));
}
}
perms.temp = wantsTemp;
perms.capabilities.add(policyType);
await ns.savePolicy();
await ns.saveContextStore();
await RequestGuard.DNRPolicy?.update();
}
return {enable: key};
Expand Down Expand Up @@ -645,13 +648,14 @@
function intersectCapabilities(policyMatch, request) {
const {cascadePermissions, cascadeRestrictions} = ns.sync;
if (request.frameId !== 0 && cascadeRestrictions || request.type != "main_frame" && cascadePermissions) {
const {tabUrl, frameAncestors} = request;
const {tabUrl, frameAncestors, cookieStoreId} = request;
const topUrl = tabUrl ||
cascadePermissions && request.frameId == 0 && request.documentUrl ||
frameAncestors && frameAncestors[frameAncestors?.length - 1]?.url ||
TabCache.get(request.tabId)?.url;
if (topUrl) {
return ns.policy.cascade(policyMatch, topUrl, {
const policy = ns.getPolicy(cookieStoreId);
return policy.cascade(policyMatch, topUrl, {
permissions: cascadePermissions,
restrictions: cascadeRestrictions,
}).capabilities;
Expand Down Expand Up @@ -719,9 +723,10 @@

function checkLANRequest(request) {
if (!ns.isEnforced(request.tabId)) return ALLOW;
let {originUrl, url} = request;
let {originUrl, url, cookieStoreId} = request;
let policy = ns.getPolicy(cookieStoreId);
if (originUrl && !Sites.isInternal(originUrl) && url.startsWith("http") &&
!ns.policy.can(originUrl, "lan", ns.policyContext(request))) {
!policy.can(originUrl, "lan", ns.policyContext(request))) {
// we want to block any request whose origin resolves to at least one external WAN IP
// and whose destination resolves to at least one LAN IP
const {proxyInfo} = request; // see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/proxy/ProxyInfo
Expand Down Expand Up @@ -756,9 +761,9 @@

normalizeRequest(request);

let {tabId, type, url, originUrl} = request;
let {tabId, type, cookieStoreId, url, originUrl} = request;

const { policy } = ns;
const policy = ns.getPolicy(cookieStoreId);

let previous = recent.find(request);
if (previous) {
Expand Down Expand Up @@ -917,12 +922,12 @@
let result = ALLOW;

pending.headersProcessed = true;
let {url, tabId, responseHeaders, type} = request;
let {url, tabId, cookieStoreId, responseHeaders, type} = request;
let isMainFrame = type === "main_frame";
try {
let capabilities;
if (ns.isEnforced(tabId)) {
const { policy } = ns;
const policy = ns.getPolicy(cookieStoreId);
const policyMatch = policy.get(url, ns.policyContext(request));
let { perms } = policyMatch;
if (isMainFrame) {
Expand Down Expand Up @@ -1013,13 +1018,14 @@
async function injectPolicyScript(details) {
await ns.initializing;
if (ns.local.debug?.disablePolicyInjection) return ''; // DEV_ONLY
const {url, tabId, frameId, type} = details;
const {url, tabId, frameId, cookieStoreId, type} = details;
const isTop = type == "main_frame";
const domPolicy = await ns.computeChildPolicy(
{ url },
{
tab: { id: tabId, url: isTop ? url : null },
frameId: isTop ? 0 : frameId,
cookieStoreId,
}
);
domPolicy.navigationURL = url;
Expand Down
15 changes: 15 additions & 0 deletions src/bg/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ var Settings = {
async update(settings) {
let {
policy,
contextStore,
xssUserChoices,
tabId,
unrestrictedTab,
Expand Down Expand Up @@ -179,6 +180,7 @@ var Settings = {
// User is resetting options:
// pick either current Tor Browser Security Level or default NoScript policy
policy = ns.local.torBrowserPolicy || this.createDefaultDryPolicy();
contextStore = new ContextStore().dry();
reloadOptionsUI = true;
}

Expand All @@ -198,6 +200,18 @@ var Settings = {
await ns.savePolicy();
}

if (contextStore) {
ns.contextStore = new ContextStore(contextStore);
}

if (policy && ns.contextStore) {
ns.contextStore.updatePresets(ns.policy);
}

if (contextStore || (policy && ns.contextStore)) {
await ns.saveContextStore();
}

if (typeof unrestrictedTab === "boolean") {
await ns.toggleTabRestrictions(tabId, !unrestrictedTab);
}
Expand Down Expand Up @@ -245,6 +259,7 @@ var Settings = {
knownCapabilities: Permissions.ALL,
},
policy: ns.policy.dry(),
contextStore: ns.contextStore.dry(),
local: ns.local,
sync: ns.sync,
xssUserChoices: XSS.getUserChoices(),
Expand Down
8 changes: 5 additions & 3 deletions src/bg/TabGuard.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,10 @@ var TabGuard = (() => {

// we suspect tabs which 1) have not been removed/discarded, 2) are restricted by policy, 3) can run JavaScript
let suspiciousTabs = [...ties].map(TabCache.get).filter(
tab => tab && !tab.discarded && ns.isEnforced(tab.id) &&
(!(tab._isExplicitOrigin = tab._isExplicitOrigin || /^(?:https?|ftps?|file):/.test(tab.url)) || ns.policy.can(tab.url, "script"))
tab => tab && !tab.discarded && ns.isEnforced(tab.id) && (
!(tab._isExplicitOrigin = tab._isExplicitOrigin || /^(?:https?|ftps?|file):/.test(tab.url)) ||
ns.getPolicy(tab.cookieStoreId).can(tab.url, "script")
)
);

return suspiciousTabs.length > 0 && (async () => {
Expand Down Expand Up @@ -222,7 +224,7 @@ var TabGuard = (() => {
}
if (tab.url !== "about:blank") {
debug(`Real origin for ${tab._externalUrl} (tab ${tab.id}) is ${tab.url}.`);
if (!ns.policy.can(tab.url, "script")) return;
if (!ns.getPolicy(tab.cookieStoreId).can(tab.url, "script")) return;
}
}
if (!tab._contentType) {
Expand Down
59 changes: 56 additions & 3 deletions src/bg/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@
}
}

if (!ns.contextStore) { // it could have been already retrieved by LifeCycle
const contextStoreData = (await Storage.get("sync", "contextStore")).contextStore;
if (contextStoreData) {
ns.contextStore = new ContextStore(contextStoreData);
await ns.contextStore.updateContainers(ns.policy);
} else {
log("No container data found. Initializing new policies.")
ns.contextStore = new ContextStore();
await ns.contextStore.updateContainers(ns.policy);
await ns.saveContextStore();
}
}

const {isTorBrowser} = ns.local;
Sites.onionSecure = isTorBrowser;
Expand Down Expand Up @@ -177,10 +189,12 @@
tabId = -1
}) {
const policy = ns.policy.dry(true);
const contextStore = ns.contextStore.dry(true);
const seen = tabId !== -1 ? await ns.collectSeen(tabId) : null;
const xssUserChoices = await XSS.getUserChoices();
await Messages.send("settings", {
policy,
contextStore,
seen,
xssUserChoices,
local: ns.local,
Expand Down Expand Up @@ -281,6 +295,7 @@
}

let _policy = null;
let _contextStore = null;

globalThis.ns = {
running: false,
Expand All @@ -289,6 +304,11 @@
RequestGuard.DNRPolicy?.update();
},
get policy() { return _policy; },
set contextStore(c) {
_contextStore = c;
RequestGuard.DNRPolicy?.update();
},
get contextStore() { return _contextStore; },
local: null,
sync: null,
initializing: null,
Expand Down Expand Up @@ -319,12 +339,29 @@
return tab?.url || documentUrl || url;
},
requestCan(request, capability) {
return !this.isEnforced(request.tabId) || this.policy.can(request.url, capability, this.policyContext(request));
return (
!this.isEnforced(request.tabId) ||
ns.getPolicy(request.cookieStoreId).can(request.url, capability, this.policyContext(request))
);
},

getPolicy(cookieStoreId){
if (
ns.contextStore &&
ns.contextStore.enabled &&
ns.contextStore.policies.hasOwnProperty(cookieStoreId)
) {
let currentPolicy = ns.contextStore.policies[cookieStoreId];
debug("id", cookieStoreId, "has cookiestore", currentPolicy);
if (currentPolicy) return currentPolicy;
}
debug("default cookiestore", cookieStoreId);
return ns.policy;
},

async computeChildPolicy({url, contextUrl}, sender) {
await ns.initializing;
let { tab, origin, frameId, documentLifecycle } = sender;
let { tab, origin, frameId, cookieStoreId, documentLifecycle } = sender;
const tabId = tab ? tab.id : -1;

if (url == sender.url) {
Expand All @@ -341,7 +378,8 @@
}
}
}
let policy = ns.policy;
if (!cookieStoreId && tab) cookieStoreId = tab.cookieStoreId;
let policy = ns.getPolicy(cookieStoreId);
const {isTorBrowser} = ns.local;
if (!policy) {
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
Expand Down Expand Up @@ -449,6 +487,19 @@
return this.policy;
},

async saveContextStore() {
if (this.contextStore) {
await Promise.allSettled([
Storage.set("sync", {
contextStore: this.contextStore.dry()
}),
session.save(),
browser.webRequest.handlerBehaviorChanged()
]);
}
return this.contextStore;
},

openOptionsPage({tab, focus, hilite}) {
const url = new URL(browser.runtime.getManifest().options_ui.page);
if (tab !== undefined) {
Expand Down Expand Up @@ -510,5 +561,7 @@ if (!browser.windows) {
log("All tabs closed: revoking temporary permissions.");
ns.policy.revokeTemp();
ns.savePolicy();
ns.contextStore.revokeTemp();
ns.saveContextStore();
});
}
4 changes: 3 additions & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"webRequestFilterResponse",
"webRequestFilterResponse.serviceWorkerScript",
"dns",
"<all_urls>"
"<all_urls>",
"contextualIdentities"
],
"host_permissions": [
"<all_urls>"
Expand All @@ -62,6 +63,7 @@
"/nscl/common/Sites.js",
"/nscl/common/Permissions.js",
"/nscl/common/Policy.js",
"/nscl/common/ContextStore.js",
"/nscl/common/locale.js",
"/nscl/common/Storage.js",
"/nscl/common/include.js",
Expand Down
2 changes: 1 addition & 1 deletion src/nscl
Loading