Is uTab - Unlimited Custom Dashboard safe?
uTab routes every new-tab search through search.utab.io with a persistent per-user id before redirecting to Bing.
Each query typed into the new-tab search box is sent to search.utab.io tagged with a persistent per-user UUID and the extension's runtime id, then forwarded on to Bing. The service worker also fetches a remote config from utab.io/api/ every 15 minutes, and that config can override where searches are sent. Separately, the URLs of your bookmarks and pages saved via the 'Save to uTab' menu are sent to the third-party screenshot service sshot.org to generate thumbnail previews.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Bookmark URLs sent to sshot.org previews
Code analysis shows that saving a page or link to uTab, or uTab preparing bookmark-folder previews, sends the saved URL to https://sshot.org in the url parameter, with a Basic authorization header using a bundled credential, redacted below.
What actually happens
You save a page or link to uTab, or ask uTab to prepare previews for a bookmark folder.
The extension sends the saved page URL to sshot.org so that service can generate a thumbnail preview.
Captured request
The fetch call expects image bytes and converts the response array buffer to a data:image/png;base64 preview.
- Authorization
- Basic <redacted>
Fields sent to the screenshot service
- Saved page URLhttps://example.com/private/roadmap?ticket=INC-4821
The exact page you saved is sent to the screenshot service, including any path and query string in that address.
- Preview dimensionswidth=200&height=112&zoom=0.15
These values tell the service what size thumbnail to render for the saved page.
- Bundled service credentialBasic <redacted>
The extension includes a credential for the screenshot service in its shipped code.
How user actions map to preview requests
same action
You didYou choose Save to uTab for the current page or link.
The extension didThe extension stores that URL and immediately requests a preview from sshot.org.
per matching bookmark
You didYou add or edit a folder that contains saved bookmarks.
The extension didThe extension requests previews for bookmark entries whose preview is still marked as loading.
The code that does this
Preview generation sends bookmark URLs to sshot.org
Folder edit/add triggers preview refresh
deobfuscated/js/background.jsFOLDER_EDIT: function(t, e) { t.commit("FOLDER_EDIT", e), chrome.runtime.sendMessage({ action: "getFolderPreviews", folder: e }), chrome.runtime.sendMessage({ action: "update_context_menu" });},FOLDER_ADD: function(t, e) { e.bookmarks.length > 0 && (e.empty = !1), t.commit("FOLDER_ADD", e), chrome.runtime.sendMessage({ action: "getFolderPreviews", folder: e }, (function() { chrome.runtime.lastError; })), chrome.runtime.sendMessage({ action: "update_context_menu" }, (function() { chrome.runtime.lastError; })), 1 == t.state.storage.settings.folders.length && t.commit("FOLDER_SELECT", e);}Save to uTab stores the page URL and requests a preview
deobfuscated/js/background.js{ key: "createContextMenu", value: function() { var t = this; chrome.contextMenus.removeAll((function() { if (!a.a.getters.isNoFolders) { var e = null; a.a.state.storage.settings.folders && a.a.state.storage.settings.folders.length > 1 && (e = "parentDropDown", chrome.contextMenus.create({ title: "Save to uTab", id: e, contexts: [ "page", "link" ] }, (function() { chrome.runtime.lastError; }))); for (var n = 0; n < a.a.state.storage.settings.folders.length; n++) { var r = a.a.state.storage.settings.folders[n], o = 'Save to "' + r.name + '"'; 1 == a.a.state.storage.settings.folders.length && (o = "Save to uTab"), chrome.contextMenus.create({ title: o, id: r.id, parentId: e, contexts: [ "page", "link" ] }, (function() { chrome.runtime.lastError; })); } chrome.contextMenus.onClicked.addListener((function(e, n) { var r = a.a.state.storage.settings.folders.find((function(t) { return t.id === e.menuItemId; })); if (r) { var o = { id: Object(u.b)(), _id: Object(u.b)(), title: n.title, url: n.url, icon: n.favIconUrl || "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB3UlEQVQ4T6WTP2hTURjFz7nP2EpxK0pBEFwcnDu3ZMiQWSKK0CQmmgSHVgdBUPEPoqBYHJLQvuZPEZSAq2CGqoOLLi4ijrqIXcVom+Qeua95+VONS+/0OJfvd893vu8RezzcXX92cXEm8nPrqYRZSQfcPclfJN63pyZOP1le/jZcMwJInS8sqWvvC4iQsBA2BbYBHQk4QJueuVJdKT4KIX2AK7Zd+xCAJXEzHoveTSQS3WTm4rzUeTX0qoxnLoeQAOBsez+2vgKiJ56oVEqfnZ7KFB5Y2DMQZnbZbncOTh517QSAVDb/2lrNkbhe88u3nZbMFmZl7btxGRvDN9XV0nwAWEjnWjSYiMei+53tHnROVsUe4JiAyREXZKvml6Z2AOdylsD32lp5xGpYkMzkn0k6NQBoG2CkvlY2fYBLeeiFLsEvoNbjseidZvPtdEfbSyCmIbwE9FjA4QEgnWuBCGa++3g0CxW/uB7qjUbDe9HccA5+91sIQ/xnYORK3S9dGLSTuybh1kiIQ2Pc14eQPoAPBvpU9csbTk+n88e71EcCdmSMQeqDRdrJwuBkfbX83H32bF+VcMPd/LVIfXvZ/CVY3XOrDKJDYBOCAXFIgkt8/CqHkD39TOO27n/6HyyR4xF0kx9xAAAAAElFTkSuQmCC", preview: "load" }; a.a.commit("BOOKMARK_MANAGER_ADD", { item: o, folder: r }), a.a.dispatch("FOLDER_BOOKMARK_ADD", { item: o, folder: r }), t.getPreview(o.url).then((function(t) { o.preview = t, a.a.commit("FOLDER_BOOKMARK_PREVIEW_SET", { folder: r, bookmark: o }); })); } })); } })); }}getPreview fetches sshot.org with the saved URL
deobfuscated/js/background.js{ key: "getPreview", value: function(e) { return new Promise((function(n, r) { var o = new Headers; o.set("Authorization", "Basic " + btoa("user:<redacted>")), fetch("https://sshot.org?url=" + encodeURIComponent(e) + "&width=200&height=112&zoom=0.15&clipRect=0%2C0%2C200%2C112", { method: "get", headers: o, responseType: "arraybuffer" }).then((function(t) { return t.arrayBuffer(); })).then((function(e) { n("data:image/png;base64," + t.from(e, "binary").toString("base64")); })).catch((function(t) { n(null); })).finally((function() { n(null); })); })); }}getFolderPreviews calls getPreview for each loading bookmark
deobfuscated/js/background.js{ key: "getFolderPreviews", value: function(t) { var e = this, n = a.a.state.storage.settings.folders.find((function(e) { return e.id == t.id; })); n.bookmarks.forEach((function(t, r) { "gap" != t.type && "load" == t.preview && e.getPreview(t.url).then((function(e) { t.preview = e, a.a.commit("FOLDER_BOOKMARK_PREVIEW_SET", { folder: n, bookmark: t }); })); })); }}External host that receives saved URLs
- sshot.org
Receives each saved page or bookmark URL and returns a thumbnail image for uTab previews.
Remote config can change new-tab searches
We observed the extension request https://utab.io/api/ during dynamic analysis.
Source code stores that JSON as config; searching from the new-tab page replaces the built-in search URL with config.searchTemplate if present.
What actually happens
You search from the uTab new-tab page after the extension has refreshed its configuration.
The extension can build the destination URL from a template returned by utab.io instead of its built-in search URL.
Captured request
200 OK observed during dynamic analysis; no request body was recorded for this config fetch.
Fields that determine the search redirect
- Search queryweather tomorrow
Your typed search terms are inserted into the selected destination URL.
- Server-supplied templatehttps://search.utab.io/search/?q={KEYWORD}
A value from the remote configuration can decide which site receives your search.
- Extension user IDf4b1d629-8d7a-4d6f-9f2c-58b0c6d7e0a2
The built-in search URL includes an extension-generated identifier alongside the query.
When this fires
Every 15 minutes
The extension refreshes the remote configuration on startup and then schedules another refresh roughly every 15 minutes.
The code that does this
Config refresh and search URL replacement in the service worker
Constructor schedules the config refresh
deobfuscated/js/background.jsnew (i()((function t() { var e = this; r()(this, t), this.config = null, this.bookmarkPreviewTabId = null, this.bookmarkPreviewTabOpenerId = null, this.searchCount = 0, this.fetchSlidersDate = null, this.updateSliderDate = Date.now(), chrome.runtime.setUninstallURL("https://utab.io/uninstall.html", (function() {})), Object(a.b)().then((function() { e.onStorageLoaded(); })), chrome.runtime.onInstalled.addListener((function(t) { "update" == t.reason && setTimeout((function() { null == a.a.state.storage.settings.background && a.a.dispatch("SET_BACKGROUND_APP", { type: "default", index: 3 }); }), 1e3), fetch("https://utab.io/beg/?y=" + t.reason).then((function(t) { return t.json(); })).then((function(n) { e.newFeaturesReply(t.reason, n); })); })), this.initListeners(), this.getConfig(), setInterval((function() { e.getConfig(); }), 9e5);})getConfig fetches JSON from utab.io/api/
deobfuscated/js/background.js{ key: "getConfig", value: function() { var t = this; Date.now() - a.a.state.storage.user.fetchConfigDate > 9e5 && fetch("https://utab.io/api/").then((function(t) { return t.json(); })).then((function(e) { a.a.commit("SET_FETCH_CONFIG_DATE", Date.now()), t.config = e; })).catch((function(t) {})); }}Search handler applies config.searchTemplate
deobfuscated/js/background.jscase "search": var o = "https://search.utab.io/search/?id=".concat(chrome.runtime.id, "&source=newtab&q=").concat(encodeURIComponent(e.query), "&uid=").concat(a.a.state.storage.user.id, "&se=") + "bing"; t.config && t.config.searchTemplate && (o = t.config.searchTemplate.replace("{KEYWORD}", e.query)), chrome.tabs.update(null, { url: o }); break;External hosts involved in this behavior
- utab.io
Provides the remote configuration fetched by the extension.
- search.utab.io
Receives searches when the built-in search URL remains in use.
What it can do
Permissions this extension asks for, as declared in version 3.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
<all_urls>
Read and change your bookmarks
bookmarks
Store data in your browser
storage
Store an unlimited amount of data in your browser
unlimitedStorage
Act on the current tab, but only after you click the extension
activeTab
Run searches with your default search engine
search
Run its own code inside the pages you visit
scripting
Add items to the right-click menu
contextMenus
Where it sends data
Destinations our analysis observed uTab - Unlimited Custom Dashboard 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.
- search.utab.io
uTab - Unlimited Custom Dashboard sends data to search.utab.io. One other extension we have analyzed sends data here.
- utab.io
uTab - Unlimited Custom Dashboard sends data to utab.io. One other extension we have analyzed sends data here.
- sshot.org
uTab - Unlimited Custom Dashboard sends data to sshot.org. No other extension we have analyzed sends data here.
