Skip to main content
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:
  1. Domain Blocking Rules - Block navigation between domains (e.g., prevent gmail.com → slack.com)
  2. 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:
background.js:1174-1177
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
The extension checks navigation rules in three scenarios:
1

Agent Mode Disabled

When pattern is ["", "domain.com"], agent mode is completely blocked on that domain.
2

No Outbound Navigation

When pattern is ["domain.com", ""], agents can’t navigate away from domain.com to any other site.
3

Bidirectional Blocking

When pattern is ["domainA", "domainB"], agents can’t navigate between these domains in either direction.

Implementation

background.js:1179-1239
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 };
}
URL blocking is enforced at three points:

1. Agent Detection

When agent mode starts:
background.js:730-740
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:
background.js:1241-1268
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:
background.js:1271-1312
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:
background.js:360-368
(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:
  1. Badge notification - Red ⛔ icon on extension badge
  2. In-page notification - Toast notification explaining why navigation was blocked
  3. 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.

Isolate Internal Tools

[
  ["internal-tool.company.com", ""]
]
Agents can access the internal tool but can’t navigate anywhere else from it.

Best Practices

  1. Start Broad: Use domain rules for general restrictions
  2. Be Specific: Use URL pair rules for page-level granularity
  3. Test Scenarios: Verify rules work for common agent workflows
  4. Document Rules: Keep notes on why each rule exists
  5. Monitor Sessions: Check visibility logs to see when rules trigger

Build docs developers (and LLMs) love