Back to skill

Security audit

Pharmaclaw Alphafold Agent

Security checks for vulnerabilities and agentic risk

Overview

The skill is framed as a drug-discovery AlphaFold and docking agent, but the implementation uses mock scientific outputs while presenting them as usable structure, pocket, and docking results.

Review this carefully before installing. It may be acceptable only as a demo scaffold, not as a source of scientific evidence. Do not use its reported structures, binding sites, or docking affinities for drug-discovery decisions unless the mock paths are replaced with validated implementations and outputs are clearly labeled with provenance. Run it only in a controlled working directory because it writes predictable output files and performs external HTTP requests.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/alphafold_agent.py:38
Finding
Predictable Output Files Permit Local File Overwrite Through Symbolic Links<![CDATA[ ## Vulnerability Details **File Location**: `scripts/alphafold_agent.py:38-39`, `scripts/alphafold_agent.py:51-52`, and `scripts/alphafold_agent.py:68-70` **Vulnerability Type**: Unsafe predictable output files and symbolic-link following **Risk Level**: Medium ### Vulnerable Code ```python with open(f'{pdb_id}.pdb', 'w') as f: f.write(requests.get(pdb_url).text) ``` ```python with open('esm_fold.pdb', 'w') as f: f.write(pdb_mock) ``` ```python with open('docked.png', 'wb') as f: img = Draw.MolToImage(ligand) img.save(f, format='PNG') ``` ### Technical Analysis The script writes results to predictable filenames in its current working directory. The files are opened in truncating write mode without checking whether the destination already exists, is a symbolic link, or points outside an intended output directory. The fixed names `esm_fold.pdb` and `docked.png` are especially susceptible to pre-creation attacks. The downloaded PDB filename is less predictable but is still derived from a public remote identifier and is opened using the same unsafe pattern. On systems where an attacker can write to the working directory, Python follows a pre-existing symbolic link when these files are opened. The target is then truncated and replaced using the privileges of the process running the Skill. ### Attack Path 1. An attacker obtains write access to the directory from which the Skill will run. 2. The attacker creates a symbolic link such as: - `esm_fold.pdb` pointing to another file writable by the victim process; or - `docked.png` pointing to another file writable by the victim process. 3. The user invokes the Skill: - An unsuccessful RCSB lookup triggers `predict_esmfold()` and writes `esm_fold.pdb`; or - A query containing a SMILES value triggers `dock_ligand()` and writes `docked.png`. 4. The `open(..., 'w')` or `open(..., 'wb')` call follows the symbolic link. 5. The linked target is truncated and replaced with mock PDB o ...[truncated 529 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create a private output directory using `tempfile.TemporaryDirectory()` or `tempfile.mkdtemp()` with restrictive permissions. - Generate unpredictable output filenames rather than using fixed names. - Avoid opening attacker-accessible paths with ordinary truncating write modes. - When persistent output is required, use exclusive creation such as mode `x` or low-level flags including `O_CREAT | O_EXCL`. - Verify that output paths remain inside an explicitly configured output directory. - Reject existing symbolic links and verify the opened file with `os.lstat()` or descriptor-based checks where applicable. - Run the Skill under a dedicated, least-privileged account and do not execute it from a shared writable directory. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/alphafold_agent.py:32
Finding
Unbounded External HTTP Requests Can Cause Resource Exhaustion and Indefinite Blocking<![CDATA[ ## Vulnerability Details **File Location**: `scripts/alphafold_agent.py:32-40` **Vulnerability Type**: Missing request timeouts, response validation, and download-size limits **Risk Level**: Medium ### Vulnerable Code ```python resp = requests.post(self.rcsb_url, json=query) if resp.ok: hits = resp.json()['result_set'] if hits: pdb_id = hits[0]['rcsb_id'][:4].lower() pdb_url = f'https://files.rcsb.org/download/{pdb_id}.pdb' with open(f'{pdb_id}.pdb', 'w') as f: f.write(requests.get(pdb_url).text) return f'{pdb_id}.pdb' ``` ### Technical Analysis Both external requests are made without connect or read timeouts. A remote endpoint or disrupted network path can therefore leave the process blocked for an indefinite period. The PDB download is accessed through `.text`, causing the response body to be buffered and decoded before being written. No maximum response size is enforced, and the download response is not checked with `raise_for_status()`. Its status code, content type, and content structure are also not validated before the body is saved as a PDB file. Although the URLs are fixed trusted HTTPS endpoints and are not directly attacker-controlled, compromise of an upstream service, a malicious network environment, DNS or trust-store compromise, or an abnormal upstream response could expose the process to excessive or invalid content. ### Attack Path 1. A user invokes the Skill with a UniProt query. 2. The Skill sends a POST request to the RCSB search API without a timeout. 3. A network fault or non-responsive upstream connection causes the process to wait indefinitely. 4. Alternatively, the search service returns a result and the Skill starts a PDB download. 5. The upstream or network path supplies an unexpectedly large body, slowly streams data, or returns an error document. 6. The script buffers the body through `.text` and writes it without a size or content check. 7. The process experiences ...[truncated 537 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Configure explicit connect and read timeouts for every request, for example `timeout=(5, 30)`. - Call `raise_for_status()` before processing either response. - Use `stream=True` for downloaded structures and process the response in bounded chunks. - Enforce a strict maximum download size using `Content-Length` when available and a running byte counter while streaming. - Validate the response content type and confirm that the downloaded data has the expected PDB structure before saving or parsing it. - Use a configured `requests.Session` with controlled retry and backoff behavior. - Catch network, JSON-decoding, and schema-validation exceptions and return a controlled error rather than leaving partial output files. - Remove incomplete files when a download fails validation. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/alphafold_agent.py:43
Finding
Unvalidated FASTA and SMILES Inputs Permit Crashes and Computational Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/alphafold_agent.py:43-46`, `scripts/alphafold_agent.py:63-67`, and `scripts/alphafold_agent.py:80-85` **Vulnerability Type**: Missing input validation and resource limits **Risk Level**: Low ### Vulnerable Code ```python def predict_esmfold(self, fasta: str) -> str: '''ESMFold mock (HF transformers heavy; prod docker).''' seq = [s for s in parse(fasta, 'fasta')][0].seq ``` ```python def dock_ligand(self, pdb: str, smiles: str) -> Dict: '''RDKit conformer mock dock.''' ligand = Chem.MolFromSmiles(smiles) AllChem.EmbedMolecule(ligand) score = -Descriptors.MolWt(ligand) * 0.05 # Mock affinity ``` ```python def execute(self, query: Dict) -> Dict: fasta = query.get('fasta', '') uniprot = query.get('uniprot', 'P01116') # KRAS ex smiles = query.get('smiles', '') pdb = self.fetch_public_pdb(uniprot) or self.predict_esmfold(fasta) sites = self.binding_sites(pdb) docking = self.dock_ligand(pdb, smiles) if smiles else None ``` ### Technical Analysis The FASTA path and SMILES string are accepted without schema, type, size, or complexity validation. The FASTA parser is fully materialized into a list before the first sequence is selected. A large file or a file containing many records can therefore consume excessive memory. An empty or malformed FASTA file causes indexing or parsing errors. The fallback can also call `predict_esmfold()` with an empty path when no structure is found and no FASTA input was supplied. `Chem.MolFromSmiles()` can return `None` for an invalid SMILES string, but the result is passed directly to `AllChem.EmbedMolecule()` and descriptor processing. Complex molecular inputs can also impose significant CPU or memory costs during parsing and conformer embedding. The script does not catch these failures, so malformed or expensive input can terminate or stall the entire process. ### Attack Path 1. An untrusted caller submits a query wi ...[truncated 1113 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Define and enforce a strict input schema for `query`, including expected string types and maximum lengths. - Require an explicit valid FASTA path before invoking the fallback. - Restrict FASTA access to an approved input directory when callers are not fully trusted. - Check file size before parsing and enforce maximum sequence length and record-count limits. - Read only the first acceptable FASTA record incrementally instead of converting the entire parser to a list. - Reject empty or malformed FASTA files with a controlled validation error. - Check whether `Chem.MolFromSmiles(smiles)` returned `None` before performing embedding or descriptor calculations. - Limit SMILES length, atom count, bond count, and allowed elements according to the application requirements. - Configure bounded conformer-generation parameters and execution time limits. - Catch Biopython, RDKit, file-access, and validation exceptions and return structured error responses. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The documented behavior materially overstates what the skill actually does, including mocked structure prediction, fake binding-site detection, and non-docking presented as docking. In a drug-discovery workflow, this can mislead downstream agents or users into treating placeholder outputs as scientifically valid, causing unsafe decisions, wasted resources, or bad experimental prioritization.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The module description and workflow overstate core capabilities that are actually mocked. Because this skill is positioned for protein structure and docking tasks, the mismatch is especially dangerous: users may trust it for scientific inference when it cannot perform the claimed analyses.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The function claims to perform ESMFold prediction but instead writes a hard-coded mock PDB. In a scientific or drug-design workflow, this can mislead downstream users and agents into making decisions based on fabricated structural data, creating integrity and safety risks.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The code advertises binding-site detection but returns a fixed pocket regardless of input structure. This is dangerous because it can produce false scientific conclusions and contaminate downstream prioritization, docking, or medicinal chemistry decisions.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The docking routine does not dock against the protein at all; it derives a fake affinity from ligand molecular weight and saves only a ligand image. In this domain, presenting such output as docking results is materially misleading and may drive invalid compound selection or experimental follow-up.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill advertises capabilities that require network access and file writing but does not declare an explicit tool scope or permission boundary. That makes the runtime trust model ambiguous and can lead to over-privileged execution, especially in agent platforms where undeclared capabilities may be inherited or loosely enforced.

Vague Triggers

Medium
Confidence
97% confidence
Finding
The trigger list includes broad terms like `structure`, `target`, `binding site`, and `fold`, which can cause the skill to activate in unrelated conversations. Unintended invocation is risky here because the skill has network and file-writing behavior and may inject misleading scientific outputs into contexts that did not request this specialized analysis.

External Transmission

Medium
Category
Data Exfiltration
Content
},
            "return_type": "polymer_entity"
        }
        resp = requests.post(self.rcsb_url, json=query)
        if resp.ok:
            hits = resp.json()['result_set']
            if hits:
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The method sends an HTTP request to RCSB and then downloads a PDB file and writes it to the local filesystem, but there is no confirmation prompt, user-facing log, or warning in this file indicating that network access and file creation will occur. Because these operations may affect privacy, connectivity expectations, or local files, they should be disclosed to the user.

Tainted flow: 'pdb_id' from requests.post (line 38, network input) → open (file write)

Medium
Category
Data Flow
Content
if hits:
                pdb_id = hits[0]['rcsb_id'][:4].lower()
                pdb_url = f'https://files.rcsb.org/download/{pdb_id}.pdb'
                with open(f'{pdb_id}.pdb', 'w') as f:
                    f.write(requests.get(pdb_url).text)
                return f'{pdb_id}.pdb'
        return None
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: 'pdb_url' from requests.post (line 39, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
pdb_id = hits[0]['rcsb_id'][:4].lower()
                pdb_url = f'https://files.rcsb.org/download/{pdb_id}.pdb'
                with open(f'{pdb_id}.pdb', 'w') as f:
                    f.write(requests.get(pdb_url).text)
                return f'{pdb_id}.pdb'
        return None
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.

Missing User Warnings

Low
Confidence
92% confidence
Finding
The code writes an `esm_fold.pdb` file to disk as part of fallback prediction, but there is no visible disclosure, prompt, or warning that a local file will be created. Even though file output may be part of the workflow, this file itself does not communicate that side effect to the user.

Missing User Warnings

Low
Confidence
93% confidence
Finding
The docking routine saves `docked.png` to the current directory, but the code provides no user-facing notice that an output image file will be created. This is a filesystem side effect that should be made explicit.

Static analysis

No suspicious patterns detected.