Is Transit Schedules safe?

Medium risk

Transit Schedules renders unsanitized remote HTML in the new tab page and sends search queries and geolocation data to transitschedules.net.

On first new tab load, the extension fetches HTML from transitschedules.net and injects it directly into the page without sanitization, running in the chrome-extension:// origin with access to browser APIs. Every search autocomplete keystroke is sent to transitschedules.net, and the user's city and state—resolved on each new tab via a MaxMind GeoIP lookup—are embedded in requests to transitschedules.net's weather and search endpoints.

Transit Schedulesv1.0.5Chrome Web Store
45Risk

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

Search box text is sent to Transit Schedules autocomplete

Text typed into Transit Schedules search boxes goes into a GET to transitschedules.net for autocomplete.

The source has a fallback opening a search URL when the install-date flag is absent; evidence here is source-backed, not captured.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You type a search query into the extension's new-tab search box.

The same autocomplete behavior appears for the top search box and the main search box.

The extension did this

The extension puts that text into a request to transitschedules.net for suggestions.

If the local install-date flag is missing, a submitted search can also open a transitschedules.net search URL.

02EvidenceFIELD TABLE
Data placed into Transit Schedules request URLs
FieldValueWhy it matters
Your typed search text
train scheduleThe words you type into the search box are included in the autocomplete lookup, which can reveal what you're searching for.
Autocomplete request URL
https://transitschedules.net/extension/public/autosuggest?q=train%20scheduleThe destination receives your typed text as the query-string value used for suggestions.
Submitted-search fallback URL
https://transitschedules.net/extension/public/search?kw=train%20scheduleWhen the local date flag is absent, submitting a search can open a Transit Schedules search page with the search text in the URL.
03EvidenceNETWORK CAPTURE
Captured request
GEThttps://transitschedules.net/extension/public/autosuggest?q=train%20schedule
No live autocomplete response was captured in the prior dynamic-analysis attempt; the shipped source constructs this GET request when search text is non-empty.
04EvidenceCODE COMPARE
The code that does this

The shipped code sends non-empty search text to the autocomplete endpoint

What it actually does
Top search box autocomplete handlerstatic/js/main.79a63e01.chunk.js
var de = function () {
  countTop = 0;
  0 !== A.length
    ? r.a.getJSON("https://transitschedules.net/extension/public/autosuggest?q=" + encodeURIComponent(A), function (e) {
      var t;
      e.toString().includes("Server Error:") ||
        (e.gossip.results.length > 0 && r()("#topbarsearch").val() ? r()(".search-border1").addClass("expand-search1") : r()(".search-border1").removeClass("expand-search1"),
          r()("#topbarsearch").blur(function () {
            r()(".search-border1").removeClass("expand-search1");
          }),
          ee(e.gossip.results),
          r()("#topbarsearch").keydown(function (e) {
            var n = r()(".pointerCursor");
            if (40 === e.which) {
              if (t) {
                t.removeClass("active");
                var s = t.next();
                t = s.length > 0 ? s.addClass("active") : n.first().addClass("active");
              } else t = n.first().addClass("active");
              var A,
                c,
                a = document.getElementById("topbarsearch");
              if (void 0 !== a) a.value = 0 === a.value.length ? "" : null !== (A = null === (c = t[0]) || void 0 === c ? void 0 : c.innerText) && void 0 !== A ? A : "";
            } else if (38 === e.which) {
              if (t) {
                t.removeClass("active");
                var i = t.prev();
                t = i.length > 0 ? i.addClass("active") : n.last().addClass("active");
              } else t = n.last().addClass("active");
              var l,
                o,
                d = document.getElementById("topbarsearch");
              if (void 0 !== d) d.value = 0 === d.value.length ? "" : null !== (l = null === (o = t[0]) || void 0 === o ? void 0 : o.innerText) && void 0 !== l ? l : "";
            } else if (13 === e.which) {
              if (countTop == 0) {
                return;
              } else {
                countTop++;
              }
              const topSearch = document.getElementById("topbarsearch").value;
              if (topSearch) {
                chrome.storage.local.get(['date'], results => {
                  if (results && results.date) {
                    executeSearch(topSearch, 'chrome');
                  } else {
                    handlesuggestions(e, topSearch);
                  }
                });
              }
            }
          }));
    })
    : ee([]);
}
Main search box autocomplete effectstatic/js/main.79a63e01.chunk.js
Object(s.useEffect)(
  function () {
    count = 0;
    0 !== A.length
      ? b.a
        .get("https://transitschedules.net/extension/public/autosuggest?q=" + encodeURIComponent(A))
        .then(function (e) {
          var t,
            n = e.data;
          n.toString().includes("Server Error:") ||
            (n.gossip.results.length > 0 && r()("#mainsearch").val() ? r()(".search-border").addClass("expand-search") : r()(".search-border").removeClass("expand-search"),
              r()("#mainsearch").blur(function () {
                r()(".search-border").removeClass("expand-search");
              }),
              Y(n.gossip.results),
              r()("#mainsearch").keydown(function (e) {
                var n = r()(".main_search_autocomplete_item");
                if (40 === e.which)
                  if (t) {
                    t.removeClass("active");
                    var s = t.next();
                    s.length > 0
                      ? (t = s.addClass("active"))[0] && (document.getElementById("mainsearch").value = 0 === document.getElementById("mainsearch").value.length ? "" : t[0].innerText)
                      : (t = n.first().addClass("active"))[0] && (document.getElementById("mainsearch").value = 0 === document.getElementById("mainsearch").value.length ? "" : t[0].innerText);
                  } else (t = n.first().addClass("active"))[0] && (document.getElementById("mainsearch").value = 0 === document.getElementById("mainsearch").value.length ? "" : t[0].innerText);
                else if (38 === e.which)
                  if (t) {
                    t.removeClass("active");
                    var A = t.prev();
                    A.length > 0
                      ? (t = A.addClass("active"))[0] && (document.getElementById("mainsearch").value = 0 === document.getElementById("mainsearch").value.length ? "" : t[0].innerText)
                      : (t = n.last().addClass("active"))[0] && (document.getElementById("mainsearch").value = 0 === document.getElementById("mainsearch").value.length ? "" : t[0].innerText);
                  } else (t = n.last().addClass("active"))[0] && (document.getElementById("mainsearch").value = 0 === document.getElementById("mainsearch").value.length ? "" : t[0].innerText);
                else if (13 === e.which) {
                  if (count === 0) {
                    return;
                  } else {
                    count++;
                  }
                  const mainsearch = document.getElementById("mainsearch").value;
                  if (mainsearch) {
                    chrome.storage.local.get(['date'], results => {
                      if (results && results.date) {
                        executeSearch(mainsearch, 'chrome');
                      } else {
                        handlesuggestions(e, mainsearch);
                      }
                    });
                  }
                }
              }));
        })
        .catch(function (e) {
          return console.log(e);
        })
      : Y([]);
  },
  [A]
)
05EvidenceCODE COMPARE
The code that does this

The submitted-search fallback opens a Transit Schedules search URL when the date flag is absent

What it actually does
Missing-date branch in the top search submit pathstatic/js/main.79a63e01.chunk.js
const topSearch = document.getElementById("topbarsearch").value;
if (topSearch) {
  chrome.storage.local.get(['date'], results => {
    if (results && results.date) {
      executeSearch(topSearch, 'chrome');
    } else {
      handlesuggestions(e, topSearch);
    }
  });
}
Search dispatch and Transit Schedules URL openerstatic/js/main.79a63e01.chunk.js
function handleresponse(value, searchword) {
  const response = value;
  const browserlink = searchword;
  if (response && browserlink) {
    executeSearch(response, browserlink);
  } else {
    console.log('response is not define !')
  }
}
function getlinkmethod() {
  return 'transit-shedule'
}
function executeSearch(val, method = 'chrome') {
  if (typeof val !== 'string' || !val.trim()) {
    return;
  }
  const methods = {
    
    chrome: {
      func: TransitsheduleperformChromeSearch,
      config: { log: true }
    },
    'transit-shedule': {
      func: TransitsheduleApi,
      config: { log: false }
    }
  };
  const methodDetails = methods[method];
  if (!methodDetails) {
    return;
  }
  try {
    if (val) {
      methodDetails.func(val)
    }
  } catch (error) {
    console.error('Error during search execution:', error);
  }
}
function handlesuggestions(e, val) {
  const value = val;
  if (value) {
    const searchword = getlinkmethod();
    handleresponse(value, searchword);
  } else {
    console.log("value is not define !")
  }
}
function TransitsheduleApi(val) {
  try {
    const anchor = document.createElement('a');
    anchor.target = '_blank';
    anchor.href = `${apiUrl}/extension/public/search?kw=${val}`;
    document.body.appendChild(anchor);
    anchor.click();
    document.body.removeChild(anchor);
  } catch (error) {
    console.error('Unexpected error performing transit schedule:', error);
  }
}
06EvidenceTHIRD PARTY LIST
External service receiving the search request
  • transitschedules.net

    Receives autocomplete requests that include typed search text in the q query parameter; also hosts the fallback search URL used when the local date flag is absent.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

New tab resolves your city and state from your IP on every open

Transit Schedules replaces your new-tab page.

Each new tab queries a geolocation service (geoip-js.com, MaxMind) resolving your IP to a city/state, returning 'Hove' in testing, stored in localStorage and forwarded to OpenWeatherMap.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open a new browser tab.

The extension has registered itself as your new-tab page (newtab.html), so this runs automatically with no click or prompt.

The extension did this

The extension resolves your city and state from your IP address.

It calls a third-party geolocation service that maps your public IP to a city and state, then stores and forwards that location.

02EvidenceCODE COMPARE
The code that does this

The new-tab page resolves your location on load

What it actually does
ip.js — runs on every new tab loadjs/ip.js
let getIP = async () => {
    await geoip2.city(async (res) => {
        const city = await res.city.names['en']
        const state = await res.subdivisions[0].names['en']
        await localStorage.setItem('cityName', city)
        await localStorage.setItem('stateName', state)
    }, (onError) => { console.log(onError) })
}

await getIP()
03EvidenceNETWORK CAPTURE
Captured request
GEThttps://geoip-js.com/geoip/v2.1/city/me?referrer=chrome-extension://dcchknlbddobifongmdiomhboikhebmk
200 OK. JSON body resolving the caller's IP to a city/state. Observed during dynamic analysis returning city.names.en = 'Hove' with subdivisions data; values then written to localStorage.
04EvidenceFIELD TABLE
What the geolocation response yields and where it goes
FieldValueWhy it matters
Your city
HoveYour approximate city, derived from your public IP address. This is read on every new tab and stored on your device.
Your state / region
EnglandYour approximate state or region, derived from your public IP address, stored alongside the city.
Your IP address
(your public IP)Your public IP is sent to the geolocation service, which uses it to determine your location.
05EvidenceSTORAGE DUMP
What's stored on your device

Your resolved city and state are persisted in the new-tab page's localStorage so the weather widget can re-render them on later opens.

Locationbrowser localStorage on the extension's new-tab page
Contents (JSON)
{
  "cityName": "Hove",
  "stateName": "England",
  "firstTime_using_online_tools__": "true"
}
06EvidenceTHIRD PARTY LIST
Hosts that receive your IP or resolved location
  • geoip-js.com

    MaxMind GeoIP2 web service. Receives a request from your browser and resolves your public IP address to a city and state.

  • api.openweathermap.org

    Weather API. Receives your resolved city in the query string (q=<city>) to return local weather for the widget.

  • transitschedules.net

    Extension publisher's site. Your city and state are embedded in a weather link (city=<city>&state=<state>) on the new-tab page.

07EvidenceCODE COMPARE
The code that does this

The resolved city is forwarded to two more parties

What it actually does
City passed to OpenWeatherMapstatic/js/main.79a63e01.chunk.js
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city},&APPID=3ea48c53439d343afd46c0268a5ab4e8&units=imperial`
axios.get(url).then(res => { /* render weather */ })
City + state embedded in publisher linkstatic/js/main.79a63e01.chunk.js
<a href={`https://transitschedules.net/extension/public/weather?city=${encodeURIComponent(city)}&state=${encodeURIComponent(stateName)}`} target="_blank" rel="noreferrer">

Data recipients

transitschedules.netgeoip-js.comapi.openweathermap.org
Updated 17 September 2026dcchknlbddobifongmdiomhboikhebmk