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.370Chrome Web Store
45Risk

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

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-522
SourceAI SANDBOX

gmgn.ai authorization header reused for wallet remarks

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.

01EvidenceCAUSE EFFECT
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.

02EvidenceFIELD TABLE
Values the code carries from the watched request into the follow-up POST
FieldValueWhy it matters
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.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://gmgn.ai/api/v1/remark_wallet
04EvidenceCODE COMPARE
The code that does this

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

What it actually does
Readable code: listener and POST handlerbackground.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 tablebackground.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
};
05EvidenceTHIRD PARTY LIST
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 17 September 2026kifcalgkjaphbpbcgokommchjiimejah