Is Frontrun safe?

Medium risk

Frontrun is medium risk. When gmgn.ai pages make wallet-remark requests, Frontrun's background reads the Authorization header and device query. A later getGmgnAuth message reuses those values to POST to gmgn.ai's remark-wallet API with that Authorization.

Frontrunv0.0.444Chrome Web Store
45Risk

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

Publishers can request a review.

Findings

gmgn.ai authorization header reused for wallet remarks

MediumunwantedCWE-522Dynamic sandbox

When gmgn.ai pages make wallet-remark requests, Frontrun's background reads the Authorization header and device query.

What actually happens

You did this

You browse gmgn.ai pages that make wallet-remark requests.

The watched URL prefix is gmgn.ai's Solana wallet remark endpoint.

The extension did this

Frontrun reads the request's Authorization value and device query string, then reuses them in a later remark-wallet request.

The follow-up request is initiated by the background message handler named getGmgnAuth.

Values the code carries from the watched request into the follow-up POST

  • Authorization value
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 (illustrative)

    This represents your signed-in gmgn.ai session. Reusing it lets the follow-up request act with the same session context.

  • Device query string
    device_id=4f7c2b8a-0e41-4c0f-9b42-7a6d8a21c014&client_id=gmgn_web (illustrative)

    This keeps the same device and request context from the gmgn.ai page request attached to the later POST.

  • Wallet remark pair
    ["8r3Xv1k7JmG9qQp3nV6sT2yA4bC5dE6fH7iJ8kL9mN0", "Primary wallet"] (illustrative)

    The later request sends the wallet address and label selected by the extension code.

Captured request

POSThttps://gmgn.ai/api/v1/remark_wallet

The code that does this

The background service worker stores gmgn.ai authorization data and reuses it

Readable version

Readable code: listener and POST handler

background.js
var eQt = md(`GetGmgnAuthMessage`),  tQt = `https://gmgn.ai/defi/quotation/v1/wallet/sol/remark_wallets`,  nQt = `https://gmgn.ai/api/v1/remark_wallet`,  rQt = null;function iQt(e) {  let t = typeof e?.address == `string` ? e.address.trim() : void 0,    n = typeof e?.primaryLabel == `string` ? e.primaryLabel : void 0;  return !t || n === void 0 || t.length > 128 || n.length > 256 ? null : {    address: t,    primaryLabel: n  }}chrome.webRequest.onBeforeSendHeaders.addListener(e => {  let t = e.requestHeaders?.find(e => e.name.toLowerCase() === `authorization`),    n = new URL(e.url).searchParams.toString();  return t?.value && n && (rQt = {    authToken: t.value,    deviceQuery: n  }), {    requestHeaders: e.requestHeaders  }}, {  urls: [`${tQt}*`]}, [`requestHeaders`]);var aQt = async (e, t) => {  if (!EYt(e.sender)) {    eQt.error(`Blocked GMGN sync from unauthorized sender`, {      origin: V5(e.sender)    }), t.send({      success: !1,      error: `Unauthorized sender`    });    return  }  let n = iQt(e.body);  if (!n) {    t.send({      success: !1,      error: `Invalid GMGN sync payload`    });    return  }  let r = rQt;  if (!r) {    t.send({      success: !1,      error: `GMGN auth unavailable`    });    return  }  try {    let e = await fetch(`${nQt}?${r.deviceQuery}`, {      method: `POST`,      headers: {        authorization: r.authToken,        "content-type": `application/json`      },      body: JSON.stringify({        chain: `sol`,        remark_addresses: [          [n.address, n.primaryLabel]        ]      })    });    if (!e.ok) {      eQt.warn(`GMGN remark request failed`, {        status: e.status      }), t.send({        success: !1,        error: `GMGN sync failed (${e.status})`      });      return    }    t.send({      success: !0    })  } catch (e) {    eQt.error(`GMGN remark request errored`, e), t.send({      success: !1,      error: L5(e, `gmgnSync`)    })  }};

Readable code: sender restriction and dispatch table

background.js
var wYt = `https://gmgn.ai`,  EYt = e => e?.origin === wYt ? !0 : !!e?.url?.startsWith(`${wYt}/`),  V5 = e => e?.origin ?? `unknown`;var F5t = {  "auth-with-google": YYt,  auth: SYt,  "auth-email-otp-send": UYt,  "auth-email-otp-verify": KYt,  "auth-email-otp-finalize": WYt,  "base-trade": cZt,  "base-transfer": vZt,  "bnb-trading": CZt,  "bridge-evm-origin": PZt,  captureSentryEvent: IZt,  checkJitoBundle: RZt,  claimTurnkeyReauthPrompt: WZt,  event: YZt,  fetchTransactionDetails: ZZt,  forwardLog: $Zt,  getGmgnAuth: aQt,  getInstallAttribution: sQt,  getOnchainTokenBalance: uQt,  getTokenDecimals: dQt,  getTurnkeySecret: pQt,  icon: gQt,  "link-telegram": xQt,  "link-twitter": EQt,  mergeBscNonces: DQt,  openSidepanel: Fit,  openTabs: AQt,  "perf-metrics-config": qQt,  "perf-metrics-report": JQt,  ping: YQt};

Host receiving the follow-up request

    • gmgn.ai

    Receives the extension-initiated remark-wallet POST with the Authorization value copied from a prior gmgn.ai request.

Updated 2 November 2026kifcalgkjaphbpbcgokommchjiimejah