Skip to content

Commit 3ea1f71

Browse files
committed
Add support for container tabs
1 parent 1b171c5 commit 3ea1f71

File tree

14 files changed

+386
-57
lines changed

14 files changed

+386
-57
lines changed

.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: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ var LifeCycle = (() => {
108108
let {url} = tab;
109109
let {cypherText, key, iv} = await encrypt(JSON.stringify({
110110
policy: ns.policy.dry(true),
111+
contextStore: ns.contextStore.dry(true),
111112
allSeen,
112113
unrestrictedTabs: [...ns.unrestrictedTabs]
113114
}));
@@ -208,14 +209,15 @@ var LifeCycle = (() => {
208209
iv
209210
}, key, cypherText
210211
);
211-
let {policy, allSeen, unrestrictedTabs} = JSON.parse(new TextDecoder().decode(encoded));
212+
let {policy, contextStore, allSeen, unrestrictedTabs} = JSON.parse(new TextDecoder().decode(encoded));
212213
if (!policy) {
213214
throw new error("Ephemeral policy not found in survival tab %s!", tabId);
214215
}
215216
ns.unrestrictedTabs = new Set(unrestrictedTabs);
216217
destroyIfNeeded();
217218
if (ns.initializing) await ns.initializing;
218219
ns.policy = new Policy(policy);
220+
ns.contextStore = new ContextStore(contextStore);
219221
await Promise.allSettled(
220222
Object.entries(allSeen).map(
221223
async ([tabId, seen]) => {
@@ -308,6 +310,17 @@ var LifeCycle = (() => {
308310
if (changed) {
309311
await ns.savePolicy();
310312
}
313+
if (ns.contextStore) {
314+
changed = false;
315+
for (let k of Object.keys(ns.contextStore.policies)){
316+
for (let p of ns.contextStore.policies[k].getPresets(presetNames)) {
317+
if (callback(p)) changed = true;
318+
}
319+
}
320+
if (changed) {
321+
await ns.saveContextStore();
322+
}
323+
}
311324
};
312325

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

src/bg/RequestGuard.js

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,9 @@
379379

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

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

384386
if (!perms.capabilities.has(policyType) ||
385387
!contextMatch && wantsContext && ctxKey) {
@@ -389,14 +391,15 @@
389391
const isDefault = perms === ns.policy.DEFAULT;
390392
perms = perms.clone();
391393
if (isDefault) perms.temp = wantsTemp;
392-
ns.policy.set(key, perms);
394+
policy.set(key, perms);
393395
if (ctxKey && wantsContext) {
394396
perms.contextual.set(ctxKey, perms = perms.clone(/* noContext = */ true));
395397
}
396398
}
397399
perms.temp = wantsTemp;
398400
perms.capabilities.add(policyType);
399401
await ns.savePolicy();
402+
await ns.saveContextStore();
400403
await RequestGuard.DNRPolicy?.update();
401404
}
402405
return {enable: key};
@@ -638,12 +641,13 @@
638641

639642
function intersectCapabilities(policyMatch, request) {
640643
if (request.frameId !== 0 && ns.sync.cascadeRestrictions) {
641-
const {tabUrl, frameAncestors} = request;
644+
const {tabUrl, frameAncestors, cookieStoreId} = request;
642645
const topUrl = tabUrl ||
643646
frameAncestors && frameAncestors[frameAncestors?.length - 1]?.url ||
644647
TabCache.get(request.tabId)?.url;
645648
if (topUrl) {
646-
return ns.policy.cascadeRestrictions(policyMatch, topUrl).capabilities;
649+
const policy = ns.getPolicy(cookieStoreId);
650+
return policy.cascadeRestrictions(policyMatch, topUrl).capabilities;
647651
}
648652
}
649653
return policyMatch.perms.capabilities;
@@ -708,9 +712,10 @@
708712

709713
function checkLANRequest(request) {
710714
if (!ns.isEnforced(request.tabId)) return ALLOW;
711-
let {originUrl, url} = request;
715+
let {originUrl, url, cookieStoreId} = request;
716+
let policy = ns.getPolicy(cookieStoreId);
712717
if (originUrl && !Sites.isInternal(originUrl) && url.startsWith("http") &&
713-
!ns.policy.can(originUrl, "lan", ns.policyContext(request))) {
718+
!policy.can(originUrl, "lan", ns.policyContext(request))) {
714719
// we want to block any request whose origin resolves to at least one external WAN IP
715720
// and whose destination resolves to at least one LAN IP
716721
const {proxyInfo} = request; // see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/proxy/ProxyInfo
@@ -745,9 +750,9 @@
745750

746751
normalizeRequest(request);
747752

748-
let {tabId, type, url, originUrl} = request;
753+
let {tabId, type, cookieStoreId, url, originUrl} = request;
749754

750-
const {policy} = ns
755+
const policy = ns.getPolicy(cookieStoreId);
751756

752757
let previous = recent.find(request);
753758
if (previous) {
@@ -907,12 +912,12 @@
907912
let promises = [];
908913

909914
pending.headersProcessed = true;
910-
let {url, documentUrl, tabId, responseHeaders, type} = request;
915+
let {url, documentUrl, tabId, cookieStoreId, responseHeaders, type} = request;
911916
let isMainFrame = type === "main_frame";
912917
try {
913918
let capabilities;
914919
if (ns.isEnforced(tabId)) {
915-
const { policy } = ns;
920+
const policy = ns.getPolicy(cookieStoreId);
916921
const policyMatch = policy.get(url, ns.policyContext(request));
917922
let { perms } = policyMatch;
918923
if (isMainFrame) {
@@ -1008,13 +1013,14 @@
10081013
async function injectPolicyScript(details) {
10091014
await ns.initializing;
10101015
if (ns.local.debug?.disablePolicyInjection) return ''; // DEV_ONLY
1011-
const {url, tabId, frameId, type} = details;
1016+
const {url, tabId, frameId, cookieStoreId, type} = details;
10121017
const isTop = type == "main_frame";
10131018
const domPolicy = await ns.computeChildPolicy(
10141019
{ url },
10151020
{
10161021
tab: { id: tabId, url: isTop ? url : null },
10171022
frameId: isTop ? 0 : frameId,
1023+
cookieStoreId,
10181024
}
10191025
);
10201026
domPolicy.navigationURL = url;
@@ -1106,4 +1112,4 @@
11061112
}, filterDocs, ["blocking", "responseHeaders"])).install();
11071113
}
11081114
}
1109-
}
1115+
}

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,
@@ -176,6 +177,7 @@ var Settings = {
176177
// User is resetting options:
177178
// pick either current Tor Browser Security Level or default NoScript policy
178179
policy = ns.local.torBrowserPolicy || this.createDefaultDryPolicy();
180+
contextStore = new ContextStore().dry();
179181
reloadOptionsUI = true;
180182
}
181183

@@ -195,6 +197,12 @@ var Settings = {
195197
await ns.savePolicy();
196198
}
197199

200+
if (contextStore) {
201+
let newContextStore = new ContextStore(contextStore);
202+
ns.contextStore = newContextStore
203+
await ns.saveContextStore();
204+
}
205+
198206
if (typeof unrestrictedTab === "boolean") {
199207
await ns.toggleTabRestrictions(tabId, !unrestrictedTab);
200208
}
@@ -242,6 +250,7 @@ var Settings = {
242250
knownCapabilities: Permissions.ALL,
243251
},
244252
policy: ns.policy.dry(),
253+
contextStore: ns.contextStore.dry(),
245254
local: ns.local,
246255
sync: ns.sync,
247256
xssUserChoices: XSS.getUserChoices(),

src/bg/main.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,19 @@
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+
}
120+
108121
const {isTorBrowser} = ns.local;
109122
Sites.onionSecure = isTorBrowser;
110123

@@ -175,10 +188,12 @@
175188
tabId = -1
176189
}) {
177190
const policy = ns.policy.dry(true);
191+
const contextStore = ns.contextStore.dry(true);
178192
const seen = tabId !== -1 ? await ns.collectSeen(tabId) : null;
179193
const xssUserChoices = await XSS.getUserChoices();
180194
await Messages.send("settings", {
181195
policy,
196+
contextStore,
182197
seen,
183198
xssUserChoices,
184199
local: ns.local,
@@ -260,6 +275,7 @@
260275
}
261276

262277
let _policy = null;
278+
let _contextStore = null;
263279

264280
globalThis.ns = {
265281
running: false,
@@ -268,6 +284,11 @@
268284
RequestGuard.DNRPolicy?.update();
269285
},
270286
get policy() { return _policy; },
287+
set contextStore(c) {
288+
_contextStore = c;
289+
RequestGuard.DNRPolicy?.update();
290+
},
291+
get contextStore() { return _contextStore; },
271292
local: null,
272293
sync: null,
273294
initializing: null,
@@ -301,13 +322,28 @@
301322
return !this.isEnforced(request.tabId) || this.policy.can(request.url, capability, this.policyContext(request));
302323
},
303324

325+
getPolicy(cookieStoreId){
326+
if (
327+
ns.contextStore &&
328+
ns.contextStore.enabled &&
329+
ns.contextStore.policies.hasOwnProperty(cookieStoreId)
330+
) {
331+
let currentPolicy = ns.contextStore.policies[cookieStoreId];
332+
debug("id", cookieStoreId, "has cookiestore", currentPolicy);
333+
if (currentPolicy) return currentPolicy;
334+
}
335+
debug("default cookiestore", cookieStoreId);
336+
return ns.policy;
337+
},
338+
304339
async computeChildPolicy({url, contextUrl}, sender) {
305340
await ns.initializing;
306-
let {tab, origin, frameId, documentLifecycle} = sender;
341+
let {tab, origin, frameId, cookieStoreId, documentLifecycle} = sender;
307342
if (url == sender.url && url == "about:blank") {
308343
url = origin;
309344
}
310-
let policy = ns.policy;
345+
if (!cookieStoreId && tab) cookieStoreId = tab.cookieStoreId;
346+
let policy = ns.getPolicy(cookieStoreId);
311347
const {isTorBrowser} = ns.local;
312348
if (!policy) {
313349
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
@@ -414,6 +450,19 @@
414450
return this.policy;
415451
},
416452

453+
async saveContextStore() {
454+
if (this.contextStore) {
455+
await Promise.allSettled([
456+
Storage.set("sync", {
457+
policy: this.contextStore.dry()
458+
}),
459+
session.save(),
460+
browser.webRequest.handlerBehaviorChanged()
461+
]);
462+
}
463+
return this.contextStore;
464+
},
465+
417466
openOptionsPage({tab, focus, hilite}) {
418467
const url = new URL(browser.runtime.getManifest().options_ui.page);
419468
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/nscl

src/ui/options.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ fieldset:disabled {
8686
flex: 2 2;
8787
}
8888

89+
.per-site-buttons {
90+
display: flex;
91+
flex-flow: row wrap;
92+
justify-content: flex-end;
93+
width: 100%;
94+
text-align: right;
95+
margin: .5em 0 0 0;
96+
}
97+
#btn-clear-container {
98+
margin-inline-start: .5em;
99+
}
100+
#copy-container {
101+
margin-inline: .5em;
102+
}
103+
#copy-container-label {
104+
margin-block: auto;
105+
}
106+
89107
#policy {
90108
display: block;
91109
margin-top: .5em;
@@ -95,6 +113,12 @@ fieldset:disabled {
95113
.hide, body:not(.debug) div.debug {
96114
display: none;
97115
}
116+
#context-store {
117+
display: block;
118+
margin-top: .5em;
119+
min-height: 20em;
120+
width: 90%;
121+
}
98122

99123
#debug-tools {
100124
padding-left: 2.5em;
@@ -114,6 +138,14 @@ fieldset:disabled {
114138
font-weight: bold;
115139
}
116140

141+
#context-store-error {
142+
background: red;
143+
color: #ff8;
144+
padding: 0;
145+
margin: 0;
146+
font-weight: bold;
147+
}
148+
117149
input, button {
118150
font-size: 1em;
119151
}

0 commit comments

Comments
 (0)