T09 · Insecure Skill Coding Practices
- Location
scripts/click.sh:15- Finding
Arbitrary Local Python Code Execution Through Unquoted Heredoc Argument Interpolation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/click.sh:15-24
Additional Affected Locations:scripts/click_coords.sh:22-31,scripts/eval_js.sh:13-22,scripts/get_content.sh:21-36,scripts/hover.sh:10-19,scripts/navigate.sh:12-22,scripts/press_key.sh:16-25,scripts/screenshot.sh:20-30,scripts/scroll.sh:12-35,scripts/select.sh:12-23,scripts/start_session.sh:25-55,scripts/type.sh:12-21, andscripts/wait_for.sh:12-21
Vulnerability Type: Python source-code injection
Risk Level: HighVulnerable Code
A representative vulnerable implementation is:
bash python3 - <<PYEOF import os, sys HELPER = "$SCRIPT_DIR/_connect.py" exec(open(HELPER).read()) try: page.click("""$SELECTOR""") print(f"Clicked: $SELECTOR") finally: cleanup() PYEOFThe same unsafe pattern is used for other caller-controlled values. Representative examples include:
bash page.goto("$URL", wait_until="$WAIT_UNTIL")bash result = page.evaluate("""$JS""")bash page.fill("""$SELECTOR""", """$TEXT""")bash page.screenshot(path="$OUTPUT", full_page=full_page)bash page.wait_for_selector("""$SELECTOR""", timeout=int("$TIMEOUT_MS"))Technical Analysis
The scripts construct a Python program in an unquoted shell heredoc and directly interpolate command-line arguments into Python string literals. Triple-quoted strings do not safely serialize arbitrary input. A value containing a closing triple quote can terminate the intended literal and append new Python statements.
For example, a malicious selector shaped like the following can break out of the generated string:
text x"""); __import__("os").system("id"); #It causes the generated Python source to contain an attacker-controlled statement conceptually equivalent to:
python page.click("""x""") __import__("os").system("id")The inje ...[truncated 2215 chars]
- Remediation
View remediation
Remediation Suggestions
Do not generate Python source code by interpolating shell arguments. Pass each value as a normal process argument and use a single-quoted heredoc so the shell cannot expand its contents:
bash python3 - "$SCRIPT_DIR/_connect.py" "$SELECTOR" <<'PY' import sys helper = sys.argv[1] selector = sys.argv[2] exec(compile(open(helper, encoding="utf-8").read(), helper, "exec")) try: page.click(selector) print(f"Clicked: {selector}") finally: cleanup() PYApply the same design to every affected script:
- Pass selectors, URLs, text, JavaScript, paths, keys, and option values through
sys.argvor a safely encoded data channel such as JSON. - Quote heredoc delimiters as
<<'PY'. - Validate numeric fields in Bash and Python before use.
- Restrict enumerated values such as mouse buttons and page load states to explicit allowlists.
- Validate navigation schemes and screenshot destinations according to the intended trust boundary.
- Add regression tests containing quotes, triple quotes, newlines, backslashes, Unicode, and attempted Python statements.
- Prefer a shared Python command-line program using
argparseover separate shell-generated Python fragments.
- Pass selectors, URLs, text, JavaScript, paths, keys, and option values through
