Is 二维码小能手 safe?

Medium risk

二维码小能手 is medium risk. This QR-code extension's worker sends an encrypted POST with your fingerprint ID/version to api.qrstrategy.com, undisclosed in the listing. The body decrypts, with the extension's own key, to JSON with the fingerprint, past cookie clears.…

llrenfubv0.0.6Chrome Web Store
45Risk

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

Fingerprint & Version Sent to Undisclosed Server on Startup

This QR-code extension's worker sends an encrypted POST with your fingerprint ID/version to api.qrstrategy.com, undisclosed in the listing.

The body decrypts, with the extension's own key, to JSON with the fingerprint, past cookie clears.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You install this QR-code scanner/generator extension and browse normally.

No fingerprinting or data collection is mentioned in the extension's Chrome Web Store description.

The extension did this

On every service worker startup, the extension encrypts your browser fingerprint and extension version and sends them to api.qrstrategy.com.

api.qrstrategy.com is unrelated to any QR-code functionality and is contacted over plain HTTP.

02EvidenceFIELD TABLE
Fields sent in the startup request
FieldValueWhy it matters
Browser fingerprint ID
30fc5c92a3dca2ce3180a3684df381fbA unique identifier from your browser and device. It persists across sessions and identifies your browser even after cookies are cleared.
Extension version
0.0.6Tells the server exactly which build of the extension you're running.
Build flag
1A fixed internal marker the extension always sends; likely used server-side to distinguish release channels.
03EvidenceOPAQUE REVEAL
Why you can't catch this in DevTools

The request body leaves your browser as an unreadable block of ciphertext, but the AES key used to produce it is hardcoded inside the extension's own shipped code (see the code block below), so anyone who reads the extension's source can decrypt it just as easily as the destination server can.

What's actually being sent
{
  "bf": 1,
  "version": "0.0.6",
  "fg": "30fc5c92a3dca2ce3180a3684df381fb"
}
04EvidenceCODE COMPARE
The code that does this

The startup request path in serviceworker.js

What it actually does
// state: cached fingerprint + server-provided config
const state = { fingerprint: "", jsonData: {} };

const sendFingerprintAndFetchConfig = (haveFingerprint) => {
  if (!haveFingerprint) {
    log("waiting", "fingerprint");
    return;
  }
  log("starting", "init");
  (async (fingerprint) => {
    try {
      const config = await (async (fingerprint) => {
        log("outbound payload", { bf: BUILD_FLAG, version: getExtensionVersion(), fg: fingerprint });

        const response = await fetch(`${C2_HOST}/sr105/qrinfo`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: encryptAesCbc(JSON.stringify({
            bf: BUILD_FLAG,
            version: getExtensionVersion(),
            fg: fingerprint,
          })),
        });
        const json = await response.json();

        if (json.success === 1) {
          const decrypted = decryptAesCbc(json.data);
          return !!decrypted && JSON.parse(decrypted);
        }
        return false;
      })(fingerprint);

      log("received config", config);
      state.jsonData = config;
      new RedirectRuleManager(state.jsonData, state.fingerprint, BUILD_FLAG).init();
    } catch {
      log("request failed", "config fetch failed");
    }
  })(state.fingerprint);
};

// Content script posts { event: "setFingerprint", body: { id: <fingerprint> } }
// when no fingerprint is stored yet; background persists it and immediately
// fires the request above.
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.event === "getFingerprint") {
    sendResponse(state.fingerprint ? { fingerprint: state.fingerprint } : false);
  }
  if (message.event === "setFingerprint") {
    const fingerprintId = message?.body?.id;
    log("received fingerprint", fingerprintId);
    if (fingerprintId) {
      chrome.storage.sync.set({ brext: fingerprintId });
      state.fingerprint = fingerprintId;
      sendFingerprintAndFetchConfig(true);
    }
  }
});

// On service-worker startup, reuse a fingerprint already persisted from a
// previous session.
chrome.storage.sync.get(["brext"], (stored) => {
  if (stored.brext) {
    log("cached fingerprint", stored.brext);
    state.fingerprint = stored.brext;
    sendFingerprintAndFetchConfig(true);
  } else {
    sendFingerprintAndFetchConfig(false);
  }
});
05EvidenceTHIRD PARTY LIST
Where the fingerprint and version are sent
  • api.qrstrategy.com

    Receives the encrypted fingerprint ID and version at startup, returning an encrypted config payload. Not disclosed in the listing, which describes only QR scanning.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Browser Fingerprint Built On Every Page, Synced Across Devices

This QR-code extension loads FingerprintJS on every website you visit and derives a stable ID from your device and browser, then saves it to synced storage that follows you to every device on your Google account.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You browse to any website with the extension installed.

The extension's content script is injected on every page, matching *://*/*, whether or not you ever open the QR tool.

The extension did this

The content script computes a browser fingerprint using a bundled copy of the FingerprintJS library, without notifying you.

Nothing on the page indicates this is happening; there is no prompt, badge, or setting to opt out.

02EvidenceFIELD TABLE
Signals combined into your fingerprint
FieldValueWhy it matters
Screen and display
1920x1080, 24-bit colorYour screen resolution and color depth are combined with other traits into a value that rarely changes between visits.
Installed plugins
PDF Viewer, Native ClientThe list of browser plugins and MIME types you have is uncommon enough to help single you out.
Hardware profile
deviceMemory: 8, hardwareConcurrency: 12Your device's memory size and CPU core count add further detail that narrows down which machine you're on.
Resulting fingerprint ID
30fc5c92a3dca2ce3180a3684df381fbAll of the above are hashed into one ID that stays the same across sessions, even after you clear cookies.
03EvidenceSTORAGE DUMP
What's stored on your device

This is synced storage, not local-only. Chrome copies the ID to every device signed into the same Google account.

Locationchrome.storage.sync, key 'brext'
Contents (JSON)
{
  "brext": "30fc5c92a3dca2ce3180a3684df381fb"
}
04EvidenceCODE COMPARE
The code that does this

Fingerprint request/store glue code in contentscript.js

What it actually does
// ie = the FingerprintJS agent, already loading in the background
const fingerprintManager = new class {
  constructor() {
    this.fingerprint = "";
  }

  init() {
    chrome.runtime.sendMessage({ event: "getFingerprint", body: {} }, (response) => {
      if (response) {
        // Service worker already has a fingerprint cached; reuse it.
        const { fingerprint } = response;
        this.fingerprint = fingerprint;
      } else {
        // No fingerprint yet: run FingerprintJS and compute one.
        (async () => {
          const agent = await fingerprintJsAgent;
          const result = await agent.get();
          const { visitorId } = await result;
          return visitorId;
        })().then((visitorId) => {
          if (debugLoggingEnabled) {
            console.log("%ccreated fingerprint", "color: #4e6ef2", visitorId);
          }
          chrome.runtime.sendMessage({ event: "setFingerprint", body: { id: visitorId } });
        });
      }
    });
  }
};

// Invoked at the bottom of the bundle on every page load:
// fingerprintManager.init();

What it can do

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

    *://*/*

  • Block and redirect the requests your browser makes

    declarativeNetRequest

  • See which of your requests its blocking rules matched

    declarativeNetRequestFeedback

  • Store data in your browser

    storage

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

    tabs

Updated 30 September 2026inkoacoebhbbfcidbbjognchggilmefm