Security Alert: Confirmed Malware
SearchLock Tab
ID: ohdphinnjkbfgimbcpdjdigbbkmngcge
Supported Languages
Extension Info & Metadata
Publisher Contextual Analysis
- Author
- https://searchlock.comView Profile
- Privacy
- Privacy Policy
- Help
- Help Center
- MX records exist
- Yes
- Domain exists
- Yes
- Is disposable
- No
- Is role-based
- Yes
- Mailbox exists
- Yes
- Website
- Visit
Privacy-enhancing search engine new tab page.
Search the web without being tracked from your new tab page! SearchLock Tab adds a privacy-friendly SearchLock search box to your browser's new tab page. When you use SearchLock, you can rest easy knowing that: • We don't track you in or out of private mode. • We don’t ask for or store your personal information. • We don't associate your searches with your personal information since we never ask for it in the first place. • We don't follow you with ads. -------------------- Extension Features: -------------------- - Updates your Chrome new tab page to show a privacy-friendly search box from SearchLock. - Use your SearchLock Tab page to search the web privately, without being tracked or profiled for advertising. Privacy policy: https://www.searchlock.com/pages/privacy Thanks for using SearchLock Tab! Get in touch with us any time at https://www.searchlock.com/support Adding this extension to Chrome sets your new tab page to SearchLock Tab.
The extension fetches a remote configuration (`dynamic_redirect_params`) from the developer's server on every startup and every 12 hours (720-minute alarm). These server-controlled parameters are then used to dynamically modify URL query strings on all searchlock.com search requests via declarativeNetRequest rules. This is a remote code/config injection vector — the server can silently change what tracking or affiliate parameters get appended to every search query without an extension update.
async function getRemoteConfig() { try { const query = new URLSearchParams({ aid: appId, a: alias, ua: limitedUA, version: `${version}_chrome`, }); const getUrl = `${baseUrl}/api/safelist/?${query.toString()}`; const response = await fetch(getUrl); const responseObj = await response.json(); dynamicRedirectParams = responseObj.dynamic_redirect_params; setOtherRedirectParams(); chrome.storage.sync.set({ dynamic_redirect_params: dynamicRedirectParams, }); } catch (error) { console.log("Could not parse Safelist."); console.log(error); logError("Safelist parse error: " + error.message); }}A hidden iframe is silently injected into every page on *.searchlock.com, pinging a remote attribution endpoint with a persistent unique user alias, extension ID, and version. The iframe has display:none to avoid any visible indication to the user. This constitutes covert tracking of the user's browsing sessions across all pages on the domain.
function injectAttribution() { if (alias) { const query = new URLSearchParams({ alias, aid: appId, v: version, extId, }); const attributionUrl = `${baseUrl}/pages/attribution?${query.toString()}`; const iframe = document.createElement("iframe"); iframe.src = attributionUrl; iframe.id = "cnvrt"; iframe.style.display = "none"; const injectInterval = setInterval(function() { // make sure document.body is already available before injecting the iframe if (document.body) { clearInterval(injectInterval); document.body.appendChild(iframe); } }, 50); }}The ident() function injects a hidden DOM element into every searchlock.com page containing the user's persistent alias (GUID), install date offset (days since install), extension ID and version — encoded as data attributes readable by any JavaScript running on the page. This exfiltrates the unique user identifier and usage duration to the host page's own scripts without explicit user consent.
chrome.storage.sync.get(["install_attributed", "alias"], function(data) {if (data.install_attributed) return;alias = data.alias;injectAttribution();});})();// When at searchlock.com, pass install date and source as an invisible divfunction ident() { chrome.storage.sync.get(["alias", "install_date"], function(data) { if (data.alias) { let snc = 0; if (data.install_date) { const jdt = data.install_date.split("-"); const sdt = new Date(jdt[0], jdt[1] - 1, jdt[2]); const edt = new Date(); snc = Math.abs(Math.round((edt - sdt) / 86400000)); } const data_params = { ext_aid: appId, ext_alias: data.alias, ext_id: chrome.runtime.id, ext_ver: chrome.runtime.getManifest().version, }; const div = document.createElement("div"); div.style.display = "none"; div.setAttribute("id", "sl_user"); div.setAttribute("class", "sl_app"); div.setAttribute("data-guid", data.alias); div.setAttribute("data-dsi", snc); div.setAttribute("data-v", chrome.runtime.getManifest().version); div.setAttribute("data-extid", chrome.runtime.id); div.setAttribute("data-params", JSON.stringify(data_params)); document.body.appendChild(div); } });}Dynamic declarativeNetRequest rules are installed at runtime using parameters fetched from a remote server, silently appending or replacing query parameters on every search request to searchlock.com. Because the parameter set is server-controlled and refreshed periodically, the operator can add arbitrary tracking or affiliate identifiers to user searches without any user visibility or further extension update review.
function updateDynamicRules() { chrome.declarativeNetRequest.updateDynamicRules({ addRules: [{ id: 1, action: { type: "redirect", redirect: { transform: { queryTransform: { addOrReplaceParams: Array.from( new URLSearchParams(otherRedirectParams) .entries() ) .map(([key, value]) => ({ key, value })), }, }, }, }, condition: { regexFilter: "searchlock.com/search/.*", resourceTypes: ["main_frame"], }, }, ], removeRuleIds: [1], });}The content script listens for window.postMessage events from any subdomain of searchlock.com and, upon receipt, stores arbitrary `pbParams` objects from the remote page into chrome.storage.sync and forwards them to the background service worker to modify all future search redirect parameters. The origin check uses a regex (`.+\.searchlock\.com`) that could match attacker-controlled subdomains, and the pbParams payload is trusted and stored/applied without further validation — any searchlock.com subdomain can control what parameters get injected into the user's search queries.
window.addEventListener( "message", function(event) { const slPattern = /.+\.searchlock\.com/i; if (slPattern.test(event.origin)) { if ( event.data.message === "sl_install_attributed" && event.data.extId === extId ) { chrome.storage.sync.set({ install_attributed: true }, function() { console.debug("Install attributed"); }); const postbackParams = event.data.pbParams; if ( typeof postbackParams === "object" && Object.keys(postbackParams).length > 0 ) { chrome.storage.sync.set({ pb_params: postbackParams }, function() { console.debug(postbackParams); chrome.runtime.sendMessage({ command: "set_redirect_params", pb_params: postbackParams, }); }); } } } }, false);Internal error messages are silently transmitted to the developer's server along with the OS, browser type, and app ID. While error logging itself is common, the unconditional exfiltration of error message strings (which could contain URL fragments or user query data from stack traces) to a remote endpoint without user consent or disclosure is a privacy concern.
function logError(message) { const params = new URLSearchParams({ message, aid: appId, os, version, }); fetch(`${baseUrl}/api/extlog/error/${browser}?${params.toString()}`);}The extension reads the user's top browsing sites via chrome.topSites and persists them to chrome.storage.sync (which syncs across all of the user's Chrome profiles). The full topSites list is also returned to the new tab page content script for use in the autocomplete feature, making the user's browsing history available to page-level JavaScript. Storing browsing history in sync storage means it is uploaded to Google and tied to the user's Google account.
function getHistory(sendResponse) { chrome.topSites.get((topsitesResponse) => { var sites = []; var slicedTopSites; chrome.storage.sync.get(["saved_favorites"], ({ saved_favorites }) => { if (!saved_favorites) { topsitesResponse.forEach(function(key, value) { sites.push({ url: value.url, title: value.title }); }); slicedTopSites = sites.slice(0, 9); chrome.storage.sync.set({ saved_favorites: slicedTopSites }, function() {} ); } sendResponse({ results: topsitesResponse }); }); });}By severity
Versions scanned
Showing 1 of 1 scanned version with more than one unique finding. Counts are unique findings that include each version.
| Extension Version | Code Review Findings |
|---|---|
| 1.0.2 | 7 |
Files with findings
2 distinct paths — top paths by unique finding count:
- js/background.js4
- js/contentScripts.js3
URLs
View the external URLs this extension communicates with to understand its network activity and data interactions.
Gain full insight into all external connections.
Upgrade for full visibility.
Gain full insight into all external connections.
Upgrade for full visibility.
Browse and explore files within this extension package
Gain full insight into all external connections.
Upgrade for full visibility.