Is Coupon Hacker: Automatic AI Coupon Finder safe?
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.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
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.
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 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.
| Authorization | Bearer <redacted> |
| Field | Value | Why it matters | |
|---|---|---|---|
Affiliate tag added to links | sz000d-20 | This 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-20 | The 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/tg | The extension asks this host which affiliate tag should be used before modifying Amazon links. | |
Built-in fallback tag | chxyz-20 | If the remote lookup fails, the extension still has an affiliate tag value it can add to Amazon URLs. |
Background code fetches a tag and rewrites Amazon navigation URLs
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;
}
};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;
}
}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;
}
}Content script rewrites Amazon links inside the page
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;
}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;
}
}- 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.
The visible Amazon setting describes coupon automation
<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>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.
The extension is installed and the automatic_on_install trigger runs.
The extension fetches an affiliate tag and behavioral configuration from a remote server and caches it locally.
| Field | Value | Why 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