Is RocketReach Chrome Extension safe?

Medium risk

RocketReach is medium risk. Dynamic analysis captured a POST from RocketReach's service worker to plugin.rocketreach.co/browser-extension/v1/m5s at startup, before use. The base64 body decoded to metric events, flushed every 10s, tagged with endpoints like 'user'.…

RocketReachv4.0.15Chrome Web Store
45Risk
Who publishes it

RocketReach LLC - no other listings under this identity, 1 shared hostname

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

Publisher
RocketReach
Declared legal entity
RocketReach LLC
Registered address
144 N 7th St, P.O. Box 421, Brooklyn, NY 11211, US
Registered contact
RocketReach co

Shared hosts - 1 hostname

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.

rocketreach.co
Also called by 6 other listings, including Mr. E by EasyLeadz, Recruiterbolt For Salesforce

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

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

Telemetry Sent on Every Extension Action

Dynamic analysis captured a POST from RocketReach's service worker to plugin.rocketreach.co/browser-extension/v1/m5s at startup, before use.

The base64 body decoded to metric events, flushed every 10s, tagged with endpoints like 'user'.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You install the extension and open Chrome.

The extension did this

The service worker begins queuing usage metric events and posts them base64-encoded to RocketReach's servers every 10 seconds.

No user interaction is required. Events are sent even before you log in or search for anyone.

02EvidenceNETWORK CAPTURE
Captured request
POSThttps://plugin.rocketreach.co/browser-extension/v1/m5s
HTTP 200 OK (empty body). Captured during dynamic analysis, fired at startup before any user interaction.
Headers
Content-Typetext/plain;charset=UTF-8
Body
eyJtZXRyaWNzIjpbeyJtZXRyaWNfdHlwZSI6ImluY3JlbWVudCIsIm5hbWUiOiJmZXRjaCIsInRhZ3MiOlt7InN0YXR1cyI6InN1Y2Nlc3MiLCJlbmRwb2ludCI6InVzZXIifV19LHsibWV0cmljX3R5cGUiOiJpbmNyZW1lbnQiLCJuYW1lIjoidXJsX3Jlc29sdXRpb24iLCJ0YWdzIjpbeyJzdGF0dXMiOiJub19tYXRjaCIsInBhZ2VfdHlwZSI6ImdlbmVyaWMifV19XX0=
03EvidenceOPAQUE REVEAL
Why you can't catch this in DevTools

The POST body is base64-encoded, so it does not appear as readable JSON in browser DevTools or network monitors. Decoding the wire value reveals the full metrics array.

What's actually being sent
{
  "metrics": [
    {
      "metric_type": "increment",
      "name": "fetch",
      "tags": [
        {
          "status": "success",
          "endpoint": "user"
        }
      ]
    },
    {
      "metric_type": "increment",
      "name": "url_resolution",
      "tags": [
        {
          "status": "no_match",
          "page_type": "generic"
        }
      ]
    }
  ]
}
04EvidenceFIELD TABLE
Fields present in each metric event inside the batch:
FieldValueWhy it matters
Event type
incrementWhether this is a count event (increment) or a measured value (histogram).
Event name
url_resolutionWhich action or API call triggered the event, e.g. whether the extension tried to resolve the current page's URL.
Tags: status
no_matchThe outcome of the action, such as whether an API call succeeded or the URL matched a known profile page.
Tags: endpoint
person_searchWhich RocketReach API endpoint was involved, reveals which features you used (person search, company search, user profile, etc.).
Tags: page type
linkedinWhat type of page you were on when the event fired (LinkedIn, recruiter portal, generic site).
05EvidenceCODE COMPARE
The code that does this

The metrics service from the extension's minified source:

What it actually does
Metric type enum
enum MetricType {
  increment = "increment",
  histogram = "histogram",
}
MetricsService class (key methods)
class MetricsService {
  static async build(): Promise<MetricsService> {
    const svc = new MetricsService();
    svc._apiService = await ApiService.build();
    svc._endpointUrl = new URL("/browser-extension/v1/m5s", svc._apiService.BASE_URL);
    svc._featureFlags = await FeatureFlagService.build();
    return svc;
  }

  encodedBody(): string {
    // base64-encodes the entire metrics batch — not encrypted, trivially reversible
    return btoa(JSON.stringify({ metrics: this._batch }));
  }

  _getBatchDelaysMS(): number {
    return 10000; // flush every 10 seconds
  }
}
06EvidenceTHIRD PARTY LIST
Where metric data is sent:
  • plugin.rocketreach.co

    Primary RocketReach API host. Receives the metrics batch at /browser-extension/v1/m5s. Sole permitted externally_connectable origin in the manifest.

07EvidenceARTIFACT
Reproduce it yourself

Decodes a base64 metrics batch body from the RocketReach extension and prints it as formatted JSON. Paste the raw POST body from DevTools and run with Node.js.

RequiresNode.js 14+
rocketreach-metrics-decoder.js · js
#!/usr/bin/env node
// rocketreach-metrics-decoder.js
// Decodes a base64-encoded metrics batch captured from RocketReach's
// POST to plugin.rocketreach.co/browser-extension/v1/m5s
//
// Usage: node rocketreach-metrics-decoder.js <base64-string>
//   or:  echo '<base64>' | node rocketreach-metrics-decoder.js

const b64 = process.argv[2] || require('fs').readFileSync('/dev/stdin', 'utf8').trim();

try {
  const json = Buffer.from(b64, 'base64').toString('utf8');
  const parsed = JSON.parse(json);
  console.log(JSON.stringify(parsed, null, 2));
  console.log(`\nTotal events in batch: ${parsed.metrics?.length ?? 0}`);
} catch (err) {
  console.error('Failed to decode:', err.message);
  console.error('Expected: a base64 string from the POST body of /browser-extension/v1/m5s');
  process.exit(1);
}
How to run it
  1. 1
    Open DevTools Network tab with RocketReach installed.
  2. 2
    Filter by 'm5s'.
  3. 3
    Copy the POST body.
  4. 4
    Run: node rocketreach-metrics-decoder.js '<base64>'.
  5. 5
    Decoded metrics print to stdout.
SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-829
SourceAI SANDBOX

Remote Feature Flags Control Extension Behavior

Dynamic analysis captured GETs to plugin.rocketreach.co/browser-extension/v1/rr-everywhere at startup, before login.

It also fetches config from /v1/user/ff3p hourly, gating all-page activation, flippable externally by that host.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open Chrome with RocketReach installed, even without logging in.

The extension did this

The extension contacts plugin.rocketreach.co to fetch feature flags that determine which capabilities are active on your browser.

This happens unconditionally on startup. The extension also repeats the fetch every 60 minutes via a Chrome alarm.

02EvidenceNETWORK CAPTURE
Captured request
GEThttps://plugin.rocketreach.co/browser-extension/v1/rr-everywhere
HTTP 200 OK. Response JSON: {"enabled": false}. Captured during dynamic analysis, fired at startup before any login or user interaction.
03EvidenceFIELD TABLE
What the remote flag responses control:
FieldValueWhy it matters
rr-everywhere toggle
{"enabled": false}Whether the extension activates on every website you visit, not just supported recruitment platforms.
Feature flags store
{"lastRefresh": 1744640400000, "features": {"new_profile_ui": true, "bulk_export": false}}A JSON object stored in chrome.storage.local that gates which extension features are enabled for your account.
ff3p config
{"features": {"sidebar_v2": "enabled", "autocomplete": false}}Third-party feature configuration fetched from /v1/user/ff3p, controls additional feature variants tied to your account.
04EvidenceCODE COMPARE
The code that does this

Feature flag fetch and alarm registration from the minified source:

What it actually does
Alarm registration
// Registers a Chrome alarm named 'util.FeatureFlag' that fires every 60 minutes.
async registerAlarm() {
  if (!chrome.alarms) return;
  if (await chrome.alarms.get(FeatureFlagService.AlarmName)) return; // already registered
  chrome.alarms.create(FeatureFlagService.AlarmName, { periodInMinutes: this._intervalMinutes }); // 60 min
}
rr-everywhere fetch
// Fetches whether the extension should activate on all sites.
// Called unconditionally — no login check, fires on every extension load.
async getAndSaveEverywhereState() {
  const url = new URL("/browser-extension/v1/rr-everywhere", this._apiService.BASE_URL);
  const response = await fetch(url, { method: "GET" });
  const data = await response.json();
  const isEnabled = data?.enabled ?? false;
  await this._setEverywhereState(isEnabled); // writes to chrome.storage.local
}
Feature flags fetch (ff3p)
// Fetches per-account feature flags. Requires login.
// Also tracked with metric event he.ff3p ('endpoint:ff3p').
async fetchFeatureFlags() {
  if (!(await this._authService.isLoggedIn())) return;
  const url = new URL("v1/user/ff3p?no_encode=true&inc1=true", this._apiService.BASE_URL);
  const response = await fetch(url);
  if (response.ok && response.status === HttpStatus.OK) {
    const data = await response.json();
    await this._saveFeatureFlags(data); // stored at 'feature-flags' in chrome.storage
  }
}
05EvidenceTHIRD PARTY LIST
Endpoints contacted for remote configuration:
  • plugin.rocketreach.co

    Primary extension API. Serves the rr-everywhere toggle and per-account feature flags. Also the sole externally_connectable origin, so this host can message the extension directly.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-319
SourceAI SANDBOX

RocketReach sends metrics as reversible base64

RocketReach posts a metrics batch to plugin.rocketreach.co/browser-extension/v1/m5s.

Evidence: the body decodes via base64 into JSON metrics; re-encoding reproduces it.

Built via btoa(JSON.stringify()): HTTPS-protected, no added encryption.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You browse while the extension is installed and enabled.

The extension did this

The extension batches activity metrics and sends them to RocketReach as reversible base64-encoded JSON.

02EvidenceNETWORK CAPTURE
Captured request
POSThttps://plugin.rocketreach.co/browser-extension/v1/m5s
03EvidenceFIELD TABLE
Decoded fields observed in the metrics body
FieldValueWhy it matters
Metric type
incrementShows what kind of measurement the extension is reporting about your extension activity.
Metric names
fetch; url_resolutionShows which browser-extension events were recorded in the batch.
Result tags
status:success; status:no_matchAdds outcome details to the metric events, such as whether a fetch succeeded or a URL resolution matched.
Context tags
endpoint:user; page_type:genericAdds page or endpoint context to the metric event so requests can be grouped by activity type.
04EvidenceTEMPORAL PATTERN
When this fires
When a batch threshold is hit

The extension waits about 10 seconds before sending the queued metrics batch.

05EvidenceCODE COMPARE
The code that does this

The metric service serializes the batch and applies base64 encoding

What it actually does
!function(r) {
  var i, o, s;
  r.OnMessageName = "util.Metric";
  class u extends re {
    constructor() {
      super(), i.set(this, void 0), o.set(this, void 0), s.set(this, void 0), this._batch = [], this._pendingBatchRequest = null, this._batchDelayMS = 1e3;
    }
    static build() {
      return n(this, void 0, void 0, (function*() {
        const e = new u;
        return t(e, i, yield Ue.Service.build(), "f"), t(e, o, new URL("/browser-extension/v1/m5s", a(e, i, "f").BASE_URL), "f"), t(e, s, yield xe(), "f"), e
      }))
    }
    increment(e, a) {
      return n(this, void 0, void 0, (function*() {
        this._enqueueMetric({
          metric_type: $e.increment,
          name: e,
          tags: a
        });
      }))
    }
    histogram(e, a, t) {
      return n(this, void 0, void 0, (function*() {
        this._enqueueMetric({
          metric_type: $e.histogram,
          name: e,
          value: a,
          tags: t
        });
      }))
    }
    _enqueueMetric(e) {
      return n(this, void 0, void 0, (function*() {
        this._batch.push(e), this._ensureBatchEnqueued();
      }))
    }
    _ensureBatchEnqueued() {
      return n(this, void 0, void 0, (function*() {
        if (this._pendingBatchRequest) return;
        const e = this._getBatchDelaysMS();
        this._pendingBatchRequest = setTimeout((() => {
          this._sendBatch(), this._batch = [], clearTimeout(this._pendingBatchRequest), this._pendingBatchRequest = null;
        }), e);
      }))
    }
    _sendBatch() {
      return n(this, void 0, void 0, (function*() {
        yield fetch(a(this, o, "f"), {
          method: "POST",
          body: this.encodedBody()
        });
      }))
    }
    encodedBody() {
      const e = JSON.stringify({
        metrics: this._batch
      });
      return btoa(e)
    }
    _getBatchDelaysMS() {
      return 1e4
    }
  }
  i = new WeakMap, o = new WeakMap, s = new WeakMap, e([u.alwaysRunInServiceWorkerContext(r.OnMessageName)], u.prototype, "increment", null), e([u.alwaysRunInServiceWorkerContext(r.OnMessageName)], u.prototype, "histogram", null), r.Service = u;
}(Ee || (Ee = {}));
06EvidenceARTIFACT
Check if you're affected

Decodes a captured RocketReach metrics body from base64 and checks whether re-encoding the decoded text produces the same body.

RequiresNode.js 18+
rocketreach-metrics-base64-check.js · js
const fs = require('fs');

const input = fs.readFileSync(0, 'utf8').trim();
if (!input) {
  console.error('Usage: node rocketreach-metrics-base64-check.js < body.txt');
  process.exit(1);
}

const decoded = Buffer.from(input, 'base64').toString('utf8');
console.log(decoded);

try {
  const parsed = JSON.parse(decoded);
  if (!Array.isArray(parsed.metrics)) {
    console.error('Decoded JSON does not contain a metrics array.');
    process.exit(2);
  }
  const roundTrip = Buffer.from(decoded, 'utf8').toString('base64');
  console.error(`metrics entries: ${parsed.metrics.length}`);
  console.error(`round-trip matches input: ${roundTrip === input}`);
} catch (err) {
  console.error(`Decoded text is not valid JSON: ${err.message}`);
  process.exit(3);
}
How to run it
  1. 1
    Save a captured /browser-extension/v1/m5s request body to body.txt.
  2. 2
    Run: node rocketreach-metrics-base64-check.js < body.txt
Updated 30 September 2026oiecklaabeielolbliiddlbokpfnmhba