Overview
ContextFort automatically detects when browser agents are active and provides comprehensive monitoring capabilities. Every agent interaction is tracked, captured, and stored locally for review and audit.
Privacy First : All screenshots and session data are stored locally on your machine. Nothing is transmitted to external servers.
How Agent Detection Works
ContextFort uses Chrome’s Tab Groups API to detect agent activity:
Tab Group Monitoring : When an agent creates or updates a tab group with the ⌛ emoji, ContextFort detects this as an active agent session
Automatic Session Creation : A new session is created and tracking begins immediately
Event Listeners : Content scripts listen for all agent interactions (clicks, inputs, navigation)
Screenshot Capture : Screenshots are automatically captured at key moments
Detection Implementation
chrome-extension/background.js
chrome . tabGroups . onUpdated . addListener ( async ( group ) => {
const previousTitle = groupTitles . get ( group . id );
const currentTitle = group . title ;
const hadSandTimer = previousTitle && previousTitle . includes ( '⌛' );
const hasSandTimer = currentTitle && currentTitle . includes ( '⌛' );
if ( ! hadSandTimer && hasSandTimer ) {
// Agent started - begin tracking
chrome . tabs . query ({ groupId: group . id }, async ( tabs ) => {
if ( tabs . length === 0 ) return ;
const tab = tabs [ 0 ];
onMessageAgentDetected ( tab , group . id );
await chrome . tabs . sendMessage ( tab . id , { type: 'START_AGENT_LISTENING' });
});
}
});
ContextFort specifically monitors for the ⌛ (hourglass) emoji used by Claude’s Computer Use agent mode.
Session Tracking
What Gets Tracked
Each agent session captures:
Session Metadata
Unique session ID
Start and end timestamps
Session duration
Tab information
Navigation History
All URLs visited
Page titles
Domain transitions
Navigation timestamps
Agent Actions
Click events with coordinates
Input field interactions
Right-click actions
Double-click events
Screenshots
Before/after action pairs
Page reads
Screenshot count per session
Full page captures
Session Lifecycle
chrome-extension/background.js
async function getOrCreateSession ( groupId , firstTabId , firstTabUrl , firstTabTitle ) {
if ( sessions . has ( groupId )) {
return sessions . get ( groupId );
}
const sessionId = Date . now ();
const session = {
id: sessionId ,
groupId: groupId ,
startTime: new Date (). toISOString (),
endTime: null ,
duration: null ,
tabId: firstTabId ,
tabTitle: firstTabTitle || 'Unknown' ,
tabUrl: firstTabUrl || 'Unknown' ,
screenshotCount: 0 ,
status: 'active' ,
visitedUrls: []
};
sessions . set ( groupId , session );
// Store in Chrome local storage
const result = await chrome . storage . local . get ([ 'sessions' ]);
const allSessions = result . sessions || [];
allSessions . unshift ( session );
await chrome . storage . local . set ({ sessions: allSessions });
return session ;
}
Screenshot Capture
Automatic Screenshot Triggers
Screenshots are captured automatically when agents:
Before Screenshot : Captured immediately when agent clicks
After Screenshot : Captured 300ms after click to show the resultchrome-extension/background.js
if ( message . action === 'click' ) {
chrome . tabs . captureVisibleTab ( tab . windowId , { format: 'png' }, ( dataUrl1 ) => {
saveEventData ( dataUrl1 , false , currentTab . url , currentTab . title , null ). then ( actionId => {
setTimeout (() => {
chrome . tabs . captureVisibleTab ( tab . windowId , { format: 'png' }, ( dataUrl2 ) => {
saveEventData ( dataUrl2 , true , resultTab . url , resultTab . title , actionId );
});
}, 300 );
});
});
}
Page Read Events : Captured when agent navigates to a new URL
No Screenshot : Only metadata is stored to track navigationchrome-extension/background.js
async function addPageReadAndVisitedUrl ( session , tabId , url , title ) {
const pageReadData = {
id: Date . now () + Math . random (),
sessionId: session . id ,
tabId: tabId ,
url: url ,
title: title ,
reason: 'page_read' ,
timestamp: new Date (). toISOString (),
dataUrl: null ,
eventType: 'page_read' ,
eventDetails: {
actionType: 'page_read'
}
};
await queuedStorageWrite ( pageReadData , activation );
}
Screenshot Storage
Data Structure
Retention Policy
Security
{
id : 1709567890123.456 ,
sessionId : 1709567800000 ,
tabId : 1234 ,
url : "https://example.com/page" ,
title : "Example Page" ,
reason : "agent_event" ,
timestamp : "2025-02-28T12:34:50.123Z" ,
dataUrl : "data:image/png;base64,..." ,
eventType : "click" ,
eventDetails : {
element : {
tag : "BUTTON" ,
id : "submit-btn" ,
className : "primary-button" ,
text : "Submit" ,
type : null ,
name : null
},
coordinates : { x : 450 , y : 300 },
inputValue : null ,
actionType : "click"
}
}
Max Screenshots : 100 screenshots stored at any time
FIFO Queue : Oldest screenshots are deleted when limit is reached
Local Storage : All data in Chrome’s local storage (encrypted at rest)
No Expiration : Screenshots never expire automatically
chrome-extension/background.js
async function processStorageQueue () {
const result = await chrome . storage . local . get ([ 'screenshots' , 'sessions' ]);
const screenshots = result . screenshots || [];
screenshots . push ( screenshotData );
if ( screenshots . length > 100 ) {
screenshots . shift (); // Remove oldest
}
await chrome . storage . local . set ({ screenshots: screenshots });
}
Screenshots may contain sensitive data : PII, PHI, financial information, credentials
All data stored locally (never transmitted)
Encrypted at rest by Chrome’s built-in encryption
User controls all retention and deletion
See DATA_POLICY.md for details
Event Listeners
Content Script Implementation
The content script (injected into every page) captures all agent interactions:
chrome-extension/content.js
let agentModeActive = false ;
function onClickCapture ( e ) {
if ( agentModeActive ) {
safeSendMessage ({
type: 'SCREENSHOT_TRIGGER' ,
action: 'click' ,
eventType: 'click' ,
element: captureElement ( e . target ),
coordinates: {
x: e . clientX * window . devicePixelRatio ,
y: e . clientY * window . devicePixelRatio
},
url: window . location . href ,
title: document . title
});
}
}
function startListening () {
if ( agentModeActive ) return ;
agentModeActive = true ;
document . addEventListener ( 'click' , onClickCapture , true );
document . addEventListener ( 'dblclick' , onDblClickCapture , true );
document . addEventListener ( 'contextmenu' , onContextMenuCapture , true );
document . addEventListener ( 'input' , onInputCapture , true );
}
Element Capture
All element metadata is captured for audit purposes:
chrome-extension/content.js
function captureElement ( target ) {
if ( ! target ) return null ;
let className = null ;
if ( target . className ) {
if ( typeof target . className === 'string' ) {
className = target . className ;
} else if ( target . className . baseVal !== undefined ) {
// Handle SVG elements
className = target . className . baseVal ;
}
}
return {
tag: target . tagName ,
id: target . id || null ,
className: className ,
text: target . textContent ?. substring ( 0 , 50 ) || null ,
type: target . type || null ,
name: target . name || null
};
}
Viewing Session History
Access the Visibility dashboard to:
View All Sessions : See complete list of all agent sessions
Session Details : Click into any session to see:
Full timeline of events
All screenshots with timestamps
Navigation history
Session duration and metadata
Export Data : Download sessions and screenshots for offline review
Open Visibility Dashboard Click the ContextFort icon in Chrome to open the dashboard
Data Deletion
Delete All Data
Go to chrome://extensions/
Find ContextFort
Click “Remove” to uninstall (deletes all data)
Delete Specific Sessions
Per-session deletion is not yet available in the dashboard. You can delete all sessions using chrome.storage.local.remove(['sessions']).
Clear Storage
// Manual deletion via console
chrome . storage . local . remove ([ 'sessions' , 'screenshots' ], () => {
console . log ( 'All session data deleted' );
});
Queued Storage Writes
To prevent storage bottlenecks, screenshot writes are queued:
chrome-extension/background.js
const storageWriteQueue = [];
let isProcessingQueue = false ;
async function queuedStorageWrite ( screenshotData , activation ) {
return new Promise (( resolve ) => {
storageWriteQueue . push ({ screenshotData , activation , resolve });
processStorageQueue ();
});
}
async function processStorageQueue () {
if ( isProcessingQueue || storageWriteQueue . length === 0 ) return ;
isProcessingQueue = true ;
while ( storageWriteQueue . length > 0 ) {
const { screenshotData , activation , resolve } = storageWriteQueue . shift ();
try {
const result = await chrome . storage . local . get ([ 'screenshots' , 'sessions' ]);
const screenshots = result . screenshots || [];
screenshots . push ( screenshotData );
await chrome . storage . local . set ({ screenshots: screenshots });
resolve ( screenshotData . id );
} catch ( error ) {
console . error ( 'Storage write failed:' , error );
resolve ( null );
}
}
isProcessingQueue = false ;
}
Next Steps
Session Isolation Learn how ContextFort swaps cookies to isolate agent sessions
Blocking Rules Configure URL and domain blocking to prevent context mixing
Screenshots Deep dive into screenshot capture and storage
Dashboard Explore the Visibility dashboard UI