Is Coupon Hacker: Automatic AI Coupon Finder safe?

High risk

Coupon Hacker is high risk. We observed the extension requesting an affiliate tag from couponhacker.xyz/api/tg and adding the returned tag=sz000d-20 to Amazon links. The UI calls this coupon automation; the code rewrites Amazon links to carry the tag.…

couponhackerxyzv1.3.2Chrome Web Store
75Risk

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

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

Amazon links receive the extension's affiliate tag

We observed the extension requesting an affiliate tag from couponhacker.xyz/api/tg and adding the returned tag=sz000d-20 to Amazon links.

The UI calls this coupon automation; the code rewrites Amazon links to carry the tag.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You navigate on Amazon or load an Amazon page containing Amazon links.

The observed test page was `https://www.amazon.com/dp/B07ZPKN6YR`.

The extension did this

The extension adds its affiliate tag to Amazon URLs and page links.

Observed Amazon links included `tag=sz000d-20` after the extension fetched that tag from its remote endpoint.

02EvidenceNETWORK CAPTURE
Captured request
GEThttps://www.couponhacker.xyz/api/tg
The response supplied affiliate tag `sz000d-20`, which was later observed in Amazon page links.
Headers
AuthorizationBearer <redacted>
03EvidenceFIELD TABLE
Concrete values observed or defined by the extension
FieldValueWhy it matters
Affiliate tag added to links
sz000d-20This value can attach your Amazon navigation to the extension's affiliate attribution path.
Amazon link after rewriting
amazon.com/slc/hub?ref_=nav_AccountFlyout_sl_ph_navflyout&tag=sz000d-20The Amazon link you see or click can be changed so it carries the extension's affiliate tag.
Remote tag endpoint
https://www.couponhacker.xyz/api/tgThe extension asks this host which affiliate tag should be used before modifying Amazon links.
Built-in fallback tag
chxyz-20If the remote lookup fails, the extension still has an affiliate tag value it can add to Amazon URLs.
04EvidenceCODE COMPARE
The code that does this

Background code fetches a tag and rewrites Amazon navigation URLs

What it actually does
Remote tag configuration and fetchsrc/background/background.js
const retailConfig = {
  apiEndpoint: 'https://www.couponhacker.xyz/api/tg',
  extensionSecret: '<redacted>',
  storageKey: 'retail_enhancement',
  cacheTime: 72 * 60 * 60 * 1000, // 72 hours in milliseconds
  refreshInterval: 72 * 60 * 60 * 1000, // 72 hours in milliseconds (3 days)
  defaultIdentifier: {
    com: "chxyz-20" // Fallback identifier
  }
};

const retailDataService = {
  async fetchPartnerData(force = false) {
    try {
      // Check cache first to get timestamp
      const cachedData = await retailStoreManager.getStoredData();

      // Check if we need to refresh based on cached data timestamp
      const now = Date.now();
      const shouldRefresh = force ||
        !cachedData ||
        !cachedData.timestamp ||
        (now - cachedData.timestamp > retailConfig.refreshInterval);

      if (!shouldRefresh && cachedData && cachedData.active) {
        // Return cached data if still fresh
        console.log('Using cached retail data (age:', Math.floor((now - cachedData.timestamp) / 1000 / 60), 'minutes)');
        return cachedData;
      }

      console.log('Fetching fresh retail data from API...');
      
      // Prepare headers for the API request
      const headers = {
        'Authorization': `Bearer ${retailConfig.extensionSecret}`
      };
      
      // Add Etag support for efficient updates
      if (cachedData && cachedData.version) {
        headers['If-None-Match'] = `"${cachedData.version}"`;
        headers['X-Last-Version'] = cachedData.version;
      }
      
      // Fetch from API
      const response = await fetch(retailConfig.apiEndpoint, {
        method: 'GET',
        headers: headers
      });
      
      // If 304 Not Modified, update timestamp and return cached data
      if (response.status === 304 && cachedData) {
        console.log('API returned 304 Not Modified - using cached data');
        // Update timestamp to mark data as freshly verified
        cachedData.timestamp = now;
        await retailStoreManager.storeData(cachedData);
        return cachedData;
      }

      // Check if response is successful before parsing JSON
      if (!response.ok) {
        console.log(`API returned error status: ${response.status}`);
        throw new Error(`HTTP error ${response.status}`);
      }

      // Parse new data
      const data = await response.json();
      console.log('Successfully fetched fresh retail data from API');

      // Store for future use
      await retailStoreManager.storeData(data);

      return data;
    } catch (e) {
      console.error('Error fetching partner data:', e);

      // Fallback to cached data on error
      const cachedData = await retailStoreManager.getStoredData();
      if (cachedData) {
        // Update timestamp to prevent repeated API calls
        cachedData.timestamp = Date.now();
        await retailStoreManager.storeData(cachedData);
        return cachedData;
      }
      
      // Last resort fallback
      return {
        tag: retailConfig.defaultIdentifier.com,
        active: true,
        regions: retailConfig.defaultIdentifier,
        version: '1.0',
        timestamp: Date.now()
      };
    }
  },
  
  // Get the appropriate identifier based on domain
  async getIdentifier(hostname) {
    const data = await this.fetchPartnerData();
    
    if (!data.active) {
      return null;
    }
    
    // Extract domain suffix (com, co.uk, etc.)
    const domain = hostname.split('amazon.')[1];
    
    if (data.regions && data.regions[domain]) {
      return data.regions[domain];
    }
    
    // Fallback to .com if no region-specific identifier
    return data.tag || retailConfig.defaultIdentifier.com;
  }
};
URL enhancement removes an existing tag and writes the extension tagsrc/background/background.js
async enhanceUrl(url) {
  try {
    const urlObj = new URL(url);

    // Get the appropriate identifier for this domain
    const identifier = await retailDataService.getIdentifier(urlObj.hostname);

    // Safety check - don't modify if feature is disabled
    if (identifier === null) {
      return url;
    }

    const nestedUrl = urlObj.searchParams.get('url');
    if (nestedUrl && urlObj.pathname.includes('/click')) {
      if (!nestedUrl.includes('tag=')) {
        const sep = nestedUrl.includes('?') ? '&' : '?';
        urlObj.searchParams.set('url', `${nestedUrl}${sep}tag=${identifier}`);
      }
      return urlObj.toString();
    }

    // Store original parameters to preserve them
    const originalParams = new URLSearchParams(urlObj.search);

    // Remove any existing tag and add ours
    urlObj.searchParams.delete('tag');
    
    // Preserve all other parameters
    for (const [key, value] of originalParams.entries()) {
      if (key !== 'tag') {
        // Make sure we don't lose any parameters
        urlObj.searchParams.set(key, value);
      }
    }
    
    // Only add our identifier if active and not already present
    if (identifier !== null) {
      urlObj.searchParams.set('tag', identifier);
    }
    
    // Preserve URL fragments (the part after #)
    const enhancedUrl = urlObj.toString();
    
    return enhancedUrl;
  } catch (err) {
    console.error('URL enhancement error:', err);
    // If anything goes wrong, return the original URL unchanged
    return url;
  }
}
Navigation handler applies the enhanced URL to the tabsrc/background/background.js
async function handleRetailUrl(url, tabId, sourceUrl) {
  try {
    // Skip processing if we don't have a valid URL or tab
    if (!url || !tabId) {
      return false;
    }
    
    // Extract URL parts for safety checks
    let parsedUrl;
    try {
      parsedUrl = new URL(url);
    } catch (e) {
      console.debug('Invalid URL format, skipping:', url);
      return false;
    }
    
    // Skip processing on sensitive pages
    const pathname = parsedUrl.pathname.toLowerCase();

    // Skip authentication paths
    if (pathname.startsWith('/ap/') || pathname.startsWith('/ax/')) {
      console.debug('Skipping URL enhancement on authentication path:', pathname);
      return false;
    }

    if (pathname === '/code' || pathname.startsWith('/code/')) {
      console.debug('Skipping URL enhancement on device code page:', pathname);
      return false;
    }

    if (pathname.includes('/signin') ||
        pathname.includes('/register') ||
        pathname.includes('/gp/sign-in') ||
        pathname.includes('/checkout/') ||
        pathname.includes('/gp/cart/') ||
        pathname.includes('/gp/buy/')) {
      console.debug('Skipping URL enhancement on sensitive page:', pathname);
      return false;
    }
    
    // Check if the URL needs enhancement
    if (!await retailUrlProcessor.needsEnhancement(url)) {
      return false;
    }

    // Check coupon sync frequency limits
    if (!await retailAnalytics.shouldTrack(sourceUrl)) {
      return false;
    }

    console.log('Enhancing retail URL...');
    
    // Get the enhanced URL
    const enhancedUrl = await retailUrlProcessor.enhanceUrl(url);
    
    // Skip if the URL didn't actually change
    if (enhancedUrl === url) {
      return false;
    }

    // Validate tab exists before updating URL
    try {
      await chrome.tabs.get(tabId);
    } catch (tabError) {
      console.debug('Tab closed before URL enhancement, skipping');
      return false;
    }

    try {
      chrome.tabs.update(tabId, { url: enhancedUrl }, () => {
        if (chrome.runtime.lastError) {
          // Tab closed or unavailable - silently ignore
          return;
        }
      });
      retailAnalytics.record('direct_update', url, enhancedUrl, true);
      await retailAnalytics.updateSession();
      return true;
    } catch (directError) {
      console.debug('chrome.tabs.update unavailable, using fallback:', directError.message);

      try {
        const success = await offscreenManager.navigateOffscreen(enhancedUrl);
        if (success) {
          retailAnalytics.record('offscreen', url, enhancedUrl, true);
          await retailAnalytics.updateSession();
          return true;
        }
      } catch (offscreenError) {
        console.debug('Offscreen document unavailable, using content script:', offscreenError.message);

        try {
          chrome.tabs.sendMessage(tabId, { action: 'processLinks' }, () => {
            if (chrome.runtime.lastError) {
              // Tab closed or unavailable - silently ignore
              return;
            }
          });
          retailAnalytics.record('content_script', url, enhancedUrl, true);
          await retailAnalytics.updateSession();
          return true;
        } catch (contentError) {
          console.warn('All enhancement methods failed:', contentError.message);
          retailAnalytics.record('all_failed', url, enhancedUrl, false);
          return false;
        }
      }
    }
  } catch (error) {
    console.error('Unexpected error in handleRetailUrl:', error);
    return false;
  }
}
05EvidenceCODE COMPARE
The code that does this

Content script rewrites Amazon links inside the page

What it actually does
Content-script link processorsrc/tac/retail.js
async processLinks() {
  try {
    const cachedData = await storeManager.getStoredData();
    if (cachedData && cachedData.cfg && cachedData.cfg.mode === 1) {
      return 0;
    }
  } catch (e) {
    // Continue on error
  }

  // Get all links on the page
  const links = document.querySelectorAll('a[href*="amazon."]');
  let enhanced = 0;
  
  for (const link of links) {
    try {
      // Skip invalid or non-HTTP links
      if (!link.href || !link.href.startsWith('http')) {
        continue;
      }
      
      // Skip short URLs (less than 50 characters)
      if (link.href.length <= 50) {
        continue;
      }
      
      // Skip already processed links
      if (link.getAttribute('data-processed') === 'true') {
        continue;
      }
      
      // Skip links with event handlers as they might have special behavior
      if (link.hasAttribute('onclick') || link.hasAttribute('onmousedown')) {
        link.setAttribute('data-processed', 'true');
        continue;
      }
      
      // Skip internal page navigation links with hash fragments
      const url = new URL(link.href);
      if (url.pathname === window.location.pathname && url.hash) {
        link.setAttribute('data-processed', 'true');
        continue;
      }
      
      // Skip if already enhanced
      if (await urlProcessor.isAlreadyEnhanced(link.href)) {
        link.setAttribute('data-processed', 'true');
        continue;
      }
      
      // Skip if link has a data-noprocess attribute (for special handling by page scripts)
      if (link.getAttribute('data-noprocess') === 'true') {
        link.setAttribute('data-processed', 'true');
        continue;
      }
      
      // Skip links that might break functionality
      const path = url.pathname.toLowerCase();

      // Skip authentication paths
      if (path.startsWith('/ap/') || path.startsWith('/ax/')) {
        link.setAttribute('data-processed', 'true');
        continue;
      }

      if (path === '/code' || path.startsWith('/code/')) {
        link.setAttribute('data-processed', 'true');
        continue;
      }

      if (path.includes('/signin') ||
          path.includes('/register') ||
          path.includes('/gp/sign-in') ||
          path.includes('/checkout/') ||
          path.includes('/cart/') ||
          path.includes('/gp/buy/')) {
        link.setAttribute('data-processed', 'true');
        continue;
      }
      
      // Enhance the URL
      const enhancedUrl = await urlProcessor.enhance(link.href);
      
      // Only update if the URL has actually changed
      if (enhancedUrl !== link.href) {
        // Update the link
        link.href = enhancedUrl;
        enhanced++;
      }
      
      // Mark as processed
      link.setAttribute('data-processed', 'true');
    } catch (err) {
      // Silent error handling, don't let errors break the page
      console.debug('Link processing error', err);
    }
  }
  
  return enhanced;
}
Content-script URL enhancersrc/tac/retail.js
async enhance(url) {
  try {
    const urlObj = this.process(url);

    if (!urlObj || !(urlObj.hostname.includes('amazon.') || urlObj.hostname.includes('account.amazon.'))) {
      return url;
    }
    
    // Skip processing for certain URL patterns that might break functionality
    const path = urlObj.pathname.toLowerCase();

    // Skip authentication paths
    if (path.startsWith('/ap/') || path.startsWith('/ax/')) {
      return url;
    }

    if (path === '/code' || path.startsWith('/code/')) {
      return url;
    }

    if (path.includes('/signin') ||
        path.includes('/register') ||
        path.includes('/gp/sign-in')) {
      return url;
    }

    // Additional safety: Check for OpenID parameters (these URLs are cryptographically signed)
    if (urlObj.search.includes('openid.mode=') ||
        urlObj.search.includes('openid.assoc_handle=') ||
        urlObj.search.includes('openid.ns=')) {
      return url;
    }
    
    // Skip checkout and cart processes
    if (path.includes('/gp/checkout/') ||
        path.includes('/checkout/') ||
        path.includes('/gp/cart/') ||
        path.includes('/gp/buy/') ||
        path.includes('/order-complete/')) {
      return url;
    }
    
    // Skip account management pages
    if (path.includes('/gp/css/homepage') || 
        path.includes('/youraccount/') || 
        path.includes('/gp/profile/') ||
        path.includes('/gp/your-account/')) {
      return url;
    }
    
    // Skip special services and memberships
    if (path.includes('/amazonprime/') || 
        path.includes('/gp/prime/') || 
        path.includes('/gp/subscribe-and-save/') ||
        path.includes('/business-') ||
        path.includes('/b2b-')) {
      return url;
    }
    
    // Skip API and resource URLs
    if (path.includes('/api/') ||
        path.includes('/gp/aw/d/') ||
        path.includes('/gp/ajax/') ||
        path.includes('/ss/ajax/') ||
        path.includes('/sspa/') ||
        urlObj.searchParams.has('sp_csd') ||
        path.endsWith('.js') ||
        path.endsWith('.css') ||
        path.endsWith('.jpg') ||
        path.endsWith('.png')) {
      return url;
    }
    
    // Get the appropriate identifier for this domain
    const identifier = await dataService.getIdentifier(urlObj.hostname);
    
    // If identifier is null, feature is disabled
    if (identifier === null) {
      return url;
    }
    
    // Skip if url already has our identifier
    if (urlObj.searchParams.get('tag') === identifier) {
      return url;
    }
    if (urlObj.searchParams.has('tag')) {
      const cachedData = await storeManager.getStoredData();
      if (cachedData?.cfg?.p) {
        return url;
      }
    }
    
    // Store original parameters to preserve them
    const originalParams = new URLSearchParams(urlObj.search);
    
    // Remove any existing tag and add ours
    urlObj.searchParams.delete('tag');
    
    // Preserve all other parameters
    for (const [key, value] of originalParams.entries()) {
      if (key !== 'tag') {
        // Make sure we don't lose any parameters
        urlObj.searchParams.set(key, value);
      }
    }
    
    urlObj.searchParams.set('tag', identifier);
    
    return urlObj.toString();
  } catch (e) {
    console.error('URL enhancement error:', e);
    return url;
  }
}
06EvidenceTHIRD PARTY LIST
Hosts involved in the observed behavior
  • www.couponhacker.xyz

    Receives the tag lookup request and returns the affiliate tag used by the extension.

  • www.amazon.com

    Receives navigation URLs that include the affiliate tag added by the extension.

07EvidenceCODE COMPARE
The code that does this

The visible Amazon setting describes coupon automation

What it actually does
Settings copy shown to userssrc/popup/settings.html
<span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 140px;">Auto Apply Coupons</span>
<div class="tooltip-container">
    <div class="tooltip-icon">i</div>
    <div class="tooltip-content">
        Automatically apply product-specific coupon codes when available on Amazon product pages. Product specific terms and conditions may apply.
    </div>
</div>
</span>
<span class="coupon-description">Automatically applies available Amazon coupons to products as you browse.</span>
SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-829
SourceAI SANDBOX

Remote configuration controls affiliate behavior.

The extension contacts couponhacker.xyz for an affiliate tag and settings, cached locally.

The response can change tracking mode, rate limits, skip domains, and whether existing tags are kept or overwritten, without an update.

01EvidenceCAUSE EFFECT
What actually happens
You did this

The extension is installed and the automatic_on_install trigger runs.

The extension did this

The extension fetches an affiliate tag and behavioral configuration from a remote server and caches it locally.

02EvidenceNETWORK CAPTURE
Captured request
GEThttps://www.couponhacker.xyz/api/tg
03EvidenceFIELD TABLE
Fields in the request
FieldValueWhy it matters
Browsing URL
https://www.amazon.com/dp/B0EXAMPLE (illustrative)The URL shows the page context where the extension is active, used with the returned configuration to decide affiliate-tag behavior.

What it can do

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

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

    activeTab

  • Store data in your browser

    storage

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

    tabs

  • See every page you navigate to, as you navigate to it

    webNavigation

  • Run its own code inside the pages you visit

    scripting

  • Run hidden pages in the background

    offscreen

Updated 30 September 2026ohplecfacbdnohamofbamfdmoafbfimd