Detecting AI Browser Extensions in the Wild
DOM injection patterns, global variables, and shadow DOM inspection techniques for detecting Claude, ChatGPT, Copilot extensions.
WebDecoy Team
WebDecoy Security Team
How to Detect AI Browser Extensions: Claude, ChatGPT, Copilot, and More
Every day, millions of users browse the web with AI assistants riding shotgun. Claude’s extension reads your screen. ChatGPT’s plugin intercepts your content. Copilot parses your code. These tools inject themselves into the DOM, wrap native APIs, and register custom elements—all to provide “helpful” AI features.
For security teams, this is a visibility problem. You don’t know who’s reading your content, what’s being exfiltrated to AI providers, or whether that “user” is a human browsing with an AI assistant or an AI agent puppeting a browser.
This post is a technical deep dive into detecting AI browser extensions. We’ll cover the fingerprints they leave behind, the techniques that work, and the detection code you can deploy today.
Why AI Extension Detection Matters
The AI browser extension market exploded in 2024. Every major AI company now ships browser plugins:
| Extension | Users (Est.) | Capabilities |
|---|---|---|
| ChatGPT for Chrome | 8M+ | Page summarization, content generation |
| Claude | 1M+ | Screen reading, context injection |
| GitHub Copilot | 15M+ | Code analysis, suggestions |
| Gemini (Google) | 5M+ | Search enhancement, summarization |
| Perplexity | 2M+ | Research, citation generation |
| Monica AI | 3M+ | Multi-model access, content tools |
These extensions don’t just passively observe. They actively modify the page, intercept network requests, and inject their UI components. The browser becomes a shared environment between your application and AI systems you never invited.
The security implications are significant:
- Data exfiltration - AI extensions can read and transmit page content to their servers
- Session hijacking - Extensions with DOM access can read tokens and cookies
- Behavioral distortion - AI-assisted users behave differently than organic users
- Competitive intelligence - Your content becomes training data
- Compliance violations - PII may be transmitted without consent
Detection isn’t about blocking legitimate users. It’s about visibility into who—and what—is accessing your application.
The Anatomy of AI Extension Injection
AI extensions inject themselves into pages through predictable patterns. Understanding these patterns is the foundation of detection.
1. DOM Element Injection
Extensions inject visible UI components (sidebars, floating buttons, context menus) and invisible scaffolding (observers, listeners, state containers).
Claude’s injection pattern:
<!-- Sidebar container -->
<div class="claude-sidebar" data-claude-root="true">
<claude-assistant>
<shadow-root>
<!-- Isolated UI components -->
</shadow-root>
</claude-assistant>
</div>
<!-- Content markers -->
<span data-claude-selected="true" class="claude-highlight">
Selected text for context
</span>ChatGPT’s injection pattern:
<!-- Floating action button -->
<div id="chatgpt-extension-root" data-chatgpt="true">
<div class="chatgpt-fab">
<!-- Trigger button -->
</div>
</div>
<!-- Content wrapper -->
<div data-chatgpt-wrapper="true">
<!-- Page content with observers -->
</div>2. Global Variable Exposure
Extensions need to maintain state across the page. They expose global variables, often namespaced to avoid conflicts:
// Claude
window.__claude__
window.__claude_extension__
window.__anthropic__
// ChatGPT
window.__chatgpt__
window.__openai__
window.__chatgpt_extension_state__
// Copilot
window.__github_copilot__
window.__copilot__
// Gemini
window.__google_ai__
window.__gemini_extension__
// Generic patterns
window.__ai_assistant__
window.__llm_extension__3. Custom Element Registration
Modern extensions use Web Components with custom element names:
// Extensions register custom elements
customElements.define('claude-assistant', ClaudeAssistant);
customElements.define('chatgpt-sidebar', ChatGPTSidebar);
customElements.define('copilot-suggestions', CopilotSuggestions);
customElements.define('gemini-panel', GeminiPanel);
customElements.define('perplexity-widget', PerplexityWidget);4. API Wrapping
Extensions intercept native browser APIs to observe network requests and DOM mutations:
// Fetch API wrapping
const originalFetch = window.fetch;
window.fetch = async function(...args) {
// Extension intercepts request
extensionObserver.logRequest(args);
return originalFetch.apply(this, args);
};
// XHR wrapping
const originalXHR = window.XMLHttpRequest;
window.XMLHttpRequest = function() {
const xhr = new originalXHR();
// Extension wraps methods
const originalOpen = xhr.open;
xhr.open = function(...args) {
extensionObserver.logRequest(args);
return originalOpen.apply(this, args);
};
return xhr;
};
// MutationObserver wrapping
const OriginalMutationObserver = window.MutationObserver;
window.MutationObserver = function(callback) {
// Extension intercepts DOM changes
const wrappedCallback = (mutations) => {
extensionObserver.logMutations(mutations);
callback(mutations);
};
return new OriginalMutationObserver(wrappedCallback);
};Detection Techniques
Technique 1: DOM Pattern Scanning
Scan for known injection patterns using attribute and class selectors:
class AIExtensionDetector {
constructor() {
this.detections = [];
this.patterns = {
claude: {
selectors: [
'[class*="claude"]',
'[data-claude]',
'[id*="claude"]',
'claude-assistant',
'claude-sidebar',
'[data-anthropic]'
],
weight: 15
},
chatgpt: {
selectors: [
'[class*="chatgpt"]',
'[data-chatgpt]',
'[id*="chatgpt"]',
'[data-openai]',
'chatgpt-sidebar',
'chatgpt-extension-root'
],
weight: 15
},
copilot: {
selectors: [
'[class*="copilot"]',
'[data-copilot]',
'[data-github-copilot]',
'copilot-suggestions',
'[id*="copilot"]'
],
weight: 15
},
gemini: {
selectors: [
'[class*="gemini"]',
'[data-gemini]',
'[data-google-ai]',
'gemini-panel',
'[id*="gemini-ext"]'
],
weight: 15
},
perplexity: {
selectors: [
'[class*="perplexity"]',
'[data-perplexity]',
'perplexity-widget',
'[id*="perplexity"]'
],
weight: 15
},
monica: {
selectors: [
'[class*="monica"]',
'[data-monica]',
'monica-ai',
'[id*="monica"]'
],
weight: 10
}
};
}
scanDOM() {
for (const [extension, config] of Object.entries(this.patterns)) {
for (const selector of config.selectors) {
try {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) {
this.detections.push({
type: 'dom_pattern',
extension,
selector,
count: elements.length,
weight: config.weight
});
}
} catch (e) {
// Invalid selector, skip
}
}
}
return this.detections;
}
}Technique 2: Global Variable Detection
Check for extension-specific global variables:
detectGlobalVariables() {
const signatures = {
// Claude / Anthropic
'__claude__': { extension: 'claude', weight: 15 },
'__claude_extension__': { extension: 'claude', weight: 15 },
'__anthropic__': { extension: 'claude', weight: 15 },
// ChatGPT / OpenAI
'__chatgpt__': { extension: 'chatgpt', weight: 15 },
'__openai__': { extension: 'chatgpt', weight: 15 },
'__chatgpt_extension_state__': { extension: 'chatgpt', weight: 15 },
// GitHub Copilot
'__github_copilot__': { extension: 'copilot', weight: 15 },
'__copilot__': { extension: 'copilot', weight: 15 },
// Google Gemini
'__google_ai__': { extension: 'gemini', weight: 15 },
'__gemini_extension__': { extension: 'gemini', weight: 15 },
// Perplexity
'__perplexity__': { extension: 'perplexity', weight: 15 },
// Monica AI
'__monica__': { extension: 'monica', weight: 10 },
'__monica_ai__': { extension: 'monica', weight: 10 },
// Generic AI patterns
'__ai_assistant__': { extension: 'unknown_ai', weight: 10 },
'__llm_extension__': { extension: 'unknown_ai', weight: 10 }
};
for (const [variable, config] of Object.entries(signatures)) {
if (typeof window[variable] !== 'undefined') {
this.detections.push({
type: 'global_variable',
extension: config.extension,
variable,
weight: config.weight
});
}
}
}Technique 3: Custom Element Registry Inspection
Modern extensions register custom HTML elements. Inspect the registry:
detectCustomElements() {
const suspiciousPatterns = [
// Primary AI assistants
{ pattern: /^claude-/, extension: 'claude', weight: 15 },
{ pattern: /^chatgpt-/, extension: 'chatgpt', weight: 15 },
{ pattern: /^copilot-/, extension: 'copilot', weight: 15 },
{ pattern: /^gemini-/, extension: 'gemini', weight: 15 },
{ pattern: /^perplexity-/, extension: 'perplexity', weight: 15 },
// Secondary AI tools
{ pattern: /^monica-/, extension: 'monica', weight: 10 },
{ pattern: /^jasper-/, extension: 'jasper', weight: 10 },
{ pattern: /^writesonic-/, extension: 'writesonic', weight: 10 },
// Generic AI patterns
{ pattern: /^ai-assistant/, extension: 'unknown_ai', weight: 10 },
{ pattern: /^llm-/, extension: 'unknown_ai', weight: 10 }
];
// Get all custom elements in the document
const allElements = document.querySelectorAll('*');
const customTags = new Set();
allElements.forEach(el => {
const tagName = el.tagName.toLowerCase();
// Custom elements must contain a hyphen
if (tagName.includes('-')) {
customTags.add(tagName);
}
});
// Check against suspicious patterns
for (const tag of customTags) {
for (const { pattern, extension, weight } of suspiciousPatterns) {
if (pattern.test(tag)) {
this.detections.push({
type: 'custom_element',
extension,
element: tag,
weight
});
}
}
}
}Technique 4: Shadow DOM Inspection
Extensions use Shadow DOM to isolate their UI. Inspect shadow roots:
detectShadowDOM() {
const elementsWithShadow = [];
// Walk the DOM tree
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ELEMENT,
null,
false
);
while (walker.nextNode()) {
const node = walker.currentNode;
if (node.shadowRoot) {
elementsWithShadow.push({
element: node,
tagName: node.tagName.toLowerCase(),
shadowRoot: node.shadowRoot
});
}
}
// Analyze shadow roots for AI extension patterns
for (const { element, tagName, shadowRoot } of elementsWithShadow) {
const shadowHTML = shadowRoot.innerHTML || '';
// Check for AI-related content in shadow DOM
const aiPatterns = [
{ regex: /claude/i, extension: 'claude' },
{ regex: /chatgpt|openai/i, extension: 'chatgpt' },
{ regex: /copilot/i, extension: 'copilot' },
{ regex: /gemini/i, extension: 'gemini' },
{ regex: /perplexity/i, extension: 'perplexity' },
{ regex: /ai-assist|llm-/i, extension: 'unknown_ai' }
];
for (const { regex, extension } of aiPatterns) {
if (regex.test(shadowHTML) || regex.test(tagName)) {
this.detections.push({
type: 'shadow_dom',
extension,
element: tagName,
weight: 15
});
}
}
}
}Technique 5: API Wrapper Detection
Detect when native APIs have been wrapped by extensions:
detectAPIWrappers() {
const wrapperSignals = [];
// Check if fetch has been wrapped
const fetchString = window.fetch.toString();
if (!fetchString.includes('[native code]')) {
wrapperSignals.push({
api: 'fetch',
reason: 'Modified fetch implementation',
weight: 5
});
}
// Check if XMLHttpRequest has been wrapped
const xhrString = window.XMLHttpRequest.toString();
if (!xhrString.includes('[native code]')) {
wrapperSignals.push({
api: 'XMLHttpRequest',
reason: 'Modified XHR implementation',
weight: 5
});
}
// Check if MutationObserver has been wrapped
const moString = window.MutationObserver.toString();
if (!moString.includes('[native code]')) {
wrapperSignals.push({
api: 'MutationObserver',
reason: 'Modified MutationObserver',
weight: 5
});
}
// Check for modified prototype methods
const prototypeChecks = [
{ obj: Element.prototype, method: 'appendChild' },
{ obj: Element.prototype, method: 'insertBefore' },
{ obj: Document.prototype, method: 'createElement' },
{ obj: EventTarget.prototype, method: 'addEventListener' }
];
for (const { obj, method } of prototypeChecks) {
const methodString = obj[method].toString();
if (!methodString.includes('[native code]')) {
wrapperSignals.push({
api: `${obj.constructor.name}.${method}`,
reason: 'Modified prototype method',
weight: 3
});
}
}
// Add detections
for (const signal of wrapperSignals) {
this.detections.push({
type: 'api_wrapper',
...signal
});
}
}Complete Detection Implementation
Here’s a production-ready detector combining all techniques:
class AIExtensionDetector {
constructor(options = {}) {
this.detections = [];
this.score = 0;
this.threshold = options.threshold || 30;
this.onDetection = options.onDetection || console.log;
}
async analyze() {
// Run all detection methods
this.scanDOM();
this.detectGlobalVariables();
this.detectCustomElements();
this.detectShadowDOM();
this.detectAPIWrappers();
// Calculate total score
this.score = this.detections.reduce((sum, d) => sum + d.weight, 0);
// Deduplicate and summarize by extension
const byExtension = {};
for (const detection of this.detections) {
const ext = detection.extension;
if (!byExtension[ext]) {
byExtension[ext] = {
extension: ext,
signals: [],
totalWeight: 0
};
}
byExtension[ext].signals.push(detection);
byExtension[ext].totalWeight += detection.weight;
}
const result = {
detected: this.score >= this.threshold,
score: Math.min(this.score, 100), // Cap at 100
extensions: Object.values(byExtension),
signals: this.detections,
timestamp: new Date().toISOString()
};
if (result.detected) {
this.onDetection(result);
}
return result;
}
// Include all detection methods from above...
scanDOM() { /* ... */ }
detectGlobalVariables() { /* ... */ }
detectCustomElements() { /* ... */ }
detectShadowDOM() { /* ... */ }
detectAPIWrappers() { /* ... */ }
}
// Usage
const detector = new AIExtensionDetector({
threshold: 30,
onDetection: (result) => {
// Send to your analytics/security backend
fetch('/api/security/ai-detection', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(result)
});
}
});
// Run detection
detector.analyze().then(result => {
if (result.detected) {
console.log('AI extensions detected:', result.extensions);
}
});Detection Scoring Model
WebDecoy uses a weighted scoring system for AI extension detection:
| Signal Type | Weight | Description |
|---|---|---|
| Primary AI Extension DOM | +15 | Claude, ChatGPT, Copilot, Gemini, Perplexity |
| Secondary AI Tool DOM | +10 | Monica, Jasper, other AI assistants |
| Global Variable (named) | +15 | window.__claude__, window.__chatgpt__, etc. |
| Custom Element (AI pattern) | +15 | <claude-assistant>, <chatgpt-sidebar> |
| Shadow DOM AI content | +15 | AI-related content in shadow roots |
| Wrapped Fetch API | +5 | Non-native fetch implementation |
| Wrapped XHR | +5 | Non-native XMLHttpRequest |
| Modified MutationObserver | +5 | Non-native MutationObserver |
| Modified prototype method | +3 | Wrapped DOM methods |
Score interpretation:
- 0-15: No AI extensions detected
- 15-30: Possible AI extension (low confidence)
- 30-50: Likely AI extension (medium confidence)
- 50-75: AI extension confirmed (high confidence)
- 75-100: Multiple AI extensions or deep integration
The score is capped at 100 and integrated with other threat signals (webdriver detection, headless browser detection, behavioral analysis) into a unified threat score.
Real-World Detection Scenarios
Scenario 1: Claude Extension User
A user with the Claude browser extension visits your application:
// Detection results
{
detected: true,
score: 45,
extensions: [{
extension: 'claude',
signals: [
{ type: 'dom_pattern', selector: '[data-claude]' },
{ type: 'global_variable', variable: '__claude__' },
{ type: 'custom_element', element: 'claude-assistant' }
],
totalWeight: 45
}]
}What this tells you: The user has Claude’s extension active. Their page content may be sent to Anthropic’s servers for context. Not necessarily malicious, but important for data governance.
Scenario 2: Multiple AI Extensions
A power user with ChatGPT, Copilot, and Monica installed:
// Detection results
{
detected: true,
score: 85,
extensions: [
{ extension: 'chatgpt', totalWeight: 30 },
{ extension: 'copilot', totalWeight: 30 },
{ extension: 'monica', totalWeight: 25 }
]
}What this tells you: This user has significant AI tooling active. Every page they visit is potentially being analyzed by three different AI systems. Consider whether this user should access sensitive content.
Scenario 3: API Wrapper Only
Detection finds wrapped APIs but no specific extension:
// Detection results
{
detected: false,
score: 15,
extensions: [{
extension: 'unknown',
signals: [
{ type: 'api_wrapper', api: 'fetch', weight: 5 },
{ type: 'api_wrapper', api: 'MutationObserver', weight: 5 },
{ type: 'api_wrapper', api: 'Element.appendChild', weight: 3 }
],
totalWeight: 15
}]
}What this tells you: Something is wrapping browser APIs, but it’s below the detection threshold. Could be an AI extension you don’t have signatures for, a browser dev tool, or a legitimate extension (password manager, ad blocker). Worth monitoring but not blocking.
Server-Side Correlation
Client-side detection should be validated server-side:
# Python/Flask example
@app.route('/api/security/ai-detection', methods=['POST'])
def log_ai_detection():
data = request.json
client_ip = request.remote_addr
user_agent = request.headers.get('User-Agent', '')
# Log detection event
detection_record = {
'timestamp': datetime.utcnow(),
'ip': client_ip,
'user_agent': user_agent,
'score': data.get('score', 0),
'extensions': data.get('extensions', []),
'session_id': session.get('id')
}
db.ai_detections.insert(detection_record)
# Correlate with other signals
session_signals = get_session_signals(session.get('id'))
# AI extension + headless browser = likely bot
if data['score'] > 30 and session_signals.get('headless_score', 0) > 50:
flag_suspicious_session(session.get('id'))
# AI extension + unusual request patterns = data scraping
if data['score'] > 30 and session_signals.get('request_rate', 0) > 100:
flag_potential_scraper(session.get('id'))
return jsonify({'status': 'logged'})Privacy and Legal Considerations
AI extension detection raises privacy questions. Be thoughtful about:
- Disclosure - Your privacy policy should mention browser fingerprinting and extension detection
- Purpose limitation - Use detection data for security, not advertising
- Data minimization - Log signals, not full browser fingerprints
- User rights - Provide transparency about what you detect and why
- Regional compliance - GDPR, CCPA, and other regulations may apply
Detection should inform your security posture, not create a surveillance system.
Evasion and Arms Race
Sophisticated extensions will evolve to avoid detection:
- Randomized class names -
claude-sidebarbecomesxK7mN2pQ - Delayed injection - Wait for detection scripts to run first
- iframe isolation - Run extension UI in separate iframe origin
- Proxy global variables - Use Proxy objects to hide signatures
- Native code spoofing - Return fake
[native code]for wrapped APIs
How to stay ahead:
- Monitor extension updates for detection evasion
- Track behavioral patterns, not just static signatures
- Combine client-side detection with server-side analysis
- Use honeypot elements that only extensions interact with
- Continuously update detection signatures
Conclusion
AI browser extensions are everywhere. Your users have them. Your competitors use them. And increasingly, AI agents use browser automation to interact with your application through these same patterns.
Detection isn’t about blocking users who want AI assistance. It’s about visibility:
- Know what’s reading your content
- Identify when AI is in the loop
- Distinguish humans from AI-assisted sessions
- Protect sensitive data from unintended exfiltration
The techniques in this post give you that visibility. DOM scanning catches injected elements. Global variable detection finds extension state. Custom element inspection reveals Web Components. Shadow DOM analysis uncovers isolated UI. API wrapper detection shows when native functions are intercepted.
Combined with headless browser detection and behavioral analysis, you can build a complete picture of who—and what—is accessing your application.
The AI extension landscape is evolving rapidly. New extensions ship monthly. Existing ones update weekly. Detection must be continuous, not one-time.
WebDecoy provides real-time AI extension detection as part of our bot detection platform. We maintain signatures for all major AI extensions, detect unknown extensions through behavioral patterns, and integrate with your existing security infrastructure.
Want to detect AI extensions in your application? Explore WebDecoy’s detection capabilities or contact our team for a demo.
Share this post
Like this post? Share it with your friends!
Want to see WebDecoy in action?
Get a personalized demo from our team.