Is Visualping: Website change detection, monitoring and alerts safe?
Visualping: Website change detection, monitoring and alerts is high risk. Setting up server-side monitoring makes Visualping read the tab's entire localStorage and send it to api.visualping.io as a setItem() replay script.…
Who publishes itWEBMONITORING TECHNOLOGIES INC. - 1 other listing from the same operator, none carrying a finding
WEBMONITORING TECHNOLOGIES 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 store account
1 other listing published from this account, 100k+ users between them, none of them carrying a finding.
Shared hosts - 1 hostname
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
Active tab's full localStorage sent to api.visualping.io on Server-tab job setup
Setting up server-side monitoring makes Visualping read the tab's entire localStorage and send it to api.visualping.io as a setItem() replay script.
A test showed all 24 entries sent, including a planted marker confirming exfiltration.
What actually happens
You set up server-side monitoring for a page you're signed into.
In the popup you switch to the Server tab, enter an email, and click Start Monitoring.
The extension reads the tab's entire localStorage and sends it to Visualping's API.
It injects JSON.stringify(localStorage), rebuilds every entry as setItem() calls, and POSTs them inside the new monitoring job.
Captured request
Observed during dynamic analysis: the POST carried a 'script' preaction (~2,880 chars, 24 localStorage.setItem calls) reproducing the active tab's full localStorage. A planted marker value was present, confirming the storage leaves the device. Real values are redacted here.
- Content-Type
- application/json
- X-Api-Client
- vp-chrome-extension-<digest>
{ "active": true, "origin": "chrome", "email": "testuser@example.com", "url": "https://chatgpt.com/", "mode": "VISUAL", "target_device": "1", "preactions": { "active": true, "actions": [ { "script": "localStorage.setItem(\"oai/apps/hasSeenOnboarding\", \"<redacted>\"); \nlocalStorage.setItem(\"statsig.stable_id\", \"<redacted>\"); \nlocalStorage.setItem(\"canary_ls_key\", \"PARROT_LS_67890\"); \n" }, { "refresh": true }, { "wait": "5" } ] }}What's stored on your device
The page's localStorage is read and rebuilt server-side, often holding login tokens; a planted marker confirmed it leaves the device.
- Location
- Active tab localStorage (read via injected JSON.stringify(localStorage))
{ "canary_ls_key": "PARROT_LS_67890 (planted marker, confirmed transmitted)", "statsig.stable_id": "<redacted>", "oai/apps/hasSeenOnboarding": "<redacted>", "<auth/session keys present on the page>": "<redacted>"}The code that does this
localStorage capture and script-preaction build
async function dl() { // read the active tab's full localStorage return new Promise(resolve => { chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => { chrome.scripting.executeScript({ target: { tabId: tab.id }, func: () => JSON.stringify(localStorage) // every key/value }, ([res]) => resolve(JSON.parse(res.result))); }); });}function ml(store) { let script = ''; Object.keys(store) .filter(k => JSON.stringify(store[k]).length < 2300) // skip oversize entries .forEach(k => { script += `localStorage.setItem("${k}", ${JSON.stringify(store[k])}); \n`; }); return script;}function fl(store) { const script = ml(store); return script === '' ? [] : [{ script }, { refresh: true }, { wait: '5' }];}// fl(localStorage) is merged into preactions.actions and POSTed to api.visualping.io/v2/jobs/Where the localStorage is sent
- api.visualping.io
Visualping's backend (Visualping Technologies Inc.). Receives the job containing the tab's localStorage as a setItem() replay script used to recreate your storage state.
Monitored site cookies sent to api.visualping.io on Server-tab job setup
During server-side monitoring, Visualping reads the page's cookies and sends them to api.visualping.io, which replays them to load the page.
A test showed 24 root-path cookies sent in plaintext, including session and CSRF cookies.
What actually happens
You set up server-side monitoring for a page you're signed into.
In the popup you switch to the Server tab, enter an email, and click Start Monitoring.
The extension reads that page's cookies and sends their values to Visualping's API.
It collects the root-path cookies for the page's domain and POSTs them inside the new monitoring job.
Captured request
Observed during dynamic analysis: the POST carried 24 root-path cookie objects for the monitored domain. A planted marker cookie was present in the body, confirming the cookies leave the device. Real session cookies were captured in plaintext; their values are redacted here.
- Content-Type
- application/json
- X-Api-Client
- vp-chrome-extension-<digest>
{ "active": true, "origin": "chrome", "email": "testuser@example.com", "url": "https://chatgpt.com/", "interval": "60", "mode": "VISUAL", "target_device": "1", "preactions": { "active": true, "actions": [ { "cookie": { "field": "__Host-next-auth.csrf-token", "value": "<redacted>", "domain": "chatgpt.com" } }, { "cookie": { "field": "oai-did", "value": "<redacted>", "domain": "chatgpt.com" } }, { "cookie": { "field": "_cfuvid", "value": "<redacted>", "domain": "chatgpt.com" } }, { "cookie": { "field": "canary_cookie", "value": "BIRD_COOKIE_12345", "domain": "chatgpt.com" } } ] }}Cookie fields placed in the job body
- Authentication / CSRF cookie__Host-next-auth.csrf-token = <redacted>
Carries your signed-in state for the site. Whoever holds it can act as you on that site without your password.
- Device / user identifier cookieoai-did = <redacted>
Ties activity back to your account or browser on that site.
- Other root-path cookies_cfuvid = <redacted>
Every cookie set at the domain's root path is included, not just the ones strictly needed to load the page.
The code that does this
Cookie collection and transmission
async function pl(e) { const t = cl(e); // domain of the monitored URL return (await chrome.cookies.getAll({ domain: t })) .filter(c => c.name && typeof c.value === 'string' && c.domain) .filter(c => c.path === '/') // keep root-path cookies .map(c => ({ cookie: { field: c.name, value: c.value, domain: c.domain } }));}// gl(): on Server-tab job creationPromise.all([pl(e.url), dl()]).then(([cookies, ls]) => { let actions = [...cookies, ...fl(ls)]; // cookies + localStorage preactions const job = { active: true, origin: 'chrome', email: e.email, url: e.url, mode: 'VISUAL', target_device: '1', preactions: { active: true, actions } // cookie values travel here }; $.ajax({ url: 'https://api.visualping.io/v2/jobs/', type: 'POST', data: JSON.stringify(job), headers: { 'Content-Type': 'application/json', 'X-Api-Client': 'vp-chrome-extension-' + digest } });});Where the cookies are sent
- api.visualping.io
Visualping's backend (Visualping Technologies Inc.). Receives the monitoring job with the site's cookie values and replays them server-side to load the page in your session.
