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
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
Bidirectional Block
Isolation Block
Restricted Access
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 messagesFormat : ["domain.com", ""]Behavior : If agent visits domain.com, it cannot navigate to ANY other domainExample :[ "admin.internal.com" , "" ]
Use Case : Force agent to stay on admin panel only (isolation mode)Format : ["", "domain.com"]Behavior : Agent cannot use agent mode on domain.com at allExample :Use Case : Completely disable agent mode for sensitive domains
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 };
}
Navigation Blocking
Blocking happens at two points:
Before Navigation (webNavigation.onBeforeNavigate)
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'
);
}
});
After URL Change (tabs.onUpdated)
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:
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
Separate Work and Personal
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
Disable Agent on Banking Site
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
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