T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/script.sh:16
- Finding
- Arbitrary Python Code Execution Through Unsafe Heredoc Interpolation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/script.sh`, lines 16-27. The same vulnerable pattern also occurs at lines 38-50, 61-73, 96-116, 126-138, 150-162, 176-189, and 202-222. **Vulnerability Type**: Python source-code injection **Risk Level**: High ### Vulnerable Code ```bash cmd_add() { local name="$1"; shift local cuisine="${1:-general}"; shift local time_mins="${1:-30}" if [ -z "$name" ]; then echo "Usage: chefpad add <name> [cuisine] [time_minutes]" return 1 fi python3 << PYEOF import json, time as t recipe = {"id": int(t.time()), "name": "$name", "cuisine": "$cuisine", "time": int("$time_mins"), "ingredients": [], "steps": [], "rating": 0, "created": t.strftime("%Y-%m-%d")} try: with open("$RECIPES_FILE") as f: data = json.load(f) except: data = [] data.append(recipe) with open("$RECIPES_FILE", "w") as f: json.dump(data, f, indent=2) print("Recipe added: {} ({}, {}min)".format("$name", "$cuisine", "$time_mins")) PYEOF } ``` ### Technical Analysis The script constructs Python programs using unquoted heredocs and inserts command-line arguments directly into Python string literals. Values such as `$name`, `$cuisine`, `$ingredient`, `$step`, `$recipe_id`, `$query`, `$rating`, and `$ingredients` are treated as Python source rather than serialized data. Shell quoting used when invoking the CLI does not make these values safe for insertion into Python source. An argument containing quotation marks, backslashes, Python syntax, or embedded newline characters can terminate the intended string or expression and introduce additional Python statements. This pattern appears in the following commands: - `add`: lines 16-27 - `ingredient`: lines 38-50 - `step`: lines 61-73 - `show`: lines 96-116 - `search`: lines 126-138 - `rate`: lines 150-162 - `random`: lines 176-189, through interpolation of the storage path - `suggest`: lines 202-222 The storage path is derived from `$HOME`, so an attacker who ...[truncated 2026 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Stop interpolating input into Python source.** Pass all dynamic values as positional arguments: ```bash python3 - "$name" "$cuisine" "$time_mins" "$RECIPES_FILE" <<'PYEOF' import json import sys import time name, cuisine, time_mins_raw, recipes_file = sys.argv[1:] time_mins = int(time_mins_raw) recipe = { "id": int(time.time()), "name": name, "cuisine": cuisine, "time": time_mins, "ingredients": [], "steps": [], "rating": 0, "created": time.strftime("%Y-%m-%d"), } try: with open(recipes_file, encoding="utf-8") as source: data = json.load(source) except (FileNotFoundError, json.JSONDecodeError): data = [] data.append(recipe) with open(recipes_file, "w", encoding="utf-8") as destination: json.dump(data, destination, indent=2) print("Recipe added: {} ({}, {}min)".format(name, cuisine, time_mins)) PYEOF ``` 2. **Quote every heredoc delimiter** as `<<'PYEOF'`. This prevents shell parameter, command, and arithmetic expansion inside the Python program. 3. **Apply the same correction to every command**, including `ingredient`, `step`, `show`, `search`, `rate`, `random`, and `suggest`. File paths must also be passed as data rather than embedded in Python source. 4. **Validate input explicitly**: - Require recipe IDs to match the intended numeric format. - Parse cooking time as an integer and enforce a reasonable range. - Continue enforcing ratings from 1 through 5. - Reject malformed input with a nonzero exit status and a clear error message. 5. **Avoid broad exception handlers** such as `except:`. Catch specific exceptions so programming errors and maliciously corrupted files are not silently treated as empty databases. 6. **Protect data integrity** by writing to a securely created temporary file in the same directory and atomically replacing the destination after successful serialization. 7. **Add regression tests** for arguments containing single and doub ...[truncated 196 chars]
