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:

ExtensionUsers (Est.)Capabilities
ChatGPT for Chrome8M+Page summarization, content generation
Claude1M+Screen reading, context injection
GitHub Copilot15M+Code analysis, suggestions
Gemini (Google)5M+Search enhancement, summarization
Perplexity2M+Research, citation generation
Monica AI3M+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:

  1. Data exfiltration - AI extensions can read and transmit page content to their servers
  2. Session hijacking - Extensions with DOM access can read tokens and cookies
  3. Behavioral distortion - AI-assisted users behave differently than organic users
  4. Competitive intelligence - Your content becomes training data
  5. 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 TypeWeightDescription
Primary AI Extension DOM+15Claude, ChatGPT, Copilot, Gemini, Perplexity
Secondary AI Tool DOM+10Monica, Jasper, other AI assistants
Global Variable (named)+15window.__claude__, window.__chatgpt__, etc.
Custom Element (AI pattern)+15<claude-assistant>, <chatgpt-sidebar>
Shadow DOM AI content+15AI-related content in shadow roots
Wrapped Fetch API+5Non-native fetch implementation
Wrapped XHR+5Non-native XMLHttpRequest
Modified MutationObserver+5Non-native MutationObserver
Modified prototype method+3Wrapped 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'})

AI extension detection raises privacy questions. Be thoughtful about:

  1. Disclosure - Your privacy policy should mention browser fingerprinting and extension detection
  2. Purpose limitation - Use detection data for security, not advertising
  3. Data minimization - Log signals, not full browser fingerprints
  4. User rights - Provide transparency about what you detect and why
  5. 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-sidebar becomes xK7mN2pQ
  • 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:

  1. Monitor extension updates for detection evasion
  2. Track behavioral patterns, not just static signatures
  3. Combine client-side detection with server-side analysis
  4. Use honeypot elements that only extensions interact with
  5. 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.

Want to see WebDecoy in action?

Get a personalized demo from our team.

Request Demo