Is Boomerang for Gmail safe?

Medium risk

Boomerang for Gmail is medium risk. Inbox Pause reads Gmail's GMAIL_AT cookie, its anti-CSRF token, granting account authority over internal endpoints incl. the Inbox-Paused filter. Testing confirmed it stays on Google's domains, but get_at_variable is publicly exposed too.…

Boomerangv1.9.5Chrome Web Store
53Risk
Who publishes it

BAYDIN INC. - 3 other listings from the same operator, none carrying a finding

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
Boomerang
Declared legal entity
BAYDIN INC.
Registered address
3120 Scott Blvd Ste 130, Santa Clara, CA 95054, US
Registered contact
BAYDIN INC.

Same store account

3 other listings published from this account, 25k+ users between them, none of them carrying a finding.

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

Publishers can request a review.

Findings

Reads Gmail's CSRF cookie to call Gmail's internal API as you

Inbox Pause reads Gmail's GMAIL_AT cookie, its anti-CSRF token, granting account authority over internal endpoints incl. the Inbox-Paused filter.

Testing confirmed it stays on Google's domains, but get_at_variable is publicly exposed too.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You click Pause Inbox (or Boomerang otherwise needs to manage the Inbox-Paused filter).

The extension did this

Boomerang reads Gmail's anti-CSRF cookie from document.cookie and uses it to call Gmail's internal filter-management API as you, installing the Inbox-Paused label-filter.

The cookie itself stays on Google domains in this build, it is used only to talk to mail.google.com, but the extension does extract it into a JavaScript variable that any future code path could ship anywhere.

What the GMAIL_AT cookie is and what it lets the extension do
  • GMAIL_AT cookie value
    AF6bupPl26NI-Sp2H2U4wrG3iGIUnioygg

    Gmail's anti-CSRF token. Whoever holds it can call Gmail's internal endpoints with full account authority: install filters, modify labels.

  • Gmail "ik" value
    11abc2def3

    A second internal Gmail identifier the extension also reads from page state to construct authenticated calls.

  • Authenticated Gmail filter request
    POST https://mail.google.com/mail/u/0/?ik=11abc2def3&at=AF6bupPl26NI-Sp2H2U4wrG3iGIUnioygg&view=up&act=df&pcd=1&mb=0&rt=c

    The extension uses your token to POST to Gmail's filter endpoint, installing the Inbox-Paused-* label-filter Boomerang depends on.

  • Public exposure on api object
    api.get.b4g_get_at_variable() // returns the live GMAIL_AT

    This cookie-reader is also exposed as api.get.b4g_get_at_variable. Future or third-party code calling it gets your Gmail token.

The code that does this

The cookie reader, the api-object exposure, and the authenticated Gmail call.

Readable version

What's actually happening

// Pull Gmail's anti-CSRF token straight out of document.cookie.function getGmailAt() {  const start = document.cookie.indexOf('GMAIL_AT=') + 9;  const end   = document.cookie.indexOf(';', document.cookie.indexOf('GMAIL_AT'));  return document.cookie.substring(start, end);}// Use it (plus the in-page "ik" value) to act as you against Gmail's// undocumented filter-management endpoint, installing/checking the// Inbox-Paused-* filter that the Pause Inbox feature depends on.const url = `${gmailBaseUrl}ik=${getGmailIk()}&at=${getGmailAt()}&view=up&act=df&pcd=1&mb=0&rt=c`;fetch(url, { method: 'POST', body: 'tfi=none&' });// Also exposed publicly on the bookmarklet's api object:api.get.b4g_get_at_variable = getGmailAt;
Confirmed: the token is not exfiltrated in this build

Dynamic analysis instrumented `document.cookie` reads and observed 216 reads in a single session (every Inbox-Pause-related action triggers them). A SQL grep of the captured outbound traffic — `SELECT COUNT(*) FROM requests WHERE url LIKE '%b4g.baydin%' AND body LIKE '%GMAIL_AT%'` — returned 0 rows. The token is used only to call mail.google.com itself in this version.

That is the safer outcome. The risk is structural: the value is yanked into a JS string and exposed on a public `api.get.b4g_get_at_variable` function. Any future code path (or an attacker-controlled script that finds its way onto a Gmail tab and can call into Boomerang's window-scope api) that ships the result of that function is a one-line patch away from full Gmail-account takeover-level credential exposure.

Every interaction sent to Google Analytics with a per-user UUID

Boomerang fires a GA event for nearly every action (~307 types), each carrying a stable localStorage UUID (b4g_ga_cid) that lets GA correlate everything you do.

Events POST from the background worker, so page-level blockers miss them.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You click anything Boomerang touches, Pause Inbox, Schedule Send, opening Compose, the OAuth dialog.

The extension did this

The background service worker POSTs a Google Analytics event with your stable per-user UUID, the action you took, and a label.

Because the request comes from the extension's service worker (not the Gmail page), browser tracker-blockers running in the page do not see or block it.

Captured request
POSThttps://www.google-analytics.com/mp/collect?measurement_id=G-48B3G8LRPQ&api_secret=<redacted>

204 No Content (GA Measurement Protocol acknowledges receipt). 3 such POSTs captured during dynamic analysis, covering: opening Gmail (automatic_oauth_dialog/show), the OAuth login attempt (automatic_oauth_dialog/login_attempt), and the resulting flow (oauth_flow/login). The same client_id UUID appears on every event, that is the stable cross-session identifier.

Headers
Origin
chrome-extension://mdanidgdpmkimeiiojknlnekblgmpdll
Content-Type
text/plain;charset=UTF-8
Body
{  "client_id": "48285cd7-e1b9-4da4-89a1-c1617887469f",  "events": [    {      "name": "automatic_oauth_dialog",      "params": {        "action": "show",        "label": "first_open"      }    }  ]}
What goes out on every event
  • client_id (your stable UUID)
    48285cd7-e1b9-4da4-89a1-c1617887469f

    A persistent random ID created on first run, stored in localStorage, and attached to every analytics event until you clear site storage.

  • events[].name (event category)
    automatic_oauth_dialog

    The Boomerang feature class you interacted with. The bookmarklet emits ~307 distinct names, covering essentially every UI action.

  • events[].params.action
    show

    What you did within that feature.

  • events[].params.label
    first_open

    Optional sub-context for the action, feature variant, source, etc.

  • measurement_id (in URL)
    G-48B3G8LRPQ

    Identifies the developer's GA4 property. Hardcoded in the extension and visible to every user on every event.

  • api_secret (in URL)
    uIAXRk5NR3m2Mef57TbpxQ

    The credential GA uses to authorize the POST, hardcoded and visible to every user; see Claim 5870 for the security implication.

The code that does this

From the page → content script hop → background SW → Google Analytics POST.

Readable version

End-to-end flow

// 1. Page-side code in the bookmarklet does:window.postMessage({type: 'B4G_TRACK_EVENT', trackedEventData: ['oauth-flow', 'login']}, '*');// 2. The b4g.js content script forwards it to the background SW.// 3. The background SW assigns the persistent UUID and POSTs to GA.//    From your perspective the request originates from the extension, not the page,//    so any blocker that only filters page requests will not see it.fetch(`https://www.google-analytics.com/mp/collect`      + `?measurement_id=G-48B3G8LRPQ&api_secret=<redacted>`,  { method: 'POST',    body: JSON.stringify({      client_id: localStorage.b4g_ga_cid,   // your stable per-user UUID      events: [{ name: 'oauth_flow', params: { action: 'login' } }]    })  });
What's stored on your device

This string is your per-user identifier in GA across every Boomerang interaction, surviving restarts, recreated only if you clear storage.

Location
Page localStorage on mail.google.com, key b4g_ga_cid
Contents
48285cd7-e1b9-4da4-89a1-c1617887469f
Where the analytics events go
    • www.google-analytics.com

    Receives Measurement Protocol POSTs to /mp/collect, credited to GA4 property G-48B3G8LRPQ. Google processes the data under its standard terms; Baydin Inc is the controller.

Your Gmail address is sent to b4g.baydin.com on every page open and click

Boomerang reads your Gmail address on every Gmail load and sends it to b4g.baydin.com as guser, automatically and on every feature used.

It's the vendor's user ID and, being in the URL, lands in access logs too.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You open Gmail (or click any Boomerang button).

The extension did this

Boomerang reads your full Gmail address out of the page header and tacks it onto a request to its own server as the "guser" parameter.

It happens automatically the moment Gmail loads, no Boomerang button click required for the first request.

Captured request
GEThttps://b4g.baydin.com/mailcruncher/hasseentutorial?guser=robertfinwitch%40gmail.com&image=True

Captured automatically on Gmail load with no Boomerang button clicked. The live test account address robertfinwitch@gmail.com appears URL-encoded in the query string. A second capture, GET /mailcruncher/checklogin2?guser=robertfinwitch%40gmail.com&includecsrf=True, fires the moment Pause Inbox is clicked.

Headers
Accept
*/*
Origin
https://mail.google.com
Referer
https://mail.google.com/
Endpoints observed (or grep-confirmed in source) sending your Gmail address as guser=
  • /mailcruncher/hasseentutorial
    ?guser=robertfinwitch%40gmail.com&image=True

    Fires automatically on Gmail load to check if you've seen Boomerang's tutorial. Your address goes out before you touch anything.

  • /mailcruncher/checklogin2
    ?guser=robertfinwitch%40gmail.com&includecsrf=True

    Fires on Pause Inbox click. Tells the vendor you are an active Boomerang user under this Gmail address.

  • checkIfHasSeenAnnouncementsAndFTUEs
    POST guser=robertfinwitch%40gmail.com

    Polls server for in-product announcements scoped to your specific email address.

  • schedulesend/return/recurring
    ?guser=robertfinwitch%40gmail.com

    Sends your address whenever you schedule, snooze, or set up a recurring email, links every scheduled-mail action to your identity.

  • sendlater, boomerangfrom* endpoints
    ?guser=robertfinwitch%40gmail.com

    Sent on every Boomerang/send-later from a conversation. Builds a record of which messages you delay and when.

  • /tr/track-sender-open
    ?guser=robertfinwitch%40gmail.com

    Open-tracking endpoint. Includes your address so opens you triggered are tied back to you, not just the recipient.

  • dialog/subs/insights/login/contacts
    https://b4g.baydin.com/insights/insightsfromgmail?guser=robertfinwitch%40gmail.com

    All UI links Boomerang opens for you carry your address in the URL, useful for the vendor's tracking, exposed in browser history.

The code that does this

How the address is extracted, and a representative endpoint that ships it.

Readable version

What the scrape does, in plain terms

// Walk the Gmail header, look for an email-shaped string in any aria-label.// Fall back to the browser tab title if the header has no match.function getGmailAddress() {  for (const a of document.querySelectorAll('header a[aria-expanded]')) {    const m = /[\w.+\-]+@[\w.\-]+\.\w+/.exec(a.getAttribute('aria-label'));    if (m) return m[0];  }  for (const w of document.title.split(' ')) {    const m = /[\w.+\-]+@[\w.\-]+\.\w+/.exec(w);    if (m) return m[0];  }  return '';}// Then every server call looks like this:fetch(`https://b4g.baydin.com/mailcruncher/<endpoint>?guser=${encodeURIComponent(getGmailAddress())}&...`);
Where your Gmail address ends up
    • b4g.baydin.com

    Boomerang's primary backend (Baydin Inc). Receives your Gmail address as guser= on every feature use, plus the tutorial check on load. It's the vendor's primary user identifier.

Reproduce it yourself

Live-tails Chrome's net log for any request to b4g.baydin.com that contains a guser= parameter. Lets you confirm in your own browser that your Gmail address is leaving on every Boomerang interaction.

Requires
  • bash
  • Google Chrome with --log-net-log support
watch-baydin-guser.sh · sh
#!/usr/bin/env bash# watch-baydin-guser.sh — see your Gmail address leaving in real time.## Usage: launch Chrome with --log-net-log=/tmp/netlog.json --net-log-capture-mode=IncludeSensitive# then run this script. Open Gmail with Boomerang installed and click around.set -euo pipefailLOG="${1:-/tmp/netlog.json}"until [[ -s "$LOG" ]]; do sleep 1; donetail -F "$LOG" | grep --line-buffered -oE 'https://b4g\.baydin\.com/[^"]*guser=[^&"]*' | while read -r url; do  echo "$(date -u +%FT%TZ)  $url"done
How to run it
  1. 1Quit Chrome.
  2. 2Relaunch: google-chrome --log-net-log=netlog.json --net-log-capture-mode=IncludeSensitive.
  3. 3Run ./watch-baydin-guser.sh netlog.json.
  4. 4Open Gmail: hasseentutorial?guser=YOUR_EMAIL appears within seconds, unclicked.

What it can do

Permissions this extension asks for, as declared in version 1.9.1. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 1.9.5, which we have not unpacked yet.

  • See, disable and uninstall your other extensions, including your security ones

    management

  • Act on the current tab, but only after you click the extension

    activeTab

Updated 30 September 2026mdanidgdpmkimeiiojknlnekblgmpdll