T09 · Insecure Skill Coding Practices
- Location
scripts/greptile.sh:92- Finding
Arbitrary Python Code Execution via Unsanitized Repository Parameters
- Content
View full analysis
Vulnerability Details
File Location:
scripts/greptile.sh, line 92
Vulnerability Type: Command injection through dynamically constructed Python source
Risk Level: HighVulnerable Code
bash REPO_ID=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${REMOTE}:${BRANCH}:${REPO}', safe=''))")Technical Analysis
The
statuscommand interpolates the user-controlledREMOTE,BRANCH, andREPOvariables directly into source code supplied topython3 -c.Because these values are placed inside a single-quoted Python string without escaping, an input containing a single quote can terminate the string and append arbitrary Python statements. Shell quoting does not prevent this vulnerability: after shell expansion, the resulting text is passed to Python as executable source code.
The affected values originate from command-line arguments:
REPOis the required repository argument.BRANCHis an optional positional argument.REMOTEcan be supplied through--remote.
For example, a crafted branch can use the following injection structure:
text x',safe=''));__import__('os').system('id');#When incorporated into the
python3 -cexpression, the payload closes the intended function call, invokesos.system, and comments out the remaining source.Attack Path
- An attacker causes the skill to invoke its
statuscommand with a crafted repository, branch, or--remotevalue. parse_repo_branchorparse_flagsstores the malicious input without validation.- Line 92 concatenates that input directly into Python source code.
python3 -cparses and executes the injected Python statements.- The injected code can launch arbitrary operating-system commands with the privileges of the user running the skill.
- Those commands can access local files and environment variables available to the process, including Greptile and repository-access credentials.
A representative invocation structure is:
`` ...[truncated 838 chars]
- Remediation
View remediation
Remediation Suggestions
Do not embed command-line data in Python source. Pass the value as a positional argument:
bash REPO_ID=$(python3 -c \ 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' \ "${REMOTE}:${BRANCH}:${REPO}")Apply additional defense-in-depth measures:
-
Restrict
REMOTEto the explicitly supported values:bash case "$REMOTE" in github|gitlab) ;; *) echo "Error: --remote must be github or gitlab" >&2 exit 1 ;; esac -
Validate repository identifiers against the syntax accepted by the target API.
-
Reject control characters, including newlines and null-like input, in repository and branch values.
-
Make
parse_flagsfail on unsupported flags and on--remotewithout a following value instead of silently ignoring malformed input. -
Add regression tests containing single quotes, double quotes, semicolons, newlines, command substitutions, and Python syntax in every user-controlled field.
-
Run the skill with least-privilege API tokens and operating-system permissions to reduce the consequences of future input-handling defects.
-
