Is PriceBlink Coupons and Price Comparison safe?
PriceBlink runs retailer configuration from tb.priceblink.com as page-context JavaScript during coupon autofill.
During auto-coupon flows, the extension fetches retailer-specific instructions from tb.priceblink.com/retailer.php and injects those strings into the page as script alongside page content and form fields. Its content scripts also accept openUrl and openCloseUrl window messages on all HTTP and HTTPS pages without an origin check, allowing web pages to make the extension open background tabs.
Who publishes itCNET Media, Inc. - 1 other listing from the same operator, none carrying a finding
CNET Media, Inc. - 1 other listing from the same operator, none carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same operator - 1 listing
Published under a different store account, but sharing the registered address, contact or declared legal entity this one gave the store.
Shared hosts - 3 hostnames
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Retailer config runs as checkout-page JavaScript
PriceBlink fetches retailer config from tb.priceblink.com for auto-coupon support.
Several response fields feed runEval, which builds and appends a script element to the page head, letting retailer code touch checkout DOM and coupon inputs.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-829
- Source
- Dynamic sandbox
You start an auto-coupon flow on a supported checkout page.
The extension has already requested retailer-specific coupon settings for that site.
The extension runs selected retailer-configuration fields as page JavaScript.
The generated script is appended to the page head, so it executes in the page context during coupon application.
JSON retailer configuration was returned; the recorded evidence did not include a response body.
- Before applying couponsac_pre_apply_codes
Lets the retailer response run code before coupon testing starts on the checkout page you are viewing.
- Before filling the coupon inputac_pre_input
Lets the retailer response run code immediately before the extension writes a coupon into the page input.
- Before submitting the couponac_pre_submit
Lets the retailer response run code while the checkout form is ready to be submitted.
- Submit fallback codecoupon_submit_selector
If the configured submit selector cannot be clicked normally, the same configured value is executed as page JavaScript.
- After submitting the couponac_post_submit
Lets the retailer response run code after each coupon submission step.
The fetched retailer object feeds page-context script injection
Background fetch for retailer configuration
js/background.jsasync function getRetailerByID_async(rid) { removeExpiredSuppressedRetailers(); const response = await fetch("https://tb.priceblink.com/retailer.php?uid=" + uid + "&browser=" + currentBrowserType + "&ver=" + ver + "&partnerid=0&rid=" + rid + "&auto_coupon_support=1", { method: "GET", headers: { "x-cdn-debug": "fastly" } }); const data = await response.json(); return data}Coupon flow executes ac_pre_apply_codes
js/app.jsasync function applyCode(ret, code, index, couponsAmount) { let acContainer = window.shadow.querySelector("#priceblink-ac-container"); if (!acContainer) { injectAutoCouponsPopup("ac-apply-coupons", (function() { updateStatus(code, index, couponsAmount); applyCode(ret, code, index, couponsAmount) })); return } if (ret.act_bypass_transition_logic == "0") { if (ret.act_pre_apply_codes) { const inputEL = $(ret.act_coupon_input_selector); const preApplyEL = $(ret.act_pre_apply_codes); if (preApplyEL.length > 0) { if (inputEL.length == 0 || inputEL.is(":hidden")) { try { preApplyEL[0].click() } catch (e) { console.warn(e) } await setTimeoutAsync(ret.act_preapply_delay) } } } } else { if (ret.ac_pre_apply_codes) { runEval(ret.ac_pre_apply_codes) } } setTimeout((async function() { updateStatus(code, index, couponsAmount); if (ret.act_bypass_transition_logic == "0") { try { console.log("MV3"); fillAndSubmitCodeV3(ret, code) } catch (e) { console.warn(e); ret.act_bypass_transition_logic = "1"; console.log("Fallback to MV2"); fillAndSubmitCodeV2(ret, code) } } else { console.log("MV2"); fillAndSubmitCodeV2(ret, code) } await sendMessagePromise({ action: "applyNextCode", url: document.location.href }); applyAllCodes(ret) }), ret.ac_submit_delay * 1e3)}Coupon input and submit hooks call runEval
js/app.jsfunction fillAndSubmitCodeV2(ret, code) { if (ret.ac_pre_input) { runEval(ret.ac_pre_input) } $(ret.coupon_input_selector)[0] && $(ret.coupon_input_selector).val(code); if (ret.ac_pre_submit) { runEval(ret.ac_pre_submit) } try { $(ret.coupon_submit_selector)[0] && $(ret.coupon_submit_selector)[0].click() } catch (e) { runEval(ret.coupon_submit_selector) } if (ret.ac_post_submit) { runEval(ret.ac_post_submit) }}runEval appends generated script into the page head
js/app.jsfunction runEval(code) { let result = document.createElement("div"); result.id = "pb-autocoupons-eval-result"; document.body.appendChild(result); let callback = function(mutationsList) { for (let mutation of mutationsList) { if (mutation.attributeName === "result") { observer.disconnect(); result.remove(); script.remove(); break } } }; let observer = new MutationObserver(callback); observer.observe(result, { attributes: true }); let script = document.createElement("script"); script.type = "text/javascript"; script.text = "(function () {\n var result = null;\n try {\n result = JSON.stringify(function(){" + code + "}());\n } catch(e) {\n e.error = true;\n result = JSON.stringify(e, ['message', 'name', 'error']);\n }\n document.getElementById('" + result.id + "').setAttribute('result', result);\n }())"; document.getElementsByTagName("head").item(0).appendChild(script)}- tb.priceblink.com
Returns retailer configuration consumed by the PriceBlink background worker and auto-coupon content script.
Where it sends data
Destinations our analysis observed PriceBlink 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.
- tb.priceblink.com
PriceBlink sends data to tb.priceblink.com. One other extension we have analysed sends data here.