Back to skill

Security audit

Chemistry Query

Security checks for vulnerabilities and agentic risk

Overview

The chemistry skill is mostly purpose-aligned, but it includes an under-disclosed public web UI and shared output-file behavior that users should review before use.

Review this skill before installing or running it. Use the CLI scripts only for intended chemistry lookups, assume compound names or search terms may be sent to PubChem, ChEMBL, or PubMed, avoid running chem_ui.py with public sharing enabled, and do not install or execute opsin.jar unless you verify its source and checksum.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (2)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
chem_ui.py:9
Finding
Unauthenticated Public Gradio Interface Uses a Shared Output File## Vulnerability Details **File Location**: `chem_ui.py`, lines 9–34 **Vulnerability Type**: Unauthenticated public service exposure and unsafe shared-file handling **Risk Level**: High **Vulnerable Code:** ```python def analyze(smiles): # Props proc = subprocess.run(["python3", "rdkit_mol.py", "--smiles", smiles, "--action", "props"], cwd=WORK_DIR, capture_output=True, text=True) props = json.loads(proc.stdout) # Draw PNG subprocess.run(["python3", "rdkit_mol.py", "--smiles", smiles, "--action", "draw", "--output", "mol.png"], cwd=WORK_DIR) img = Image.open(os.path.join(WORK_DIR, "mol.png")) # ADMET proc = subprocess.run(["python3", "admet_predict.py", "--smiles", smiles], cwd=WORK_DIR, capture_output=True, text=True) admet = json.loads(proc.stdout) return props, img, admet iface = gr.Interface( fn=analyze, inputs=gr.Textbox(label="SMILES", value="CCO"), outputs=[gr.JSON(label="Props"), gr.Image(label="2D Viz"), gr.JSON(label="ADMET")], title="Chemistry Query Agent 🧪", description="PubChem/RDKit analysis" ) if __name__ == "__main__": iface.launch(share=True) ``` ### Technical Analysis Calling `iface.launch(share=True)` instructs Gradio to create an externally reachable share tunnel. No authentication, authorization, request-size restriction, or rate limiting is configured. Starting the UI can therefore expose its chemistry-processing functions beyond the local host without an explicit access-control boundary. Each request launches multiple local Python subprocesses and performs RDKit processing on attacker-controlled SMILES input. Consequently, a remote caller can repeatedly consume local CPU, memory, and process resources. The drawing workflow also uses the fixed path `mol.png` for every request. Generation and reading are separate operations, with no locking or request-specific filename. Concurrent requests c ...[truncated 1688 chars]
Remediation
## Remediation Suggestions 1. Disable public tunneling by default: ```python iface.launch(share=False, server_name="127.0.0.1") ``` 2. Require explicit, documented operator opt-in before enabling any public share URL. 3. If remote access is required, add authentication and place the application behind an access-controlled reverse proxy. 4. Add per-client rate limits, request timeouts, concurrency limits, and maximum input-length or molecular-complexity limits. 5. Replace the fixed output path with a unique temporary file for each request: ```python import tempfile from pathlib import Path with tempfile.TemporaryDirectory() as temp_dir: output = Path(temp_dir) / "mol.png" # Generate and read the request-specific image here. ``` 6. Prefer returning an in-memory image directly, avoiding shared filesystem state. 7. Check subprocess return codes before parsing output or opening generated files. 8. Add operational documentation that clearly explains whether the UI is local-only or externally reachable.

T08 · Insecure Dependencies

Warning
Location
scripts/opsin_name_to_smiles.py:12
Finding
Unverified OPSIN JAR Is Executed with Local User Privileges## Vulnerability Details **File Location**: `scripts/opsin_name_to_smiles.py`, lines 12–19 **Vulnerability Type**: Unverified executable dependency **Risk Level**: Medium **Vulnerable Code:** ```python jar_path = os.path.join(os.path.dirname(__file__), "opsin.jar") if not os.path.exists(jar_path): print(json.dumps({"error": "opsin.jar missing—wget https://github.com/dan2097/opsin/releases/download/v2.8.0/opsin-core-2.8.0.jar"}), file=sys.stderr) sys.exit(1) cmd = ["java", "-jar", jar_path, "--stdin", "--output", "smiles"] proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) stdout, stderr = proc.communicate(input=args.name) ``` ### Technical Analysis The helper instructs users to download an executable JAR but does not provide or verify a cryptographic checksum or signature. It subsequently executes any file present at `scripts/opsin.jar` using `java -jar`. The suggested download filename, `opsin-core-2.8.0.jar`, also differs from the expected local filename, `opsin.jar`. This requires an undocumented rename or relocation step and makes provenance harder to enforce. HTTPS protects the download in transit under normal conditions, but it does not provide artifact-level integrity against a compromised release account, replaced upstream artifact, unsafe mirror, accidental substitution, or local tampering after download. Because the file is executable Java bytecode, a malicious replacement can perform any action permitted to the invoking user. ### Attack Path 1. A user runs the helper without `scripts/opsin.jar` installed. 2. The script prints a command directing the user to download the OPSIN release JAR. 3. The user downloads, copies, or renames an artifact to `scripts/opsin.jar` without verifying a checksum or signature. 4. The artifact is malicious, corrupted, substituted, or later replaced by an attacker with write access to that path. 5. The u ...[truncated 1000 chars]
Remediation
## Remediation Suggestions 1. Pin the dependency to an exact official OPSIN release. 2. Publish the expected SHA-256 digest in trusted project metadata. 3. Verify the digest before every execution and fail closed on mismatch. 4. Where available, verify an upstream cryptographic signature in addition to the checksum. 5. Provide an installation script that downloads to a temporary path, verifies integrity, and only then atomically installs the JAR. 6. Ensure the documented download filename and the runtime path are consistent. 7. Prefer a trusted package manager or reproducible dependency-management mechanism over manual download instructions. 8. Restrict write permissions on the installed JAR so untrusted local users or processes cannot replace it. 9. Document the provenance, version, license, checksum, and update procedure for the executable dependency.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (31)

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
Presenting a broad chemistry toolkit while only implementing a narrow hard-coded reaction behavior can create unsafe reliance and conceal actual limitations or side effects. Even if not malicious, inaccurate documentation weakens review, permissioning, and user consent, which are core security properties for agent skills.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Presenting a broad chemistry toolkit while only implementing a narrow hard-coded reaction behavior can create unsafe reliance and conceal actual limitations or side effects. Even if not malicious, inaccurate documentation weakens review, permissioning, and user consent, which are core security properties for agent skills.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
Presenting a broad chemistry toolkit while only implementing a narrow hard-coded reaction behavior can create unsafe reliance and conceal actual limitations or side effects. Even if not malicious, inaccurate documentation weakens review, permissioning, and user consent, which are core security properties for agent skills.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Presenting a broad chemistry toolkit while only implementing a narrow hard-coded reaction behavior can create unsafe reliance and conceal actual limitations or side effects. Even if not malicious, inaccurate documentation weakens review, permissioning, and user consent, which are core security properties for agent skills.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Presenting a broad chemistry toolkit while only implementing a narrow hard-coded reaction behavior can create unsafe reliance and conceal actual limitations or side effects. Even if not malicious, inaccurate documentation weakens review, permissioning, and user consent, which are core security properties for agent skills.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
Presenting a broad chemistry toolkit while only implementing a narrow hard-coded reaction behavior can create unsafe reliance and conceal actual limitations or side effects. Even if not malicious, inaccurate documentation weakens review, permissioning, and user consent, which are core security properties for agent skills.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill advertises and invokes capabilities that imply shell execution, network access, and file writing, but it does not declare any tool scope or permission boundaries. That makes it harder for a host system or reviewer to constrain behavior, increasing the risk of unintended command execution, outbound requests, or disk writes if the skill is invoked automatically.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The trigger language is broad enough to activate on ordinary chemistry conversation, which increases the chance of accidental invocation of network, shell, or file-producing behavior without clear user intent. In an agent environment, overbroad triggers can become a security issue because they expand the situations in which sensitive capabilities are exercised.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def analyze(smiles):
    # Props
    proc = subprocess.run(["python3", "rdkit_mol.py", "--smiles", smiles, "--action", "props"], cwd=WORK_DIR, capture_output=True, text=True)
    props = json.loads(proc.stdout)
    
    # Draw PNG
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
props = json.loads(proc.stdout)
    
    # Draw PNG
    subprocess.run(["python3", "rdkit_mol.py", "--smiles", smiles, "--action", "draw", "--output", "mol.png"], cwd=WORK_DIR)
    img = Image.open(os.path.join(WORK_DIR, "mol.png"))
    
    # ADMET
Confidence
78% confidence
Finding
The code writes a fixed filename (mol.png) in a shared working directory and then immediately opens it, with no per-request isolation or integrity check. In a multi-user Gradio app, concurrent requests can overwrite each other's output or cause one user to receive another user's generated image, creating cross-request data leakage and race-condition issues.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The interface description states only PubChem/RDKit analysis, but the backend also performs ADMET prediction. This hidden behavior expands processing beyond what users are told, which can undermine informed consent, surprise operators, and expose additional model/tooling functionality not reflected in the UI or skill description.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
img = Image.open(os.path.join(WORK_DIR, "mol.png"))
    
    # ADMET
    proc = subprocess.run(["python3", "admet_predict.py", "--smiles", smiles], cwd=WORK_DIR, capture_output=True, text=True)
    admet = json.loads(proc.stdout)
    
    return props, img, admet
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
Launching Gradio with share=True creates a publicly accessible tunnel, exposing the interface beyond the likely local or controlled skill context. That increases the attack surface by allowing untrusted external users to send arbitrary inputs to the subprocess-backed chemistry workflow.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
Public sharing is enabled without any visible disclosure or warning to users, so operators may unintentionally expose the app to the internet. In this skill context, that is more dangerous because the app accepts arbitrary chemistry strings and invokes local analysis scripts, making external abuse and denial-of-service attempts more feasible.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if not smiles:
        if not name:
            raise ValueError('Require "smiles" or "name" in input')
        proc = subprocess.run(
            [sys.executable, os.path.join(script_dir, 'query_pubchem.py'),
             '--compound', name, '--type', 'structure', '--format', 'smiles'],
            cwd=script_dir, capture_output=True, text=True, timeout=30)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
When SMILES is absent, the skill forwards the user-provided compound name to a PubChem lookup subprocess, which likely results in an external network request, but this file provides no user-facing notice or consent. In a chemistry skill, names of target compounds can reveal proprietary R&D interests, internal projects, or regulated substance inquiries, making undisclosed transmission more sensitive than in many general-purpose tools.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The code creates a visualization directory and persists molecule images without any disclosure, opt-in, retention policy, or cleanup. In an agent setting, chemical structures may be proprietary, sensitive, or regulated research data, so silent local persistence increases confidentiality and data-governance risk.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
warnings = []

        # Props
        proc = subprocess.run(
            [sys.executable, os.path.join(script_dir, 'rdkit_mol.py'),
             '--smiles', canonical_smiles, '--action', 'props'],
            cwd=script_dir, capture_output=True, text=True, timeout=30)
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
warnings.append(proc.stderr.strip())

        # Draw
        proc = subprocess.run(
            [sys.executable, os.path.join(script_dir, 'rdkit_mol.py'),
             '--smiles', canonical_smiles, '--action', 'draw', '--output', png_path],
            cwd=script_dir, capture_output=True, text=True, timeout=30)
Confidence
76% confidence
Finding
This call writes a rendered PNG to a path partly derived from user-controlled input, but the filename sanitization only replaces slashes and truncates length. That leaves room for unsafe filenames such as dotfiles, collisions, or special characters that may overwrite existing files in the viz directory or create unintended artifacts, especially in shared agent environments.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
viz_files = [png_path] if os.path.exists(png_path) else []

        # Retro
        proc = subprocess.run(
            [sys.executable, os.path.join(script_dir, 'rdkit_mol.py'),
             '--target', canonical_smiles, '--action', 'retro', '--depth', '2'],
            cwd=script_dir, capture_output=True, text=True, timeout=30)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'assay_url' from requests.get (line 30, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
if args.type == "assay":
        assay_url = f"{base_url}/molecule/{mol_chembl}/assay_count"
        assays = requests.get(assay_url).json()
        print(json.dumps({"chembl_id": mol_chembl, "assay_count": assays.get("assay_count", 0)}))
        
        # Top assays
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'activity_url' from requests.get (line 35, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
# Top assays
        activity_url = f"{base_url}/molecule/{mol_chembl}/activities"
        params = {"limit": 10, "order_by": "-standard_value"}
        resp = requests.get(activity_url, params=params)
        activities = resp.json().get("activities", [])
        formatted = [{"assay": a["assay_chembl_id"], "target": a.get("target_chembl_id"), "type": a["activity_type"], "std_value": a.get("standard_value"), "std_unit": a.get("standard_units")} for a in activities]
        print(json.dumps({"top_assays": formatted}))
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'mech_url' from requests.get (line 42, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
print(json.dumps({"top_assays": formatted}))
    elif args.type == "mechanism":
        mech_url = f"{base_url}/mechanism/{mol_chembl}"
        resp = requests.get(mech_url)
        mechanisms = resp.json().get("mechanisms", [])
        formatted = [{"target": m["target_chembl_id"], "mechanism": m["mechanism_of_action"], "action_type": m["action_type"]} for m in mechanisms]
        print(json.dumps({"mechanisms": formatted}))
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
sys.exit(1)

    cmd = ["java", "-jar", jar_path, "--stdin", "--output", "smiles"]
    proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    stdout, stderr = proc.communicate(input=args.name)
    if proc.returncode != 0:
        print(json.dumps({"error": stderr.strip()}), file=sys.stderr)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
This file adds a PubMed literature-search capability even though the skill metadata describes PubChem and RDKit chemistry functionality, creating a scope mismatch. Scope expansion is security-relevant because it increases the agent's reachable external capabilities and data flows beyond what users and reviewers expect, which can enable unintended behavior or policy bypass in agentic systems.

Static analysis

No suspicious patterns detected.