container strict mode feature

This commit is contained in:
Fabian Freund
2026-07-05 16:09:31 +02:00
parent a46bafc189
commit 69f0417c51
24 changed files with 498 additions and 52 deletions
@@ -149,19 +149,36 @@ export default class BackgroundMain {
}
const url = URL.parse(options.url);
if (url !== null && this.store.isSiteOriginAssigned(url)) {
let cookieStoreId: string
if (url === null) {
return {};
}
if (tab.cookieStoreId?.startsWith(containerIdentifier) === true) {
cookieStoreId = tab.cookieStoreId.substring(containerIdentifier.length)
} else if (tab.cookieStoreId === privateIdentifier) {
// Handle private tabs - use 'private' as identifier
cookieStoreId = 'private'
} else {
cookieStoreId = 'general'
}
let cookieStoreId: string
if (cookieStoreId == 'private' || this.store.isSiteOriginInSameContext(url, cookieStoreId)) {
if (tab.cookieStoreId?.startsWith(containerIdentifier) === true) {
cookieStoreId = tab.cookieStoreId.substring(containerIdentifier.length)
} else if (tab.cookieStoreId === privateIdentifier) {
// Handle private tabs - use 'private' as identifier
cookieStoreId = 'private'
} else {
cookieStoreId = 'general'
}
// Private tabs are never strict. For other tabs, strict enforcement is
// keyed on the tab's cookie-store context (base container context, or an
// isolation context of a strict container's isolated tab).
const isStrict = cookieStoreId !== 'private' &&
this.store.isContextStrict(cookieStoreId)
if (this.store.isSiteOriginAssigned(url)) {
// In a strict context the site must be assigned to *this* container's own
// base context (exact match, no proxy equivalence). Non-strict contexts
// keep the looser proxy/direct-equivalence matching used for routing.
const allowed = isStrict
? this.store.isSiteOriginStrictlyAllowed(url, cookieStoreId)
: (cookieStoreId == 'private' || this.store.isSiteOriginInSameContext(url, cookieStoreId))
if (allowed) {
if (tab.highlighted) {
port.postMessage({
"type": "assignedSiteRequested",
@@ -171,7 +188,8 @@ export default class BackgroundMain {
"originUrl": options.originUrl,
"url": options.url,
"tabId": options.tabId,
"blocked": false
"blocked": false,
"strict": false
}
});
}
@@ -180,6 +198,9 @@ export default class BackgroundMain {
// even if the tab is not highlighted.
return {};
} else {
// Assigned to a different container: cancel here and let the app re-open
// it in its assigned container. This applies to strict contexts too —
// the site still doesn't load here, it just routes to where it belongs.
//Only send events when tab is selected
if (tab.highlighted) {
port.postMessage({
@@ -190,7 +211,8 @@ export default class BackgroundMain {
"originUrl": options.originUrl,
"url": options.url,
"tabId": options.tabId,
"blocked": true
"blocked": true,
"strict": false
}
});
}
@@ -199,6 +221,28 @@ export default class BackgroundMain {
cancel: true,
};
}
} else if (isStrict) {
// Strict mode: this container may only load origins assigned to it. The
// origin is not assigned to any container, so cancel the navigation and
// report it as a strict block (no destination container).
if (tab.highlighted) {
port.postMessage({
"type": "assignedSiteRequested",
"id": options.requestId,
"status": "success",
"result": {
"originUrl": options.originUrl,
"url": options.url,
"tabId": options.tabId,
"blocked": true,
"strict": true
}
});
}
return {
cancel: true,
};
}
return {};
@@ -18,7 +18,8 @@ interface Message {
'clearContainerProxy' |
'removeContainerProxyRelation' |
'healthcheck' |
'setSiteAssignments';
'setSiteAssignments' |
'setStrictContexts';
args: any;
}
@@ -92,6 +93,10 @@ port.onMessage.addListener((raw: unknown): void => {
console.log('set site assignments ' + JSON.stringify(message.args))
store.setSiteAssignments(entries);
break
case "setStrictContexts":
console.log('set strict contexts ' + JSON.stringify(message.args))
store.setStrictContexts(new Map(Object.entries(message.args)) as Map<string, string[]>);
break
case "healthcheck":
port.postMessage({
"type": "healthcheck",
@@ -89,6 +89,39 @@ export class Store {
private siteAssignments: Map<string, string> = new Map<string, string>()
private wildcardAssignments: WildcardAssignment[] = []
// Maps an enforced cookie-store context (a strict container's base context,
// or an isolation context of one of its isolated tabs) to the base contexts
// its site assignments are keyed on.
private strictContexts: Map<string, Set<string>> = new Map<string, Set<string>>()
setStrictContexts(contexts: Map<string, string[]>): void {
const next = new Map<string, Set<string>>()
for (const [contextId, assignmentContexts] of contexts) {
next.set(contextId, new Set(assignmentContexts))
}
this.strictContexts = next
}
isContextStrict(contextId: string): boolean {
return this.strictContexts.has(contextId)
}
/**
* True when [uri]'s origin is assigned to a base context that [contextId]
* enforces. Strict mode requires an exact assignment match (via
* [lookupAssignment], which handles exact + wildcard entries) against the
* container's base contexts — proxy/direct equivalence is deliberately NOT
* consulted, so a site assigned only to a different (even proxy-equivalent)
* container does not load here.
*/
isSiteOriginStrictlyAllowed(uri: URL, contextId: string): boolean {
const assignmentContexts = this.strictContexts.get(contextId)
const assignedContext = this.lookupAssignment(uri)
return assignedContext !== undefined &&
assignmentContexts !== undefined &&
assignmentContexts.has(assignedContext)
}
setSiteAssignments(sites: Map<string, unknown>): void {
const exact = new Map<string, string>()
const wildcard: WildcardAssignment[] = []