T09 · Insecure Skill Coding Practices
- 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: MediumVulnerable 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.pydoes not request or accept an explicit consent flag. Nevertheless, the self-check internally callsstep_mint()withi_consent=True.That forced value reaches the write branch in
scripts/mint_walkthrough.py:107-120, which appends a record tostate/walkthrough_ledger.jsonland writesstate/walkthrough_canonical.json. This behavior conflicts with the stated permission boundary inSKILL.md:20-22andreferences/SECURITY.md:4, which says state is written with--i-consent.The temporary file is also created with
delete=Falseand 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
- A user follows the documented self-check command in
SKILL.md. self_check.pycreates a temporary Markdown file withdelete=False.- The script invokes
step_mint()while internally settingi_consent=True, without obtaining explicit user consent. step_mint()appends the mint record to the persistent ledger and rewrites the canonical database understate/.- The record includes the temporary file's absolute path.
- 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]
- A user follows the documented self-check command in
- Remediation
View remediation
Remediation Suggestions
-
Change the self-check to call
step_mint(..., i_consent=False)when testing canonicalization and hashing without persistent writes. -
If testing ledger writes is necessary, add an explicit
--i-consentargument toself_check.pyand forward the user's choice rather than hardcoding consent. -
Isolate write tests by making the state directory configurable and directing test output to a temporary directory.
-
Use
tempfile.TemporaryDirectory()for the test pack, or delete the file in afinallyblock: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) -
Add regression tests asserting that a default self-check neither creates or changes files under
state/nor leaves temporary files behind. -
Update the documentation if any self-check filesystem mutation is intentionally retained, clearly describing every write before execution.
-
