T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/check_releases.sh:36
- Finding
- Arbitrary Python Code Execution Through Unsafe String Interpolation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/check_releases.sh`, lines 36 and 42–46 **Vulnerability Type**: Python code injection **Risk Level**: High ### Vulnerable Code ```bash last_seen=$(python3 -c "import json; d=json.load(open('$STATE_FILE')); print(d.get('$repo',''))" 2>/dev/null) ``` ```bash python3 -c " import json with open('$STATE_FILE','r') as f: d=json.load(f) d['$repo']='$tag' with open('$STATE_FILE','w') as f: json.dump(d,f,indent=2) " ``` ### Technical Analysis The script directly interpolates shell variables into Python source passed to `python3 -c`. The values are placed inside single-quoted Python string literals without escaping or argument binding. The affected values include: - `$tag`, obtained from release metadata returned by GitHub. - `$repo`, obtained from the repository configuration. - `$STATE_FILE`, which can be supplied through an environment variable. An input containing a single quote can terminate the intended Python string literal and append arbitrary Python statements. For example, a release tag shaped like: ```text ';__import__("os").system("id");# ``` would produce Python source equivalent to: ```python d['owner/repository']='';__import__("os").system("id");#' ``` The injected `os.system` call would consequently execute on the local host. Shell quoting around the earlier `gh api` command does not prevent this issue because the injection occurs when dynamically constructing Python code. A malicious release tag is the most direct externally controlled exploitation vector. Repository maintainers or attackers who compromise release-publishing permissions can control tag names returned by the GitHub API. The environment-controlled state-file path is an additional unsafe interpolation boundary. ### Attack Path 1. A victim adds an attacker-controlled repository to `repos.txt`, or already monitors a repository whose release-publishing account is later compromised. 2. The attacker publishes a GitHub ...[truncated 1256 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Do not interpolate untrusted values into Python source code. Pass values as positional arguments and retrieve them through `sys.argv`. For example, the lookup can be implemented as: ```bash last_seen=$( python3 - "$STATE_FILE" "$repo" <<'PY' import json import sys state_file, repo = sys.argv[1], sys.argv[2] with open(state_file, encoding="utf-8") as f: state = json.load(f) print(state.get(repo, "")) PY ) ``` The update can be implemented similarly: ```bash python3 - "$STATE_FILE" "$repo" "$tag" <<'PY' import json import sys state_file, repo, tag = sys.argv[1:4] with open(state_file, encoding="utf-8") as f: state = json.load(f) state[repo] = tag with open(state_file, "w", encoding="utf-8") as f: json.dump(state, f, indent=2) PY ``` Additional hardening should include: 1. Validate repository entries against the expected `owner/repository` format before invoking `gh`. 2. Continue treating all GitHub release fields, including tags, names, and release bodies, as untrusted input. 3. Validate that `STATE_FILE` points to an intended regular file and reject unexpected locations where appropriate. 4. Write state changes to a securely created temporary file and atomically rename it to reduce corruption risks. 5. Run scheduled checks using a dedicated, least-privileged account with minimal filesystem and credential access. 6. Add regression tests containing quotes, semicolons, newlines, and Python syntax in release tags and file paths to verify that values remain data rather than executable code. ]]>
