Is Shop Deals safe?

Medium risk

Shop Deals sends Google search result URLs to Takeads and rewrites eligible results to affiliate links.

When you use Google Search, Shop Deals reads the result links on the page and sends those URLs to Takeads to check for monetization matches. If Takeads returns partner data, the extension changes matching search result links to affiliate tracking URLs and adds a discounts button.

jayb20262v1.3.0Chrome Web Store
49Risk

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

Publishers can request a review.

Findings

Shop Deals ships a working Takeads API token inside its public code

Shop Deals carries a working Takeads API key in the code every user downloads, so anyone who unpacks the extension can read it.

We extracted that key and used it on its own against the live Takeads API, which accepted the call.

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

You run a search on google.com with the extension installed.

The content script is scoped to https://www.google.com/* and runs at document_end on every results page.

The extension did this

The extension sends your result links to the Takeads monetization API, proving who it is with an API key that is written into the code every user downloads.

The same key is reused by the popup when it looks up coupons for the page you are on.

The code that does this

The same bearer token authorizes two different Takeads endpoints

Readable version

What the content script call does

assets/chunk-3db9f861.js
const body = JSON.stringify({  iris: linkHrefs,          // every organic result link on the page  subId: partnerId || "common",  withImages: true});const response = await fetch(  "https://api.takeads.com/v1/product/monetize-api/v2/resolve",  {    method: "PUT",    headers: {      "Content-Type": "application/json",      // static credential, identical in every copy of the extension      Authorization: "Bearer 99dc7ff2<redacted 40-char hex token>"    },    body  });

What the popup call does

assets/chunk-433c5e1b.js
const body = JSON.stringify({  iris: [activeTab.url],    // the page you are currently viewing  subId: partnerId});fetch(  "https://api.takeads.com/v1/product/monetize-api/v1/coupon/search",  {    method: "POST",    headers: {      "Content-Type": "application/json",      // same credential as the content script path      Authorization: "Bearer 99dc7ff2<redacted 40-char hex token>"    },    body  }).then(r => r.json());
Captured request
PUThttps://api.takeads.com/v1/product/monetize-api/v2/resolve

HTTP 201 with an empty data array. The test URL had no matching partner offer, but the API authenticated and accepted the request, which is what shows the key is live.

Headers
Content-Type
application/json
Authorization
Bearer 99dc7ff2<redacted>
Body
{  "iris": [    "https://www.amazon.com/dp/B08N5WRWNW"  ],  "subId": "common",  "withImages": true}
What the bundled key lets a third party do
  • Takeads API key
    Bearer 99dc7ff2<redacted>

    One key shared by every install. It cannot be revoked for a single user without breaking the extension for all of them.

  • Links being monetized
    https://www.amazon.com/dp/B08N5WRWNW

    The search result links, or the page you are viewing, that get sent for an affiliate match.

  • Partner attribution ID
    common

    Identifies which distribution partner gets paid for the traffic. It is fetched at install time and stored.

  • Returned tracking link
    https://tatrck.com/h/0Hu30zkd0RRb?url=https%3A%2F%2Fwww.amazon.com%2Fdp%2FB08N5WRWNW&s=common

    The replacement affiliate link the API hands back, which the extension writes over the original result link.

Hosts involved in this flow
    • api.takeads.com

    Takeads monetization API. Receives the result links and returns affiliate deeplinks. This is the endpoint the bundled bearer token authenticates to.

    • extads.monetize.software

    Install attribution service. Read at install time for a partner ID that is then stored and sent as subId on monetization calls.

    • tatrck.com

    Takeads click redirector. Serves the tracking links returned by the API and written into your search results.

Check if you're affected

Scans an unpacked extension directory for Authorization: Bearer values written directly into shipped JavaScript, and reports which file and which endpoint each one is attached to.

Requires
  • Node.js 18 or newer
  • an unpacked copy of the extension
find-bundled-bearer-tokens.js · js
#!/usr/bin/env node// Find hardcoded bearer credentials in an unpacked browser extension.// Usage: node find-bundled-bearer-tokens.js /path/to/unpacked-extensionconst fs = require('fs');const path = require('path');const root = process.argv[2];if (!root) {  console.error('usage: node find-bundled-bearer-tokens.js <unpacked-extension-dir>');  process.exit(1);}const BEARER = /Bearer\s+([A-Za-z0-9._\-]{16,})/g;const URL_RE = /https?:\/\/[^"'`\s\\]+/g;function walk(dir, out = []) {  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {    const full = path.join(dir, entry.name);    if (entry.isDirectory()) walk(full, out);    else if (/\.(js|mjs|html|json)$/i.test(entry.name)) out.push(full);  }  return out;}const findings = new Map();for (const file of walk(root)) {  const text = fs.readFileSync(file, 'utf8');  let m;  BEARER.lastIndex = 0;  while ((m = BEARER.exec(text)) !== null) {    const token = m[1];    // nearest URL literal before the header, which is normally the endpoint    const before = text.slice(Math.max(0, m.index - 400), m.index);    const urls = before.match(URL_RE) || [];    const endpoint = urls.length ? urls[urls.length - 1] : '(endpoint not resolved)';    const key = token + '|' + path.relative(root, file) + '|' + endpoint;    findings.set(key, { token, file: path.relative(root, file), endpoint });  }}if (findings.size === 0) {  console.log('No hardcoded bearer tokens found.');  process.exit(0);}const byToken = new Map();for (const f of findings.values()) {  if (!byToken.has(f.token)) byToken.set(f.token, []);  byToken.get(f.token).push(f);}for (const [token, uses] of byToken) {  const masked = token.slice(0, 8) + '...' + '(' + token.length + ' chars)';  console.log('\nToken ' + masked + ' shipped in the bundle, used ' + uses.length + ' time(s):');  for (const u of uses) {    console.log('  ' + u.file + '  ->  ' + u.endpoint);  }  if (uses.length > 1) {    console.log('  NOTE: one credential reused across multiple call sites.');  }}
How to run it
  1. 1Unpack the extension CRX into a directory.
  2. 2Run: node find-bundled-bearer-tokens.js ./unpacked-extension.
  3. 3Read the report; each line pairs a shipped token with the endpoint it authenticates to.

What it can do

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

    <all_urls>

  • Store data in your browser

    storage

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

    cookies

  • See the address and title of every tab you have open

    tabs

  • Act on the current tab, but only after you click the extension

    activeTab

Where it sends data

Destinations our analysis observed Shop Deals 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.takeads.com

    Shop Deals sends data to api.takeads.com. 3 other extensions we have analysed send data here.

Updated 30 September 2026lgflpjoaeinnkgjlmmaobdbfplakjihb