Back to skill

Security audit

LYGO LLM Console

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed local LLM console, but its runnable kit needs review because it can execute local commands and the bundled launcher/supply-chain path is not tightly contained.

Install only if you are comfortable running a local web console that can execute commands and Python as your user. Verify the kit hash first, review or fix the BAT launcher so it runs from the verified extracted folder, obtain llama-server.exe only from the official source with your own integrity checks, keep it on loopback, and avoid exposing the console on a LAN unless you understand the token and command-execution risks.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T08 · Insecure Dependencies

Warning
Location
SKILL.md:60
Finding
Operator-Supplied llama-server Executable Is Not Cryptographically Verified<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:60-62` **Vulnerability Type**: Unverified third-party executable dependency **Risk Level**: Medium ### Vulnerable Code Snippet ```markdown Place ggml-org **CPU** `llama-server.exe` from release tag **b10988** (`llama-*-bin-win-cpu-x64.zip`) into `engine/`. Do not use a nested Ollama copy. Run as an **unprivileged** user: `LYGO_LLM_CONSOLE.bat`. Default bind `127.0.0.1:9641`. LAN bind needs `--lan --i-consent`. ``` The same unverified workflow is also documented in `examples/quickstart.md:10-13`: ```markdown Place `llama-server.exe` from ggml-org tag b10988 into `engine/`. Run `LYGO_LLM_CONSOLE.bat` as a normal (non-admin) user. ``` ### Technical Analysis The instructions pin the expected llama.cpp release tag and archive naming convention, but they do not provide an expected SHA-256 digest, publisher signature, or other cryptographic verification procedure for the downloaded archive or executable. The bundled console subsequently launches the operator-supplied `llama-server.exe`. Pinning only a release tag does not establish the integrity or authenticity of the downloaded binary. An attacker could distribute a substituted archive through a counterfeit download page, compromised mirror, poisoned search result, or compromised upstream release channel. Although the documentation requires running as an unprivileged user, this only limits the immediate privilege level. A malicious executable would still run with all permissions available to that user. ### Attack Path 1. The operator follows the documentation and searches for or otherwise obtains the CPU archive associated with tag `b10988`. 2. An attacker causes the operator to receive a modified archive, such as through a counterfeit release page, malicious mirror, compromised download channel, or substituted local file. 3. The operator places the altered `llama-server.exe` in the kit's `engine/` directory. 4. No documented checksum or sig ...[truncated 803 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Publish the exact canonical HTTPS URL for the required release archive. 2. Provide expected SHA-256 values for both: - The downloaded `llama-*-bin-win-cpu-x64.zip` archive. - The extracted `llama-server.exe`. 3. Add a verification script that computes the executable digest and fails closed if it differs from the embedded trusted value. 4. Verify the upstream publisher's digital signature where one is available. 5. Modify the BAT launcher to refuse to execute an absent, unexpected, or unverified engine binary. 6. Document that operators must not obtain the executable through search results, unofficial mirrors, or third-party repackaging sites. 7. Keep the non-administrator execution requirement as defense in depth. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:35
Finding
Installation Command Downloads and Immediately Executes an Unverified npm CLI<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:35-41` **Vulnerability Type**: Unverified package execution through npx **Risk Level**: Low ### Vulnerable Code Snippet ```markdown Pinned install (do not use `@latest`): ```bash npx --yes clawhub@0.23.3 install deepseekoracle/lygo-llm-console python scripts/self_check.py python scripts/verify_kit.py ``` ``` ### Technical Analysis The installation command pins `clawhub` to version `0.23.3`, which prevents automatic migration to a newer version. However, `npx --yes` can retrieve the package from the configured npm registry and execute it immediately. The project does not provide a package integrity digest, lockfile, vendored package, or signature-verification procedure for this CLI dependency. The subsequent `self_check.py` and `verify_kit.py` commands do not validate the CLI that has already executed. `verify_kit.py` only verifies the bundled runtime ZIP. There is no evidence in the reviewed project that `clawhub@0.23.3` is malicious. The issue is that the documented installation process relies on the registry-delivered package without an independent trust check. ### Attack Path 1. An attacker compromises the published `clawhub@0.23.3` package, its registry account, or the package-delivery path. 2. A user follows the documented `npx --yes` installation command. 3. `npx` retrieves and immediately executes the affected package without a separate review or project-provided integrity check. 4. Malicious package lifecycle or CLI code runs with the user's permissions. 5. The later project self-check and ZIP verification do not detect or reverse execution by the compromised CLI. ### Impact Assessment A compromised CLI package could execute arbitrary code with the invoking user's privileges. Potential scope includes: - Reading or modifying user-accessible files. - Altering the installed Skill contents. - Accessing environment variables and user-level configuration. - Making network connections ...[truncated 189 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Document the canonical npm registry and publisher identity for the CLI. 2. Publish and verify the expected npm package integrity value before execution. 3. Prefer a two-stage process: - Download or package the dependency without executing it. - Verify its integrity or signature. - Execute it only after successful verification. 4. Provide a lockfile or a vendored, reproducibly built CLI artifact where practical. 5. Avoid `--yes` for security-sensitive installation workflows when it suppresses an opportunity for operator review. 6. Add guidance for inspecting npm provenance and publisher signatures where supported. 7. Continue pinning the exact CLI version, but treat version pinning as supplementary rather than sufficient supply-chain protection. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (33)

Ae1

High
Category
analysis-evasion
Content
kit_zip: "kit/lygo-llm-console-public.zip"
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
kit_zip: "kit/lygo-llm-console-public.zip"
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Cloud Metadata Access

High
Category
Server-Side Request Forgery
Content
"127.0.0.1",
    "0.0.0.0",
    "::1",
    "169.254.169.254",
    "meta" + "data.google.internal",
)
Confidence
90% confidence
Finding
Code accesses a cloud instance metadata endpoint (e.g. 169.254.169.254). A single request can return temporary IAM credentials, making this a high-value SSRF target for credential theft.

Ae3

High
Category
analysis-evasion
Confidence
90% confidence
Finding
Text artifact contains embedded NUL bytes

Ae3

High
Category
analysis-evasion
Confidence
90% confidence
Finding
Text artifact contains embedded NUL bytes

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
MCP server references in the skill manifest without version pinning are a rug-pull risk.

Ae4

Medium
Category
analysis-evasion
Confidence
80% confidence
Finding
Suspicious Unicode normalization or mixed-script content

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Ae4

Medium
Category
analysis-evasion
Confidence
80% confidence
Finding
Suspicious Unicode normalization or mixed-script content

Ae4

Medium
Category
analysis-evasion
Confidence
80% confidence
Finding
Suspicious Unicode normalization or mixed-script content

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if mmproj and Path(mmproj).is_file() and kind == "chat":
        argv.extend(["--mmproj", str(mmproj)])
    stop_port(port)
    proc = subprocess.Popen(
        argv,
        cwd=str(exe.parent),
        env=_clean_env(),
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Internal Network Request

Medium
Category
Server-Side Request Forgery
Content
def ollama_port_open() -> bool:
    try:
        urllib.request.urlopen("http://127.0.0.1:11434/api/tags", timeout=1)
        return True
    except Exception:
        return False
Confidence
70% confidence
Finding
Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if re.search(r"[|&><`$]", cmd):
            return {"ok": False, "error": "metachar"}
        try:
            p = subprocess.run(
                ["cmd.exe", "/c", cmd],
                shell=False,
                cwd=str(WORKSPACE),
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if _SHELL_DENY.search(code) or gate_prompt(code).get("verdict") == "QUARANTINE":
            return {"ok": False, "error": "p0_blocked"}
        try:
            p = subprocess.run(
                [sys.executable, "-c", code],
                cwd=str(WORKSPACE),
                capture_output=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Internal Network Request

Medium
Category
Server-Side Request Forgery
Content
timeout: float = 600.0,
) -> tuple[int, bytes, str]:
    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        f"http://127.0.0.1:{port}/v1/chat/completions",
        data=data,
        headers={
Confidence
70% confidence
Finding
Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Internal Network Request

Medium
Category
Server-Side Request Forgery
Content
timeout: float = 600.0,
) -> tuple[int, bytes, str]:
    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        f"http://127.0.0.1:{port}/v1/chat/completions",
        data=data,
        headers={
Confidence
70% confidence
Finding
Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Internal Network Request

Medium
Category
Server-Side Request Forgery
Content
timeout: float = 600.0,
) -> tuple[int, bytes, str]:
    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        f"http://127.0.0.1:{port}/v1/chat/completions",
        data=data,
        headers={
Confidence
70% confidence
Finding
Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Ae4

Medium
Category
analysis-evasion
Confidence
80% confidence
Finding
Suspicious Unicode normalization or mixed-script content

Ae4

Medium
Category
analysis-evasion
Confidence
80% confidence
Finding
Suspicious Unicode normalization or mixed-script content

Ae4

Medium
Category
analysis-evasion
Confidence
80% confidence
Finding
Suspicious Unicode normalization or mixed-script content

Ae4

Medium
Category
analysis-evasion
Confidence
80% confidence
Finding
Suspicious Unicode normalization or mixed-script content

Unsafe Defaults

Medium
Category
Tool Misuse
Content
TOKEN = ""
LLAMA_KEY = ""
BIND = "127.0.0.1"
AUTH_REQUIRED = False
MOCK_ONLY = False
BUILD = "v1.1-20260916b"
STATE: dict[str, Any] = {"brain": "missing", "selected": None, "error": None, "scan_n": 0}
Confidence
80% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Content
"This console does not call ollama.exe."
            )
            if use_tools and "status" in user.lower():
                name, result = "kernel_status", __import__("tools").dispatch("kernel_status", {})
                traces.append({"name": name, "result": result})
            if want_stream:
                emit_sse({"type": "token", "delta": assistant, "verdict": gate.get("verdict")})
Confidence
75% confidence
Finding
Dynamic __import__() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
env["PYTHONPATH"] = str(stack_dir)
    env["PYTHONIOENCODING"] = "utf-8"
    try:
        p = subprocess.run(
            [sys.executable, "-c", code],
            cwd=str(stack_dir),
            env=env,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.