Is MYNT: Material You New Tab safe?

Medium risk

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.โ€ฆ

itz-rj-herev3.3.802Chrome Web Store
49Risk
Who publishes it

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.

Publisher
itz-rj-here

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.

prem-k-r.github.io
Also called by 2 other listings, including MYNT: Material You New Tab
mynt-proxy.rhythmcorehq.com
Also called by 3 other listings, including MYNT: Material You New Tab
cdn.weatherapi.com
Also called by 6 other listings, including MYNT: Material You New Tab

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
What actually happens
You did this

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 did this

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.

Fields placed into the proxied suggestion request
  • Typed search text
    weather tomorrow

    This can reveal what you are searching for as you type in the new-tab page.

  • Suggestion service URL
    https://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 destination
    mynt-proxy.rhythmcorehq.com

    This host receives the encoded suggestion URL before the selected provider responds.

Captured request
GEThttps://mynt-proxy.rhythmcorehq.com/proxy?url=https%3A%2F%2Fwww.google.com%2Fcomplete%2Fsearch%3Fclient%3Dchrome%26q%3Dweather%2520tomorrow

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 code that does this

The shipped proxy setup and autocomplete fetch path

Readable version

Deobfuscated default proxy selection

scripts/search-suggestions.js
let 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.js
searchInput.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.js
async 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.js
document.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 extension warns before enabling the proxy

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
What actually happens
You did this

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 did this

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.

Captured request
GEThttps://ipinfo.io/json/

{"ip": "82.3.220.102", "loc": "50.8284,-0.1395", "city": "Brighton", "country": "GB"}

Data returned by ipinfo.io and used for weather lookup
  • IP address
    82.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 coordinates
    50.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 country
    Brighton, GB

    City-level location inferred from your IP address by ipinfo.io.

The code that does this

Location fallback in weather.js, fires on every new tab when no location is saved

Readable version
// 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();    }})();
External services that receive data on each new tab load
    • 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.

Updated 30 September 2026jjpokbgpiljgndebfoljdeihhkpcpfgl