T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/generate-tts.sh:19
- Finding
- Arbitrary Python Code Injection Through Shell Argument Interpolation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate-tts.sh`, lines 19–39 **Vulnerability Type**: Python source injection through an unquoted, shell-expanded heredoc **Risk Level**: High ### Vulnerable Code ```bash python3 << PYEOF import asyncio import json import edge_tts with open("$SECTIONS_JSON") as f: segments = json.load(f) voice = "$VOICE" rate = "$RATE" output_dir = "$OUTPUT_DIR" async def generate(): for i, text in enumerate(segments, 1): comm = edge_t_tts.Communicate(text, voice, rate=rate) fname = f"{output_dir}/seg{i:02d}.mp3" await comm.save(fname) print(f"Generated {fname}") asyncio.run(generate()) PYEOF ``` The source file uses `edge_tts.Communicate` rather than `edge_t_tts.Communicate`; the spelling above should be read as the corresponding `edge_tts.Communicate` call shown in the audited file. ### Technical Analysis The heredoc delimiter is unquoted, so Bash performs parameter expansion before passing the generated program to Python. The values of `SECTIONS_JSON`, `VOICE`, `RATE`, and `OUTPUT_DIR` originate from command-line arguments and are inserted directly into Python string literals. Shell quoting around assignments such as `SECTIONS_JSON="${1:?...}"` only protects the shell assignment. It does not make those values safe for insertion into Python source code. An attacker-controlled value containing a quotation mark, newline, and Python statements can terminate the intended string literal and introduce arbitrary Python code. For example, a malicious argument can conceptually transform: ```python voice = "$VOICE" ``` into code shaped like: ```python voice = "" # Attacker-controlled Python statements execute here. x = "" ``` The injected statements execute under the same account and privileges as the user or automation invoking the skill. ### Attack Path 1. An attacker gains control over, or persuades a user or agent to use, one of the script arguments: - Sections ...[truncated 1251 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Do not interpolate shell values into executable Python source. 1. Quote the heredoc delimiter to disable shell expansion: ```bash python3 - "$SECTIONS_JSON" "$OUTPUT_DIR" "$VOICE" "$RATE" <<'PYEOF' import asyncio import json import sys from pathlib import Path import edge_tts sections_json = Path(sys.argv[1]) output_dir = Path(sys.argv[2]) voice = sys.argv[3] rate = sys.argv[4] with sections_json.open(encoding="utf-8") as f: segments = json.load(f) async def generate(): for i, text in enumerate(segments, 1): comm = edge_tts.Communicate(text, voice, rate=rate) fname = output_dir / f"seg{i:02d}.mp3" await comm.save(str(fname)) print(f"Generated {fname}") asyncio.run(generate()) PYEOF ``` 2. Validate `VOICE` against a strict allowlist of supported voice identifiers. 3. Validate `RATE` against the precise syntax and acceptable range expected by `edge-tts`, such as a signed integer percentage with bounded values. 4. Verify that the decoded JSON value is an array and that every element is a string. 5. Resolve and validate input and output paths if the script operates across a trust boundary. 6. Add regression tests using arguments containing quotes, newlines, backslashes, command substitutions, and Python syntax to confirm they remain inert data. ]]>
