T09 · Insecure Skill Coding Practices
- Location
scripts/check-ci-failures.sh:11- Finding
Arbitrary Python Code Execution Through CI_STATE_FILE
- Content
View full analysis
Vulnerability Details
File Location:
scripts/check-ci-failures.sh, lines 11, 25-30, and 50-51
Vulnerability Type: Environment-variable-driven Python code injection
Risk Level: HighVulnerable Code
bash STATE_FILE="${CI_STATE_FILE:-$HOME/.openclaw/workspace/memory/ci-check-state.json}" # Initialize state file if missing if [ ! -f "$STATE_FILE" ]; then mkdir -p "$(dirname "$STATE_FILE")" echo '{"lastCheck":"2026-01-01T00:00:00Z"}' > "$STATE_FILE" fi LAST_CHECK=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['lastCheck'])" 2>/dev/null || echo "2026-01-01T00:00:00Z") # Update last check time python3 -c "import json; json.dump({'lastCheck':'$NOW'}, open('$STATE_FILE','w'))" 2>/dev/nullTechnical Analysis
CI_STATE_FILEis an environment-controlled value. Although it is quoted for the shell, its expanded value is interpolated directly into Python source code passed topython3 -c.Shell quoting does not make the resulting Python string literal safe. An attacker can include Python quote characters and expressions in the environment variable, causing the value to escape its intended string-literal context. For example, a path expression shaped like:
text '+str(__import__("os").system("ATTACKER_COMMAND"))+'causes
os.system()to be evaluated while Python constructs the argument toopen(). The command executes even if the subsequent file operation fails.The same variable controls the destination used to initialize and update the state file. Without path restrictions, it can also target arbitrary locations writable by the process or interact with attacker-created symbolic links.
Attack Path
- An attacker gains control of the
CI_STATE_FILEenvironment variable through cron configuration, an automation wrapper, a CI environment, or another invocation mechanism. - The attacker supplies a value containing a Python expression that exits the ...[truncated 941 chars]
- An attacker gains control of the
- Remediation
View remediation
Remediation Suggestions
Never place environment-controlled values inside generated Python source. Pass paths and timestamps as positional arguments:
bash LAST_CHECK=$( python3 -c \ 'import json, sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["lastCheck"])' \ "$STATE_FILE" 2>/dev/null || echo "2026-01-01T00:00:00Z" ) python3 -c \ 'import json, sys; json.dump({"lastCheck": sys.argv[2]}, open(sys.argv[1], "w", encoding="utf-8"))' \ "$STATE_FILE" "$NOW"Additional hardening should include:
- Resolve the path to a canonical absolute path and enforce that it remains under an approved state directory.
- Reject symbolic links and non-regular files.
- Create the state directory with restrictive permissions such as
0700. - Create and replace the state file atomically with restrictive permissions such as
0600. - Fail explicitly when the state cannot be read or updated instead of silently suppressing all errors.
- Run the scheduled task using a dedicated least-privileged account.
