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. ]]>
