Skip to content

Add support for container tabs #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[submodule "nscl"]
path = src/nscl
url = ../nscl.git
branch = container-tabs
15 changes: 14 additions & 1 deletion src/bg/LifeCycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ var LifeCycle = (() => {
let {url} = tab;
let {cypherText, key, iv} = await encrypt(JSON.stringify({
policy: ns.policy.dry(true),
contextStore: ns.contextStore.dry(true),
allSeen,
unrestrictedTabs: [...ns.unrestrictedTabs]
}));
Expand Down Expand Up @@ -188,14 +189,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.all(
Object.entries(allSeen).map(
async ([tabId, seen]) => {
Expand Down Expand Up @@ -274,6 +276,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();
}
}
};

let configureNewCap = async (cap, presetNames, capsFilter) => {
Expand Down
28 changes: 17 additions & 11 deletions src/bg/RequestGuard.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,10 @@ var RequestGuard = (() => {
}
let key = [siteKey, origin][ret.option || 0];
if (!key) return;
let cookieStoreId = sender.tab && sender.tab.cookieStoreId;
let policy = ns.getPolicy(cookieStoreId);
let contextUrl = sender.tab.url || documentUrl;
let {siteMatch, contextMatch, perms} = ns.policy.get(key, contextUrl);
let {siteMatch, contextMatch, perms} = policy.get(key, contextUrl);
let {capabilities} = perms;
if (!capabilities.has(policyType)) {
let temp = sender.tab.incognito; // we don't want to store in PBM
Expand All @@ -294,8 +296,9 @@ var RequestGuard = (() => {
perms = new Permissions(new Set(capabilities), false, contextualSites);
}
*/
ns.policy.set(key, perms);
policy.set(key, perms);
await ns.savePolicy();
await ns.saveContextStore();
}
return {enable: key};
},
Expand Down Expand Up @@ -397,7 +400,7 @@ var RequestGuard = (() => {
};

function intersectCapabilities(perms, request) {
let {frameId, frameAncestors, tabId} = request;
let {frameId, frameAncestors, tabId, cookieStoreId} = request;
if (frameId !== 0 && ns.sync.cascadeRestrictions) {
let topUrl = frameAncestors && frameAncestors.length
&& frameAncestors[frameAncestors.length - 1].url;
Expand All @@ -406,7 +409,8 @@ var RequestGuard = (() => {
if (tab) topUrl = tab.url;
}
if (topUrl) {
return ns.policy.cascadeRestrictions(perms, topUrl).capabilities;
let policy = ns.getPolicy(cookieStoreId);
return policy.cascadeRestrictions(perms, topUrl).capabilities;
}
}
return perms.capabilities;
Expand Down Expand Up @@ -468,9 +472,10 @@ var RequestGuard = (() => {

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
let {proxyInfo} = request; // see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/proxy/ProxyInfo
Expand Down Expand Up @@ -504,7 +509,6 @@ var RequestGuard = (() => {
normalizeRequest(request);
initPendingRequest(request);

let {policy} = ns
let {tabId, type, url, originUrl} = request;

if (type in policyTypesMap) {
Expand All @@ -527,7 +531,9 @@ var RequestGuard = (() => {
}
return ALLOW;
}
let {cookieStoreId} = request;
let isFetch = "fetch" === policyType;
let policy = ns.getPolicy(cookieStoreId);

if ((isFetch || "frame" === policyType) &&
(((isFetch && !originUrl
Expand Down Expand Up @@ -639,12 +645,12 @@ var RequestGuard = (() => {
let promises = [];

pending.headersProcessed = true;
let {url, documentUrl, tabId, responseHeaders, type} = request;
let {url, documentUrl, tabId, cookieStoreId, responseHeaders, type} = request;
let isMainFrame = type === "main_frame";
try {
let capabilities;
if (ns.isEnforced(tabId)) {
let policy = ns.policy;
let policy = ns.getPolicy(cookieStoreId);
let {perms} = policy.get(url, ns.policyContext(request));
if (isMainFrame) {
if (policy.autoAllowTop && perms === policy.DEFAULT) {
Expand Down Expand Up @@ -769,8 +775,8 @@ var RequestGuard = (() => {
}

function injectPolicyScript(details) {
let {url, tabId, frameId} = details;
let policy = ns.computeChildPolicy({url}, {tab: {id: tabId}, frameId});
let {url, tabId, frameId, cookieStoreId} = details;
let policy = ns.computeChildPolicy({url}, {tab: {id: tabId}, frameId, cookieStoreId});
policy.navigationURL = url;
let debugStatement = ns.local.debug ? `
let mark = Date.now() + ":" + Math.random();
Expand Down
9 changes: 9 additions & 0 deletions src/bg/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ var Settings = {
async update(settings) {
let {
policy,
contextStore,
xssUserChoices,
tabId,
unrestrictedTab,
Expand Down Expand Up @@ -146,6 +147,7 @@ var Settings = {
if (settings.sync === null) {
// user is resetting options
policy = this.createDefaultDryPolicy();
contextStore = new ContextStore().dry();

// overriden defaults when user manually resets options

Expand All @@ -170,6 +172,12 @@ var Settings = {
await ns.savePolicy();
}

if (contextStore) {
let newContextStore = new ContextStore(contextStore);
ns.contextStore = newContextStore
await ns.saveContextStore();
}

if (typeof unrestrictedTab === "boolean") {
ns.unrestrictedTabs[unrestrictedTab ? "add" : "delete"](tabId);
}
Expand Down Expand Up @@ -213,6 +221,7 @@ var Settings = {
export() {
return JSON.stringify({
policy: ns.policy.dry(),
contextStore: ns.contextStore.dry(),
local: ns.local,
sync: ns.sync,
xssUserChoices: XSS.getUserChoices(),
Expand Down
69 changes: 55 additions & 14 deletions src/bg/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,19 @@
}
}

if (!ns.contextStore) { // it could have been already retrieved by LifeCycle
let 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();
}
}

let {isTorBrowser} = ns.local;
Sites.onionSecure = isTorBrowser;

Expand Down Expand Up @@ -178,11 +191,13 @@
tabId = -1
}) {
let policy = ns.policy.dry(true);
let contextStore = ns.contextStore.dry(true);
let seen = tabId !== -1 ? await ns.collectSeen(tabId) : null;
let xssUserChoices = await XSS.getUserChoices();
let anonymyzedTabInfo =
await Messages.send("settings", {
policy,
contextStore,
seen,
xssUserChoices,
local: ns.local,
Expand Down Expand Up @@ -272,6 +287,7 @@
var ns = {
running: false,
policy: null,
contextStore: null,
local: null,
sync: null,
initializing: null,
Expand All @@ -293,21 +309,22 @@
return !this.isEnforced(request.tabId) || this.policy.can(request.url, capability, this.policyContext(request));
},

computeChildPolicy({url, contextUrl}, sender) {
let {tab, frameId} = sender;
let policy = ns.policy;
let {isTorBrowser} = ns.local;
if (!policy) {
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
return {
permissions: new Permissions(Permissions.DEFAULT).dry(),
unrestricted: false,
cascaded: false,
fallback: true,
isTorBrowser,
};
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;
},

computeChildPolicy({url, contextUrl}, sender) {
let {tab, frameId, cookieStoreId} = sender;
let tabId = tab ? tab.id : -1;
let topUrl;
if (frameId === 0) {
Expand All @@ -319,6 +336,20 @@
if (!topUrl) topUrl = url;
if (!contextUrl) contextUrl = topUrl;

if (!cookieStoreId && tab) cookieStoreId = tab.cookieStoreId;
let policy = ns.getPolicy(cookieStoreId);
let {isTorBrowser} = ns.local;
if (!policy) {
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
return {
permissions: new Permissions(Permissions.DEFAULT).dry(),
unrestricted: false,
cascaded: false,
fallback: true,
isTorBrowser,
};
}

if (Sites.isInternal(url) || !ns.isEnforced(tabId)) {
policy = null;
}
Expand Down Expand Up @@ -384,7 +415,7 @@
await Storage.set("sync", {
policy: this.policy.dry()
});
await browser.webRequest.handlerBehaviorChanged()
await browser.webRequest.handlerBehaviorChanged();
}
return this.policy;
},
Expand All @@ -401,6 +432,16 @@
browser.tabs.create({url: url.toString() });
},

async saveContextStore() {
if (this.contextStore) {
await Storage.set("sync", {
contextStore: this.contextStore.dry()
});
await browser.webRequest.handlerBehaviorChanged();
}
return this.contextStore;
},

async save(obj) {
if (obj && obj.storage) {
let toBeSaved = {
Expand Down
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 @@
"webRequest",
"webRequestBlocking",
"dns",
"<all_urls>"
"<all_urls>",
"contextualIdentities"
],

"background": {
Expand All @@ -58,6 +59,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
32 changes: 32 additions & 0 deletions src/ui/options.css
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ fieldset:disabled {
flex: 2 2;
}

.per-site-buttons {
display: flex;
flex-flow: row wrap;
justify-content: flex-end;
width: 100%;
text-align: right;
margin: .5em 0 0 0;
}
#btn-clear-container {
margin-inline-start: .5em;
}
#copy-container {
margin-inline: .5em;
}
#copy-container-label {
margin-block: auto;
}

#policy {
display: block;
margin-top: .5em;
Expand All @@ -91,6 +109,12 @@ fieldset:disabled {
.hide, body:not(.debug) div.debug {
display: none;
}
#context-store {
display: block;
margin-top: .5em;
min-height: 20em;
width: 90%;
}

#debug-tools {
padding-left: 2.5em;
Expand All @@ -110,6 +134,14 @@ fieldset:disabled {
font-weight: bold;
}

#context-store-error {
background: red;
color: #ff8;
padding: 0;
margin: 0;
font-weight: bold;
}

input, button {
font-size: 1em;
}
Expand Down
Loading