Back to skill

Security audit

Med Info

Security checks for vulnerabilities and agentic risk

Overview

This medication lookup skill is mostly purpose-aligned, but it needs Review because an optional openFDA API key can be exposed in command output if an HTTP error occurs.

Review before installing if you plan to set OPENFDA_API_KEY or run this in CI/agent logs. Prefer using it without a key, or patch error handling to redact api_key in exceptions before configuring one. Do not enter PHI, and be aware that optional dataset flags may fetch/cache public files and the hazardous-drug path may invoke pdftotext.

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
scripts/med_info.py:91
Finding

openFDA API Key Disclosure Through Unredacted HTTP Error Output

Content
View full analysis

Vulnerability Details

File Location: scripts/med_info.py:91-110, with related credential handling at scripts/med_info.py:249-255 and output propagation at scripts/med_info.py:1796-1805
Vulnerability Type: Sensitive credential exposure in error messages
Risk Level: Medium

Vulnerable Code

The HTTP helper includes the original, unredacted request URL in exceptions:

python
def http_get_json(url: str, headers: Optional[Dict[str, str]] = None, *, allow_404: bool = False) -> Any:
    _log_url(url)
    req = urllib.request.Request(url)
    req.add_header("User-Agent", USER_AGENT)
    if headers:
        for k, v in headers.items():
            req.add_header(k, v)
    try:
        with urllib.request.urlopen(req, timeout=TIMEOUT_S) as resp:
            data = resp.read()
        return json.loads(data.decode("utf-8"))
    except urllib.error.HTTPError as e:
        if allow_404 and e.code == 404:
            # openFDA returns 404 when there are no matches.
            return {"results": []}
        try:
            body = e.read().decode("utf-8", errors="replace")
        except Exception:
            body = ""
        raise RuntimeError(f"HTTP {e.code} for {url}: {body[:500]}") from e

The optional API key is embedded in the request URL:

python
def openfda_url(path: str, query: str, limit: int = 1) -> str:
    base = f"https://api.fda.gov{path}.json"
    params = {"search": query, "limit": str(limit)}
    api_key = os.environ.get("OPENFDA_API_KEY")
    if api_key:
        params["api_key"] = api_key
    return base + "?" + urllib.parse.urlencode(params)

The exception text is then exposed through JSON or console output:

python
    except Exception as e:
        out["error"] = str(e)

    if args.print_url or args.json:
        out.setdefault("debug", {})["urls"] = URL_LOG

    if args.json:
        sys.stdout.write(json.dumps(out, ens
...[truncated 2547 chars]
Remediation
View remediation

Remediation Suggestions

  1. Redact the URL before including it in every exception message:

    python
    safe_url = _redact_url(url)
    raise RuntimeError(
        f"HTTP {e.code} for {safe_url}: {body[:500]}"
    ) from e
    
  2. Centralize sanitization so logging, normal errors, retry errors, and debug output all use the same safe URL representation.

  3. If supported by openFDA, transmit the API key in a request header rather than in the query string. This reduces accidental exposure through URLs, proxies, logs, and exception text.

  4. Avoid returning raw upstream response bodies unless needed. Sanitize and constrain error bodies before exposing them to users or automated logs.

  5. Add regression tests using a sentinel key, such as TEST_SECRET_DO_NOT_LOG, and simulate HTTP failures. Assert that the sentinel never appears in:

    • Raised exception messages
    • JSON output
    • Human-readable output
    • Debug URL logs
    • CI or application logs
  6. Rotate any openFDA API key that may already have appeared in logs or captured output.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
87% confidence
Finding

If the code performs network probing and operational status reporting rather than medication-answer generation, the declared purpose materially misrepresents runtime behavior. A skill that can probe external resources and use environment-gated keys presents a different security profile than a read-mostly citation helper, and that mislabeling can conceal reconnaissance-like behavior or unauthorized external communications.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

If the code performs network probing and operational status reporting rather than medication-answer generation, the declared purpose materially misrepresents runtime behavior. A skill that can probe external resources and use environment-gated keys presents a different security profile than a read-mostly citation helper, and that mislabeling can conceal reconnaissance-like behavior or unauthorized external communications.

Content

No source excerpt is available for this finding.

Self-Modification

High
Category
Rogue Agent
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.

Content

Scanner excerpt · task_plan.md (reported line 41)May include surrounding context.

md
- [ ] Add safety flags block (boxed warning present, schedule guess)
- [ ] Add NIOSH hazardous list flag (best-effort, cached; uses NIOSH 2024 list PDF)
- [ ] Add FDA REMS best-effort lookup/linking (graceful degrade if blocked)
- [ ] Update SKILL.md examples + docs + resources manifest
- [ ] Run smoke tests (py_compile + a few real queries)
- **Status:** in_progress

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill advertises executable capabilities including environment access, file read/write, network, and shell use, but it declares no explicit tool scope or permission boundary. That creates an over-privileged integration surface where a caller may invoke code with broader access than the manifest communicates, increasing the risk of unintended data access, filesystem modification, or outbound requests.

Content

No source excerpt is available for this finding.

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 · scripts/med_info.py (reported line 469)May include surrounding context.

python
def openfda_event_count(search: str, count_field: str, limit: int = 10) -> List[Dict[str, Any]]:
    base = "https://api.fda.gov/drug/event.json"
    params = {
        "search": search,
        "count": count_field,

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 · scripts/resources_manifest.json (reported line 7)May include surrounding context.

json
def openfda_event_count(search: str, count_field: str, limit: int = 10) -> List[Dict[str, Any]]:
    base = "https://api.fda.gov/drug/event.json"
    params = {
        "search": search,
        "count": count_field,

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The manifest emphasizes label-backed medication answers with citations/traceable IDs and optional recalls, shortages, FAERS, and interactions. However, the code additionally queries PubChem for chemical properties, downloads and parses Orange Book, Purple Book, NIOSH hazardous-drug PDFs, and scrapes FDA REMS pages, which expands the skill into broader drug-regulatory and chemistry enrichment rather than staying primarily label-backed.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The MedlinePlus request hard-codes informationRecipient.languageCode.c to en, which imposes an English-only locale choice in a user-facing information retrieval flow. The file does not offer a language option or document this as a justified region-specific constraint.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

For the --hazardous feature, the code checks for pdftotext and executes it via subprocess.run on a downloaded PDF. Spawning local executables is a materially different capability than retrieving medication information from public APIs and is not suggested by the manifest description.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/med_info.py (reported line 1074)May include surrounding context.

python
p_txt = cache_path("niosh", "2025-103.txt")
    if (not p_txt.exists()) or (p_txt.stat().st_mtime < p_pdf.stat().st_mtime):
        subprocess.run(["pdftotext", str(p_pdf), str(p_txt)], check=True)

    txt = p_txt.read_text(encoding="utf-8", errors="replace")
    records = parse_niosh_hazardous_pdf_text(txt)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The probe records up to 200 characters of remote response bodies into the JSON report, even though the script is described as a lightweight reachability/content-type smoke test. If a probed endpoint returns sensitive content, credentials in error pages, internal metadata, or copyrighted/regulated data, that content is persisted to disk and could later be exposed through logs, artifacts, or CI outputs. In this skill context, the manifest drives requests to external medication-related resources, so the danger is somewhat reduced because targets are intended to be public, but it still creates unnecessary data retention from untrusted remote content.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.