Is Labs Token PRO safe?

Medium risk

Labs Token PRO captures Google OAuth Bearer tokens from every website visited, not just labs.google, storing the last 5 unencrypted locally.

The extension is designed to grab your Google Labs (labs.google) session token and cookies for you to copy from its popup. To do this it watches every outgoing web request's Authorization header and also patches fetch/XMLHttpRequest and storage events on every site you visit, not only labs.google, looking for the ya29. Bearer token format. Any match is written to chrome.storage.local as a 5-entry token history that persists between browser sessions.

45Risk

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

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-522
SourceAI FOUND

Labs Token PRO reads your Google OAuth token on every site you visit

Code analysis shows background.js reads the Authorization header of every outgoing request on every site, not just labs.google, and saves any Google OAuth2 token it finds to local storage indefinitely.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You browse to any website that calls a Google API with an OAuth2 bearer token.

For example, a Google Photos or Drive web app, unrelated to labs.google.

The extension did this

The background service worker reads that token from the request header and saves it.

This runs for every site, because the listener is registered with an all-sites filter.

02EvidenceFIELD TABLE
What gets written to chrome.storage.local
FieldValueWhy it matters
Your Google OAuth token
ya29.a0AfH6SMBex1pQz... (illustrative format, not a captured value)A live credential that can call Google APIs on your behalf until it expires or is revoked.
Token history, last 5
Keeps a rolling log of your five most recent Google tokens, even after you move to other sites.
Last capture time
1758213600000 (illustrative Unix ms)Records exactly when a token was last captured, confirming this runs continuously as you browse.
03EvidenceCODE COMPARE
The code that does this

The all-sites capture logic in background.js and content.js

What it actually does
Background service worker, annotatedbackground.js
// Background service worker: intercepts the Authorization header on
// every outgoing HTTP request across every site (urls: <all_urls>).
let latestToken = '';
let tokenHistory = []; // rolling log of the 5 most recent tokens
const MAX_HISTORY = 5;

// --- Method 1: browser-level network listener, all sites ---
chrome.webRequest.onBeforeSendHeaders.addListener(
  (details) => {
    const authHeader = details.requestHeaders.find(
      header => header.name.toLowerCase() === 'authorization'
    );

    if (authHeader && authHeader.value) {
      let token = null;

      // Matches Google OAuth2 access tokens (format: ya29.*)
      if (authHeader.value.startsWith('Bearer ya29.')) {
        token = authHeader.value.substring(7); // strip "Bearer "
      } else if (authHeader.value.startsWith('ya29.')) {
        token = authHeader.value;
      }

      if (token && token !== latestToken) {
        latestToken = token;

        tokenHistory.unshift(token);
        if (tokenHistory.length > MAX_HISTORY) {
          tokenHistory = tokenHistory.slice(0, MAX_HISTORY);
        }

        // Persists to chrome.storage.local, unencrypted
        chrome.storage.local.set({
          googleToken: token,
          tokenHistory: tokenHistory,
          lastUpdate: Date.now()
        });
      }
    }
  },
  { urls: ['<all_urls>'] }, // no site restriction, despite the manifest's labs.google scope
  ['requestHeaders']
);

// --- Method 2: receives tokens relayed by content.js as a fallback ---
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.token) {
    let token = msg.token;
    if (token.startsWith('Bearer ')) {
      token = token.substring(7);
    }
    if (token && token !== latestToken) {
      latestToken = token;
      tokenHistory.unshift(token);
      if (tokenHistory.length > MAX_HISTORY) {
        tokenHistory = tokenHistory.slice(0, MAX_HISTORY);
      }
      chrome.storage.local.set({
        googleToken: token,
        tokenHistory: tokenHistory,
        lastUpdate: Date.now()
      });
    }
  }

  // Popup asks for the in-memory token
  if (msg.action === 'getToken') {
    sendResponse({
      token: latestToken,
      history: tokenHistory,
      lastUpdate: msg.lastUpdate || Date.now()
    });
    return true;
  }

  // Popup asks for the persisted token if memory was cleared (e.g. service worker restart)
  if (msg.action === 'getTokenFromStorage') {
    chrome.storage.local.get(['googleToken', 'tokenHistory', 'lastUpdate'], (result) => {
      if (result.googleToken && !latestToken) {
        latestToken = result.googleToken;
        tokenHistory = result.tokenHistory || [];
      }
      sendResponse({
        token: result.googleToken || latestToken,
        history: result.tokenHistory || tokenHistory,
        lastUpdate: result.lastUpdate
      });
    });
    return true;
  }
});

// --- Restores the last-known token from storage on service worker startup ---
chrome.storage.local.get(['googleToken', 'tokenHistory'], (result) => {
  if (result.googleToken) {
    latestToken = result.googleToken;
    tokenHistory = result.tokenHistory || [];
  }
});
Content script fallback, annotatedcontent.js
// Content script fallback: runs on every site (manifest matches *://*/*,
// document_start), independent of the background listener above.
(() => {
  if (window.__tokenExtractorV2) return;
  window.__tokenExtractorV2 = true;

  // Fallback A: wraps window.fetch to read the Authorization header
  // of every fetch call made by the page itself.
  const origFetch = window.fetch;
  window.fetch = async function(...args) {
    const [resource, config] = args;
    let auth = null;

    if (config?.headers) {
      if (config.headers.get) {
        auth = config.headers.get('Authorization') || config.headers.get('authorization');
      } else if (typeof config.headers === 'object') {
        auth = config.headers.Authorization || config.headers.authorization;
      }
    }

    if (auth) {
      if (auth.startsWith('Bearer ya29.') || auth.startsWith('ya29.')) {
        try {
          chrome.runtime.sendMessage({ token: auth }); // relay to background.js
        } catch (e) {
          // extension context invalidated, ignore
        }
      }
    }

    return origFetch.apply(this, args);
  };

  // Fallback B: wraps XMLHttpRequest.setRequestHeader for pages that use XHR instead of fetch
  const origOpen = XMLHttpRequest.prototype.open;
  const origSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;

  XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
    if (header.toLowerCase() === 'authorization') {
      if (value.startsWith('Bearer ya29.') || value.startsWith('ya29.')) {
        try {
          chrome.runtime.sendMessage({ token: value });
        } catch (e) {
          // ignore
        }
      }
    }
    return origSetRequestHeader.apply(this, arguments);
  };

  // Fallback C: listens for the window 'storage' event and scans any
  // localStorage/sessionStorage key containing 'token' or 'auth' for a
  // ya29./Bearer value, in case a page persists the token instead of
  // sending it directly.
  const storageHandler = (e) => {
    if (e.key && (e.key.includes('token') || e.key.includes('auth'))) {
      const val = e.newValue;
      if (val && (val.includes('ya29.') || val.includes('Bearer'))) {
        try {
          chrome.runtime.sendMessage({ token: val });
        } catch (e) {
          // ignore
        }
      }
    }
  };
  window.addEventListener('storage', storageHandler);

  console.log('Token extractor loaded, multiple fallbacks active');
})();
04EvidenceARTIFACT
Reproduce it yourself

Reads this extension's own local storage to show whether it has captured a Google token, and when.

RequiresChrome or a Chromium-based browser with Labs Token PRO installed
check-labs-token-pro-storage.js · js
// Run this in Labs Token PRO's OWN service worker console:
// chrome://extensions -> enable Developer mode -> click "service worker"
// under the Labs Token PRO card to open its DevTools, then paste this.
chrome.storage.local.get(['googleToken', 'tokenHistory', 'lastUpdate'], (result) => {
  console.log('Currently stored Google token:', result.googleToken || '(none yet)');
  console.log('Token history, most recent first:', result.tokenHistory || []);
  console.log(
    'Last capture time:',
    result.lastUpdate ? new Date(result.lastUpdate).toISOString() : '(none yet)'
  );
  console.log(
    'If googleToken is set and you never visited labs.google or opened the ' +
    'popup, the token was captured from another site you visited.'
  );
});
How to run it
  1. 1
    Open chrome://extensions and enable Developer mode.
  2. 2
    Click 'service worker' under the Labs Token PRO card.
  3. 3
    Paste this into the console that opens and press Enter.
05EvidencePLAIN NOTE
Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

Updated 20 September 2026jmfjjblpfpfcmpokjdefgccklkgghcbb