Is JSON Formatter safe?

Low risk

JSON Formatter embeds a GiveFreely affiliate tracking library that monitors shopping browsing and sends search result URLs across all websites.

On every page load, the extension checks the current hostname against a remote GiveFreely merchant list and fires analytics events — including the page URL, a persistent device ID, and the names of competing affiliate extensions installed — to GiveFreely's servers. On Google search pages it collects all result URLs (including sponsored ads) and reports them to GiveFreely. When a user accepts a donation offer through the GiveFreely popup, the extension opens a hidden pinned tab to a wild.link affiliate URL that encodes the original page URL, user ID, and charity identifiers before closing after 30 seconds.

Code Formatterv0.10.0Chrome Web Store
20Risk

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

Publishers can request a review.

Findings

SeverityLOW
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

Extension Sends Your IP to MaxMind Using a Hardcoded API Key

JSON Formatter's GiveFreely feature calls MaxMind's GeoIP API on install, using a hardcoded account ID/key, same for all users, before any action.

Gets HTTP 401, so lookup fails, but the IP reaches MaxMind, uncached, on every popup mount.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You install the extension, or its GiveFreely charity popup loads on a page you visit.

No search, click, or settings change is required -- the check runs as soon as the popup component initializes.

The extension did this

The extension sends your IP address to MaxMind's GeoIP API using a credential built into its code.

The same MaxMind account ID and license key ship inside every install, in both the content script and the background service worker.

02EvidenceNETWORK CAPTURE
Captured request
GEThttps://geoip.maxmind.com/geoip/v2.1/country/me
HTTP 401 -- {"code":"AUTHORIZATION_INVALID","error":"Your account ID or license key could not be authenticated."}. MaxMind rejects the embedded credential, but by the time it responds, the request carrying your IP address has already reached its servers.
Headers
Content-Typeapplication/json
AuthorizationBasic OTEzOTkxOnFlYmpaWF9DOGNRY0lxSHA4WTVjNGxzU1pRSlM2VW9MMExzTF9tbWs=
03EvidenceOPAQUE REVEAL
Why you can't catch this in DevTools

The Authorization header is base64, not encryption -- decoding it recovers the account credential in plain text. MaxMind currently rejects this specific credential (HTTP 401), so the decoded value below does not expose a working account.

What's actually being sent
913991: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
04EvidenceCODE COMPARE
The code that does this

Country/IP lookup, content/core.js:297 and worker/worker.js:1

What it actually does
content/core.js — runs when the GiveFreely popup mountscontent/core.js
// content/core.js:297 -- runs when the GfApp popup component mounts on any page
const countryCode = await (async () => {
  return (
    (await storage.get("countryCode")) ||
    (async () => {
      logger.debug("detecting country");
      const requestOptions = {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
          // Hardcoded MaxMind account credential, identical on every install
          Authorization:
            "Basic OTEzOTkxOnFlYmpaWF9DOGNRY0lxSHA4WTVjNGxzU1pRSlM2VW9MMExzTF9tbWs=",
        },
      };
      try {
        const response = await fetch(
          "https://geoip.maxmind.com/geoip/v2.1/country/me",
          requestOptions,
        );
        logger.debug("Received geolocation response", { status: response.status });
        if (!response.ok) {
          logger.error("Failed to get geolocation data", {
            status: response.status,
            statusText: response.statusText,
          });
          return;
        }
        const geo = await response.json();
        if (!geo) throw Error("There was an error fetching geoip look up");
        await storage.set("countryCode", geo.country.iso_code);
        return geo.country.iso_code;
      } catch (err) {
        logger.error("Failed to get geolocation data", { error: err }, true);
      }
    })()
  );
})() ?? "us";
worker/worker.js — background service worker's country-detection functionworker/worker.js
// worker/worker.js:1 -- named `E3` in the shipped bundle
const getCountryCode = async () =>
  (await storage.get("countryCode")) ||
  (async () => {
    logger.debug("detecting country");
    const requestOptions = {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        // Same hardcoded MaxMind account credential as content/core.js
        Authorization:
          "Basic OTEzOTkxOnFlYmpaWF9DOGNRY0lxSHA4WTVjNGxzU1pRSlM2VW9MMExzTF9tbWs=",
      },
    };
    try {
      const response = await fetch(
        "https://geoip.maxmind.com/geoip/v2.1/country/me",
        requestOptions,
      );
      logger.debug("Received geolocation response", { status: response.status });
      if (!response.ok) {
        logger.error("Failed to get geolocation data", {
          status: response.status,
          statusText: response.statusText,
        });
        return;
      }
      const geo = await response.json();
      if (!geo) throw Error("There was an error fetching geoip look up");
      await storage.set("countryCode", geo.country.iso_code);
      return geo.country.iso_code;
    } catch (err) {
      logger.error("Failed to get geolocation data", { error: err }, true);
    }
  })();
05EvidenceTHIRD PARTY LIST
Third-party destinations
  • geoip.maxmind.com

    MaxMind, Inc. GeoIP2 API. Resolves the IP to a country. Extension authenticates with a fixed embedded credential; MaxMind returns 401 but still gets the IP each request.

06EvidenceARTIFACT
Reproduce it yourself

Decodes the Basic-Auth Authorization header the extension sends to MaxMind on every install, revealing the embedded MaxMind account ID and license key.

RequiresNode.js 18+ (built-in Buffer, no dependencies)
maxmind-basic-auth-decode.js · js
// Decodes the hardcoded Basic-Auth Authorization header that
// content/core.js and worker/worker.js send to MaxMind's GeoIP API
// (https://geoip.maxmind.com/geoip/v2.1/country/me) on every install.
//
// The header is present verbatim in the shipped extension code and was
// observed on the wire during dynamic analysis, from both the background
// service worker and a live content-script context.

const authHeader =
  "Basic OTEzOTkxOnFlYmpaWF9DOGNRY0lxSHA4WTVjNGxzU1pRSlM2VW9MMExzTF9tbWs=";

const base64Credential = authHeader.replace(/^Basic\s+/, "");
const decoded = Buffer.from(base64Credential, "base64").toString("utf8");
const [accountId, licenseKey] = decoded.split(":");

console.log("Authorization header (as sent on the wire):");
console.log("  " + authHeader);
console.log();
console.log("Decoded credential (account_id:license_key):");
console.log("  " + decoded);
console.log();
console.log("MaxMind account ID: " + accountId);
console.log("MaxMind license key: " + licenseKey);
console.log();
console.log("Note: MaxMind currently returns HTTP 401 AUTHORIZATION_INVALID for this");
console.log("credential, so it no longer authenticates a real lookup. The outbound");
console.log("request -- and the IP address it discloses to MaxMind -- still happens");
console.log("every time the extension's country-detection code runs, regardless of");
console.log("whether MaxMind accepts the credential.");
How to run it
  1. 1
    node maxmind-basic-auth-decode.js

What it can do

Permissions this extension asks for, as declared in version 0.10.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

    *://*/* and 1 more

  • Store data in your browser

    storage

  • Store an unlimited amount of data in your browser

    unlimitedStorage

  • Watch every request your browser makes

    webRequest

Where it sends data

Destinations our analysis observed JSON Formatter 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.

  • cdn.givefreely.com

    JSON Formatter sends data to cdn.givefreely.com. 5 other extensions we have analysed send data here.

  • wild.link

    JSON Formatter sends data to wild.link. 2 other extensions we have analysed send data here.

  • geoip.maxmind.com

    JSON Formatter sends data to geoip.maxmind.com. One other extension we have analysed sends data here.

Updated 30 September 2026bcjindcccaagfpapjjmafapmmgkkhgoa