fix: avoid shell=True in run_script.py build helpers - #2240
Conversation
subprocess.run(..., shell=True) spawned a shell for every build/start/test command. In default_extension_cpp the TEN_EXTRA_GN_ARGS environment variable was interpolated directly into that shell string, so its contents were executed as shell code rather than passed as arguments. Pass argument lists and run without a shell instead. TEN_EXTRA_GN_ARGS is split with shlex, and npm is spelled npm.cmd on Windows because CreateProcess does not apply PATHEXT resolution. The .tent templates are updated as well, so newly generated apps and extensions no longer inherit the pattern. Closes TEN-framework#2107
from_scratch.yml patches the generated run_script.py on Windows with `sed s/ten_enable_standalone_test=true/... vs_version=2022/`. Making the argument a bare list element turned that into a single glued argv token `ten_enable_standalone_test=true vs_version=2022` instead of two arguments. Keep the gn args as one string and expand it with shlex.split, so text appended to it in place still becomes separate arguments.
|
Pushed a follow-up commit after finding a regression in my own change.
Because the first commit turned that value into a bare list element, the sed produced one glued argv token Fix: keep the GN args as a single string and expand it with gn_args = "ten_enable_standalone_test=true"
rc = run_cmd([tgn, "gen", os, arch, BUILD_TYPE, "--", *shlex.split(gn_args)])Verified by simulating both CI seds against the template at
sed 2 is Windows-only path syntax and can't resolve on POSIX ( The original 22-check argv suite still passes, including the injection control. Note also that this sed is the workaround the |
Closes #2107
Problem
run_script.pyran every build/start/test command throughsubprocess.run(..., shell=True).For most call sites the commands are hardcoded literals, so this was hygiene rather than an exploitable bug. But in
default_extension_cpptheTEN_EXTRA_GN_ARGSenvironment variable was interpolated directly into the shell string:Anything in that variable was executed as shell code rather than passed as an argument.
Scope
The issue reported one file, but the same pattern existed in seven. Two of them are the
.tenttemplates thattmanuses to scaffold new C++ apps and extensions, so every newly generated project inherited it — fixing only the reported file would have let the pattern keep reproducing.packages/core_apps/default_app_cpp/tools/run_script.py(+.tent)packages/core_extensions/default_extension_cpp/tools/run_script.py(+.tent)packages/core_extensions/default_extension_nodejs/tools/run_script.pypackages/example_apps/transcriber_demo/ten_packages/extension/vtt_nodejs/tools/run_script.pyai_agents/agents/examples/voice-assistant-nodejs/tenapp/ten_packages/extension/main_nodejs/tools/run_script.pyChange
run_cmdnow takes alist[str]and runs without a shell.TEN_EXTRA_GN_ARGSis split withshlex.split, so multi-word and quoted GN args still work while shell metacharacters stay inert.Two cross-platform details preserved:
npm.cmd.CreateProcessdoes not applyPATHEXTresolution, so a barenpmwithshell=Falsewould fail — this keepstman_full_win_x64working.tgn.batvstgnbranching is unchanged.CreateProcessdoes execute.bat/.cmddirectly, so these still run withshell=False.Verification
Rather than only inspecting the diff, I stubbed
npm/tgnand the built binaries ontoPATH, executed each script for real, and asserted the exact argv produced. 22/22 checks pass:npminvocations, bothtgn gen/tgn buildpaths, and thestart/testsubcommands produce the expected argv.TEN_EXTRA_GN_ARGS='vs_version=2022 label="a b"'splits intovs_version=2022andlabel=a b..tenttemplates render ({{package_name}}substituted) and execute correctly.Injection test with
TEN_EXTRA_GN_ARGS='; touch PWNED':maincreated thePWNEDfile, confirming the injection was real.";","touch","PWNED".Formatting checked with
black --line-length 79(the convention 4 of the 5 files already followed). One pre-existing deviation indefault_app_cppsits in an untouched block and was deliberately left alone to avoid unrelated diff noise.Threat model (scoping this honestly)
To avoid overstating severity:
main_nodejs/tools/run_script.py) the three commands are hardcoded literals with no external input flowing in, soshell=Truethere was not exploitable — it is a static-analysis finding, not a live vulnerability.TEN_EXTRA_GN_ARGSis the only genuine injection sink, andgrepshows nothing in this repo ever sets it (it appears only in a TODO comment infrom_scratch.yml). An attacker who can set your build environment variables generally has easier options already.So this PR is best read as hardening and stopping the pattern from propagating through the templates, not as a fix for an actively exploitable bug. No runtime behaviour changes: the argv reaching
npm/tgnis identical, which the argv suite asserts.