Is Mojo - Automatic Coupons at Checkout safe?

Medium risk

Mojo runs JavaScript function strings fetched from its own server inside every website you visit.

On every http/https page, Mojo asks api.mojocart.com for that merchant's coupon-testing configuration, which can include 'func' entries containing raw JavaScript. The extension injects a script into the page itself and runs those server-supplied functions with new Function(), giving them the same access to the page's DOM, cookies, and local storage as the site's own code. Separately, the coupon codes you type, the checkout page URL, and cart/price details it extracts are sent to Mojocart's backend on every coupon submission and price check.

developerv3.2.0Chrome Web Store
57Risk

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

Publishers can request a review.

Findings

Mojocart's remote store config can run vendor code on checkout pages

Code analysis shows that on stores Mojocart supports, this extension fetches config from Mojocart's server and, if it carries a function callback, runs that code directly in the page.

We did not observe it firing in our test session.

Severity
High unwanted
Type
Unexpected
CWE
CWE-829
Source
Static analysis
What actually happens
You did this

You open a checkout page on a store that Mojocart supports.

Content scripts run on every http(s) page except a short denylist.

The extension did this

The extension fetches that store's config from Mojocart's server and, if a callback is present, runs it directly in the page.

The callback runs with jQuery and page data in scope, with the page's own DOM and cookie access.

The code that does this

The message handler that turns a config string into running code

Readable version
// scripts/executor.js -- window "message" listener, runs in the page's MAIN worldwindow.addEventListener("message", (event) => {  const { origin, source: senderWindow, data: { type, data: msg = {} } = {} } = event;  // Same-document check only -- NOT an allowlist of trusted senders.  if (origin === window.location.origin && type === "FUNCTION_EXECUTOR_REQUEST") {    const { funcStr, envVariables = {}, envVariableKeys = [], key } = msg;    const result = (() => {      try {        const env = sanitizeEnvKeys(envVariables, envVariableKeys);        // Builds and RUNS the server-supplied function body, with jQuery        // and the env values in scope, inside the page's own JS context.        return {          result: new Function("$", ...Object.keys(env), funcStr)(            jQuery, ...Object.values(env)          )        };      } catch (err) {        return { error: err.message, result: undefined };      }    })();    senderWindow.postMessage(      { type: "FUNCTION_EXECUTOR_RESPONSE", data: { key, funcStr, ...result } },      origin    );  }}, false);
What a config callback entry can control
  • Callback kind
    func

    Whether the entry is a fixed value, a DOM read, or code to run

  • Callback code
    return document.querySelector('#promo-code').value

    The JavaScript the extension passes to new Function() and executes

  • Where it runs
    checkout.examplestore.com

    Directly in the visited page, with that page's DOM and cookie access

Check if you're affected

Scans a captured get_store_config response for 'func' callback entries and prints the code each one would run.

Requires
  • Node.js 14+
mojocart-config-func-scanner.js · js
#!/usr/bin/env node/** * mojocart-config-func-scanner.js * * Scans a captured Mojocart /get_store_config JSON response and reports * any 'func' callback entries -- the config-driven code path that * scripts/executor.js runs with new Function() in the visited page. * * Usage: node mojocart-config-func-scanner.js response.json */const fs = require("fs");const file = process.argv[2];if (!file) {  console.error("Usage: node mojocart-config-func-scanner.js <response.json>");  process.exit(1);}const config = JSON.parse(fs.readFileSync(file, "utf8"));const CALLBACK_LISTS = [  "code_testing_config_list",  "cart_config_list",  "checkout_input_config_list"];const CALLBACK_KEYS = [  "code_input_triggered_callbacks",  "code_applied_callbacks",  "code_removed_callbacks",  "code_testing_finished_callbacks"];let found = 0;for (const listKey of CALLBACK_LISTS) {  const entries = config[listKey];  if (!Array.isArray(entries)) continue;  for (const entry of entries) {    for (const key of CALLBACK_KEYS) {      const callbacks = entry[key];      if (!Array.isArray(callbacks)) continue;      for (const cb of callbacks) {        if (cb && cb.type === "func") {          found++;          console.log(`[${listKey}.${key}] func callback found:`);          console.log(cb.value);          console.log("---");        }      }    }    if (Array.isArray(entry.extractors)) {      for (const extractor of entry.extractors) {        const action = extractor.action;        if (action && action.type === "func") {          found++;          console.log(`[${listKey}.extractors.action] func extractor found:`);          console.log(action.value);          console.log("---");        }      }    }  }}console.log(  found    ? `${found} 'func' callback(s) found. Each one runs as new Function() in the visited page.`    : "No 'func' callbacks in this response.");
How to run it
  1. 1Capture a get_store_config response body and save it as response.json.
  2. 2Run: node mojocart-config-func-scanner.js response.json.
  3. 3Any printed entry is code the extension will execute via new Function().
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.

Signed Mojocart config can execute a server-supplied function on any page

Code analysis shows every http(s) page this extension runs on can receive a signed config response from Mojocart naming a 'func' callback, which the extension then executes with new Function() in that page's own origin.

Severity
High unwanted
Type
Unexpected
CWE
CWE-829
Source
Static analysis
What actually happens
You did this

You load a page whose domain is on Mojocart's supported-store list.

matches is http://*/*, https://*/*, so almost any http(s) page qualifies.

The extension did this

content.js requests a signed store config from Mojocart; if it names a 'func' callback, the extension runs that code in the page.

Only a same-document origin check gates execution, not an allowlist of trusted senders.

The code that does this

Signing the request, then running what it returns

Readable version

Signed request builder

background.js
// background.js -- signs every get_store_config request before it leaves// the service worker. The signature proves the REQUEST came from this// extension; it says nothing about whether the RESPONSE is safe to run.async function fetchWithSignature(path, requestParams) {  const { params, sign, tm } = await buildSignature(requestParams);  const response = await httpGet(path, params, {    baseURL: MOJOCART_API_BASE,    headers: { "X-Signature": sign, "X-Timestamp": tm }  });  return response.status === 200 ? parseStoreConfigResponse(response.data) : null;}

Posting the callback to the executor

feature.js
// feature.js -- posts a callback string to the page-world executor// and waits for its result via a matching window message.function callExecutor(funcStr, envVariables) {  return new Promise((resolve) => {    const requestKey = `${Date.now()}-${1000 + Math.floor(9000 * Math.random())}`;    const onMessage = (event) => {      const { origin, data: { type, data: msg } = {} } = event;      const { funcStr: echoedFuncStr, result, error, key } = msg || {};      if (        origin === window.location.origin &&        type === "FUNCTION_EXECUTOR_RESPONSE" &&        echoedFuncStr === funcStr &&        key === requestKey      ) {        window.removeEventListener("message", onMessage, false);        resolve(error ? undefined : result);      }    };    window.addEventListener("message", onMessage, false);    window.postMessage(      {        type: "FUNCTION_EXECUTOR_REQUEST",        data: {          funcStr,          envVariableKeys: Object.keys(envVariables),          envVariables,          key: requestKey        }      },      window.location.origin    );  });}
The response schema explicitly allows a code callback
  • Callback kind
    func

    Schema-sanctioned values are a fixed value, a DOM read, or code to run

  • Callback body
    return document.querySelector('#promo-code').value

    The JavaScript source the extension executes via new Function()

  • Request signature
    X-Signature: 3f9c1a... X-Timestamp: 1758230400

    Proves the request came from the extension; says nothing about the response

Check if you're affected

Reads the extension's manifest.json and prints exactly which http(s) pages the config-driven executor path can run on.

Requires
  • Node.js 14+
mojocart-host-reach.js · js
#!/usr/bin/env node/** * mojocart-host-reach.js * * Reads the extension's own manifest.json and reports which pages the * content script -- and therefore the get_store_config-driven executor * path -- can run on. * * Usage: node mojocart-host-reach.js manifest.json */const fs = require("fs");const file = process.argv[2];if (!file) {  console.error("Usage: node mojocart-host-reach.js <manifest.json>");  process.exit(1);}const manifest = JSON.parse(fs.readFileSync(file, "utf8"));const mainScript = (manifest.content_scripts || []).find(  (cs) => Array.isArray(cs.js) && cs.js.includes("content.js"));if (!mainScript) {  console.log("No content.js content_scripts entry found in this manifest.");  process.exit(0);}console.log("Matches:", mainScript.matches);console.log("Excluded (exclude_globs):", mainScript.exclude_globs || []);console.log(  "\nThe content script, and the get_store_config-driven executor path it " +  "can trigger, loads on every http(s) page whose hostname is not one of " +  "the excluded domains above.");
How to run it
  1. 1Point this at the extension's manifest.json.
  2. 2Run: node mojocart-host-reach.js manifest.json.
  3. 3It prints the matches/exclude_globs pair showing the executor path is not scoped to a merchant allowlist at the manifest level.
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.

What it can do

Permissions this extension asks for, as declared in version 3.2.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Read and change your data on every site you visit

    http://*/*

  • Read and change your data on every secure site you visit

    https://*/*

  • Read and change cookies, including the ones that keep you signed in

    cookies

  • Store data in your browser

    storage

  • Watch every request your browser makes

    webRequest

  • Run its own code inside the pages you visit

    scripting

Where it sends data

Destinations our analysis observed Mojo - Automatic Coupons at Checkout contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • api.mojocart.com

    Mojo - Automatic Coupons at Checkout sends data to api.mojocart.com. No other extension we have analysed sends data here.

Updated 30 September 2026doedeaghekakhljnkpgpgcgpmecdomhh