Is iGive Button safe?
iGive Button initializes PostHog analytics with autocapture on every page you visit, not just cashback merchant sites.
iGive Button's content script runs on every website and starts the PostHog analytics SDK before it checks whether the current page is a cashback merchant. PostHog's autocapture then records click and page-view events (including the page's URL and host) on all sites, tagged with either your logged-in member ID or a persistent installation identifier, and sends this data to us.posthog.com.
Who publishes itiGive.com - no other listings under this identity, 7 shared hostnames
iGive.com - no other listings under this identity, 7 shared hostnames
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 7 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
PostHog autocapture tracks every website you visit, not just merchant pages
Code analysis shows the content script, which runs on every page, starts PostHog autocapture before checking whether the page is an iGive merchant.
Clicks and page data from every site are sent to us.posthog.com.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Static analysis
You visit any website with the extension installed, whether or not it is one of iGive's cashback merchants.
The content script's manifest match pattern is *://*/*, so it loads on every page.
Before checking if the page belongs to a merchant, the extension starts PostHog with autocapture on and begins recording your clicks and the page's URL.
The merchant lookup that would gate the cashback UI runs only afterward, so tracking is not conditioned on it.
The content-script PostHog config is the only context without a session-recording guard
Content-script entry point (init)
src/entries/contentScript/main.jsasync function init() { const app = f(ContentService); const injections = f(InjectionControllerService); await Promise.all([ injections.init(), app.init() ]); await app.initPostHog(); await app.initSlider();}init();getConfig() -- per-context PostHog settings
assets/posthog.service-D0TUZEMA.jsconst context = getExtensionContext();switch (context) { case ExtensionContext.ContentScript: return { ...shared, capture_pageview: false, autocapture: true, persistence: "memory" }; case ExtensionContext.ServiceWorker: return { ...shared, persistence: "memory", capture_pageview: false, autocapture: false, disable_session_recording: true, disable_surveys: true }; case ExtensionContext.Background: return { ...shared, persistence: "localStorage", capture_pageview: false, autocapture: false, disable_session_recording: true, disable_surveys: true }; case ExtensionContext.Popup: case ExtensionContext.Options: case ExtensionContext.Devtools: return { ...shared, capture_pageview: true, autocapture: true, persistence: "localStorage", disable_session_recording: true }; default: return { ...shared, capture_pageview: false, autocapture: false, persistence: "memory" };}- Your tracking ID8841207
A persistent ID that ties every page you visit back to the same identity. It becomes your logged-in iGive member ID once you sign in.
- Page you're onhttps://en.wikipedia.org/wiki/Cashback
The full URL of the page, including path and query string, for every site you visit while the extension is active.
- Site domainen.wikipedia.org
The hostname of the page, sent alongside the full URL.
- What you clicked$autocapture on button#search-submit
PostHog's autocapture records clicks, form submits, and other page interactions on any page, not just merchant sites.
- us.posthog.com
PostHog Inc's US-region analytics ingestion endpoint. Receives the autocapture events and tracking ID from every page load, not only merchant/checkout pages.
Downloads a published extension build and checks whether its PostHog getConfig() still gives the content-script context autocapture:true with no disable_session_recording flag, unlike the service-worker context.
- Node.js 18+
- unzip
#!/usr/bin/env node// verify-posthog-scope.js//// Downloads a published .xpi build of an extension and checks whether its// PostHog getConfig() switch gives the content-script context// autocapture:true with no disable_session_recording flag, while the// service-worker/background context sets that flag. Confirms the code-path// asymmetry described in this finding is present in the build you point it at.//// Usage: node verify-posthog-scope.js <xpi-download-url>const fs = require('fs');const path = require('path');const os = require('os');const https = require('https');const { execSync } = require('child_process');function download(url, dest) { return new Promise((resolve, reject) => { const file = fs.createWriteStream(dest); https.get(url, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { file.close(); download(res.headers.location, dest).then(resolve, reject); return; } res.pipe(file); file.on('finish', () => file.close(resolve)); }).on('error', reject); });}async function main() { const url = process.argv[2]; if (!url) { console.error('Usage: node verify-posthog-scope.js <xpi-download-url>'); process.exit(1); } const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'xpi-check-')); const xpiPath = path.join(tmp, 'extension.xpi'); console.log(`Downloading ${url} ...`); await download(url, xpiPath); console.log('Unpacking...'); const outDir = path.join(tmp, 'unpacked'); fs.mkdirSync(outDir); execSync(`unzip -oq "${xpiPath}" -d "${outDir}"`); const manifestPath = path.join(outDir, 'manifest.json'); const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); const csEntry = manifest.content_scripts && manifest.content_scripts[0]; console.log('content_scripts[0].matches:', csEntry && csEntry.matches); let posthogFile = ''; try { posthogFile = execSync( `grep -rl "getExtensionContext" "${outDir}" || true` ).toString().trim().split('\n')[0]; } catch (e) { posthogFile = ''; } if (!posthogFile) { console.log('Could not locate a PostHog config bundle in this build; the code may have moved or the SDK may have been replaced.'); return; } const src = fs.readFileSync(posthogFile, 'utf8'); const switchIdx = src.indexOf('switch (context)') !== -1 ? src.indexOf('switch (context)') : src.indexOf('switch(context)'); if (switchIdx === -1) { console.log('Could not locate the getConfig() context switch in this build.'); return; } const snippet = src.slice(switchIdx, switchIdx + 2000); const csIdx = snippet.indexOf('ExtensionContext.ContentScript'); const swIdx = snippet.indexOf('ExtensionContext.ServiceWorker'); const bgIdx = snippet.indexOf('ExtensionContext.Background'); if (csIdx === -1 || swIdx === -1) { console.log('Could not locate ContentScript/ServiceWorker branches in this build.'); return; } const contentScriptBlock = snippet.slice(csIdx, swIdx); const serviceWorkerBlock = snippet.slice(swIdx, bgIdx !== -1 ? bgIdx : swIdx + 400); const csAutocapture = /autocapture:\s*true/.test(contentScriptBlock); const csSessionGuard = /disable_session_recording/.test(contentScriptBlock); const swSessionGuard = /disable_session_recording/.test(serviceWorkerBlock); console.log('\n--- Result ---'); console.log('ContentScript branch sets autocapture:true ->', csAutocapture); console.log('ContentScript branch sets disable_session_recording ->', csSessionGuard); console.log('ServiceWorker branch sets disable_session_recording ->', swSessionGuard); if (csAutocapture && !csSessionGuard && swSessionGuard) { console.log('\nCONFIRMED: this build ships the same code-path asymmetry described in the finding.'); } else { console.log('\nThe pattern was not found as described; the code may have changed in this build.'); }}main().catch((err) => { console.error(err); process.exit(1);});- 1Get an .xpi download URL for the build to check.
- 2Run `node verify-posthog-scope.js <url>`.
- 3CONFIRMED means content-script still lacks the session-recording guard the service worker sets.
Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.
What it can do
Permissions this extension asks for, as declared in version 11.3.1. 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
*://*/*
See the address and title of every tab you have open
tabs
Watch every request your browser makes
webRequest
See every page you navigate to, as you navigate to it
webNavigation
Store data in your browser
storage
Read and change cookies, including the ones that keep you signed in
cookies
Schedule its own background tasks
alarms
Where it sends data
Destinations our analysis observed iGive Button 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.
- us.posthog.com
iGive Button sends data to us.posthog.com. 5 other extensions we have analyzed send data here.