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.

72Risk

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

Publishers can request a review.

Findings

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

HighunwantedCWE-200Dynamic sandbox

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.

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.

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-Type
application/json;charset=utf-8
Authorization
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIyNTJiYjZjZi0xOGE4LTQ0OWUtYmFkNy1mZjUzZWQyYTUxNGYiLCJpYXQiOjE3NDQ3MDAwMDB9.example
Body
{"eventType":1,"request":{"enRequest":"\"AAECBAUGB+vXzpqLkYz8Sl3mVHKxOeT5DqFbgBCAiJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycq/y83Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+""}}

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.

Decoded value
[  {    "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"  }]

What the extension sends on every page you visit

  • The URL you are visiting
    https://www.amazon.com/dp/B0CXYZ12345

    The exact page you navigated to, including any query parameters.

  • The URL you came from
    https://www.google.com/search?q=amazon+deals

    The previous page you were on in that tab, builds a chain of your browsing activity.

  • Your persistent user ID
    252bb6cf-18a8-449e-bad7-ff53ed2a514f

    A UUID that stays the same across sessions, letting the server link all your visits together over time.

  • Timestamp
    2026-04-15T09:48:07.123Z

    When the visit happened, in both ISO date format and Unix milliseconds.

  • Request type
    GET

    Always 'GET', a fixed label the server uses to classify the event type.

The code that does this

The navigation listener and encryption logic from the extension source

Readable version

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));}

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).

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.

Requires
  • Node.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 keyasync 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. 1Intercept a POST to stream.meetextension.com/process (DevTools or mitmproxy).
  2. 2Copy enRequest's inner base64 value, no quotes.
  3. 3Run: node meet-ext-decrypt.js '<value>'.
  4. 4Output shows plaintext targetUrl, referrerUrl, userId.
Updated 21 September 2026dghkhbmpagbapkadlehcicngkldfieln