Back to skill

Security audit

LinkedIn Skills

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real LinkedIn automation package, but it gives a local bridge powerful unattended access to your logged-in browser session, so it needs Review before installation.

Install only if you are comfortable granting an unpacked extension access to your logged-in LinkedIn browser session. Run it only in a trusted local environment, avoid leaving the bridge running, do not use it with sensitive Chrome profiles, and be aware that local processes could potentially drive LinkedIn actions or read session data while the bridge is active.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (5)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/bridge_server.py:33
Finding

Unauthenticated WebSocket Bridge Exposes Privileged Browser Automation

Content
View full analysis
None: try: raw = await asyncio.wait_for(ws.recv(), timeout=10) except (TimeoutError, Exception) as e: logger.warning("Handshake timeout or failure: %s", e) return try: msg = json.loads(raw) except json.JSONDecodeError: return role = msg.get("role") if role == "extension": await self._handle_extension(ws) elif role == "cli": await self._handle_cli(ws, msg) else: logger.warning("Unknown role: %s", role) ``` ```python async def _handle_cli(self, ws: ServerConnection, msg: dict) -> None: if msg.get("method") == "ping_server": await ws.send( json.dumps({"result": {"extension_connected": self._extension_ws is not None}}) ) return if not self._extension_ws: err = ( "Extension not connected. Please ensure Chrome has " "the LinkedIn Bridge extension installed and enabled." ) await ws.send(json.dumps({"error": err})) return msg_id = str(uuid.uuid4()) msg["id"] = msg_id loop = asyncio.get_event_loop() future: asyncio.Future[Any] = loop.create_future() self._pending[msg_id] = future await self._extension_ws.send(json.dumps(msg)) ``` ```python async with websockets.serve(server.handle, "localhost", port): logger.info("Bridge server started: ws://localhost:%d", port) logger.info("Waiting for browser extension to connect...") await asyncio.Future() ``` The extension dispatches forwarded messages to privileged operations: ```javascript async function handleCommand(msg ...[truncated 3415 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
extension/background.js:66
Finding

Unnecessary Raw LinkedIn Cookie Access Violates Least Privilege

Content
View full analysis
list: result = self._call("get_cookies", {"domain": domain}) return result if isinstance(result, list) else [] ``` ### Technical Analysis The extension requests the sensitive Chrome `cookies` permission and exposes raw cookie records through the WebSocket command dispatcher. The reviewed Python application does not call `get_cookies`; login detection and logout are implemented through browser-page behavior instead. The permission and command therefore exceed the privileges required by the implemented application workflow. This becomes particularly dangerous when combined with the unauthenticated bridge: an unauthorized bridge client can directly request credential-bearing cookie records. The default domain is LinkedIn, but the extension accepts a caller-provided domain. Chrome host-permission enforcement may constrain the final result, but relying on browser-side permission filtering is not an adequate substitute for removing an unused credential-access primitive. ### Attack Path 1. The user installs the extension, granting it cookie access for the configured host scope. 2. The user logs in to LinkedIn. 3. An attacker connects to the unauthenticated local WebSocket bridge as a CLI client. 4. The attacker sends: ```json { "role": "cli", "method": "get_cookies", "params": { "domain": "linkedin.com" } } ...[truncated 702 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/image_downloader.py:16
Finding

Unrestricted Image URL Fetching Enables SSRF and Resource Exhaustion

Content
View full analysis
str: """Download an image from URL and cache locally. Returns: Absolute path to the cached image file. """ os.makedirs(CACHE_DIR, exist_ok=True) req = urllib.request.Request(url, headers={"User-Agent": "linkedin-skills/1.0"}) with urllib.request.urlopen(req, timeout=30) as resp: # noqa: S310 content = resp.read() content_type = resp.headers.get("Content-Type", "") ext = _guess_extension(url, content_type) filename = f"{int(time.time() * 1000)}{ext}" filepath = os.path.join(CACHE_DIR, filename) if not os.path.exists(filepath): with open(filepath, "wb") as f: f.write(content) logger.info("Downloaded: %s -> %s", url, filepath) else: logger.debug("Cache hit: %s", filepath) return filepath ``` ```python def process_images(paths: list[str]) -> list[str]: result = [] for path in paths: if path.startswith("http://") or path.startswith("https://"): try: local = download_image(path) result.append(local) except Exception as e: logger.error("Failed to download %s: %s", path, e) else: abs_path = os.path.abspath(path) if os.path.exists(abs_path): result.append(abs_path) else: logger.warning("Image file not found: %s", abs_path) return result ``` ### Technical Analysis The downloader fetches arbitrary user-supplied HTTP and HTTPS URLs with `urllib.request.urlopen`. It does not validate the destination hostname or resolved IP address and does not reject loopback, private, link-local, reserved, or cloud metadata addresses. Redirects are ...[truncated 2125 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/linkedin/urls.py:10
Finding

Unvalidated Full URLs Can Navigate the Trusted LinkedIn Tab to External Sites

Content
View full analysis
str: """Build LinkedIn profile URL from a username/slug or return as-is if already a URL.""" if username.startswith("http"): return username return f"https://www.linkedin.com/in/{username}/" def make_company_url(slug: str) -> str: """Build company page URL from slug or return as-is if already a URL.""" if slug.startswith("http"): return slug return f"https://www.linkedin.com/company/{slug}/" ``` ```python def make_post_url(post_url: str) -> str: """Normalize a post URL. Accepts full URL or URN.""" if post_url.startswith("http"): return post_url # Handle urn:li:activity:... format return f"https://www.linkedin.com/feed/update/{post_url}/" ``` ```javascript async function cmdNavigate({ url }) { const tab = await getOrOpenLinkedInTab(); const target = { tabId: tab.id }; await chrome.debugger.attach(target, "1.3"); await chrome.debugger.sendCommand(target, "Page.enable"); const dialogHandler = (source, method) => { if (source.tabId === tab.id && method === "Page.javascriptDialogOpening") { chrome.debugger .sendCommand(target, "Page.handleJavaScriptDialog", { accept: true }) .catch(() => {}); } }; chrome.debugger.onEvent.addListener(dialogHandler); try { await chrome.tabs.update(tab.id, { url }); await waitForTabComplete(tab.id, url, 60000); } finally { chrome.debugger.onEvent.removeListener(dialogHandler); await chrome.debugger.detach(target).catch(() => {}); } return null; } ``` ### Technical Analysis The URL helpers treat any string beginning with `http` as an acceptable full URL. They do not parse the URL, requi ...[truncated 1933 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
SKILL.md:12
Finding

Dependency Installation Uses an Unbounded Version Without Integrity Pinning

Content
View full analysis
=12.0 || uv sync" - "Load extension/ as unpacked Chrome extension via chrome://extensions" ``` ```toml dependencies = [ "websockets>=12.0", ] ``` ### Technical Analysis The installation instruction permits any current or future release satisfying `websockets>=12.0`. No upper bound, exact version, lockfile, or cryptographic package hash is present in the reviewed project. This is not evidence that the named package is malicious. The risk is that the effective dependency can change after the Skill has been audited. A compromised package publisher, package index, mirror, or unsafe future release could introduce code that runs during installation or when the bridge imports the dependency. The shell fallback also changes installation behavior depending on whether the first command succeeds, reducing reproducibility between environments. ### Attack Path 1. A user follows the Skill installation instruction. 2. `pip` or `uv` queries the configured package index. 3. The resolver selects the newest available package satisfying `>=12.0`. 4. If the selected distribution or package-index path has been compromised, attacker-controlled package content is installed. 5. The code executes through installation hooks where applicable or when the application imports `websockets`. 6. The compromised dependency inherits the privileges of the user running the LinkedIn bridge. ### Impact Assessment A compromised dependency could execute arbitrary Python code with the permissions of the installing or running user. Potential consequences include: - Reading local files and environment data. - Accessing browser-automation traffic and LinkedIn page data. - Modifying bridge beh ...[truncated 285 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (41)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The documented purpose is LinkedIn automation, but the skill also describes downloading arbitrary user-supplied image URLs and writing them to a local cache directory. That expands the attack surface into generic remote-content retrieval and local file handling, which can be abused for SSRF-style local network access, malicious content staging, or storage of unsafe files under a trusted skill.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The documented purpose is LinkedIn automation, but the skill also describes downloading arbitrary user-supplied image URLs and writing them to a local cache directory. That expands the attack surface into generic remote-content retrieval and local file handling, which can be abused for SSRF-style local network access, malicious content staging, or storage of unsafe files under a trusted skill.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The header comment claims cookie access is limited to linkedin.com, but the implementation accepts an arbitrary domain parameter. This discrepancy is security-relevant because it conceals a broader capability from reviewers and users, increasing the chance that dangerous cookie access ships unnoticed and is relied upon by a local bridge client.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The WebSocket bridge exposes a generic get_cookies command and forwards the returned cookies back over the socket, while allowing params.domain || "linkedin.com". That means any local process able to talk to ws://localhost:9336 can request authenticated cookies and receive them without user approval, turning the extension into a cookie-exfiltration proxy rather than a narrowly scoped LinkedIn automation tool.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

The extension can return authenticated LinkedIn cookies over the local WebSocket with no user-facing prompt, indication, or approval step. Sensitive credential material is being exposed through automation infrastructure silently, which materially increases the harm if another local process can connect to the bridge or if the companion service is compromised.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

cmdEvaluateViaDebugger runs arbitrary JavaScript expressions in the context of the LinkedIn tab via the Chrome debugger protocol and returns the result to the bridge. This gives any bridge client a powerful read/write capability over authenticated page state, DOM content, and in-page JavaScript objects, which exceeds normal LinkedIn automation needs and can be used to steal data or perform stealthy account actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

Arbitrary debugger-based JavaScript execution occurs without any disclosure or confirmation to the user. Because this capability can inspect private feed content, messages, CSRF tokens, and trigger account actions invisibly, the lack of transparency substantially raises the risk of covert misuse in an authenticated LinkedIn session.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The debugger permission gives powerful visibility and control over browser activity, including the ability to inspect, instrument, and potentially manipulate authenticated LinkedIn sessions. For a LinkedIn automation bridge, this is excessive and materially increases the risk of credential theft, session hijacking, or stealthy browser automation beyond user expectations.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill declares shell, network, and file-write capable behavior but does not define an explicit tool scope or permissions boundary. In an automation skill that can post, message, download remote content, and write into the user's home directory, this omission increases the risk of unintended command execution or privilege creep beyond the user's expectations.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The trigger conditions are broad enough to activate on many normal LinkedIn-related requests without strong exclusion criteria. For a skill that can access a logged-in browser session and perform high-impact actions like posting, messaging, and connecting, overbroad activation raises the chance of unintended execution in the wrong context.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The intent-routing phrases are highly generic ('search', 'browse', 'message', 'analyze') and can match ambiguous user requests. In a skill tied to session-backed social actions, ambiguous routing can escalate harmless information requests into account actions or unnecessary access to LinkedIn content.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
78% confidence
Finding

The skill is explicitly designed to operate through the user's active logged-in LinkedIn session via browser cookies, which creates persistent session-based authority for subsequent actions. Even without exposing raw credentials, this increases the blast radius of accidental or unauthorized commands because the skill can act as the user until the session is cleared or expires.

Content

Scanner excerpt · SKILL.md (reported line 43)May include surrounding context.

md
Route user intent by priority:

1. **Authentication** ("login / check login / log out") → Execute `linkedin-auth` skill.
2. **Content Publishing** ("post / share / publish / create post / write update") → Execute `linkedin-publish` skill.
3. **Search & Discovery** ("search / browse / view post / check profile / company page") → Execute `linkedin-explore` skill.
4. **Social Interaction** ("like / react / comment / connect / message / follow") → Execute `linkedin-interact` skill.
5. **Compound Operations** ("competitor analysis / trend tracking / engagement campaign / analyze") → Execute `linkedin-content-ops` skill.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The document asserts tight network and data-flow boundaries, yet it also advertises lead generation and outreach capabilities that imply broader collection and interaction behavior than the enumerated commands and boundaries support. This kind of inconsistency can hide undeclared data movement or future capability expansion, weakening user trust and making it harder to assess what the skill may actually do with session-derived data.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

cmdNavigate accepts an arbitrary url from the bridge and updates the browser tab to that destination. In an automation extension advertised as LinkedIn-focused, unrestricted navigation broadens the extension into a general browser-driving primitive that can be abused for phishing flows, cross-site automation, or staging other sensitive actions in the same controlled tab.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

cmdSetFileInput lets the bridge specify any CSS selector and local file paths to inject into a page file input through the debugger API. This can silently cause uploads of local files from the host machine during an automated session, which is broader than the stated skill purpose and creates a data-leak path if a local controller is compromised or untrusted.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

Programmatic file-input population can cause local files to be uploaded without any user-facing warning. Even if intended for automation convenience, silent file attachment is a sensitive action because it bridges local filesystem data into a web session and may expose private documents unintentionally.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The remove_element command performs a destructive DOM operation by deleting a selected element from the active page. In this file there is no confirmation prompt, logging, or inline warning describing that page content may be removed, which matches the missing user warning criterion for code files.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The manifest describes a limited 'bridge' function, but the granted permissions enable far broader control over LinkedIn pages, cookies, script injection, alarms, and browser debugging. This mismatch is dangerous because it can conceal the true power of the extension from reviewers and users, making abuse of authenticated LinkedIn sessions easier to hide.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/cli.py (reported line 56)May include surrounding context.

python
kwargs: dict = {}
        if sys.platform == "win32":
            kwargs["creationflags"] = subprocess.CREATE_NEW_CONSOLE
        subprocess.Popen(
            [sys.executable, str(scripts_dir / "bridge_server.py")],
            **kwargs,
        )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/cli.py (reported line 93)May include surrounding context.

python
for cmd in [["open", "-a", "Google Chrome"], ["google-chrome"], ["chromium-browser"]]:
        try:
            subprocess.Popen(cmd)
            return
        except FileNotFoundError:
            continue

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The delete-cookies command performs a logout action immediately with no confirmation, dry-run, or explicit warning at execution time. In an agent-driven automation context, a mistaken or loosely authorized invocation could disrupt the user's active LinkedIn session and create confusing side effects.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The function downloads arbitrary remote content from a user-supplied URL and writes it to disk automatically, with no validation, allowlisting, size checks, or user confirmation. In an automation skill context, this can enable SSRF-style access to internal resources or untrusted content retrieval and persistence, which is more dangerous because the skill is explicitly designed to act on external LinkedIn-related inputs and media URLs.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The file upload helper converts caller-supplied paths to absolute local filesystem paths and transmits them over the browser bridge without any validation, restriction, or confirmation at this layer. In a LinkedIn automation skill, this increases the risk that higher-level prompts or workflow logic could cause unintended exfiltration of sensitive local files to the browser/session for upload.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This method exposes browser cookies for a domain through a simple API call with no authorization checks, minimization, or user notification in this component. In a LinkedIn automation context, cookie access is especially sensitive because session cookies can enable account hijacking, persistent authentication abuse, or downstream exfiltration by other parts of the skill.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This file explicitly implements human-behavior simulation and threshold-avoidance controls for LinkedIn automation, including randomized delays, Gaussian timing, and a rate limiter tuned to stay under LinkedIn safety thresholds. In the context of a LinkedIn automation skill, these features materially increase the capability to evade platform anti-abuse detection and facilitate scalable automated actions that may violate platform rules or be used for spam, scraping, or account abuse.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.