Command execution via child_process.exec

CRITICALSECURITYL1scan5-1f0f611d178642f9clinico/security_lab.py:105

Fix this ->

Evidence - Taint Flow

- SOURCE Untrusted source clinico/security_lab.py:104

104      namespace = scope or {}

- FLOW Taint flow clinico/security_lab.py:105

105  data flows through intermediate assignments before sink

- SINK Sensitive sink clinico/security_lab.py:105

105      exec(code, {}, namespace)

Repair Plan

๐Ÿฆ†

Rubber Duck - Repair Plan

I will replace shell invocation with subprocess.run(shell=False), sanitize input, and add a regression test for clinico/security_lab.py:105. This reduces injection risk without changing the public API.

Patch Preview

+ import { execFile } from "node:child_process";

"""

Fixtures intentionally insecure for scanner validation.

This module is for testing security detection only. Do not import it in

production code paths.

"""

import hashlib

import os

import pickle

import sqlite3

import subprocess

import requests

import yaml

def insecure_shell(command: str) -> str:

os.system(command)

subprocess.run(command, shell=True, check=False)

return command

def insecure_sql_lookup(patient_id: str) -> list[tuple]:

connection = sqlite3.connect("db.sqlite3")

cursor = connection.cursor()

query = f"SELECT * FROM paciente_paciente WHERE nid = '{patient_id}'"

cursor.execute(query)

rows = cursor.fetchall()

connection.close()

return rows

def insecure_deserialization(raw_payload: bytes, yaml_payload: str) -> tuple[object, object]:

loaded_pickle = pickle.loads(raw_payload)

loaded_yaml = yaml.load(yaml_payload, Loader=yaml.Loader)

return loaded_pickle, loaded_yaml

def insecure_request(url: str) -> dict:

response = requests.get(url, verify=False, timeout=5)

return {"status": response.status_code, "body": response.text[:120]}

def insecure_eval(expression: str) -> object:

return eval(expression)

def weak_password_fingerprint(secret: str) -> str:

return hashlib.md5(secret.encode("utf-8")).hexdigest()

def insecure_yaml_only(payload: str) -> object:

return yaml.load(payload, Loader=yaml.Loader)

def insecure_pickle_file(path: str) -> object:

with open(path, "rb") as handle:

return pickle.load(handle)

def swallow_failure(callback):

try:

return callback()

except Exception:

pass

return None

def tls_bypass_json(url: str) -> dict:

response = requests.get(url, verify=False, timeout=3)

return response.json()

def shell_exec_from_user(binary: str, argument: str) -> None:

subprocess.run(f"{binary} {argument}", shell=True, check=False)

def insecure_sql_update(nid: str, phone: str) -> None:

connection = sqlite3.connect("db.sqlite3")

cursor = connection.cursor()

query = f"UPDATE paciendfte_paciente SET telefone = '{phone}' WHERE nid = '{nid}'"

cursor.execute(query)

connection.commit()

connection.close()

def insecure_yaml_config(path: str) -> object:

with open(path, "r", encoding="utf-8") as handle:

return yaml.load(handle.read(), Loader=yaml.Loader)

def weak_signature_token(username: str, role: str) -> str:

raw = f"{username}:{role}:demo-secret"

return hashlib.sha1(raw.encode("utf-8")).hexdigest()

def insecure_download(url: str) -> str:

response = requests.get(url, verify=False, timeout=5)

return response.text

def dynamic_exec(code: str, scope: dict | None = None) -> dict:

namespace = scope or {}

exec(code, {}, namespace)

return namespace

def silent_patient_lookup(patient_id: str):

try:

return insecure_sql_lookup(patient_id)

except Exception:

pass

return []

def hardcoded_admin_credentials() -> dict[str, str]:

return {

"username": "admin",

"password": "Admin123!",

"api_key": "test-key-unsafe-12345",

}

def path_traversal_read(base_dir: str, user_path: str) -> str:

target_path = os.path.join(base_dir, user_path)

with open(target_path, "r", encoding="utf-8") as handle:

return handle.read()

def insecure_tar_extract(archive_path: str, destination: str) -> None:

import tarfile

with tarfile.open(archive_path, "r:*") as archive:

archive.extractall(destination)

def insecure_temp_script(script_name: str, user_input: str) -> str:

script_path = os.path.join("/tmp", script_name)

with open(script_path, "w", encoding="utf-8") as handle:

handle.write(user_input)

os.system(f"python {script_path}")

return script_path

def open_redirect(next_url: str) -> str:

return f"/login?next={next_url}"

def insecure_cookie_value(session_id: str) -> str:

return f"session={session_id}; HttpOnly"

def unsafe_subprocess_env(command: str, secret: str) -> None:

env = os.environ.copy()

env["APP_SECRET"] = secret

subprocess.run(command, shell=True, check=False, env=env)

def insecure_jinja_render(template: str, user_data: dict | None = None) -> str:

from jinja2 import Template

payload = user_data or {}

return Template(template).render(**payload)

def insecure_xml_parse(xml_text: str):

import xml.etree.ElementTree as ET

return ET.fromstring(xml_text)

def unsafe_regex_from_user(pattern: str, text: str):

import re

return re.match(pattern, text)

Change Safety Guardrails

Diff:+1 / -0 (1 lines)Blast:1 file / 27 symbol(s)Risk:21/100 ยท semantic highAPI changes:27 function/class removal(s); 1 removed call edge(s)

Semantic Guardrails

Status:failSemantic risk:highChange count:28API surface:Potential break
~Semantic diff[ast] Function/class 'dynamic_exec' was REMOVED (was at line 103)
~Semantic diff[ast] Function/class 'hardcoded_admin_credentials' was REMOVED (was at line 117)
~Semantic diff[ast] Function/class 'insecure_cookie_value' was REMOVED (was at line 150)
~Semantic diff[ast] Function/class 'insecure_deserialization' was REMOVED (was at line 34)
~Semantic diff[ast] Function/class 'insecure_download' was REMOVED (was at line 98)
~Semantic diff[ast] Function/class 'insecure_eval' was REMOVED (was at line 45)

Verification Results

โœ“L2Patch generatedReplaced exec with execFile in clinico/security_lab.py:103.
!L1Target sink removedUnsafe pattern is still present near the patched target area.
!L2Safe replacement presentExpected safe replacement was not found near the patched target area.
!L3Post-patch syntax checkPython syntax error: invalid syntax at line 1
โœ“L2No obvious unsafe regressionsPatch did not introduce new blocked dangerous patterns.
โœ“L3Post-patch test commandSkipped executable verification in GitHub-only mode for test command: pytest -q
!L2Semantic diff riskSemantic risk high with 28 tracked change(s); allowed up to medium.
!L2Semantic blast radius27 symbol-level change(s), 28 total semantic change(s).
!L1API surface stabilityPotentially breaking semantic changes detected: 27 function/class removal(s), 1 removed call edge(s).

Task status: OPEN