DEV Community

Cover image for Is Surfe Safe on LinkedIn? I Read Its Extension's Source Code
Michael Harris
Michael Harris

Posted on

Is Surfe Safe on LinkedIn? I Read Its Extension's Source Code

Quick answer: Surfe sends no connection requests, no InMails, and no sequences. That is genuinely reassuring. But the LinkedIn-to-CRM enrichment extension at surfe.com, formerly Leadjet, extension ID kojhcdejfimplnokhhhekhiapceggamn - not Safe Surfer, SurfEasy VPN, Surfer SEO, or surfe.pro - still exposes an architectural restriction risk at zero outreach volume.

I unpacked Surfe v3.2.3 (Manifest V3), whose CRX was fetched on 2026-06-05, and reviewed it again in 2026-08. The shipped source harvests JSESSIONID, li_at, and li_a by name on every page-load cycle and holds them in extension storage, with api.prod.surfe.com called on every cycle. That is not proof that the token values are uploaded. It is why my session verdict is session upload suspected, not confirmed.

What Surfe actually is

Surfe is type 2 by mechanism and type 1 by execution: cookie-bridge machinery, but local execution. It has the cookies permission, stores named session tokens, and synchronously contacts the vendor backend. Yet it has no remote-control queue, vendor-hosted browser, or server-side LinkedIn login. The Voyager requests run in your browser, through your session, from your IP.

That distinction matters. I did not run a live cloud-IP test because there is no cloud-side LinkedIn session or address to measure (has_remote_control_queue: false). There is therefore no exit-IP result or cross-account isolation result. The genuine reassurance is that LinkedIn sees your IP, your geolocation, and your device fingerprint, not a foreign vendor-cloud fingerprint. A parallel session or 48-point fingerprint mismatch would be conditional on server-side replay, which I did not observe.

The dated extension-list result is another real negative. Surfe's ID was not on the target list checked for the v3.2.3 audit round, using the 6,167+ entry target list. AED is the label visible in LinkedIn's own production JavaScript, where results are sent as an AedEvent; LinkedIn has never publicly acknowledged it, so it is neither an official feature name nor my coinage. BrowserGate, an independent 2025-26 investigation that took apart LinkedIn's production bundle, and Linked Helper's security study - static audits of 16 extensions plus live two-account tests of 7 cloud tools - document the mechanism. It is a Chromium-only check.

The growth context is 38 entries in 2017 -> about 461 in 2024 -> 5,459 in December 2025 -> 6,167 in February 2026, roughly a dozen additions per day. "Not listed" is perishable; a listed result only becomes more true. And not listed does not mean not detectable: Surfe exposes assets/* and inject.css to , leaving an extension-probe path open.

Where your session goes

Here is the chain in code order. The numeric signals below are string occurrences in the shipped source, not runtime telemetry.

Step 1 - Read the LinkedIn cookies locally

js chrome.cookies.getAll({domain: www.${Uf}}, e => { // Uf = "linkedin.com" const t = ["JSESSIONID", "li_at", "li_a"], n = {}; e.forEach(e => { "JSESSIONID" === e.name ? n.sessionID = e.value.split('"').join("") : t.includes(e.name) && ( n[e.name] = e.value, ("li_a" === e.name || "li_at" === e.name) && (n.li_a_expiration = e.expirationDate?.toString()) ); }); eh(n); // writes {sessionID, li_at, li_a, li_a_expiration} to chrome.storage.local });
Jv() runs after every chrome.tabs.onUpdated event whose status is complete, not only on LinkedIn tabs, and on a refresh-li-cookies message. The source contains five chrome.cookies references, nine li_at references, and two JSESSIONID references.
What this means for detection: Nothing yet. A local cookie read produces no LinkedIn-visible signal. The tokens are harvested by name and held in extension storage, but this step alone does not show them leaving the browser.

Step 2 - Call Surfe and set li-protect

js const e = await (async () => (await Bp("GET", { url: "/li-protect" })).data)(); chrome.cookies.set({url:"https://www.linkedin.com", name:"li-protect", value: e.protect ? "true" : "false"});
Bp() authenticates to the code constant Lf = "https://api.prod.surfe.com" with a Surfe Bearer JWT. This call follows every harvest cycle, proving that Surfe's infrastructure knows each cycle happened in real time. It does not prove the three cookie values are in that request.

What this means for detection: LinkedIn cannot directly see the request to Surfe. It can see the second artifact: an extension-managed li-protect cookie on linkedin.com, carried with requests to LinkedIn. That artifact does not map cleanly to a documented detection vector, so I will not invent one. Parallel-session and fingerprint-mismatch vectors apply only if the session is replayed elsewhere, which this read did not establish.

Step 3 - Inject on every HTTPS site

json { "host_permissions": ["https:///"], "content_scripts": [ {"matches":["<all_urls>"],"js":["assets/scripts/boot.js"],"run_at":"document_start","all_frames":true}, {"matches":["<all_urls>"],"js":["inject.js"],"css":["inject.css"],"run_at":"document_end"} ], "web_accessible_resources": [ {"resources":["assets/*","inject.css"],"matches":["<all_urls>"]} ] }
The scanner field injects_into_linkedin:false is only a manifest URL-filter heuristic. includes LinkedIn, and the scripts load at both ends of page construction. It is wrong to translate that heuristic into "does not inject into LinkedIn."

What this means for detection: LinkedIn's Spectroscopy scanner recursively searches the DOM for chrome-extension://, extracts a 32-character extension ID, and reports a SpectroscopyEvent without needing a target list. Its page-snapshot worker can also send traces server-side. Separately, the web-accessible resources keep an AED-style probe possible even though Surfe was not listed in the dated snapshot.

Step 4 - Call LinkedIn's private API

text direct_linkedin_api: true headers: csrf-token, x-li-*, x-restli bundles: background.js, inject.js, sidepanel.js string occurrences: voyager: 2, graphql: 7, csrf: 12

These are credential-bearing Voyager/GraphQL requests on the user's session. They execute locally, so this is not a vendor cloud driving the account.

What this means for detection: This creates the request-map anomaly: an ordinary profile visit produces markup, API calls, prefetches, and telemetry together. An API-only read can access the profile without the surrounding page visit and its companion traffic. LinkedIn can see that mismatch in server logs; BrowserGate and Linked Helper's security study describe this vector.

Step 5 - Synthesize UI events and leave telemetry alone

text string occurrences in the shipped bundles: synthetic_event: 77, programmatic_click: 5 manifest: declarative_net_request: null (blocks_traffic: false) settings.json: timeZone default "UTC"; action delay presets 0, 1.2, 1.5, 4, 8, 15 seconds

The event patterns are dispatchEvent and new KeyboardEvent in the sidebar and overlay code, not evidence of invitations being sent. The settings also have no daily cap, working-hours scheduler, or randomisation. The vendor's blog offers advice - about 500 profile views per day and 100-150 invitations per week - but the build enforces no cap. Advice in the blog post, no cap in the code. These controls concern enrichment actions, not invite automation.

What this means for detection: Script-created events have read-only isTrusted:false; a content script cannot make them human-trusted. Because Surfe blocks no traffic, LinkedIn's li/track telemetry and fingerprint endpoints receive the events in full. That is also a credit: Surfe never triggers the self-exposure signal caused by imperfect telemetry blocking. Separately, a default UTC timezone can mismatch a non-UTC user's locale, while fixed delays including zero and no caps feed the behavioral scoring layer.

Step 6 - Export data and widen the blast radius

text GET/POST https://api.prod.surfe.com/auth/contacts/export GET/POST https://api.prod.surfe.com/auth/organizations/export LinkedIn fields: name, linkedinID, salesNavURL, email, company HubSpot cookies: hubspotapi, hubspotapi-csrf, __cf_bm from .hubspot.com Telemetry: Datadog RUM, Intercom

The contact and organization endpoints move profile data through Surfe to a connected CRM such as HubSpot, Salesforce, Pipedrive, Outreach, Salesloft, or Aircall. The same harvest function also collects the three named HubSpot cookies. Surfe's Trust Center at surfe.com/security, fetched 2026-08-27, covers encryption, ISO 27001, GDPR, and CCPA, but does not mention LinkedIn, cookies, or session tokens.

What this means for detection: No new LinkedIn detection vector beyond the private-API reads in Step 4. This is a privacy and blast-radius finding: one extension holds authenticated material for LinkedIn and HubSpot. Datadog RUM and Intercom add behavioral/support telemetry, but the export itself is not a separate LinkedIn signal.

flow surfe

The diagram is a reconstruction of the shipped flow: named tokens into extension storage; the vendor API called each cycle; Voyager requests leaving from your browser and IP, not Surfe acting from its own address.

That local-execution point is not a footnote. It prevents this teardown from being generalized into a claim about vendor-cloud automation. The uncomfortable part is the browser surface: broad permissions, page injection, token storage, private-API traffic, and deterministic enrichment behavior. The reassuring part is equally concrete: no hosted LinkedIn login, no remote driver, and no evidence of a second simultaneous session.

How to repeat the check

Pull extension ID kojhcdejfimplnokhhhekhiapceggamn from the Chrome Web Store and unpack the CRX. Read the extension's manifest.json for permissions, host_permissions, content_scripts.matches, and web_accessible_resources. Beautify the JavaScript bundles, then grep them for chrome.cookies, li_at, JSESSIONID, voyager, csrf-token, and dispatchEvent. Also search LinkedIn's own JavaScript bundles for AedEvent. The claims above are falsifiable against the build, not dependent on trusting my label for it.

Finding Detection or account meaning How I know
JSESSIONID, li_at, li_a harvested after every completed page load No signal at local-read stage; replay would be conditional and unobserved Code read
Vendor API called after every harvest; li-protect set on LinkedIn Vendor knows each cycle; cookie is a real artifact with no clean documented vector Code read
<all_urls> at start/end, all frames, HTTPS wildcard Spectroscopy and page-snapshot exposure Code read
assets/* and inject.css exposed on <all_urls> AED-style probe path despite the clean dated list result Code read
Voyager, GraphQL, and CSRF strings appear 2, 7, and 12 times Request-map anomaly from private API access Static signal count
Synthetic-event and click strings appear 77 and 5 times isTrusted:false; unblocked telemetry records the surface Static signal count
UTC; delays 0/1.2/1.5/4/8/15 seconds; no cap/schedule/randomisation Timezone/locale mismatch and behavioral scoring Code read
HubSpot session material harvested in the same function Privacy and single-point-of-failure issue, not LinkedIn detection Code read

What this means for your account

LinkedIn uses a scoring model, not a tripwire. The injected surface, private-API traffic, synthetic events, UTC default, and behavioral settings add signals; none means an instant restriction. Surfe also removes two major surfaces: it does not send outreach and it executes locally from your IP and fingerprint.

The review corpus is unusually clear: nobody is complaining, because nobody was looking at this layer. Across 95 rows, sentiment was strongly positive - 58 delighted and 24 satisfied - and neither of the two incidental keyword hits concerned account risk. There is no supported report of Surfe causing a ban or restriction.

One five-star G2 reviewer noticed passive behavior without treating it as security evidence:

"If I'm just looking at a company page and the extension is installed, I don't really think this should count as an 'action'. I believe a more accurate count of actions would just include active ones like clicking to go to the CRM or syncing…" G2, 5-star, Head of Marketing (mid-market), 2023-04-14.

The interesting phrase is "just looking." The reviewer saw that installation alone registered activity; the source explains why the extension has passive presence on every completed tab load.

The counter-voice is positive and deserves to stay positive:
"Easy to integrate, no hassle at all, data quality looks very promising as they scrape various tools." G2, 4.5-star, enterprise, 2024-10-17.

Here "scrape" describes enrichment sourcing, while Surfe's safety messaging describes the absence of bulk LinkedIn actions. Both can be true. The gap between enrichment and browser mechanics is precisely why reading the source adds information a feature review cannot.
A two-star reviewer described a governance surprise rather than account risk:
"Turns out it adds leads to your CRM contact and company records, as well as Leads. This bloats your CRM with contacts and companies records with data you have never talked to." G2, 2-star, small business, 2024-07-29.
"Data you have never talked to" is the useful detail: where enrichment lands was not obvious before purchase. It supports scrutiny of the export path, not a restriction claim.

Category evidence should not be mislabelled as Surfe evidence:
"We lost about €17K in client refunds last year because the chrome extension-based tool we were using got five of our client accounts banned in a single month." Reddit, r/salesdevelopment, 2026-02-13
That post names no tool and cannot be attached to Surfe. It shows that extension architecture can have costed consequences elsewhere; it does not override the absence of a Surfe-specific report.

FAQ

Is Surfe safe to use on LinkedIn?
It does not send connection requests, InMails, or sequences, so its behavioral restriction risk is lower than an outreach sequencer's. Its ID was also absent from the dated AED target-list check. The remaining exposure is structural: named session-token harvesting into extension storage, all-site content scripts, private-API calls, synthetic events, and unblocked telemetry. No automation tool is ban-proof.

Does Surfe have access to my LinkedIn account?
Its extension reads li_at, li_a, and JSESSIONID by name after every completed page load and holds them in extension storage. Surfe's production API is called on every cycle, so its infrastructure knows the harvest occurred. The source does not prove that the values themselves are transmitted, and I do not claim it does.

Does Surfe collect user data - and exactly what?
Yes: the three LinkedIn session tokens go into extension storage; LinkedIn profile/company fields including name, linkedinID, salesNavURL, email, and company go to api.prod.surfe.com and the connected CRM; Datadog RUM and Intercom receive behavioral/support telemetry; and the same function takes hubspotapi, hubspotapi-csrf, and __cf_bm from .hubspot.com.

Does the extension only run on LinkedIn?
No. Its content scripts match at document_start and document_end, and its host permission is https:///. It is present in every HTTPS tab.

Does LinkedIn detect Surfe?
The extension ID was not on the AED list in the checked snapshot, but that is not immunity. A DOM-wide scanner needs no list, while exposed web-accessible resources leave an AED-style probe path. The target list grows by roughly a dozen entries daily, making a negative result perishable.

Can Surfe get my LinkedIn account banned or restricted?
It can add restriction risk through detectable browser and request patterns, but detection is not an instant ban. Surfe does not send outreach and runs from your own IP and fingerprint. I found no user report of a Surfe-caused restriction.

How much does Surfe cost, and what is a safer alternative?
From Surfe's pricing page fetched 2026-08-27: Free is $0; Essential is $49/user/month or $39 billed annually; Pro is $89/user/month or $79 billed annually; Enterprise is custom. Architecturally, moving away from a browser extension removes the extension-ID and injection surfaces, though no tool removes all restriction risk.

Top comments (0)