T09 · Insecure Skill Coding Practices
- Location
run.py:35- Finding
Unrestricted Sub-Skill Path Allows Execution of Unintended Python Handlers
- Content
View full analysis
Vulnerability Details
File Location:
run.py:35-45
Vulnerability Type: Unvalidated path traversal leading to local code execution
Risk Level: Highpython subskill = sys.argv[1] handler = os.path.join(SKILL_ROOT, "sub-skills", subskill, "scripts", "handler.py") if not os.path.isfile(handler): print(f"错误:未找到 subskill '{subskill}',路径不存在:{handler}", file=sys.stderr) sys.exit(1) result = subprocess.run( [sys.executable, handler] + sys.argv[2:], cwd=SKILL_ROOT, )Technical Analysis
The dispatcher directly incorporates the user-controlled
subskillargument into a filesystem path and then executes the resulting file with the current Python interpreter. It only verifies that the resulting path references an existing file; it does not verify that the path remains inside the intendedsub-skillsdirectory or corresponds to one of the six bundled sub-skills.Python's
os.path.join()discards preceding components when a later component is absolute. Relative traversal components such as..can also escape the intended directory after filesystem resolution. Therefore, a crafted value can causehandlerto resolve to an unintended file matching the expectedscripts/handler.pysuffix.The use of an argument array in
subprocess.run()prevents shell metacharacter injection, but it does not prevent the path-selection vulnerability. The selected Python file is executed as code with the same permissions and environment as the dispatcher.Attack Path
- An attacker identifies or creates a writable directory containing
scripts/handler.py, such as/tmp/attacker-skill/scripts/handler.py. - The attacker places arbitrary Python code in that handler.
- The attacker invokes the dispatcher with an absolute path:
bash python run.py /tmp/attacker-skill os.path.join()resolves the handler to:text /tmp/attacker-skill/scripts/handler.py
...[truncated 1023 chars]
- An attacker identifies or creates a writable directory containing
- Remediation
View remediation
Remediation Suggestions
-
Replace dynamic path construction with an explicit allowlist:
python HANDLERS = { "block-trades": "sub-skills/block-trades/scripts/handler.py", "margin-trading-details": "sub-skills/margin-trading-details/scripts/handler.py", "stock-ipos": "sub-skills/stock-ipos/scripts/handler.py", "stock-list-all-stocks": "sub-skills/stock-list-all-stocks/scripts/handler.py", "stock-quotes-list": "sub-skills/stock-quotes-list/scripts/handler.py", "stock-security-info": "sub-skills/stock-security-info/scripts/handler.py", } relative_handler = HANDLERS.get(subskill) if relative_handler is None: print("Error: unsupported sub-skill", file=sys.stderr) sys.exit(1) handler = os.path.join(SKILL_ROOT, relative_handler) -
Apply defense-in-depth containment checks using canonical paths:
python subskills_root = os.path.realpath(os.path.join(SKILL_ROOT, "sub-skills")) handler = os.path.realpath(handler) if os.path.commonpath([subskills_root, handler]) != subskills_root: print("Error: handler path escapes sub-skills directory", file=sys.stderr) sys.exit(1) -
Reject sub-skill values containing absolute paths, path separators, empty components, or
.., even when an allowlist is used. -
Keep the existing argument-array form of
subprocess.run()and do not introduceshell=True. -
Add negative tests covering absolute paths,
../traversal, symlink escapes, unsupported names, and valid bundled sub-skills.
-
