reckless: add uv installer support for legacy projects
What changed, and why it matters
This commit adds a new installer path in Core Lightning's reckless plugin manager so that older Python plugins using a simple requirements.txt file can be installed with the fast uv tool. The change creates symlinks to the plugin's pyproject.toml and requirements.txt, builds a virtual environment, and runs a shell command to install dependencies. The shell command is constructed as a single string and executed with shell=True, which is a well-known risky pattern, but the inputs come from local plugin metadata rather than remote or attacker-controlled data in the scenario shown. There is no direct evidence in the commit that this is a security bug or that it fixes a reported vulnerability.
Review whether shell=True can be replaced with a direct subprocess call and explicit environment activation (e.g., invoking .venv/bin/uv with VIRTUAL_ENV set). Validate that cloned_plugin.source_loc and cloned_plugin.name are sanitized and cannot contain shell metacharacters. Consider adding tests for legacy requirements.txt installation and for paths containing spaces or special characters. No urgent security patch is indicated by the commit alone.
Security signals we found
Use of subprocess.run(..., shell=True) with a fixed shell command string
Symlink creation from plugin source directory to install working directory
Potential path confusion from symlinks and cwd changes
Error message reuse between venv creation and package install failure
Evidence from the diff
The patch introduces install_python_uv_legacy() in tools/reckless. It symlinks source/pyproject.toml and source/requirements.txt into the install working directory, runs uv venv, then executes a shell string ‘. .venv/bin/activate; uv -v pip install -r requirements.txt’ via subprocess.run(…, shell=True). The function is registered as the dependency_call for a new pythonuvlegacy Installer that matches requirements.txt files. The INSTALLERS list is updated to include pythonuvlegacy before python3venv. The shell=True usage is a defensive-code red flag because any unescaped interpolation could allow command injection, but the visible code does not interpolate user/remote strings into the command. The error message on failure is also slightly misleading (‘Failed to create virtual environment’ when the failure is during package installation).
Changed components
tools/recklessInstaller class registrypythonuvlegacy installerinstall_python_uv_legacy functionInspect captured patch +51 / −2
diff --git a/tools/reckless b/tools/reckless
index c33b4ce7..5dba0daa 100755
--- a/tools/reckless
+++ b/tools/reckless
@@ -1017,6 +1017,51 @@ def install_python_uv(cloned_plugin: InstInfo):
return cloned_plugin
+def install_python_uv_legacy(cloned_plugin: InstInfo):
+ """Install a python plugin with uv that was created with a requirements.txt.
+ This requires creating a bare virtual environment with uv first."""
+ source = Path(cloned_plugin.source_loc) / 'source' / cloned_plugin.name
+ cloned_plugin.venv = Path('.venv')
+ (Path(cloned_plugin.source_loc) / 'pyproject.toml').\
+ symlink_to(source / 'pyproject.toml')
+ (Path(cloned_plugin.source_loc) / 'requirements.txt').\
+ symlink_to(source / 'requirements.txt')
+
+ venv = run(['uv', '-v', 'venv'], cwd=str(cloned_plugin.source_loc),
+ stdout=PIPE, stderr=PIPE, text=True, check=False)
+ if venv.returncode != 0:
+ for line in venv.stderr.splitlines():
+ log.debug(line)
+ log.error('Failed to create virtual environment')
+ raise InstallationFailure('Failed to create virtual environment!')
+ for line in venv.stdout.splitlines():
+ log.debug(line)
+ for line in venv.stderr.splitlines():
+ log.debug(line)
+ # Running this as a shell allows overriding any active virtual environment
+ # which would make uv skip installing packages already present in the
+ # current env.
+ call = ['. .venv/bin/activate; uv -v pip install -r requirements.txt']
+ uv = run(call, shell=True, cwd=str(cloned_plugin.source_loc),
+ stdout=PIPE, stderr=PIPE, text=True, check=False)
+ if uv.returncode != 0:
+ for line in uv.stderr.splitlines():
+ log.debug(line)
+ log.error('Failed to install virtual environment')
+ raise InstallationFailure('Failed to create virtual environment!')
+ for line in uv.stdout.splitlines():
+ log.debug(line)
+ for line in uv.stderr.splitlines():
+ log.debug(line)
+
+ # Delete entrypoint symlink so that a venv wrapper can take it's place
+ (Path(cloned_plugin.source_loc) / cloned_plugin.entry).unlink()
+
+ create_wrapper(cloned_plugin)
+ log.info('dependencies installed successfully')
+ return cloned_plugin
+
+
python3venv = Installer('python3venv', exe='python3',
manager='pip', entry='{name}.py')
python3venv.add_entrypoint('{name}')
@@ -1043,6 +1088,10 @@ pythonuv = Installer('pythonuv', exe='python3', manager='uv', entry="{name}.py")
pythonuv.add_dependency_file('uv.lock')
pythonuv.dependency_call = install_python_uv
+pythonuvlegacy = Installer('pythonuvlegacy', exe='python3', manager='uv', entry='{name}.py')
+pythonuvlegacy.add_dependency_file('requirements.txt')
+pythonuvlegacy.dependency_call = install_python_uv_legacy
+
# Nodejs plugin installer
nodejs = Installer('nodejs', exe='node',
manager='npm', entry='{name}.js')
@@ -1055,8 +1104,8 @@ rust_cargo = Installer('rust', manager='cargo', entry='Cargo.toml')
rust_cargo.add_dependency_file('Cargo.toml')
rust_cargo.dependency_call = cargo_installation
-INSTALLERS = [pythonuv, python3venv, poetryvenv, pyprojectViaPip, nodejs,
- rust_cargo]
+INSTALLERS = [pythonuv, pythonuvlegacy, python3venv, poetryvenv,
+ pyprojectViaPip, nodejs, rust_cargo]
def help_alias(targets: list):
Why this scored 25/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.