echokit

CORS Implementation Methods - Comparison

Different Approaches to CORS Override in Chrome Extensions

There are 3 main approaches to implementing CORS override in Chrome extensions, each with different trade-offs:


1. webRequest API (Manifest V2 - LEGACY) 🔴

How Moesif CORS Extension Works

Implementation:

chrome.webRequest.onHeadersReceived.addListener(
  function(details) {
    details.responseHeaders.push({
      name: "Access-Control-Allow-Origin",
      value: "*"
    });
    details.responseHeaders.push({
      name: "Access-Control-Allow-Methods", 
      value: "*"
    });
    details.responseHeaders.push({
      name: "Access-Control-Allow-Headers",
      value: "*"
    });
    return { responseHeaders: details.responseHeaders };
  },
  { urls: ["<all_urls>"] },
  ["blocking", "responseHeaders", "extraHeaders"]
);

Pros ✅

Cons ❌

Status: 🔴 DEPRECATED - DO NOT USE FOR NEW EXTENSIONS


2. declarativeNetRequest API (Manifest V3 - OUR CURRENT APPROACH) 🟢

How EchoKit Implements CORS

Implementation:

chrome.declarativeNetRequest.updateDynamicRules({
  addRules: [{
    id: 1001,
    priority: 1,
    action: {
      type: 'modifyHeaders',
      responseHeaders: [
        { header: 'Access-Control-Allow-Origin', operation: 'set', value: '*' },
        { header: 'Access-Control-Allow-Methods', operation: 'set', value: '*' },
        { header: 'Access-Control-Allow-Headers', operation: 'set', value: '*' }
      ]
    },
    condition: { 
      urlFilter: '|http',
      resourceTypes: ['xmlhttprequest', 'main_frame', ...]
    }
  }]
});

Pros ✅

Cons ❌


3. Hybrid: declarativeNetRequest + webRequest (Non-blocking) 🟡

How Some Extensions Work Around MV3 Limitations

Implementation:

// Use DNR for most CORS cases (fast, declarative)
chrome.declarativeNetRequest.updateDynamicRules({...});

// Use non-blocking webRequest for observation/analytics ONLY
chrome.webRequest.onHeadersReceived.addListener(
  function(details) {
    // Can READ headers but CANNOT modify them (no 'blocking')
    console.log('Request to:', details.url);
    console.log('Response headers:', details.responseHeaders);
  },
  { urls: ["<all_urls>"] },
  ["responseHeaders"] // NO 'blocking' - observation only
);

Pros ✅

Cons ❌

Status: 🟡 USEFUL FOR ANALYTICS, DOESN’T SOLVE CREDENTIALS ISSUE


Comparison Table

Feature webRequest (MV2) declarativeNetRequest (MV3) Our Implementation
Manifest V3 Compatible ❌ No ✅ Yes ✅ Yes
Future-proof ❌ Deprecated 2025+ ✅ Yes ✅ Yes
Performance ❌ Slow (intercepts all) ✅ Fast (declarative) ✅ Fast
Privacy-friendly ❌ Reads all traffic ✅ No traffic access ✅ No traffic access
Scary permissions ❌ Yes (webRequestBlocking) ✅ No ✅ No
CORS credentials support ✅ Yes (exact origin) ❌ No (wildcard only) ❌ No
Tab/Domain scoping ✅ Yes (custom logic) ✅ Yes (session rules) ✅ Yes
Dynamic header values ✅ Yes ❌ No (static only) ❌ No
Read response body ✅ Yes ❌ No ❌ No
Zero overhead when off ❌ No ✅ Yes ✅ Yes
Enterprise only (MV3) ✅ Yes (blocking) ❌ No ❌ No

Why Moesif CORS Extension Still Works Better

Moesif is still on Manifest V2 using the deprecated webRequest API.

Their advantages:

  1. Can set exact origin - Access-Control-Allow-Origin: https://example.com
  2. Credentials work - Can use Access-Control-Allow-Credentials: true with exact origin
  3. Dynamic behavior - Can read request origin and echo it back
  4. Handles preflight - Full control over OPTIONS requests with extraHeaders

Their problem:


Should We Switch Approaches?

Verdict: YES - This is the right approach for MV3

Reasoning:

Trade-off: Can’t support Access-Control-Allow-Credentials: true use cases


Option B: Add Observational webRequest (HYBRID) 🟡

Verdict: MAYBE - Limited benefits for our use case

What we could add:

// Add to background.js
chrome.webRequest.onHeadersReceived.addListener(
  function(details) {
    // Can only READ headers (non-blocking)
    // Log CORS-related info for debugging
    const corsHeaders = details.responseHeaders?.filter(h =>
      h.name.toLowerCase().startsWith('access-control')
    );
    console.log('[EchoKit CORS Debug]', details.url, corsHeaders);
  },
  { urls: ["<all_urls>"] },
  ["responseHeaders"] // NO 'blocking' - observation only
);

Benefits:

Downsides:

Recommendation: NOT WORTH IT - Our diagnostics tool already provides sufficient debugging


Option C: Hybrid with Content Script Injection 🟡

Verdict: POSSIBLE - But very hacky

How it works:

  1. Use DNR for basic CORS override
  2. For credentialed requests, inject content script to:
    • Intercept fetch/XHR before it’s sent
    • Rewrite requests to use a proxy endpoint
    • Proxy adds correct origin header server-side

Example:

// Content script injected into page
const originalFetch = window.fetch;
window.fetch = function(url, options) {
  if (needsCredentials(options)) {
    // Route through extension proxy
    return originalFetch('chrome-extension://ID/proxy', {
      method: 'POST',
      body: JSON.stringify({ url, options })
    });
  }
  return originalFetch(url, options);
};

Benefits:

Downsides:

Recommendation: NO - Way too complex for minimal gain


Advanced Techniques Other Extensions Use

1. Per-Site Rule Management (Like AlexBatok/cors-bypass)

What we could adopt:

Implementation:

// Store per-domain CORS settings
const corsEnabledDomains = new Set(['localhost', 'dev.example.com']);

// Update DNR rules to match only enabled domains
await chrome.declarativeNetRequest.updateSessionRules({
  addRules: [{
    id: 1001,
    action: { type: 'modifyHeaders', ... },
    condition: {
      requestDomains: Array.from(corsEnabledDomains),
      resourceTypes: [...]
    }
  }]
});

Benefit: More granular control, safer


2. Configurable CORS Headers

What we could adopt:

Implementation:

// User-configurable CORS settings
const corsConfig = {
  allowOrigin: { enabled: true, value: '*' },
  allowMethods: { enabled: true, value: 'GET,POST,PUT,DELETE' },
  allowHeaders: { enabled: true, value: '*' },
  allowCredentials: { enabled: false }, // Can't use with wildcard
  exposeHeaders: { enabled: false, value: '' },
  maxAge: { enabled: true, value: '86400' }
};

// Build headers array from config
const responseHeaders = Object.entries(corsConfig)
  .filter(([k, v]) => v.enabled)
  .map(([k, v]) => ({
    header: `Access-Control-${pascalCase(k)}`,
    operation: 'set',
    value: v.value
  }));

Benefit: More flexibility for edge cases


3. Debug Panel with Request Modification Count

What we could adopt from AlexBatok:

UI Enhancement:

CORS Override: ON (Domain scope)
━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Active on: localhost:3000
📊 Requests modified: 47
🔍 Last modified: 2s ago
   POST /api/users → 200 OK

Benefit: Better visibility into what’s happening


Recommendations for EchoKit

Immediate (Already Implemented) ✅

Short-term Improvements (OPTIONAL) 🟡

  1. Per-domain CORS toggle (instead of global ON/OFF)
    • Let users enable CORS for specific domains
    • Default blocklist for sensitive sites
    • Prevents accidental breakage
  2. CORS modification counter
    • Show “Modified 47 requests” badge
    • Help users confirm CORS is working
    • Simple UI enhancement
  3. Configurable headers
    • Let users customize which CORS headers to inject
    • Add Access-Control-Expose-Headers option
    • Add Access-Control-Max-Age configuration

Final Verdict

Our implementation is CORRECT and OPTIMAL for Manifest V3

Why:

  1. Uses the right API (declarativeNetRequest)
  2. Future-proof (won’t be deprecated)
  3. Privacy-friendly (no traffic inspection)
  4. Performance-efficient (declarative rules)
  5. Scope-aware (tab/domain/global)
  6. Good diagnostics (debugging support)

Why Moesif appears “better”:

Trade-offs we accept:

Why these trade-offs are acceptable:

No need to change our approach - We made the right choice! 🎯