T09 · Insecure Skill Coding Practices
- Location
action.yml:38- Finding
GitHub Action Inputs Can Be Injected into Shell and Python Code
- Content
View full analysis
/tmp/scar_audit_result.json 2>&1 || true ``` ```yaml run: | FAIL_ON="${{ inputs.fail-on }}" python3 -c " import json, sys d=json.load(open('/tmp/scar_audit_result.json')) levels=['LOW','MEDIUM','HIGH','CRITICAL'] threshold=levels.index('$FAIL_ON') if '$FAIL_ON' in levels else 3 for f in d.get('findings',[]): sev=f.get('severity','') if sev in levels and levels.index(sev) >= threshold: print(f'FAIL: {sev} finding — {f[\"description\"]}') sys.exit(1) print('PASS: No findings at or above $FAIL_ON severity') " ``` ### Technical Analysis GitHub Actions expressions are interpolated into the generated shell script before Bash parses it. Placing `${{ inputs.path }}` inside double quotes does not safely treat it as inert data: shell constructs introduced by the interpolated value, such as command substitution, can become executable syntax. The `fail-on` input is exposed through two unsafe contexts: 1. It is interpolated into a Bash assignment. 2. It is subsequently embedded inside Python source code passed to `python3 -c`. A malicious value containing quote characters and Python syntax can terminate the intended Python string and append attacker-controlled statements. The action does not validate either input before constructing executable shell or Python code. Exploitation requires the attacker to influence action inputs, such as through an unsafe reusable workflow, workflow-dispatch plumbing, or another workflow that derives these values from untrusted metadata. ### Attack Path 1. A workflow invokes the composite action and passes an attacker-influenced value to `path` or `fail-on`. 2. GitHub ...[truncated 1051 chars]- Remediation
View remediation
"$RUNNER_TEMP/scar_audit_result.json" ``` - Read and validate `fail-on` as data: ```yaml env: FAIL_ON: ${{ inputs.fail-on }} run: | python3 - <<'PY' import json import os import sys levels = ["LOW", "MEDIUM", "HIGH", "CRITICAL"] fail_on = os.environ["FAIL_ON"] if fail_on not in levels: raise SystemExit(f"Invalid fail-on value: {fail_on!r}") # Continue using fail_on as a value, not generated source. PY ``` - Validate `path` against the intended workspace root. - Avoid constructing Python programs with shell-expanded values. - Add tests containing quotes, command substitutions, newlines, and shell metacharacters in every action input. ]]>
