URL blocking rules prevent agents from navigating between specific domains or URLs during a session. This ensures sensitive context doesn’t leak across different websites.
Overview
ContextFort supports two types of URL blocking:
- Domain Blocking Rules - Block navigation between domains (e.g., prevent gmail.com → slack.com)
- URL Pair Blocking Rules - Block navigation between specific full URLs
Storage Structure
Domain Blocking Rules
Stored as an array of domain pairs in chrome.storage.local under urlBlockingRules:
[
["gmail.com", "slack.com"],
["github.com", ""],
["", "internal-tool.company.com"]
]
Rule types:
["domainA", "domainB"] - Blocks bidirectional navigation between domainA and domainB
["domain", ""] - Blocks navigation FROM domain to ANY other domain
["", "domain"] - Blocks agent mode entirely on domain (no agent access allowed)
URL Pair Blocking Rules
Stored as an array of full URL pairs in chrome.storage.local under urlPairBlockingRules:
[
[
"https://app.example.com/settings",
"https://analytics.example.com/dashboard"
]
]
These rules block navigation between exact URLs, providing more granular control than domain rules.
How It Works
Domain Matching
Domain matching supports subdomains:
function matchesHostname(hostname, pattern) {
if (pattern === "") return true;
return hostname === pattern || hostname.endsWith('.' + pattern);
}
Examples:
- Pattern
"example.com" matches:
example.com ✓
www.example.com ✓
api.example.com ✓
app.staging.example.com ✓
Navigation Blocking Logic
The extension checks navigation rules in three scenarios:
Agent Mode Disabled
When pattern is ["", "domain.com"], agent mode is completely blocked on that domain.
No Outbound Navigation
When pattern is ["domain.com", ""], agents can’t navigate away from domain.com to any other site.
Bidirectional Blocking
When pattern is ["domainA", "domainB"], agents can’t navigate between these domains in either direction.
Implementation
function shouldBlockNavigation(newUrl, visitedUrls) {
const newHostname = getHostname(newUrl);
if (!newHostname) return { blocked: false };
// Check if agent mode is completely disabled on this domain
for (const [domain1, domain2] of urlBlockingRules) {
if (domain1 === "" && matchesHostname(newHostname, domain2) &&
visitedUrls.some(url => !matchesHostname(getHostname(url), domain2))) {
return {
blocked: true,
reason: `Use of Agent mode is not allowed in ${newHostname}.`,
conflictingUrl: null
};
}
}
// Check visited URLs against blocking rules
for (const visitedUrl of visitedUrls) {
const visitedHostname = getHostname(visitedUrl);
if (!visitedHostname) continue;
for (const [domain1, domain2] of urlBlockingRules) {
// No outbound navigation rule
if (domain2 === "" && matchesHostname(visitedHostname, domain1)) {
return {
blocked: true,
reason: `Context from ${visitedHostname} cannot persist in other URLs.`,
conflictingUrl: visitedUrl
};
}
// Bidirectional blocking
if (domain1 !== "" && domain2 !== "") {
const match1 = matchesHostname(visitedHostname, domain1) &&
matchesHostname(newHostname, domain2);
const match2 = matchesHostname(visitedHostname, domain2) &&
matchesHostname(newHostname, domain1);
if (match1 || match2) {
return {
blocked: true,
reason: `Navigation to ${newHostname} is blocked because context from ${visitedHostname} persists.`,
conflictingUrl: visitedUrl
};
}
}
}
}
// Check URL pair blocking rules
for (const visitedUrl of visitedUrls) {
for (const [url1, url2] of urlPairBlockingRules) {
const match1 = newUrl === url2 && visitedUrl === url1;
const match2 = newUrl === url1 && visitedUrl === url2;
if (match1 || match2) {
return {
blocked: true,
reason: `Navigation blocked due to URL pair rule.`,
conflictingUrl: visitedUrl
};
}
}
}
return { blocked: false };
}
Navigation Listeners
URL blocking is enforced at three points:
1. Agent Detection
When agent mode starts:
function onMessageAgentDetected(tab, groupId) {
if (groupId && groupId !== chrome.tabGroups.TAB_GROUP_ID_NONE) {
getOrCreateSession(groupId, tab.id, tab.url, tab.title).then(async session => {
const blockCheck = shouldBlockNavigation(tab.url, session.visitedUrls);
if (blockCheck.blocked) {
sendStopAgentMessage(tab.id);
stopAgentTracking(tab.id, groupId);
showBlockNotification(tab.id, blockCheck, tab.url);
return;
}
// ... continue agent activation ...
});
}
}
2. Before Navigation
Intercepts navigation before it happens:
chrome.webNavigation.onBeforeNavigate.addListener(async (details) => {
if (details.frameId !== 0) return;
const tabId = details.tabId;
const newUrl = details.url;
const activation = activeAgentTabs.get(tabId);
if (!activation) return;
const session = sessions.get(activation.groupId);
if (!session) return;
const blockCheck = shouldBlockNavigation(newUrl, session.visitedUrls);
if (blockCheck.blocked) {
sendStopAgentMessage(tabId);
stopAgentTracking(tabId, activation.groupId);
showBadgeNotification('⛔', '#FF0000');
showInPageNotification(tabId, '⛔ Agent Mode Denied', blockCheck.reason, 'error');
return;
}
// ... continue with navigation ...
});
3. Tab Updates
Monitors URL changes from tab updates:
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
if (changeInfo.url) {
const newUrl = changeInfo.url;
const activation = activeAgentTabs.get(tabId);
if (!activation) return;
const session = sessions.get(activation.groupId);
if (!session) return;
const blockCheck = shouldBlockNavigation(newUrl, session.visitedUrls);
if (blockCheck.blocked) {
sendStopAgentMessage(tabId);
stopAgentTracking(tabId, activation.groupId);
showBadgeNotification('⛔', '#FF0000');
showInPageNotification(tabId, '⛔ Agent Mode Denied', blockCheck.reason, 'error');
return;
}
await addVisitedUrl(session, newUrl);
}
});
Loading Rules
Rules are loaded on extension startup and updated via messages:
(async () => {
const result = await chrome.storage.local.get([
'urlBlockingRules',
'urlPairBlockingRules',
'blockedActions',
'governanceRules'
]);
if (result.urlBlockingRules) {
urlBlockingRules = result.urlBlockingRules;
}
if (result.urlPairBlockingRules) {
urlPairBlockingRules = result.urlPairBlockingRules;
}
})();
User Notifications
When navigation is blocked, users see:
- Badge notification - Red ⛔ icon on extension badge
- In-page notification - Toast notification explaining why navigation was blocked
- Agent stops - The stop button is automatically clicked to halt the agent
Blocked navigation automatically stops the agent. Users must manually restart agent mode after addressing the issue.
Example Configurations
Prevent Email-to-Slack Context Leakage
[
["gmail.com", "slack.com"],
["outlook.com", "slack.com"]
]
Agents can’t navigate from email to Slack or vice versa.
Block Agents on Admin Panels
[
["", "admin.company.com"],
["", "settings.company.com"]
]
Agent mode is completely disabled on these domains.
[
["internal-tool.company.com", ""]
]
Agents can access the internal tool but can’t navigate anywhere else from it.
Best Practices
- Start Broad: Use domain rules for general restrictions
- Be Specific: Use URL pair rules for page-level granularity
- Test Scenarios: Verify rules work for common agent workflows
- Document Rules: Keep notes on why each rule exists
- Monitor Sessions: Check visibility logs to see when rules trigger