#2352·OpenCLI

LinkedIn: browser-realm decodeLinkedInSafetyUrl accepts credentialed URLs that the Node-side helper rejects

Author: jackwenerCreated Aug 23, 2026Updated Sep 15, 2026

Summary

decodeLinkedInSafetyUrl exists in two realms with different security postures for credential-bearing URLs. The Node-side helper rejects user:pass@ URLs; the three browser-realm (page-evaluate) copies do not, so credentialed targets — including via the /safety/go/?url= redirect — reach extraction output.

This is pre-existing behaviour, not introduced by #2351. It was found while reviewing that PR and deliberately kept out of scope, since #2351 was a pure Node-side consolidation and the browser strings were a hard negative boundary.

Measured on current main (de16476e)

input Node (shared.js) browser-realm
https://user:[email protected]/x "" "https://user:[email protected]/x"
/safety/go/?url=<encoded https://user:[email protected]/> "" "https://user:[email protected]/"
/in/someone "" "https://www.linkedin.com/in/someone"
javascript:alert(1) "" "" (consistent)

Why they differ

  • Node (clis/linkedin/shared.js) routes through normalizeHttpUrl, which explicitly rejects parsed.username || parsed.password.
  • Browser copies check only protocol === 'http:' || 'https:'. They cannot import normalizeHttpUrl (page realm) and resolve against location.origin.

Browser-realm copies on current main:

  • clis/linkedin/profile-experience.js:149 (extraction)
  • clis/linkedin/profile-experience.js:393 (dialog)
  • clis/linkedin/profile-projects.js:92 (extraction)

Note these are not merely duplicated code — they are realm-local implementations that cannot be replaced by a Node import (normalizeWhitespace is undefined in the page realm; location is undefined in Node), and they have a genuinely different relative-URL contract.

Decision needed

Should browser-side extraction also reject credentialed URLs, matching the Node side?

  • If yes: add a username/password check to all three page-realm copies, plus tests for credentials, safety/go redirect targets, and relative URLs.
  • If no: document why extraction output may legitimately contain credentials, so the divergence is intentional rather than accidental.

Either way the relative-URL divergence (/in/someone"" vs origin-resolved) is entangled: aligning the realms touches that contract too, so it should be decided together rather than patching only the credential check.

The Node side's rejection is now pinned by clis/linkedin/shared.test.js, so one side of the divergence is test-protected regardless of the outcome.