Back to skill

Security audit

Lsp Assist

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent local code-navigation skill, but it deserves review because its recommended daemon is unauthenticated by default and can expose project analysis results to other local processes.

Install only if you are comfortable running a local LSP daemon over your codebase. Use one-shot mode or start daemon mode with a strong token on single-user machines, avoid logging startup output that contains tokens, and prefer pinned/project-local language-server installs over the documented global/latest-version commands.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/lsp_client.py:433
Finding

Optional Authentication Exposes State-Changing Local HTTP Endpoints

Content
View full analysis

Vulnerability Details

File Location: scripts/lsp_client.py:433-440, scripts/lsp_client.py:462-503
Vulnerability Type: Missing authentication for a state-changing service
Risk Level: Medium

Vulnerable Code

python
def _check_auth(self) -> bool:
    """Validate Bearer token if daemon was started with --token."""
    expected = getattr(self.server, "auth_token", None)
    if expected is None:
        return True  # No token required
    header = self.headers.get("Authorization", "")
    if header == f"Bearer {expected}":
        return True
    self._respond({"error": "unauthorized"}, 401)
    return False

The unauthenticated access applies to all POST routes, including the shutdown endpoint:

python
def do_POST(self):
    if not self._check_auth():
        return

    client = self.server.lsp_client  # type: ignore

    if not client.is_alive():
        self._respond({"error": "Language server is not running"}, 503)
        return

    try:
        body = self._read_body()
    except Exception:
        self._respond({"error": "invalid JSON body"}, 400)
        return

    try:
        if self.path == "/goto":
            result = client.query_goto(body["file"], body["line"], body["col"])
            self._respond({"result": result})
        elif self.path == "/refs":
            result = client.query_refs(body["file"], body["line"], body["col"])
            self._respond({"result": result})
        elif self.path == "/hover":
            result = client.query_hover(body["file"], body["line"], body["col"])
            self._respond({"result": result})
        elif self.path == "/diag":
            result = client.query_diag(body["file"])
            self._respond({"result": result})
        elif self.path == "/symbols":
            query = body.get("query", "")
            result = client.query_symbols(query)
            self._respond({"r
...[truncated 2552 chars]
Remediation
View remediation

Remediation Suggestions

  • Require authentication by default instead of treating a missing token as authorization.
  • Generate a cryptographically random token at startup when the operator does not supply one.
  • Reject empty or trivially weak tokens.
  • Compare bearer tokens with hmac.compare_digest() to avoid ordinary string-comparison timing differences.
  • Apply stricter authorization to /shutdown, such as a separate administrative token or disabling remote shutdown by default.
  • Validate Host and, where applicable, Origin headers to reduce localhost request abuse.
  • Consider replacing TCP with a Unix-domain socket protected by filesystem permissions on supported platforms.
  • Add request rate limits and maximum processing durations to reduce resource-exhaustion attacks.
  • Document that unauthenticated mode is unsafe on shared systems rather than presenting it as the ordinary default.

T08 · Insecure Dependencies

Note
Location
SKILL.md:27
Finding

Unpinned Third-Party Language Server Installation

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:27-31, SKILL.md:157-161
Vulnerability Type: Uncontrolled third-party dependency versions
Risk Level: Low

Vulnerable Code

markdown
- **Language servers** (install separately):
  - TypeScript: `npm i -g typescript-language-server typescript`
  - Python: `pip install pyright`

The same unpinned commands are repeated in the supported-language table:

markdown
| Language | Server | Install |
|----------|--------|---------|
| TypeScript/JavaScript | typescript-language-server | `npm i -g typescript-language-server typescript` |
| Python | pyright | `pip install pyright` |

Technical Analysis

These commands resolve mutable latest versions from external package registries. No reviewed versions, hashes, lockfiles, or trusted registry configuration are supplied. Consequently, two users following the same instructions at different times may install materially different code.

The documented package names correspond to the declared language servers, and the audit found no evidence of typosquatting or intentionally malicious package selection. The risk arises from supply-chain drift and unrestricted future releases. The global npm installation also broadens the installation's system-level footprint within the user's writable npm environment.

Package installation and subsequent language-server execution run third-party code with the privileges of the invoking user. A compromised upstream release, registry account, dependency, or package-resolution path could therefore result in code execution.

Attack Path

  1. A user follows the installation instructions in SKILL.md.
  2. npm or pip queries its configured external package registry without a version constraint.
  3. The registry resolves the current package release and its dependency graph.
  4. If a resolved package or transitive dependency has been compromised, its installation behavior ...[truncated 817 chars]
Remediation
View remediation

Remediation Suggestions

  • Pin each language server and its required companion packages to reviewed, exact versions.
  • Provide a lockfile or equivalent reproducible dependency manifest.
  • Where supported, verify downloaded artifacts using trusted hashes or signatures.
  • Install Python dependencies inside a dedicated virtual environment rather than the user's global environment.
  • Avoid global npm installation; use a project-local installation and invoke the pinned executable through the package manager.
  • Document the expected registry source and recommend trusted registry configuration.
  • Establish a dependency-update process that reviews release changes and security advisories before changing pinned versions.
  • Consider publishing a verified container or similarly isolated runtime for the language servers.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/lsp_client.py:527
Finding

Bearer Authentication Token Disclosed Through Standard Error Output

Content
View full analysis

Vulnerability Details

File Location: scripts/lsp_client.py:527-529
Vulnerability Type: Plaintext sensitive data exposure
Risk Level: Low

Vulnerable Code

python
if token:
    print(f"  Auth: Bearer token required", file=sys.stderr)
    print(f'  Example: curl -H "Authorization: Bearer {token}" http://127.0.0.1:{port}/ping', file=sys.stderr)

Technical Analysis

When token authentication is enabled, the complete bearer token is interpolated into an example command and written to standard error. Standard error is commonly retained by terminal recording, CI systems, shell wrappers, process supervisors, container logging drivers, and centralized logging services.

A bearer token provides access solely through possession; no secondary proof of identity is required. Anyone who can read a retained copy can reuse the token while the daemon remains active. Printing it therefore expands the secret's exposure from the daemon's startup configuration to every system that captures its logs.

Attack Path

  1. An operator starts the daemon with --token <secret>.
  2. Startup output is captured by a service manager, CI job, terminal recorder, container platform, or redirected stderr file.
  3. The full token appears in the captured curl example.
  4. Another principal obtains read access to that output.
  5. The principal sends requests with Authorization: Bearer <secret> to the daemon.
  6. _check_auth() accepts the copied token.
  7. The principal can invoke all exposed endpoints, including /shutdown.

Impact Assessment

Successful token reuse grants the attacker the same HTTP API access as the authorized operator. This includes project-related LSP queries and process termination through /shutdown.

The token does not independently confer elevated operating-system privileges, and the service remains bound to localhost. Exploitation therefore requires both access to the leaked output ...[truncated 110 chars]

Remediation
View remediation

Remediation Suggestions

  • Never include the bearer token value in startup output, examples, errors, or logs.
  • Replace the current example with a placeholder such as Authorization: Bearer <redacted>.
  • Prefer reading the token from a permission-restricted file descriptor, protected file, or secret manager rather than a command-line argument.
  • Review logging pipelines for previously captured tokens and remove or restrict affected records.
  • Rotate any token that may already have been logged.
  • Use short-lived, randomly generated tokens and invalidate them when the daemon stops.
  • Add automated tests that assert startup and error output never contains the configured secret.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (15)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/lsp_client.py daemon --lang typescript --root /path/to/project --port 9876

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/lsp_client.py daemon --lang typescript --root /path/to/project --port 9876

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/lsp_client.py daemon --lang typescript --root /path/to/project --port 9876

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/lsp_client.py daemon --lang typescript --root /path/to/project --port 9876

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/lsp_client.py daemon --lang typescript --root /path/to/project --port 9876

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/lsp_client.py daemon --lang typescript --root /path/to/project --port 9876

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/lsp_client.py daemon --lang typescript --root /path/to/project --port 9876

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/lsp_client.py daemon --lang typescript --root /path/to/project --port 9876

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill exposes capabilities that imply file access, shell execution, and local network use, but it does not declare an explicit tool scope such as permissions or allowed-tools. That omission weakens governance and increases the chance that an agent will invoke broader capabilities than intended, especially because the skill starts a daemon and shells out to language servers.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

md
python3 scripts/lsp_client.py daemon --lang typescript --root /path/to/project --port 9876 --token mysecret

# Query via HTTP (all output JSON)
curl -s http://127.0.0.1:9876/goto   -d '{"file":"src/main.ts","line":10,"col":5}'
curl -s http://127.0.0.1:9876/refs   -d '{"file":"src/main.ts","line":10,"col":5}'
curl -s http://127.0.0.1:9876/hover  -d '{"file":"src/main.ts","line":10,"col":5}'
curl -s http://127.0.0.1:9876/diag   -d '{"file":"src/main.ts"}'

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/lsp_client.py (reported line 20)May include surrounding context.

python
Usage (daemon):
  lsp_client.py daemon --lang ts --root <dir> [--port 9876]
  curl -s http://127.0.0.1:9876/goto    -d '{"file":"src/main.ts","line":10,"col":5}'
  curl -s http://127.0.0.1:9876/refs    -d '{"file":"src/main.ts","line":10,"col":5}'
  curl -s http://127.0.0.1:9876/hover   -d '{"file":"src/main.ts","line":10,"col":5}'
  curl -s http://127.0.0.1:9876/diag    -d '{"file":"src/main.ts"}'

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/lsp_client.py (reported line 92)May include surrounding context.

python
def start(self):
        """Start language server and keep it running (daemon mode)."""
        self.process = subprocess.Popen(
            self.server_cmd,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,

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/lsp_client.py (reported line 132)May include surrounding context.

python
def run_query(self, query_fn):
        """One-shot: start server, run query, shutdown."""
        try:
            self.process = subprocess.Popen(
                self.server_cmd,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The /shutdown endpoint allows any caller that can reach the daemon to terminate the entire process via os._exit(0), causing an immediate denial of service. Because authentication is optional and the service is meant for agent workflows, this makes disruption trivial if another local process or an authorized-but-untrusted client can access the port.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/lsp_client.py (reported line 529)May include surrounding context.

python
print(f"LSP daemon ready on http://127.0.0.1:{port}", file=sys.stderr)
    if token:
        print(f"  Auth: Bearer token required", file=sys.stderr)
        print(f'  Example: curl -H "Authorization: Bearer {token}" http://127.0.0.1:{port}/ping', file=sys.stderr)
    print('  POST /goto    -d \'{"file":"...","line":N,"col":N}\'', file=sys.stderr)
    print('  POST /refs    -d \'{"file":"...","line":N,"col":N}\'', file=sys.stderr)
    print('  POST /hover   -d \'{"file":"...","line":N,"col":N}\'', file=sys.stderr)