T09 · Insecure Skill Coding Practices
- Location
scripts/run_heyvideogen.py:25- Finding
Shell Command Injection Through User-Controlled Project Names and Paths
- Content
View full analysis
Vulnerability Details
File Location:
scripts/run_heyvideogen.py, lines 25-38 and 131-138
Vulnerability Type: OS command injection and unrestricted project-path handling
Risk Level: Highpython def cmd(cmd: str, cwd=None, shell=True): print(f"\n>>> {cmd}") result = subprocess.run(cmd, cwd=cwd, shell=shell, capture_output=False) return result.returncode == 0 def run_init(project_name: str): """初始化 HyperFrames 项目""" target = PROJECT_ROOT / project_name if target.exists(): print(f"⚠️ 项目 {project_name} 已存在") return rc = cmd(f"npx hyperframes init {project_name} --non-interactive", cwd=PROJECT_ROOT)The render path contains a second instance of the same unsafe pattern:
python def run_render(project: str): """渲染 HyperFrames 项目""" project_path = PROJECT_ROOT / project output = project_path / "heyvideogen-output" / "final.mp4" print(f"\n🎬 开始渲染:{project_path}") ok = cmd(f"npx hyperframes lint", cwd=project_path) if not ok: print("⚠️ Lint 有警告,继续渲染...") cmd(f"npx hyperframes render --output {output} --fps 30 --quality standard", cwd=project_path) print(f"✅ 渲染完成:{output}")Technical Analysis
The shared
cmd()helper executes command strings withshell=True. Theproject_nameandprojectvalues originate from command-line arguments and are used without shell-safe argument separation or restrictive validation.In
run_init(), the project name is interpolated directly into a shell command. Shell metacharacters in this value are consequently interpreted by the command shell rather than passed only as data to HyperFrames.The code also constructs paths by joining
PROJECT_ROOTwith an unvalidated user value. Absolute paths can override the preceding root, while traversal components can resolve outside the intended workspace. Inrun_render(), this can select an unintended work ...[truncated 1383 chars]- Remediation
View remediation
Remediation Suggestions
- Remove
shell=Trueand represent each command as an argument list:python def cmd(argv, cwd=None): result = subprocess.run(argv, cwd=cwd, shell=False, check=False) return result.returncode == 0 cmd( ["npx", "hyperframes", "init", project_name, "--non-interactive"], cwd=PROJECT_ROOT, ) - Apply a conservative project-name allowlist, such as letters, digits, underscores, and hyphens with a bounded length.
- Resolve every project path before use and verify that it remains beneath the resolved project root:
python root = PROJECT_ROOT.resolve() project_path = (root / project).resolve() if not project_path.is_relative_to(root): raise ValueError("Project path must remain inside the project root") - Reject absolute paths, traversal components, null bytes, and unexpected separators.
- Pass render output paths as separate subprocess arguments rather than embedding them in a command string.
- Use
check=Trueor explicitly stop subsequent processing when initialization or rendering fails.
- Remove
