Back to skill

Security audit

SMILES-to-Docking Virtual Screening

Security checks for vulnerabilities and agentic risk

Overview

This looks like a real molecular-docking workflow, but crafted ligand names or paths could trigger shell command execution or write files outside the intended output folder.

Install only in an isolated environment and run only on trusted ligand/input filenames and trusted output paths. Avoid shared or sensitive directories, and treat SMILES files and docking-result directories from other people as untrusted until the shell=True calls and filename validation are fixed.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/prepare_ligand.py:30
Finding
Shell Command Injection in Ligand Conversion Fallback<![CDATA[ ## Vulnerability Details **File Location**: `scripts/prepare_ligand.py:15-32` **Vulnerability Type**: OS command injection through shell command construction **Risk Level**: High ### Vulnerable Code ```python def sdf_to_pdbqt(sdf_path: str, out_pdbqt: str) -> bool: """Convert SDF ligand to PDBQT using Meeko with Open Babel fallback.""" try: mol = Chem.SDMolSupplier(str(sdf_path), removeHs=False)[0] if mol is None: print(f" WARNING: Could not read SDF: {sdf_path}") return False preparator = MoleculePreparation() setup_list = preparator.prepare(mol) preparator.write_pdbqt_file(out_pdbqt, setup_list) return True except Exception as e: print(f" WARNING Meeko failed for {Path(sdf_path).name}, fallback to Open Babel: {e}") cmd = f'obabel "{sdf_path}" -O "{out_pdbqt}" --partialcharge gasteiger -h' result = subprocess.run(cmd, shell=True, capture_output=True) return result.returncode == 0 ``` The same unsafe implementation is reproduced in the source listing embedded in `SKILL.md`. ### Technical Analysis The fallback constructs a shell command by interpolating `sdf_path` and `out_pdbqt` into a string and executes it with `shell=True`. Although the values are surrounded by double quotes, embedded quote characters and shell metacharacters are not escaped. Consequently, quoting can be terminated and additional shell syntax introduced. Both values can be influenced by users: - `sdf_path` comes from files under the caller-selected `--sdf_dir`. - `out_pdbqt` is constructed under the caller-selected `--output_dir`. - In the full workflow, SDF filenames can originate from unsanitized ligand names. The vulnerable branch is reached whenever Meeko raises an exception during parsing, preparation, or writing. ### Attack Path 1. An attacker supplies a directory containing an `.sdf` file whose filename includes shell-significant characters, or infl ...[truncated 869 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not invoke Open Babel through a shell. Pass each argument as a distinct list element: ```python result = subprocess.run( [ "obabel", str(sdf_path), "-O", str(out_pdbqt), "--partialcharge", "gasteiger", "-h", ], shell=False, capture_output=True, text=True, check=False, ) ``` Additionally: 1. Validate ligand filenames against a conservative allowlist. 2. Reject path separators, `..`, control characters, quotes, and shell metacharacters in ligand identifiers. 3. Resolve input and output paths and verify that they remain under their approved directories. 4. Verify that the Open Babel executable is an expected regular executable rather than relying blindly on `PATH`. 5. Log conversion failures without exposing unnecessary absolute paths. 6. Update the duplicate source code in `SKILL.md` so users do not copy the vulnerable implementation. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/prepare_protein.py:98
Finding
Shell Command Injection in Receptor Preparation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/prepare_protein.py:82-106` **Vulnerability Type**: OS command injection through shell command construction **Risk Level**: High ### Vulnerable Code ```python def prepare_protein(pdb_path: str, output_dir: str) -> str: """Remove waters and non-standard residues, then convert to PDBQT with Open Babel.""" out_dir = Path(output_dir) out_dir.mkdir(parents=True, exist_ok=True) parser = PDBParser(QUIET=True) structure = parser.get_structure("protein", pdb_path) temp_pdb = out_dir / "protein_no_water.pdb" io = PDBIO() io.set_structure(structure) io.save(str(temp_pdb), NonWaterStandardSelect()) out_pdbqt = out_dir / "protein_prepared.pdbqt" cmd = f'obabel "{temp_pdb}" -O "{out_pdbqt}" -xr -h --partialcharge gasteiger' result = subprocess.run(cmd, shell=True, capture_output=True, text=True) if temp_pdb.exists(): os.remove(temp_pdb) if result.returncode != 0: raise RuntimeError(f"Open Babel failed:\n{result.stderr}") print(f" Protein prepared: {out_pdbqt}") return str(out_pdbqt) ``` The same unsafe implementation is reproduced in `SKILL.md`. ### Technical Analysis The caller-controlled `output_dir` is used to create both `temp_pdb` and `out_pdbqt`. These paths are interpolated into a command string executed with `shell=True`. Double-quoting does not neutralize an embedded quote in a directory component, allowing the shell command structure to be modified. The PDB is parsed before the vulnerable subprocess executes, so exploitation requires a valid or sufficiently parseable input PDB. No privilege escalation is necessary because the command runs directly as the workflow user. ### Attack Path 1. The attacker invokes the receptor-preparation script or full workflow with a crafted `--output_dir`. 2. The supplied protein file is successfully parsed and the temporary PDB is written. 3. The crafted directory name becomes part ...[truncated 619 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Replace string-based shell execution with direct argument invocation: ```python result = subprocess.run( [ "obabel", str(temp_pdb), "-O", str(out_pdbqt), "-xr", "-h", "--partialcharge", "gasteiger", ], shell=False, capture_output=True, text=True, check=False, ) ``` Further hardening should include: 1. Resolve `output_dir` against an explicitly approved output root. 2. Reject paths containing control characters and ensure the resolved path remains within that root. 3. Use a securely created temporary file or private temporary directory rather than a predictable shared filename. 4. Check that output files were created successfully and are regular files. 5. Apply the same correction to the source listing in `SKILL.md`. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/rank_results.py:57
Finding
Shell Command Injection During Top-N Complex Export<![CDATA[ ## Vulnerability Details **File Location**: `scripts/rank_results.py:55-59` **Vulnerability Type**: OS command injection through shell command construction **Risk Level**: High ### Vulnerable Code ```python def merge_complex_obabel(protein_pdb: str, ligand_pdb: str, out_complex: str) -> bool: """Merge protein and ligand PDBs into a complex using Open Babel.""" cmd = f'obabel "{protein_pdb}" "{ligand_pdb}" -O "{out_complex}" --sort' result = subprocess.run(cmd, shell=True, capture_output=True) return result.returncode == 0 ``` The same unsafe implementation is included in `SKILL.md`. ### Technical Analysis All three path values are interpolated into a shell command: - `protein_pdb` is supplied through `--protein_pdb`. - `ligand_pdb` is derived from docking-result directories and files. - `out_complex` includes the caller-selected output directory and a ligand-derived name. A quote in any path can terminate the intended quoted argument and inject shell syntax. The export function calls this helper after locating a parseable Vina log and a docked PDBQT model. ### Attack Path 1. An attacker controls the protein path, ranking output path, or a ligand directory name inside the supplied docking-results directory. 2. The docking directory contains a `vina_log.txt` with a parseable result and a PDBQT file with at least one `MODEL` record. 3. The ligand is selected within the requested Top-N results. 4. The crafted path reaches `merge_complex_obabel`. 5. The path is interpolated into `cmd` and executed with `shell=True`. 6. The system shell executes the injected command under the workflow user's account. ### Impact Assessment An attacker can execute arbitrary commands with the privileges of the user performing ranking. This can compromise local files and docking artifacts, tamper with ranked results, run additional programs, or expose user-accessible data. The vulnerability does not inherently cross operating-system privilege boundarie ...[truncated 8 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Invoke Open Babel directly without a command shell: ```python result = subprocess.run( [ "obabel", str(protein_pdb), str(ligand_pdb), "-O", str(out_complex), "--sort", ], shell=False, capture_output=True, text=True, check=False, ) ``` Also: 1. Validate and canonicalize the protein, ligand, and output paths. 2. Verify ligand directory names against a conservative identifier allowlist. 3. Ensure output paths remain inside the configured ranking directory. 4. Require input files to be regular files and reject unexpected symbolic links where the trust model requires it. 5. Correct the duplicated implementation in `SKILL.md`. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/smiles_to_sdf.py:29
Finding
Unsanitized Ligand Names Allow Path Traversal and Arbitrary File Placement<![CDATA[ ## Vulnerability Details **File Location**: `scripts/smiles_to_sdf.py:29-33` and `scripts/smiles_to_sdf.py:74-77` **Vulnerability Type**: Path traversal and unsafe filename construction **Risk Level**: Medium ### Vulnerable Code ```python parts = line.split("\t") smiles = parts[0].strip() name = parts[1].strip() if len(parts) > 1 else f"lig_{lineno:04d}" entries.append((smiles, name)) ``` The unvalidated name is subsequently used as a path component: ```python for i, (smiles, name) in enumerate(entries, 1): sdf_path = ligands_dir / f"{name}.sdf" if sdf_path.exists(): print(f" SKIP [{i}/{len(entries)}] {name} already exists") ``` When molecule generation succeeds, `SDWriter(str(sdf_path))` writes to that path. ### Technical Analysis The ligand name is accepted verbatim from the tab-separated input file. There is no rejection of: - Absolute paths - `..` traversal components - Directory separators - Quote characters - Shell metacharacters - Control characters - Duplicate or reserved names With `pathlib`, joining a base path to an absolute child discards the base path. Relative traversal components can also escape `ligands_dir`. As a result, generated SDF data can be placed outside the intended workflow directory. Hostile names may subsequently become filenames consumed by the shell-injectable ligand fallback. ### Attack Path 1. An attacker supplies a SMILES input containing a valid SMILES value and a ligand name with an absolute path or traversal components. 2. The SMILES is successfully converted into a molecule. 3. The untrusted name is appended with `.sdf` and combined with `ligands_dir`. 4. Path resolution targets a location outside the intended ligand directory. 5. `SDWriter` creates or overwrites the targeted `.sdf` file if the workflow user has permission. 6. If the hostile filename is later processed by `prepare_ligand.py`, shell-significant characters can also contribute to command injection when the Meeko fallback run ...[truncated 459 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Treat the human-readable ligand name as metadata, not as a filesystem path. Generate a safe identifier separately: ```python import re def safe_ligand_id(name: str, lineno: int) -> str: candidate = re.sub(r"[^A-Za-z0-9_.-]+", "_", name).strip("._") return candidate or f"lig_{lineno:04d}" ``` Before writing: 1. Reject absolute names, path separators, `..`, control characters, and reserved names. 2. Resolve the candidate output path: ```python target = (ligands_dir / f"{safe_name}.sdf").resolve() root = ligands_dir.resolve() if target.parent != root: raise ValueError("Invalid ligand name") ``` 3. Detect duplicate sanitized names and assign stable unique identifiers. 4. Keep the original ligand name only inside SDF metadata or a mapping file. 5. Apply equivalent validation wherever ligand or docking directory names are consumed. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:30
Finding
Unpinned Third-Party Dependencies Create Supply-Chain and Reproducibility Risk<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:30-32` **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Low ### Vulnerable Code ```bash pip install "numpy<2" pip install rdkit-pypi meeko biopython conda install -c conda-forge openbabel autodock-vina ``` ### Technical Analysis The documented installation procedure resolves most dependencies without exact versions, hashes, or a lockfile. Even `numpy<2` permits installation of any matching future or historical release selected by the resolver. The named packages and channels are consistent with the declared molecular-docking functionality, and the audit found no suspicious package URL, dependency-confusion namespace, or remote payload retrieval. Nevertheless, unconstrained installation makes builds non-reproducible and leaves them exposed to compromised upstream releases, unexpected compatibility changes, or transitive dependency changes. ### Attack Path 1. A user follows the installation instructions at a later date. 2. The package manager resolves the unpinned requirements against the configured repositories. 3. A changed, compromised, or incompatible direct or transitive package version is selected. 4. Installation scripts or imported package code execute in the user's environment. 5. The environment may be compromised or the workflow may produce unreliable results. This path depends on an upstream or repository compromise, resolver behavior, or an unsafe user-configured package source; no malicious dependency is directly demonstrated in the audited project. ### Impact Assessment Potential impact ranges from build failure and scientifically inconsistent results to arbitrary code execution if a selected upstream package is malicious. Package installation and package code run with the permissions of the installing or workflow user. The current evidence supports a supply-chain hardening concern rather than a confirmed malicious package. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct dependency to a reviewed exact version. 2. Record transitive dependencies in a lockfile or reproducible Conda environment file. 3. For pip-based installations, use hashes and enforce them with `--require-hashes`. 4. Pin Conda channel priority and document the trusted channels. 5. Test dependency updates in an isolated environment before changing pins. 6. Publish supported Python and operating-system versions. 7. Avoid installing as root; use a dedicated virtual environment or container with minimal permissions. 8. Periodically scan the locked dependency set for known vulnerabilities. ]]>
Vulnerability Patterns
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (19)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
except Exception as e:
        print(f"Meeko failed for {Path(sdf_path).name}, fallback to Open Babel: {e}")
        cmd = f'obabel "{sdf_path}" -O "{out_pdbqt}" --partialcharge gasteiger -h'
        result = subprocess.run(cmd, shell=True, capture_output=True)
        return result.returncode == 0
Confidence
98% confidence
Finding
The code builds a shell command with user-influenced file paths and executes it with shell=True. Although paths are wrapped in double quotes, shell metacharacters such as embedded quotes or command substitutions can still break out of quoting and trigger arbitrary command execution if an attacker controls filenames or paths.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
out_pdbqt = out_dir / "protein_prepared.pdbqt"
    cmd = f'obabel "{temp_pdb}" -O "{out_pdbqt}" -xr -h --partialcharge gasteiger'
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)

    if temp_pdb.exists():
        os.remove(temp_pdb)
Confidence
98% confidence
Finding
This function constructs an Open Babel command string from filesystem paths and runs it through the shell. If a supplied protein path or derived temporary/output path contains shell-special content, an attacker may achieve command injection and execute arbitrary OS commands with the privileges of the user running the workflow.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
def merge_complex_obabel(protein_pdb: str, ligand_pdb: str, out_complex: str) -> bool:
    cmd = f'obabel "{protein_pdb}" "{ligand_pdb}" -O "{out_complex}" --sort'
    result = subprocess.run(cmd, shell=True, capture_output=True)
    return result.returncode == 0
Confidence
98% confidence
Finding
The merge helper interpolates protein, ligand, and output paths into a shell command and executes it with shell=True. Because these values can be influenced by upstream filenames and user arguments, a crafted path containing shell syntax could escape quoting and run arbitrary commands during result export.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
except Exception as e:
        print(f"  WARNING Meeko failed for {Path(sdf_path).name}, fallback to Open Babel: {e}")
        cmd = f'obabel "{sdf_path}" -O "{out_pdbqt}" --partialcharge gasteiger -h'
        result = subprocess.run(cmd, shell=True, capture_output=True)
        return result.returncode == 0
Confidence
98% confidence
Finding
Using subprocess.run with shell=True on a command string derived from filesystem inputs is a real parameter-abuse issue because untrusted filenames can alter how the shell interprets the command. In this batch-processing context, ligand files may originate from external datasets, which makes the fallback conversion path more dangerous if an attacker can introduce crafted filenames.

Self-Modification

High
Category
Rogue Agent
Content
)
    parser.add_argument("--sdf_dir", required=True, help="Input SDF directory")
    parser.add_argument("--output_dir", required=True, help="Output PDBQT directory")
    parser.add_argument("--force", action="store_true", help="Overwrite existing files")
    args = parser.parse_args()

    sdf_dir = Path(args.sdf_dir)
Confidence
85% confidence
Finding
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
out_pdbqt = out_dir / "protein_prepared.pdbqt"
    cmd = f'obabel "{temp_pdb}" -O "{out_pdbqt}" -xr -h --partialcharge gasteiger'
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    if temp_pdb.exists():
        os.remove(temp_pdb)
Confidence
98% confidence
Finding
This is a true tool-parameter abuse issue because the code forwards attacker-influenced path values into a shell command that invokes Open Babel. In an agent or automation context where file names or output directories may come from upstream tasks or user input, this materially increases the risk of arbitrary command execution rather than mere misuse of the external tool.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
def merge_complex_obabel(protein_pdb: str, ligand_pdb: str, out_complex: str) -> bool:
    """Merge protein and ligand PDBs into a complex using Open Babel."""
    cmd = f'obabel "{protein_pdb}" "{ligand_pdb}" -O "{out_complex}" --sort'
    result = subprocess.run(cmd, shell=True, capture_output=True)
    return result.returncode == 0
Confidence
99% confidence
Finding
This is a true tool-parameter-abuse issue because attacker-influenced values are passed into an Open Babel invocation through the shell. Although the paths are wrapped in double quotes, shell expansion still occurs inside double quotes for constructs like $(...), so crafted filenames or CLI-supplied paths can trigger arbitrary command execution.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
]
    for candidate in candidates:
        if candidate and (os.path.isfile(candidate) or candidate == "vina"):
            result = subprocess.run(
                shlex.split(f"{candidate} --version") if candidate != "vina" else ["vina", "--version"],
                capture_output=True, text=True,
            )
Confidence
90% confidence
Finding
The script executes a user-supplied `vina_path` after tokenizing it with `shlex.split`, allowing the caller to append arbitrary extra arguments when the script probes `--version`. For example, a crafted `vina_path` containing additional flags can alter what binary is run or how it behaves, and because executable trust is based on successful execution, an attacker who controls that path can cause execution of an unintended program.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if result.returncode == 0:
                print(f"  Found Vina: {candidate}")
                return candidate
    result = subprocess.run(["which", "vina"], capture_output=True, text=True)
    return result.stdout.strip() if result.returncode == 0 else ""
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
log_path = ligand_out_dir / "vina_log.txt"
    try:
        result = subprocess.run(
            cmd, capture_output=True, text=True,
            cwd=str(ligand_out_dir), timeout=600,
        )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def step1_smiles_to_sdf(smiles_file: str, output_dir: str) -> str:
    script = SCRIPT_DIR / "smiles_to_sdf.py"
    subprocess.run([sys.executable, str(script),
                    "--smiles_file", smiles_file,
                    "--output_dir", output_dir], check=True)
    return output_dir
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def step2_prepare(sdf_dir: str, protein_pdb: str, output_dir: str) -> tuple[str, str]:
    lig_script = SCRIPT_DIR / "prepare_ligand.py"
    lig_out = os.path.join(output_dir, "ligands")
    subprocess.run([sys.executable, str(lig_script),
                    "--sdf_dir", sdf_dir,
                    "--output_dir", lig_out], check=True)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"--output_dir", lig_out], check=True)

    prot_script = SCRIPT_DIR / "prepare_protein.py"
    subprocess.run([sys.executable, str(prot_script),
                    "--protein", protein_pdb,
                    "--output_dir", output_dir], check=True)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
]
    if vina_path:
        cmd += ["--vina_path", vina_path]
    subprocess.run(cmd, check=True)
    return output_dir
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def step4_rank(docking_dir: str, protein_pdb: str, top_n: int, output_dir: str) -> str:
    script = SCRIPT_DIR / "rank_results.py"
    subprocess.run([sys.executable, str(script),
                    "--docking_dir", docking_dir,
                    "--protein_pdb", protein_pdb,
                    "--top_n", str(top_n),
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
except Exception as e:
        print(f"  WARNING Meeko failed for {Path(sdf_path).name}, fallback to Open Babel: {e}")
        cmd = f'obabel "{sdf_path}" -O "{out_pdbqt}" --partialcharge gasteiger -h'
        result = subprocess.run(cmd, shell=True, capture_output=True)
        return result.returncode == 0
Confidence
97% confidence
Finding
The fallback path builds a shell command by interpolating file paths into a string and executes it with shell=True. If an attacker can control filenames or output paths, shell metacharacters inside those values can trigger command injection despite the surrounding quotes, especially because shell parsing still honors constructs like embedded quotes or command substitutions.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
out_pdbqt = out_dir / "protein_prepared.pdbqt"
    cmd = f'obabel "{temp_pdb}" -O "{out_pdbqt}" -xr -h --partialcharge gasteiger'
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    if temp_pdb.exists():
        os.remove(temp_pdb)
Confidence
97% confidence
Finding
The script constructs a shell command from file paths and executes it with shell=True, which enables shell interpretation of special characters embedded in user-controlled inputs such as --output_dir or derived file names. An attacker who can supply crafted arguments could achieve command injection and run arbitrary OS commands with the privileges of the script.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def merge_complex_obabel(protein_pdb: str, ligand_pdb: str, out_complex: str) -> bool:
    """Merge protein and ligand PDBs into a complex using Open Babel."""
    cmd = f'obabel "{protein_pdb}" "{ligand_pdb}" -O "{out_complex}" --sort'
    result = subprocess.run(cmd, shell=True, capture_output=True)
    return result.returncode == 0
Confidence
98% confidence
Finding
The code builds a shell command by interpolating untrusted file path inputs into a single string and executes it with shell=True. An attacker who controls --protein_pdb, ligand-derived filenames, or output paths could inject shell metacharacters and execute arbitrary OS commands, leading to full command execution in the context of the script.

Missing User Warnings

Low
Confidence
87% confidence
Finding
This markdown file documents how to run an end-to-end workflow that creates many output directories and files, and the embedded code includes overwrite behavior such as `--force` for ligand preparation and direct writes of summaries, configs, and exported structures. The README-style usage and overview sections do not include any user warning about these persistent filesystem changes or advising users to choose an isolated output directory.

Static analysis

No suspicious patterns detected.