Is Boomerang for Gmail safe?
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.…
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.
You click Pause Inbox (or Boomerang otherwise needs to manage the Inbox-Paused filter).
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.
| Field | Value | Why it matters | |
|---|---|---|---|
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 cookie reader, the api-object exposure, and the authenticated Gmail call.
// 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;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.
You click anything Boomerang touches, Pause Inbox, Schedule Send, opening Compose, the OAuth dialog.
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.
| Origin | chrome-extension://mdanidgdpmkimeiiojknlnekblgmpdll |
| Content-Type | text/plain;charset=UTF-8 |
{
"client_id": "48285cd7-e1b9-4da4-89a1-c1617887469f",
"events": [
{
"name": "automatic_oauth_dialog",
"params": {
"action": "show",
"label": "first_open"
}
}
]
}| Field | Value | Why it matters | |
|---|---|---|---|
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. |
From the page → content script hop → background SW → Google Analytics POST.
// 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' } }]
})
});This string is your per-user identifier in GA across every Boomerang interaction, surviving restarts, recreated only if you clear storage.
Page localStorage on mail.google.com, key b4g_ga_cid48285cd7-e1b9-4da4-89a1-c1617887469f
- 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.
You open Gmail (or click any Boomerang button).
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.
| Accept | */* |
| Origin | https://mail.google.com |
| Referer | https://mail.google.com/ |
| Field | Value | Why it matters | |
|---|---|---|---|
/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. |
How the address is extracted, and a representative endpoint that ships it.
// 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())}&...`);- 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.
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.
#!/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 pipefail
LOG="${1:-/tmp/netlog.json}"
until [[ -s "$LOG" ]]; do sleep 1; done
tail -F "$LOG" | grep --line-buffered -oE 'https://b4g\.baydin\.com/[^"]*guser=[^&"]*' | while read -r url; do
echo "$(date -u +%FT%TZ) $url"
done- 1Quit Chrome.
- 2Relaunch: google-chrome --log-net-log=netlog.json --net-log-capture-mode=IncludeSensitive.
- 3Run ./watch-baydin-guser.sh netlog.json.
- 4Open Gmail: hasseentutorial?guser=YOUR_EMAIL appears within seconds, unclicked.