Skip to content

Commit 20b883e

Browse files
committed
Add support for container tabs
1 parent 1b559aa commit 20b883e

14 files changed

Lines changed: 386 additions & 58 deletions

.gitmodules

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[submodule "nscl"]
22
path = src/nscl
33
url = ../nscl.git
4+
branch = container-tabs

src/bg/LifeCycle.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ var LifeCycle = (() => {
7070
));
7171

7272
const policy = ns.policy.dry(true);
73+
const contextStore = ns.contextStore.dry(true);
7374
const unrestrictedTabs = [...ns.unrestrictedTabs];
7475

7576
if (policy.sites.temp.length == 0 &&
@@ -118,6 +119,7 @@ var LifeCycle = (() => {
118119
let {url} = tab;
119120
let {cypherText, key, iv} = await encrypt(JSON.stringify({
120121
policy,
122+
contextStore,
121123
allSeen,
122124
unrestrictedTabs,
123125
}));
@@ -225,14 +227,15 @@ var LifeCycle = (() => {
225227
iv
226228
}, key, cypherText
227229
);
228-
let {policy, allSeen, unrestrictedTabs} = JSON.parse(new TextDecoder().decode(encoded));
230+
let {policy, contextStore, allSeen, unrestrictedTabs} = JSON.parse(new TextDecoder().decode(encoded));
229231
if (!policy) {
230232
throw new error("Ephemeral policy not found in survival tab %s!", tabId);
231233
}
232234
ns.unrestrictedTabs = new Set(unrestrictedTabs);
233235
destroyIfNeeded();
234236
if (ns.initializing) await ns.initializing;
235237
ns.policy = new Policy(policy);
238+
ns.contextStore = new ContextStore(contextStore);
236239
await Promise.allSettled(
237240
Object.entries(allSeen).map(
238241
async ([tabId, seen]) => {
@@ -336,6 +339,17 @@ var LifeCycle = (() => {
336339
if (changed) {
337340
await ns.savePolicy();
338341
}
342+
if (ns.contextStore) {
343+
changed = false;
344+
for (let k of Object.keys(ns.contextStore.policies)){
345+
for (let p of ns.contextStore.policies[k].getPresets(presetNames)) {
346+
if (callback(p)) changed = true;
347+
}
348+
}
349+
if (changed) {
350+
await ns.saveContextStore();
351+
}
352+
}
339353
};
340354

341355
const configureNewCap = async (cap, presetNames, capsFilter) => {

src/bg/RequestGuard.js

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,9 @@
381381

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

384-
let { siteMatch, contextMatch, perms } = ns.policy.get(key, contextUrl);
384+
let cookieStoreId = sender.tab && sender.tab.cookieStoreId;
385+
let policy = ns.getPolicy(cookieStoreId);
386+
let { siteMatch, contextMatch, perms } = policy.get(key, contextUrl);
385387

386388
if (!perms.capabilities.has(policyType) ||
387389
!contextMatch && wantsContext && ctxKey) {
@@ -391,14 +393,15 @@
391393
const isDefault = perms === ns.policy.DEFAULT;
392394
perms = perms.clone();
393395
if (isDefault) perms.temp = wantsTemp;
394-
ns.policy.set(key, perms);
396+
policy.set(key, perms);
395397
if (ctxKey && wantsContext) {
396398
perms.contextual.set(ctxKey, perms = perms.clone(/* noContext = */ true));
397399
}
398400
}
399401
perms.temp = wantsTemp;
400402
perms.capabilities.add(policyType);
401403
await ns.savePolicy();
404+
await ns.saveContextStore();
402405
await RequestGuard.DNRPolicy?.update();
403406
}
404407
return {enable: key};
@@ -645,13 +648,14 @@
645648
function intersectCapabilities(policyMatch, request) {
646649
const {cascadePermissions, cascadeRestrictions} = ns.sync;
647650
if (request.frameId !== 0 && cascadeRestrictions || request.type != "main_frame" && cascadePermissions) {
648-
const {tabUrl, frameAncestors} = request;
651+
const {tabUrl, frameAncestors, cookieStoreId} = request;
649652
const topUrl = tabUrl ||
650653
cascadePermissions && request.frameId == 0 && request.documentUrl ||
651654
frameAncestors && frameAncestors[frameAncestors?.length - 1]?.url ||
652655
TabCache.get(request.tabId)?.url;
653656
if (topUrl) {
654-
return ns.policy.cascade(policyMatch, topUrl, {
657+
const policy = ns.getPolicy(cookieStoreId);
658+
return policy.cascade(policyMatch, topUrl, {
655659
permissions: cascadePermissions,
656660
restrictions: cascadeRestrictions,
657661
}).capabilities;
@@ -719,9 +723,10 @@
719723

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

757762
normalizeRequest(request);
758763

759-
let {tabId, type, url, originUrl} = request;
764+
let {tabId, type, cookieStoreId, url, originUrl} = request;
760765

761-
const { policy } = ns;
766+
const policy = ns.getPolicy(cookieStoreId);
762767

763768
let previous = recent.find(request);
764769
if (previous) {
@@ -917,12 +922,12 @@
917922
let result = ALLOW;
918923

919924
pending.headersProcessed = true;
920-
let {url, tabId, responseHeaders, type} = request;
925+
let {url, tabId, cookieStoreId, responseHeaders, type} = request;
921926
let isMainFrame = type === "main_frame";
922927
try {
923928
let capabilities;
924929
if (ns.isEnforced(tabId)) {
925-
const { policy } = ns;
930+
const policy = ns.getPolicy(cookieStoreId);
926931
const policyMatch = policy.get(url, ns.policyContext(request));
927932
let { perms } = policyMatch;
928933
if (isMainFrame) {
@@ -1013,13 +1018,14 @@
10131018
async function injectPolicyScript(details) {
10141019
await ns.initializing;
10151020
if (ns.local.debug?.disablePolicyInjection) return ''; // DEV_ONLY
1016-
const {url, tabId, frameId, type} = details;
1021+
const {url, tabId, frameId, cookieStoreId, type} = details;
10171022
const isTop = type == "main_frame";
10181023
const domPolicy = await ns.computeChildPolicy(
10191024
{ url },
10201025
{
10211026
tab: { id: tabId, url: isTop ? url : null },
10221027
frameId: isTop ? 0 : frameId,
1028+
cookieStoreId,
10231029
}
10241030
);
10251031
domPolicy.navigationURL = url;

src/bg/Settings.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ var Settings = {
100100
async update(settings) {
101101
let {
102102
policy,
103+
contextStore,
103104
xssUserChoices,
104105
tabId,
105106
unrestrictedTab,
@@ -179,6 +180,7 @@ var Settings = {
179180
// User is resetting options:
180181
// pick either current Tor Browser Security Level or default NoScript policy
181182
policy = ns.local.torBrowserPolicy || this.createDefaultDryPolicy();
183+
contextStore = new ContextStore().dry();
182184
reloadOptionsUI = true;
183185
}
184186

@@ -198,6 +200,12 @@ var Settings = {
198200
await ns.savePolicy();
199201
}
200202

203+
if (contextStore) {
204+
let newContextStore = new ContextStore(contextStore);
205+
ns.contextStore = newContextStore
206+
await ns.saveContextStore();
207+
}
208+
201209
if (typeof unrestrictedTab === "boolean") {
202210
await ns.toggleTabRestrictions(tabId, !unrestrictedTab);
203211
}
@@ -245,6 +253,7 @@ var Settings = {
245253
knownCapabilities: Permissions.ALL,
246254
},
247255
policy: ns.policy.dry(),
256+
contextStore: ns.contextStore.dry(),
248257
local: ns.local,
249258
sync: ns.sync,
250259
xssUserChoices: XSS.getUserChoices(),

src/bg/main.js

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,18 @@
105105
}
106106
}
107107

108+
if (!ns.contextStore) { // it could have been already retrieved by LifeCycle
109+
const contextStoreData = (await Storage.get("sync", "contextStore")).contextStore;
110+
if (contextStoreData) {
111+
ns.contextStore = new ContextStore(contextStoreData);
112+
await ns.contextStore.updateContainers(ns.policy);
113+
} else {
114+
log("No container data found. Initializing new policies.")
115+
ns.contextStore = new ContextStore();
116+
await ns.contextStore.updateContainers(ns.policy);
117+
await ns.saveContextStore();
118+
}
119+
}
108120

109121
const {isTorBrowser} = ns.local;
110122
Sites.onionSecure = isTorBrowser;
@@ -177,10 +189,12 @@
177189
tabId = -1
178190
}) {
179191
const policy = ns.policy.dry(true);
192+
const contextStore = ns.contextStore.dry(true);
180193
const seen = tabId !== -1 ? await ns.collectSeen(tabId) : null;
181194
const xssUserChoices = await XSS.getUserChoices();
182195
await Messages.send("settings", {
183196
policy,
197+
contextStore,
184198
seen,
185199
xssUserChoices,
186200
local: ns.local,
@@ -281,6 +295,7 @@
281295
}
282296

283297
let _policy = null;
298+
let _contextStore = null;
284299

285300
globalThis.ns = {
286301
running: false,
@@ -289,6 +304,11 @@
289304
RequestGuard.DNRPolicy?.update();
290305
},
291306
get policy() { return _policy; },
307+
set contextStore(c) {
308+
_contextStore = c;
309+
RequestGuard.DNRPolicy?.update();
310+
},
311+
get contextStore() { return _contextStore; },
292312
local: null,
293313
sync: null,
294314
initializing: null,
@@ -322,9 +342,23 @@
322342
return !this.isEnforced(request.tabId) || this.policy.can(request.url, capability, this.policyContext(request));
323343
},
324344

345+
getPolicy(cookieStoreId){
346+
if (
347+
ns.contextStore &&
348+
ns.contextStore.enabled &&
349+
ns.contextStore.policies.hasOwnProperty(cookieStoreId)
350+
) {
351+
let currentPolicy = ns.contextStore.policies[cookieStoreId];
352+
debug("id", cookieStoreId, "has cookiestore", currentPolicy);
353+
if (currentPolicy) return currentPolicy;
354+
}
355+
debug("default cookiestore", cookieStoreId);
356+
return ns.policy;
357+
},
358+
325359
async computeChildPolicy({url, contextUrl}, sender) {
326360
await ns.initializing;
327-
let { tab, origin, frameId, documentLifecycle } = sender;
361+
let { tab, origin, frameId, cookieStoreId, documentLifecycle } = sender;
328362
const tabId = tab ? tab.id : -1;
329363

330364
if (url == sender.url) {
@@ -341,7 +375,8 @@
341375
}
342376
}
343377
}
344-
let policy = ns.policy;
378+
if (!cookieStoreId && tab) cookieStoreId = tab.cookieStoreId;
379+
let policy = ns.getPolicy(cookieStoreId);
345380
const {isTorBrowser} = ns.local;
346381
if (!policy) {
347382
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
@@ -449,6 +484,19 @@
449484
return this.policy;
450485
},
451486

487+
async saveContextStore() {
488+
if (this.contextStore) {
489+
await Promise.allSettled([
490+
Storage.set("sync", {
491+
contextStore: this.contextStore.dry()
492+
}),
493+
session.save(),
494+
browser.webRequest.handlerBehaviorChanged()
495+
]);
496+
}
497+
return this.contextStore;
498+
},
499+
452500
openOptionsPage({tab, focus, hilite}) {
453501
const url = new URL(browser.runtime.getManifest().options_ui.page);
454502
if (tab !== undefined) {

src/manifest.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"webRequestFilterResponse",
3737
"webRequestFilterResponse.serviceWorkerScript",
3838
"dns",
39-
"<all_urls>"
39+
"<all_urls>",
40+
"contextualIdentities"
4041
],
4142
"host_permissions": [
4243
"<all_urls>"
@@ -62,6 +63,7 @@
6263
"/nscl/common/Sites.js",
6364
"/nscl/common/Permissions.js",
6465
"/nscl/common/Policy.js",
66+
"/nscl/common/ContextStore.js",
6567
"/nscl/common/locale.js",
6668
"/nscl/common/Storage.js",
6769
"/nscl/common/include.js",

src/ui/options.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,24 @@ fieldset:disabled {
114114
flex: 2 2;
115115
}
116116

117+
.per-site-buttons {
118+
display: flex;
119+
flex-flow: row wrap;
120+
justify-content: flex-end;
121+
width: 100%;
122+
text-align: right;
123+
margin: .5em 0 0 0;
124+
}
125+
#btn-clear-container {
126+
margin-inline-start: .5em;
127+
}
128+
#copy-container {
129+
margin-inline: .5em;
130+
}
131+
#copy-container-label {
132+
margin-block: auto;
133+
}
134+
117135
#policy {
118136
display: block;
119137
margin-top: .5em;
@@ -123,6 +141,12 @@ fieldset:disabled {
123141
.hide, body:not(.debug) div.debug {
124142
display: none;
125143
}
144+
#context-store {
145+
display: block;
146+
margin-top: .5em;
147+
min-height: 20em;
148+
width: 90%;
149+
}
126150

127151
#debug-tools {
128152
padding-left: 2.5em;
@@ -142,6 +166,14 @@ fieldset:disabled {
142166
font-weight: bold;
143167
}
144168

169+
#context-store-error {
170+
background: red;
171+
color: #ff8;
172+
padding: 0;
173+
margin: 0;
174+
font-weight: bold;
175+
}
176+
145177
input, button {
146178
font-size: 1em;
147179
}

0 commit comments

Comments
 (0)