Back to skill

Security audit

Biomedical Literature Search

Security checks for vulnerabilities and agentic risk

Overview

This skill is a straightforward biomedical literature search helper that uses disclosed PubMed and bioRxiv API calls, with reliability hardening recommended.

Installers should understand that search terms and date/category filters may be sent to PubMed or bioRxiv. Add request timeouts, status checks, and explicit network-scope metadata before using this in production workflows.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
examples/basic_example.py:39
Finding

Unbounded outbound HTTP requests can indefinitely block skill execution

Content
View full analysis

Vulnerability Details

File Location: examples/basic_example.py:39-57 and examples/basic_example.py:130
Additional Documentation Locations: SKILL.md:34, SKILL.md:39, and SKILL.md:47
Vulnerability Type: Outbound HTTP requests without timeouts or status validation
Risk Level: Medium

Vulnerable Code

python
response = requests.get(search_url, params=search_params)
search_data = response.json()

pmids = search_data["esearchresult"]["idlist"]

if not pmids:
    return []

# Step 2: Fetch paper details
fetch_url = f"{base_url}/efetch.fcgi"
fetch_params = {
    "db": "pubmed",
    "id": ",".join(pmids),
    "rettype": "abstract",
    "retmode": "xml"
}

response = requests.get(fetch_url, params=fetch_params)
root = ET.fromstring(response.content)

The bioRxiv request has the same issue:

python
# Fetch data
response = requests.get(url)
data = response.json()

The documented examples reproduce the insecure request pattern:

python
response = requests.get(search_url, params=params)
python
response = requests.get(fetch_url, params={"db": "pubmed", "id": ",".join(pmids), "rettype": "abstract", "retmode": "xml"})
python
response = requests.get(url)

Technical Analysis

Python Requests does not impose a network timeout unless one is explicitly supplied. Consequently, each requests.get() call can wait indefinitely while establishing a connection or receiving a response. The implementation also does not call response.raise_for_status(), meaning HTTP error responses are passed directly to JSON or XML parsers.

The destination hosts are fixed official PubMed and bioRxiv endpoints, which limits attacker control and prevents this issue from acting as arbitrary server-side request forgery. Nevertheless, an unavailable upstream service, a compromised network path, a malicious proxy, or a server that accepts connections but ...[truncated 1422 chars]

Remediation
View remediation

Remediation Suggestions

Apply bounded connect and read timeouts to every outbound request, validate HTTP status codes before parsing, and handle network and parsing failures explicitly.

python
import requests
import xml.etree.ElementTree as ET

try:
    response = requests.get(
        search_url,
        params=search_params,
        timeout=(5, 30),
    )
    response.raise_for_status()
    search_data = response.json()
except (
    requests.Timeout,
    requests.ConnectionError,
    requests.HTTPError,
    requests.exceptions.JSONDecodeError,
) as exc:
    raise RuntimeError("PubMed search request failed") from exc

Apply equivalent handling to the PubMed fetch and bioRxiv requests. Recommended additional hardening includes:

  • Use separate bounded connect and read timeout values.
  • Call raise_for_status() before parsing every response.
  • Catch requests.RequestException, JSON decoding errors, XML parsing errors, and missing response fields.
  • Use a requests.Session with a retry adapter that retries only safe transient failures, applies exponential backoff, and enforces a small retry limit.
  • Enforce a maximum acceptable response size before parsing large JSON or XML bodies.
  • Preserve the fixed HTTPS endpoint allowlist and do not permit untrusted input to replace the scheme or host.
  • Update all examples in SKILL.md so users do not copy the insecure request pattern.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill includes executable examples that perform outbound network access to PubMed and bioRxiv, but the manifest does not declare any tool scope such as permissions or allowed-tools. This creates a transparency and policy-enforcement gap: an agent or reviewer cannot easily determine from the skill metadata that external connectivity is required, which can lead to overbroad execution in environments that expect explicit declarations.

Content

No source excerpt is available for this finding.

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

Medium
Category
Data Flow
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.

Content

Scanner excerpt · examples/basic_example.py (reported line 54)May include surrounding context.

python
"retmode": "xml"
    }

    response = requests.get(fetch_url, params=fetch_params)
    root = ET.fromstring(response.content)

    papers = []

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 51)May include surrounding context.

md
start_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")

    # Build URL
    url = f"https://api.biorxiv.org/details/biorxiv/{start_date}/{end_date}"

    if category:
        url += f"?category={category}"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 110)May include surrounding context.

md
start_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")

    # Build URL
    url = f"https://api.biorxiv.org/details/biorxiv/{start_date}/{end_date}"

    if category:
        url += f"?category={category}"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · examples/basic_example.py (reported line 140)May include surrounding context.

python
start_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")

    # Build URL
    url = f"https://api.biorxiv.org/details/biorxiv/{start_date}/{end_date}"

    if category:
        url += f"?category={category}"

Static analysis

No suspicious patterns detected.