Is MYNT: Material You New Tab safe?
Material You New Tab is medium risk. With search suggestions and the CORS bypass proxy on, text typed in the search bar goes into an autocomplete URL routed through mynt-proxy.rhythmcorehq.com before reaching the suggestion service. The warning says it's off by default.โฆ
Who publishes ititz-rj-here - no other listings under this identity, 3 shared hostnames
itz-rj-here - no other listings under this identity, 3 shared hostnames
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 3 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
Search query URLs route through MYNT's proxy
With search suggestions and the CORS bypass proxy on, text typed in the search bar goes into an autocomplete URL routed through mynt-proxy.rhythmcorehq.com before reaching the suggestion service.
The warning says it's off by default.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You enable search suggestions and the proxy option, then type in the new-tab search box.
The proxy toggle shows a confirmation prompt before it stays enabled.
The extension sends the autocomplete request through mynt-proxy.rhythmcorehq.com.
The query is first encoded into the selected search provider's suggestion URL, then that whole URL is encoded into the proxy request.
- Typed search textweather tomorrow
This can reveal what you are searching for as you type in the new-tab page.
- Suggestion service URLhttps://www.google.com/complete/search?client=chrome&q=weather%20tomorrow
This shows which search provider receives the autocomplete request and includes your typed query.
- Proxy destinationmynt-proxy.rhythmcorehq.com
This host receives the encoded suggestion URL before the selected provider responds.
No response body was recorded during dynamic analysis; the URL shape is produced by the shipped fetch path when the proxy option is enabled.
The shipped proxy setup and autocomplete fetch path
Deobfuscated default proxy selection
scripts/search-suggestions.jslet proxyurl;document.addEventListener("DOMContentLoaded", () => { const userProxyInput = document.getElementById("userproxy"); const saveProxyButton = document.getElementById("saveproxy"); const savedProxy = localStorage.getItem("proxy"); const defaultProxyURL = "https://mynt-proxy.rhythmcorehq.com/proxy?url="; //Default proxy url if (savedProxy && savedProxy !== defaultProxyURL) { userProxyInput.value = savedProxy; } // Allow pressing Enter to save the proxy userProxyInput.addEventListener("keydown", (event) => { if (event.key === "Enter") { saveProxyButton.click(); } }); // Save the proxy to localStorage saveProxyButton.addEventListener("click", () => { proxyurl = userProxyInput.value.trim(); // If the input is empty, use the default proxy. if (proxyurl === "") { proxyurl = defaultProxyURL; } else { // Validate if input starts with "http://" or "https://" if (!(proxyurl.startsWith("http://") || proxyurl.startsWith("https://"))) { proxyurl = "https://" + proxyurl; } } // Set the proxy in localStorage, clear the input, and reload the page localStorage.setItem("proxy", proxyurl); userProxyInput.value = ""; location.reload(); }); // Determine which proxy URL to use proxyurl = savedProxy || defaultProxyURL;});Deobfuscated search input trigger
scripts/search-suggestions.jssearchInput.addEventListener("input", async function () { const searchsuggestionscheckbox = document.getElementById("searchsuggestionscheckbox"); if (searchsuggestionscheckbox.checked) { const query = this.value; // Store original text when user starts typing originalSearchText = query; if (query.length > 0) { try { // Fetch autocomplete suggestions const suggestions = await getAutocompleteSuggestions(query); if (suggestions === "") { hideResultBox(); } else { // Clear the result box resultBox.innerHTML = ""; // Add suggestions to the result box suggestions.forEach((suggestion, index) => { const resultItem = document.createElement("div"); resultItem.classList.add("resultItem"); resultItem.textContent = suggestion; resultItem.setAttribute("data-index", index); resultItem.onclick = () => { performSearch(suggestion); }; resultItem.addEventListener("mouseenter", () => { // Remove existing highlight const currentlyActive = resultBox.querySelector(".active"); if (currentlyActive) currentlyActive.classList.remove("active"); // Mark this as active resultItem.classList.add("active"); lastInteractionBy = "mouse"; }); resultBox.appendChild(resultItem); }); // Check if the dropdown of search shortcut is open const dropdown = document.querySelector(".dropdown-content"); if (dropdown.style.display === "block") { dropdown.style.display = "none"; } showResultBox(); } } catch (error) { // Handle the error (if needed) } } else { hideResultBox(); } }});Deobfuscated proxy rewrite and GET request
scripts/search-suggestions.jsasync function getAutocompleteSuggestions(query) { const clientParam = getClientParam(); // Get the browser client parameter dynamically var selectedOption = document.querySelector('input[name="search-engine"]:checked').value; // ๐ Throttle Reddit API calls const now = Date.now(); if (selectedOption === "engine7") { if (now - lastRedditRequestTime < 1000) { return []; // skip call if within 1 second } lastRedditRequestTime = now; } const searchSuggestionsAPI = { engine0: `https://duckduckgo.com/ac/?q=${encodeURIComponent(query)}&type=list`, engine1: `https://www.google.com/complete/search?client=${clientParam}&q=${encodeURIComponent(query)}`, engine2: `https://duckduckgo.com/ac/?q=${encodeURIComponent(query)}&type=list`, engine4: `https://search.brave.com/api/suggest?q=${encodeURIComponent(query)}&rich=true&source=web`, engine5: `https://www.google.com/complete/search?client=${clientParam}&ds=yt&q=${encodeURIComponent(query)}`, engine7: `https://www.reddit.com/search.json?q=${encodeURIComponent(query)}&sort=relevance&limit=15`, engine8: `https://${languageCode}.wikipedia.org/w/api.php?action=opensearch&search=${encodeURIComponent(query)}&format=json` }; const useproxyCheckbox = document.getElementById("useproxyCheckbox"); let apiUrl = searchSuggestionsAPI[selectedOption] || searchSuggestionsAPI["engine1"]; if (useproxyCheckbox.checked && selectedOption !== "engine7") { apiUrl = proxyurl + encodeURIComponent(apiUrl); } try { const response = await fetch(apiUrl); const data = await response.json(); if (selectedOption === "engine4") { const suggestions = data[1].map(item => { if (item.is_entity) { return `${item.q} - ${item.name} (${item.category ? item.category : "No category"})`; } else { return item.q; } }); return suggestions; } else if (selectedOption === "engine7") { const suggestions = []; if (data && data.data && data.data.children) { data.data.children.forEach(post => { if (post.data && post.data.title) { const subreddit = post.data.subreddit_name_prefixed; suggestions.push(`${post.data.title} (${subreddit})`); } }); } return suggestions; } else { return data[1]; } } catch (error) { console.error("Error fetching autocomplete suggestions:", error); return []; }}Deobfuscated proxy toggle and disclaimer gate
scripts/search-suggestions.jsdocument.addEventListener("DOMContentLoaded", function () { const searchsuggestionscheckbox = document.getElementById("searchsuggestionscheckbox"); const proxybypassField = document.getElementById("proxybypassField"); const proxyinputField = document.getElementById("proxyField"); const useproxyCheckbox = document.getElementById("useproxyCheckbox"); const searchSuggestionsHeader = document.getElementById("searchSuggestionsHeader"); const proxyOptions = document.querySelector(".proxyOptions"); // This function shows the proxy disclaimer. async function showProxyDisclaimer() { const message = translations[currentLanguage]?.ProxyDisclaimer || translations["en"].ProxyDisclaimer; return await confirmPrompt(message, agreeText, cancelText); } // Requests optional host permissions for suggestion APIs async function requestHostPermissions() { if (!chrome?.permissions?.request) return false; return new Promise((resolve) => { chrome.permissions.request({ origins: [ "https://www.google.com/complete/search?client=*", "https://duckduckgo.com/ac/?q=*", "https://search.brave.com/api/suggest?q=*", "https://*.wikipedia.org/w/api.php?action=opensearch&search=*" ] }, (granted) => { resolve(granted); }); }); } function applySearchSuggestionsState(enabled) { proxyOptions.classList.toggle("not-applicable", !enabled); proxybypassField.classList.toggle("inactive", !enabled); if (!enabled) { useproxyCheckbox.checked = false; saveCheckboxState("useproxyCheckboxState", useproxyCheckbox); proxyinputField.classList.add("inactive"); saveActiveStatus("proxyinputField", "inactive"); setTimeout(() => searchSuggestionsHeader.style.borderBottom = "none", 80); } else searchSuggestionsHeader.style.borderBottom = ""; } // Add change event listeners for the checkboxes searchsuggestionscheckbox.addEventListener("change", async function () { saveCheckboxState("searchsuggestionscheckboxState", searchsuggestionscheckbox); const enabled = searchsuggestionscheckbox.checked; applySearchSuggestionsState(enabled); if (enabled && !isFirefoxAll) { await requestHostPermissions(); } }); useproxyCheckbox.addEventListener("change", async function () { if (useproxyCheckbox.checked) { // Show the disclaimer and check the user's choice const userConfirmed = await showProxyDisclaimer(); if (userConfirmed) { // Only enable the proxy if the user confirmed saveCheckboxState("useproxyCheckboxState", useproxyCheckbox); proxyinputField.classList.remove("inactive"); saveActiveStatus("proxyinputField", "active"); } else { // Revert the checkbox state if the user did not confirm useproxyCheckbox.checked = false; } } else { // If the checkbox is unchecked, disable the proxy saveCheckboxState("useproxyCheckboxState", useproxyCheckbox); proxyinputField.classList.add("inactive"); saveActiveStatus("proxyinputField", "inactive"); } }); // Load and apply the saved checkbox states and display statuses loadCheckboxState("searchsuggestionscheckboxState", searchsuggestionscheckbox); loadCheckboxState("useproxyCheckboxState", useproxyCheckbox); loadActiveStatus("proxyinputField", proxyinputField); loadActiveStatus("proxybypassField", proxybypassField); applySearchSuggestionsState(searchsuggestionscheckbox.checked);});The English locale text shown by the proxy confirmation says: "All proxy features are off by default. If you enable search suggestions and CORS bypass proxy, it is strongly recommended to host your own proxy for enhanced privacy. By default, the proxy will be set to https://mynt-proxy.rhythmcorehq.com, meaning all your data will go through this service, which may pose privacy concerns."
Weather widget sends your IP to ipinfo.io on every new tab
When the weather widget is on and no home location is saved, the extension contacts ipinfo.io with your IP on each new tab. ipinfo.io infers city and coordinates, used to load the forecast.
Happens every new tab, no notice.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You open a new browser tab with the weather widget enabled and no saved location.
This is the default state for any user who has not configured a manual location or enabled GPS in the extension settings.
The extension contacts ipinfo.io with your IP address to determine your city and approximate coordinates.
Those coordinates are then forwarded to weatherapi.com to fetch the weather forecast for your inferred location.
{"ip": "82.3.220.102", "loc": "50.8284,-0.1395", "city": "Brighton", "country": "GB"}
- IP address82.3.220.102
Your public IP address is disclosed to ipinfo.io. In enterprise environments this can identify a VPN exit node or corporate office gateway.
- Approximate coordinates50.8284,-0.1395
Latitude and longitude derived from your IP, accurate to within a few miles. These coordinates are passed directly to weatherapi.com.
- City and countryBrighton, GB
City-level location inferred from your IP address by ipinfo.io.
Location fallback in weather.js, fires on every new tab when no location is saved
// weather.js โ executed on each new tab loadawait (async function initializeLocation() { try { // Priority 1: use GPS if user opted in if (useGPS) currentUserLocation = await fetchGPSLocation(); // Priority 2: if still no location, silently query ipinfo.io if (!currentUserLocation) { const locationData = await fetch("https://ipinfo.io/json/"); // IP exposed here const ipLocation = await locationData.json(); currentUserLocation = ipLocation.loc; // e.g. "50.8284,-0.1395" } fetchWeather(); // passes currentUserLocation to weatherapi.com } catch (error) { // if ipinfo.io fails, fall back to weatherapi's own IP-based lookup currentUserLocation = "auto:ip"; fetchWeather(); }})();- ipinfo.io
IP geolocation service. Receives the browser's public IP address; returns city, country, and approximate coordinates. Operated by IPinfo LLC (US).
- api.weatherapi.com
Weather data provider. Receives the coordinates returned by ipinfo.io alongside a weather API key bundled in the extension source code.