Back to skill

Security audit

LYGO Mint Walkthrough

Security checks for vulnerabilities and agentic risk

Overview

This skill is mostly a local hashing walkthrough, but its advertised self-check silently writes persistent local state despite saying writes require explicit consent.

Review before installing. The main workflow appears local and purpose-aligned, but do not run scripts/self_check.py unless you are comfortable with it writing local ledger files and leaving a temporary test file. Avoid minting packs that contain secrets because ledger records store file paths and hashes.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/self_check.py:11
Finding

Self-Check Bypasses the Explicit Consent Boundary and Leaves an Unsafe Temporary File

Content
View full analysis

Vulnerability Details

File Location: scripts/self_check.py:11-16
Vulnerability Type: Consent bypass and unsafe temporary-file lifecycle
Risk Level: Medium

Vulnerable Code

python
with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8") as f:
    f.write("# Test Pack\n\nHello LYGO.\n")
    pack = f.name
minted = m.step_mint(pack, "test.v1", "Test Pack", i_consent=True)
digest = minted.get("record", {}).get("sha256", "")
ver = m.step_verify(pack=pack)

Technical Analysis

The documented command python scripts/self_check.py does not request or accept an explicit consent flag. Nevertheless, the self-check internally calls step_mint() with i_consent=True.

That forced value reaches the write branch in scripts/mint_walkthrough.py:107-120, which appends a record to state/walkthrough_ledger.jsonl and writes state/walkthrough_canonical.json. This behavior conflicts with the stated permission boundary in SKILL.md:20-22 and references/SECURITY.md:4, which says state is written with --i-consent.

The temporary file is also created with delete=False and is never removed. Its resolved absolute path is stored in the persistent ledger through the mint record. This is an unsafe temporary-file lifecycle and produces undeclared filesystem residue.

Attack Path

  1. A user follows the documented self-check command in SKILL.md.
  2. self_check.py creates a temporary Markdown file with delete=False.
  3. The script invokes step_mint() while internally setting i_consent=True, without obtaining explicit user consent.
  4. step_mint() appends the mint record to the persistent ledger and rewrites the canonical database under state/.
  5. The record includes the temporary file's absolute path.
  6. The self-check exits without deleting the temporary file, leaving both persistent state changes and an orphaned file.

No external attacker-controlled content i ...[truncated 842 chars]

Remediation
View remediation

Remediation Suggestions

  1. Change the self-check to call step_mint(..., i_consent=False) when testing canonicalization and hashing without persistent writes.

  2. If testing ledger writes is necessary, add an explicit --i-consent argument to self_check.py and forward the user's choice rather than hardcoding consent.

  3. Isolate write tests by making the state directory configurable and directing test output to a temporary directory.

  4. Use tempfile.TemporaryDirectory() for the test pack, or delete the file in a finally block:

    python
    pack_path = None
    try:
        with tempfile.NamedTemporaryFile(
            "w", suffix=".md", delete=False, encoding="utf-8"
        ) as f:
            f.write("# Test Pack\n\nHello LYGO.\n")
            pack_path = Path(f.name)
    
        minted = m.step_mint(
            str(pack_path),
            "test.v1",
            "Test Pack",
            i_consent=False,
        )
    finally:
        if pack_path is not None:
            pack_path.unlink(missing_ok=True)
    
  5. Add regression tests asserting that a default self-check neither creates or changes files under state/ nor leaves temporary files behind.

  6. Update the documentation if any self-check filesystem mutation is intentionally retained, clearly describing every write before execution.

Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.