LinkedIn: browser-realm decodeLinkedInSafetyUrl accepts credentialed URLs that the Node-side helper rejects
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 throughnormalizeHttpUrl, which explicitly rejectsparsed.username || parsed.password. - Browser copies check only
protocol === 'http:' || 'https:'. They cannot importnormalizeHttpUrl(page realm) and resolve againstlocation.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/passwordcheck to all three page-realm copies, plus tests for credentials,safety/goredirect 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.
Source: jackwener/OpenCLI