Is AVG SafePrice safe?

Medium risk

AVG SafePrice is medium risk. AVG SafePrice injects a script into every page overwriting navigation functions, logging every page visited, even untyped ones. Dynamic analysis caught it reporting Amazon transitions to AVG's worker. Onward transmission is off, changeable.

AVG Technologiesv25.7.0.906Chrome Web Store
45Risk
Who publishes it

Gen Digital - 17 other listings 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.

Publisher
AVG Technologies
Declared legal entity
Gen Digital
Registered address
60 E Rio Salado Pkwy, Tempe, AZ 85281-9124, US
Registered contact
Gen Digital Inc

Same store account

1 other listing published from this account, 500k+ users between them, none of them carrying a finding.

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

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

MAIN-World Script Watches Every URL You Navigate To

AVG SafePrice injects a script into every page overwriting navigation functions, logging every page visited, even untyped ones.

Dynamic analysis caught it reporting Amazon transitions to AVG's worker.

Onward transmission is off, changeable.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You load or navigate to any page over HTTP or HTTPS.

Fires on chrome.webNavigation.onCommitted for the top frame of any http:// or https:// tab -- there is no check for a shopping/merchant domain before this fires.

The extension did this

The extension injects a script into the page's own JavaScript context that watches every way you can navigate away from it.

chrome.scripting.executeScript loads redirectionChainSiteScript.js with world:'MAIN', giving it the same JS context the page itself runs in, rather than the isolated content-script sandbox most extensions use.

02EvidenceCODE COMPARE
The code that does this

Unconditional injection on every navigation, and the navigation-API patches it installs

What it actually does
Injector registered on every committed HTTP/HTTPS navigationbackground.js:55815
const REDIRECTION_CHAIN_SCRIPT = "redirectionChainSiteScript.js";

class RedirectionChainSiteScriptInjector {
  constructor(logger) {
    this.logger = logger;
    this.started = false;
  }

  start() {
    if (this.started) return;
    this.started = true;

    // Registered on chrome.webNavigation.onCommitted -- fires for every
    // committed navigation in every tab, not just shopping sites.
    webNavigationOnCommitted.addListener((navDetails) => {
      if (navDetails.tabId < 0 || navDetails.frameId !== 0) return;
      const url = navDetails.url;
      if (url && (url.startsWith("http://") || url.startsWith("https://"))) {
        // No allowlist check against a merchant domain list here --
        // any committed http/https top-frame navigation qualifies.
        chrome.scripting
          .executeScript({
            siteScriptPath: [REDIRECTION_CHAIN_SCRIPT],
            tabId: navDetails.tabId,
            frameIds: [0],
            world: "MAIN", // runs in the page's own JS context, not the isolated content-script world
          })
          .catch((err) => {
            this.logger.debug(`Failed to inject ${REDIRECTION_CHAIN_SCRIPT} into tab ${navDetails.tabId}:`, err);
          });
      }
    });
  }
}
Location/history/window.open patches installed in the page's own JS contextredirectionChainSiteScript.js
const HOOK_FLAG = "__centlyRdSiteHookInstalled__";
const win = window;
if (win[HOOK_FLAG]) return; // only patch once per page

function reportHop(payload) {
  const message = { source: "cently-rd-site", payload };
  try {
    window.postMessage(message, "*");
  } catch (e) {
    try {
      window.postMessage(JSON.parse(JSON.stringify(message)), "*");
    } catch (e2) {}
  }
}

function captureStack() {
  const { stack } = new Error();
  if (stack) return stack.split("\n").slice(2, 8).join("\n");
}

function resolveUrl(value) {
  if (value == null) return;
  try {
    return new URL(value.toString(), location.href).href;
  } catch (e) {
    return value.toString();
  }
}

win[HOOK_FLAG] = true;
const locationProto = Location.prototype;

// Patch location.assign / .replace / .reload
function patchLocationMethod(methodName, label) {
  const original = locationProto[methodName];
  if (typeof original !== "function") return;
  const patched = function (...args) {
    const targetUrl = methodName === "reload" ? location.href : (resolveUrl(args[0]) ?? location.href);
    reportHop({ kind: "js_location_change", url: location.href, targetUrl, method: label, stack: captureStack() });
    return original.apply(this, args);
  };
  Object.defineProperty(locationProto, methodName, { value: patched, writable: true, configurable: true });
}
patchLocationMethod("assign", "assign");
patchLocationMethod("replace", "replace");
patchLocationMethod("reload", "reload");

// Patch location.href / pathname / hash / search / host / hostname / port / protocol setters
const locationProps = ["href", "pathname", "hash", "search", "host", "hostname", "port", "protocol"];
for (const prop of locationProps) {
  const descriptor = Object.getOwnPropertyDescriptor(locationProto, prop);
  if (!descriptor || typeof descriptor.set !== "function") continue;
  const originalGet = descriptor.get;
  const originalSet = descriptor.set;
  Object.defineProperty(locationProto, prop, {
    configurable: true,
    enumerable: descriptor.enumerable,
    get: originalGet,
    set(value) {
      const targetUrl = prop === "href" ? (resolveUrl(value) ?? value) : (() => {
        try {
          const u = new URL(location.href);
          u[prop] = value;
          return u.href;
        } catch (e) {
          return value;
        }
      })();
      reportHop({ kind: "js_location_change", url: location.href, targetUrl, method: prop, stack: captureStack() });
      return originalSet.call(this, value);
    },
  });
}

// Patch window.location assignment itself
try {
  const proto = Object.getPrototypeOf(window);
  const descriptor = (proto && Object.getOwnPropertyDescriptor(proto, "location")) || Object.getOwnPropertyDescriptor(window, "location");
  if (descriptor && typeof descriptor.set === "function") {
    const originalSet = descriptor.set;
    Object.defineProperty(window, "location", {
      configurable: true,
      enumerable: descriptor.enumerable ?? true,
      get: descriptor.get,
      set(value) {
        reportHop({ kind: "js_location_change", url: location.href, targetUrl: resolveUrl(value) ?? value, method: "window_location_setter", stack: captureStack() });
        return originalSet.call(window, value);
      },
    });
  }
} catch (e) {}

// Patch window.open, history.pushState, history.replaceState
const originalOpen = window.open;
function patchHistoryMethod(methodName) {
  const original = history[methodName];
  history[methodName] = function (state, title, url) {
    reportHop({ kind: "history_state_change", url: location.href, method: methodName, targetUrl: resolveUrl(url ?? undefined), stack: captureStack() });
    return original.call(this, state, title, url);
  };
}
if (typeof originalOpen === "function") {
  window.open = function (url, target, features) {
    reportHop({ kind: "js_window_open", url: location.href, targetUrl: resolveUrl(url), target, features, stack: captureStack() });
    return originalOpen.call(window, url, target, features);
  };
}
patchHistoryMethod("pushState");
patchHistoryMethod("replaceState");
03EvidenceFIELD TABLE
Fields reported on every intercepted navigation
FieldValueWhy it matters
Page you were on
https://www.amazon.com/s?k=usb+cableThe full URL of the page you were viewing right before the navigation happened.
Page you're going to
https://www.amazon.com/dp/B08JPMTV2S (illustrative -- exact captured product URL not quoted in the DA evidence record)The full URL you're navigating to, resolved to an absolute URL even if the page only passed a relative path.
How the navigation happened
history_state_change (history.replaceState)Which browser API triggered the report -- a location property set, history.pushState/replaceState, or window.open.
04EvidenceTHIRD PARTY LIST
Where the assembled navigation data is headed, and what currently blocks it
  • eb.nextgenshopping.com

    Serves the live remote config, including the 'sendAnalytics' flag controlling onward transmission. Returned false in every test session, June-August 2026.

  • c.nextgenshopping.com

    Snowplow collector that would receive the assembled redirect-chain event if sendAnalytics is enabled remotely. No such event was observed; the flag was off in all tests.

05EvidencePLAIN NOTE
What we did and didn't observe

We confirmed the injection and the API patching fire on ordinary navigation, and we confirmed the patched hooks capture real before/after URLs during a live browsing session. We did not observe the final step -- the assembled chain being sent to AVG's analytics collector -- because AVG's own server-side configuration currently returns sendAnalytics=false. That flag lives entirely on AVG's servers and can be changed at any time without a client-side update, so its current 'off' state is not a guarantee about how the extension behaves for all users or at all times.

Updated 30 September 2026mbckjcfnjmoiinpgddefodcighgikkgn