Is Font Changer safe?

Medium risk

Font Changer rewrites shopping links and queries deals99.com for affiliate promotion URLs.

When you visit supported shopping sites such as AliExpress, Agoda, Booking, or Banggood, its content script scans page links and changes matching URLs to affiliate links. For AliExpress pages, it sends the site's hostname and product identifiers from links to deals99.com to receive promotion URLs before replacing those links.

ExtensionHubv3.0.2Firefox Add-ons
47Risk

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

Publishers can request a review.

Findings

Extension Swaps In Its Own Affiliate ID On Shopping Links

On Agoda, Booking.com, and Banggood, the extension rewrites links to carry its own affiliate ID.

Testing found all 111 Booking.com and 120 Banggood links rewritten.

A matching AliExpress/deals99.com lookup exists in code, unobserved firing.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-506
Source
Dynamic sandbox
What actually happens
You did this

You open a hotel or product listing page on Agoda, Booking.com, or Banggood with the extension installed.

The extension's content script is already running on the page; it injects at document_start on every site.

The extension did this

The extension rewrites every outbound link on the page so it carries its own hardcoded affiliate identifier before you can click it.

A DOM MutationObserver re-runs the rewrite as the page updates, so links that load in later get the same treatment.

Links rewritten on live pages during dynamic-analysis testing
  • Agoda hotel link
    https://www.agoda.com/room2-chiswick-hometel/hotel/london-gb.html?checkIn=2026-08-15&los=1&room=1&adults=2&cid=1763404

    The hotel link you'd click carries the extension's own Agoda affiliate ID, so any booking is credited to the extension's account.

  • Booking.com hotel link
    https://www.booking.com/hotel/gb/art-otel-london-hoxton.html?aid=2082838

    Every hotel link on a Booking.com results page, 111 out of 111 in our test, was rewritten with the same affiliate parameter.

  • Banggood product link
    https://www.banggood.com/USLION-5A-USB-to-Type-C-Cable-QC-SCP-AFC-PPS-MTK-PE-Fast-Charging-Data-Transmission-Nylon-Core-Line-1M-or-2M-Long-for-Samsung-Galaxy-Note-20-for-Huawei-Mate50-for-Xiaomi-Mi12-p-1975796.html?cur_warehouse=CN&ID=457636287830&rmmds=search&p=7W24238151091201612I

    Every product link on a Banggood results page, 120 out of 120 in our test, was rewritten with the same affiliate token.

  • AliExpress product lookup (deals99.com)
    {"site":"aliexpress","hostname":"www.aliexpress.com","source_ids":"1005006331891004","account":"2","tracking_id":"ea_medium_14"}

    On AliExpress, code sends the hostname and product IDs to deals99.com and swaps in the returned link. Present in code, not observed firing.

The code that does this

Per-site affiliate-ID rewrite functions

Readable version

Agoda rewrite

// Replaces (or appends) the Agoda "cid" affiliate parameter with the// extension's own hardcoded ID (window._ea.aid.AGODA === "1763404")function rewriteAgodaLink(existingCidMatch, originalUrl) {  const extensionAgodaId = window._ea?.aid?.AGODA || ""; // "1763404"  if (existingCidMatch) {    return originalUrl.replace(existingCidMatch[0], `cid=${extensionAgodaId}`);  }  return `${originalUrl}${originalUrl.includes("?") ? "&" : "?"}cid=${extensionAgodaId}`;}

Banggood rewrite

// Replaces (or appends) the Banggood "p" affiliate token with the// extension's own hardcoded value (window._ea.aid.BANGGOOD === "7W24238151091201612I")function rewriteBanggoodLink(existingTokenMatch, originalUrl) {  const extensionBanggoodToken = window._ea?.aid?.BANGGOOD || "";  if (existingTokenMatch) {    return originalUrl.replace(existingTokenMatch[0], `p=${extensionBanggoodToken}`);  }  return `${originalUrl}${originalUrl.includes("?") ? "&" : "?"}p=${extensionBanggoodToken}`;}

Booking.com rewrite

// Replaces (or appends) the Booking.com "aid" affiliate parameter.// getBookingAffiliateId() reads a browser-specific hardcoded ID:// default "2082838", Firefox "2085046", Edge "2344472".function rewriteBookingLink(existingAidMatch, originalUrl) {  const aidParam = `aid=${getBookingAffiliateId()}`;  if (existingAidMatch) return originalUrl.replace(existingAidMatch[0], aidParam);  if (/(\/searchresults\.(.+)\.html)|(\/index\.(.+)\.html)/.test(originalUrl)) {    const [path, query] = originalUrl.split("?");    return `${path}?${aidParam}&${query}`;  }  return `${originalUrl}${originalUrl.endsWith(".html") ? "?" : ";"}${aidParam}`;}

Link-rewrite dispatcher

// Runs once immediately, then again on every DOM mutation (via a// MutationObserver wired up by J()). Walks every <a href> on the page// not already processed and rewrites it in place.function rewriteLinksForSite(site) {  if (!site) return;  const extractProductOrCid = siteExtractors[site]; // one of Y/z/Q/tt  if (!extractProductOrCid) return;  const pendingAliExpressIds = [];  (async () => {    const links = document.querySelectorAll('[href]:not(.ea-processed, link)');    if (links.length === 0) return;    links.forEach(anchor => {      let href = anchor.getAttribute('href') || '';      if (href === '#' || href.startsWith('javascript:')) {        anchor.classList.add('ea-processed');        return;      }      const match = extractProductOrCid(href);      if (match) {        if (site === 'agoda') { applyRewrite(/cid=\d+/, m => rewriteAgodaLink(m, href)); return; }        if (site === 'banggood') { applyRewrite(/p=\w+/, m => rewriteBanggoodLink(m, href)); return; }        if (site === 'booking') { applyRewrite(/aid=\d+/, m => rewriteBookingLink(m, href)); return; }        // AliExpress: queue the product ID for a batched deals99.com lookup        if (!pendingAliExpressIds.includes(match)) pendingAliExpressIds.push(match);        anchor.classList.add('ea-promotion');        anchor.setAttribute('data-ea-pid', match);      } else {        anchor.classList.add('ea-processed');      }    });    // Fetch replacement promotion URLs for every queued AliExpress product ID    const promotionUrls = await fetchPromotionUrls(site, pendingAliExpressIds);    document.querySelectorAll('[href].ea-promotion:not(.ea-processed, link)').forEach(anchor => {      const pid = anchor.getAttribute('data-ea-pid');      if (pid) {        const url = promotionUrls[pid];        if (url != null && url.startsWith('http')) anchor.setAttribute('href', url);      }      anchor.classList.add('ea-processed');    });  })();}
Captured request
GEThttps://www.agoda.com/room2-chiswick-hometel/hotel/london-gb.html?checkIn=2026-08-15&los=1&room=1&adults=2&cid=1763404

Top-level navigation to the Agoda hotel page after the content script ran; cid=1763404 matches the hardcoded AGODA affiliate ID in the extension's own code (window._ea.aid.AGODA), not a site-default or third-party affiliate value.

The code that does this

AliExpress remote lookup, present in the code, did not fire during this test session

Readable version

Batch AliExpress product IDs and request promotion URLs

// Called once per page with the site name ("aliexpress") and the list// of AliExpress product IDs found by the link scanner. Caches results// for 1 hour, batches in groups of 10, and asks the background worker// to fetch https://deals99.com/jsonp/product/kanai-urls for each batch.async function fetchAliExpressPromotionUrls(site, productIds) {  const trackingId = window._ea?.aid?.AE || ""; // "ea_medium_14" (browser-specific)  const cacheKey = `promotion_urls:${site}`;  const now = Date.now();  let cached = cache.get(cacheKey) || {};  cached = keepUnexpired(cached, now);  const uncachedIds = productIds.filter(id => !(id in cached));  if (uncachedIds.length) {    const { hostname } = window.location;    const expiresAt = now + 60 * 60 * 1000; // 1 hour    for (const batch of chunk(uncachedIds, 10)) {      const response = await backgroundApiRequest('/product/kanai-urls', {        site,        hostname,        source_ids: batch.join(','),        account: '2',        tracking_id: trackingId,      });      for (const [productId, promoUrl] of Object.entries(response.records)) {        cached[productId] = `${promoUrl}:EA:${expiresAt}`;      }    }    cache.set(cacheKey, cached);  }  return stripExpiryMarker(cached);}

Background worker performs the fetch on the content script's behalf

// The content script cannot fetch cross-origin from the page context in// this build, so it sends a runtime message and the background service// worker (which has host permissions for <all_urls>) makes the request.async function handleApiMessage(request, sendResponse) {  let { url } = request;  const { data, method } = request;  if (method === 'GET' && data && Object.keys(data).length > 0) {    url = `${url}?${new URLSearchParams(data)}`;  }  try {    const res = await fetch(url, {      method,      body: ['GET', 'HEAD'].includes(method) ? undefined : JSON.stringify(data),    });    if (!res.ok) throw new Error(res.statusText);    sendResponse({ type: 'success', content: await res.json() });  } catch (err) {    sendResponse({ type: 'fail', content: err });  }}browser.runtime.onMessage.addListener((request, sender, sendResponse) => {  if (request.action === 'api') handleApiMessage(request, sendResponse);  return true; // keep the message channel open for the async response});
Where rewritten links point, and who else receives data
    • agoda.com

    Normal navigation destination; hotel links are rewritten locally in the browser to carry cid=1763404, the extension's own affiliate ID.

    • booking.com

    Normal navigation destination; hotel links are rewritten locally to carry aid=2082838 (or a browser-specific variant), the extension's own affiliate ID.

    • banggood.com

    Normal navigation destination; product links are rewritten locally to carry p=7W24238151091201612I, the extension's own affiliate token.

    • deals99.com

    Receives the page hostname and AliExpress product IDs, returns promotion URLs replacing product links. Coded in; not observed firing during testing.

What it can do

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

    <all_urls>

  • Store data in your browser

    storage

Where it sends data

Destinations our analysis observed Font Changer 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.

  • deals99.com

    Font Changer sends data to deals99.com. 2 other extensions we have analysed send data here.

Updated 30 September 2026amo-2630923