Skip to main content

Overview

Blocking Rules are ContextFort’s primary mechanism for preventing context mixing — the dangerous scenario where information from one website leaks into another website within the same agent session.
Context Mixing Risk: An agent reading your Gmail inbox and then navigating to a support ticket site could accidentally paste confidential email content into a public ticket.

Types of Blocking Rules

Domain Blocking

Block navigation between specific domains

URL Pair Blocking

Block specific URL combinations

Action Blocking

Block clicks/inputs on specific elements

Domain Blocking Rules

Rule Format

Domain blocking rules are stored as an array of domain pairs:
const urlBlockingRules = [
  ["gmail.com", "support.example.com"],
  ["docs.google.com", ""],
  ["", "admin.internal.com"]
];

Rule Types

Format: ["domain1.com", "domain2.com"]Behavior: If agent visits domain1.com, it cannot navigate to domain2.com (and vice versa)Example:
["gmail.com", "slack.com"]
Use Case: Prevent email content from leaking into Slack messages

Implementation

chrome-extension/background.js
function matchesHostname(hostname, pattern) {
  if (pattern === "") return true;
  return hostname === pattern || hostname.endsWith('.' + pattern);
}

function shouldBlockNavigation(newUrl, visitedUrls) {
  const newHostname = getHostname(newUrl);
  if (!newHostname) return { blocked: false };

  // Check restricted access rules (["", "domain"])
  for (const [domain1, domain2] of urlBlockingRules) {
    if (domain1 === "" && matchesHostname(newHostname, domain2)) {
      // Check if any visited URL is NOT this domain
      const hasOtherDomain = visitedUrls.some(url => 
        !matchesHostname(getHostname(url), domain2)
      );
      if (hasOtherDomain) {
        return {
          blocked: true,
          reason: `Use of Agent mode is not allowed in ${newHostname}.`,
          conflictingUrl: null
        };
      }
    }
  }

  // Check isolation rules (["domain", ""])
  for (const visitedUrl of visitedUrls) {
    const visitedHostname = getHostname(visitedUrl);
    if (!visitedHostname) continue;

    for (const [domain1, domain2] of urlBlockingRules) {
      if (domain2 === "" && matchesHostname(visitedHostname, domain1)) {
        return {
          blocked: true,
          reason: `Context from ${visitedHostname} cannot persist in other URLs. Please start a new chat.`,
          conflictingUrl: visitedUrl
        };
      }

      // Check bidirectional rules
      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. Please start a new chat.`,
            conflictingUrl: visitedUrl
          };
        }
      }
    }
  }

  return { blocked: false };
}
Blocking happens at two points:
chrome-extension/background.js
chrome.webNavigation.onBeforeNavigate.addListener(async (details) => {
  if (details.frameId !== 0) return; // Only main frame

  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'
    );
  }
});
chrome-extension/background.js
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'
      );
    }
  }
});

URL Pair Blocking

For more granular control, block specific URL combinations (not just domains):
const urlPairBlockingRules = [
  [
    "https://mail.google.com/mail/u/0/#inbox",
    "https://support.example.com/new-ticket"
  ],
  [
    "https://docs.google.com/document/d/abc123/edit",
    "https://github.com/myorg/repo/issues/new"
  ]
];

URL Pair Implementation

chrome-extension/background.js
function shouldBlockNavigation(newUrl, visitedUrls) {
  // ... (domain blocking logic above) ...

  // Check URL pair blocking rules
  for (const visitedUrl of visitedUrls) {
    for (const [url1, url2] of urlPairBlockingRules) {
      // Compare full URLs
      const match1 = newUrl === url2 && visitedUrl === url1;
      const match2 = newUrl === url1 && visitedUrl === url2;
      if (match1 || match2) {
        const visitedHostname = getHostname(visitedUrl);
        const newHostname = getHostname(newUrl);
        return {
          blocked: true,
          reason: `Navigation to ${newHostname} is blocked because context from ${visitedHostname} persists. Please start a new chat.`,
          conflictingUrl: visitedUrl
        };
      }
    }
  }

  return { blocked: false };
}
URL Pair vs Domain Blocking: Use URL pairs when you need to block specific page combinations but allow general navigation between domains.

Action Blocking

Block agent clicks or inputs on specific elements:

Blocked Action Format

const blockedActions = [
  {
    url: "https://example.com/admin",
    title: "Admin Panel - Example",
    actionType: "click",
    elementTag: "BUTTON",
    elementId: "delete-all-users",
    elementClass: "btn-danger",
    elementText: "Delete All Users",
    elementType: null,
    elementName: null
  },
  {
    url: "https://mail.google.com/mail/u/0/",
    title: null, // Match any title
    actionType: "input",
    elementTag: "TEXTAREA",
    elementId: null,
    elementClass: "compose-textarea",
    elementText: null,
    elementType: null,
    elementName: "body"
  }
];

Action Blocking Implementation

chrome-extension/content.js
let blockedElements = [];

// Load blocked actions from storage
(async () => {
  const result = await chrome.storage.local.get(['blockedActions']);
  if (result.blockedActions) {
    blockedElements = result.blockedActions;
  }
})();

function isElementBlocked(element, metadata) {
  const tag = element.tagName;
  const id = element.id || null;
  const className = element.className || null;
  const text = element.textContent?.trim() || null;
  const elementType = element.type || null;
  const elementName = element.name || null;

  return (
    metadata.elementTag === tag &&
    metadata.elementId === id &&
    metadata.elementClass === className &&
    (metadata.elementText === null || metadata.elementText === text) &&
    metadata.elementType === elementType &&
    metadata.elementName === elementName
  );
}

function shouldBlockClick(element) {
  const currentUrl = window.location.href;
  const currentTitle = document.title;
  let currentElement = element;

  // Walk up the DOM tree to check parent elements
  while (currentElement && currentElement !== document.body) {
    for (const blockedMeta of blockedElements) {
      if (blockedMeta.actionType !== 'click') continue;
      if (blockedMeta.url && blockedMeta.url !== currentUrl) continue;
      if (blockedMeta.title && blockedMeta.title !== currentTitle) continue;

      if (isElementBlocked(currentElement, blockedMeta)) {
        return true;
      }
    }
    currentElement = currentElement.parentElement;
  }
  return false;
}

function onBlockedElementClick(e) {
  if (shouldBlockClick(e.target)) {
    e.preventDefault();
    e.stopPropagation();
    e.stopImmediatePropagation();
    showBlockedFeedback(e.target); // Red border flash
    safeSendMessage({
      type: 'ACTION_BLOCKED',
      actionType: 'click',
      url: window.location.href,
      title: document.title
    });
    return false;
  }
}

// Capture phase listener (runs before agent listeners)
document.addEventListener('click', onBlockedElementClick, true);

Visual Feedback

chrome-extension/content.js
function showBlockedFeedback(element) {
  const originalBorder = element.style.border;
  element.style.border = "2px solid red";

  setTimeout(() => {
    element.style.border = originalBorder;
  }, 500);
}
Why capture phase? Action blocking listeners run in the capture phase (addEventListener(..., true)) to intercept events before the agent’s event listeners can process them.

Governance Rules (Advanced)

Governance rules use Chrome’s Declarative Net Request (DNR) API for more powerful blocking:
chrome-extension/background.js
let governanceRules = {
  disallow_clickable_urls: false,
  disallow_query_params: false
};

const DNR_RULE_IDS = {
  DISALLOW_CLICKABLE_URLS: 1000,
  DISALLOW_QUERY_PARAMS: 1001
};

async function updateDNRRules() {
  const rulesToAdd = [];
  const ruleIdsToRemove = [];

  if (governanceRules.disallow_clickable_urls) {
    rulesToAdd.push({
      id: DNR_RULE_IDS.DISALLOW_CLICKABLE_URLS,
      priority: 1,
      action: { type: "block" },
      condition: {
        initiatorDomains: ["fcoeoabgfenejglbffodgkkbkcdhcgfn"],
        resourceTypes: ["main_frame"],
        regexFilter: "^https?://"
      }
    });
  }

  if (governanceRules.disallow_query_params) {
    rulesToAdd.push({
      id: DNR_RULE_IDS.DISALLOW_QUERY_PARAMS,
      priority: 1,
      action: { type: 'block' },
      condition: {
        initiatorDomains: ["fcoeoabgfenejglbffodgkkbkcdhcgfn"],
        resourceTypes: ['main_frame'],
        urlFilter: '|http*://*?*'
      }
    });
  }

  await chrome.declarativeNetRequest.updateDynamicRules({
    removeRuleIds: ruleIdsToRemove,
    addRules: rulesToAdd
  });
}
Governance Rules are experimental and may block legitimate agent behavior. Use with caution.

User Notifications

When a blocking rule triggers, ContextFort shows an in-page notification:
chrome-extension/background.js
function showInPageNotification(tabId, title, message, type = 'error') {
  try {
    chrome.tabs.sendMessage(tabId, {
      type: 'SHOW_NOTIFICATION',
      title: title,
      message: message,
      notificationType: type
    });
  } catch (e) {
    console.error('[ContextFort] Failed to show in-page notification:', e);
  }
}

Notification UI

chrome-extension/content.js
function showInPageNotification(title, message, type = 'error') {
  const notification = document.createElement('div');
  notification.id = 'contextfort-notification';
  notification.style.cssText = `
    position: fixed;
    top: 20px;
    right: 20px;
    min-width: 320px;
    background: white;
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
    z-index: 2147483647;
    padding: 16px;
    border-left: 4px solid ${type === 'error' ? '#DC2626' : '#2563EB'};
  `;

  notification.innerHTML = `
    <div style="display: flex; align-items: flex-start; gap: 12px;">
      <div style="font-size: 24px;">${type === 'error' ? '⛔' : 'ℹ️'}</div>
      <div style="flex: 1;">
        <div style="font-weight: 600; font-size: 14px; margin-bottom: 4px;">
          ${title}
        </div>
        <div style="font-size: 13px; color: #6B7280;">
          ${message}
        </div>
      </div>
    </div>
  `;

  document.body.appendChild(notification);

  // Auto-dismiss after 5 seconds
  setTimeout(() => notification.remove(), 5000);
}

Managing Blocking Rules

All blocking rules can be managed through the Dashboard:

Load Rules from Storage

chrome-extension/background.js
(async () => {
  const result = await chrome.storage.local.get([
    'urlBlockingRules',
    'urlPairBlockingRules',
    'blockedActions',
    'governanceRules'
  ]);

  if (result.urlBlockingRules) {
    urlBlockingRules = result.urlBlockingRules;
  }
  if (result.urlPairBlockingRules) {
    urlPairBlockingRules = result.urlPairBlockingRules;
  }
  if (result.blockedActions) {
    blockedActions = result.blockedActions;
  }
  if (result.governanceRules) {
    governanceRules = result.governanceRules;
    await updateDNRRules();
  }
})();

Update Rules via Dashboard

// Add new domain blocking rule
chrome.storage.local.get(['urlBlockingRules'], (result) => {
  const rules = result.urlBlockingRules || [];
  rules.push(["gmail.com", "slack.com"]);
  chrome.storage.local.set({ urlBlockingRules: rules }, () => {
    chrome.runtime.sendMessage({ type: 'RELOAD_BLOCKING_RULES', rules });
  });
});

Example Use Cases

Scenario: Agent should not navigate from Gmail to any other siteRule:
urlBlockingRules.push(["gmail.com", ""]);
Result: Agent can only operate within Gmail, cannot leak email content elsewhere
Scenario: Agent should not mix work Slack with personal GmailRule:
urlBlockingRules.push(["slack.com", "gmail.com"]);
Result: Bidirectional block prevents context mixing between work and personal accounts
Scenario: Never allow agent mode on banking websiteRule:
urlBlockingRules.push(["", "mybank.com"]);
Result: Agent mode immediately stops if agent attempts to navigate to banking site
Scenario: Prevent agent from clicking “Delete All” buttonRule:
blockedActions.push({
  url: "https://admin.example.com",
  actionType: "click",
  elementTag: "BUTTON",
  elementId: "delete-all-btn",
  elementClass: null,
  elementText: "Delete All",
  elementType: null,
  elementName: null
});
Result: Click event is intercepted and blocked with red border flash

Next Steps

Visibility Dashboard

See blocking events in real-time

Session Isolation

Understand how cookie swapping works with blocking rules

Dashboard Controls

Configure blocking rules in the UI

Security Policy

Learn about ContextFort’s security model

Build docs developers (and LLMs) love