Back to skill

Security audit

PharmGx Reporter

Security checks for vulnerabilities and agentic risk

Overview

The skill is not deceptive or exfiltrating data, but it produces high-stakes medication guidance from sensitive genetic files with unsafe fail-open defaults and weak privacy/safety controls.

Install only if you treat the output as educational and keep reports private. Do not use the generated medication recommendations to start, stop, avoid, or change dose for any drug without clinician review and clinical-grade confirmatory testing, especially when the input data may be incomplete or from a consumer genetics provider.

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

other

Error
Location
pharmgx_reporter.py:785
Finding

Missing or Unsupported Genetic Data Silently Produces Normal Phenotypes and Standard-Dose Recommendations

Content
View full analysis

Vulnerability Details

File Location: pharmgx_reporter.py:785-910
Vulnerability Type: Fail-open medical inference and result-integrity failure
Risk Level: High

Vulnerable Code

python
def call_diplotype(gene, pgx_snps):
    gdef = GENE_DEFS[gene]

    if gdef.get("type") == "genotype":
        rsid = gdef["rsid"]
        if rsid in pgx_snps:
            return pgx_snps[rsid]["genotype"]
        return gdef["ref"] + gdef["ref"]

    detected = []
    for rsid, vdef in gdef["variants"].items():
        if rsid in pgx_snps:
            gt = pgx_snps[rsid]["genotype"]
            alt = vdef["alt"].upper()
            alt_count = gt.count(alt) if alt != "DEL" and alt != "INS" and alt != "TA7" else 0
            if alt_count > 0:
                detected.append({"rsid": rsid, "allele": vdef["allele"],
                                 "copies": alt_count, "effect": vdef["effect"]})

    if gdef.get("type") == "dpyd":
        if not detected:
            return "Normal/Normal"
        v = detected[0]
        if v["copies"] == 2:
            return f"{v['allele']}/{v['allele']}"
        return f"Normal/{v['allele']}"

    if not detected:
        return f"{gdef['ref']}/{gdef['ref']}"
python
def call_phenotype(gene, diplotype):
    gdef = GENE_DEFS[gene]
    norm = diplotype.upper()
    for desc, conditions in gdef["phenotypes"].items():
        for cond in conditions:
            if norm == cond.upper():
                return desc
            parts = cond.split("/")
            if len(parts) == 2 and norm == f"{parts[1]}/{parts[0]}".upper():
                return desc
    return "Normal (inferred)"
python
def phenotype_to_key(phenotype_desc):
    """Map phenotype description to GUIDELINES rec key."""
    mapping = {
        "Normal Metabolizer": "normal_metabolizer",
        "Intermediate Metabolizer": "intermediate_metabolizer",

...[truncated 3878 chars]
Remediation
View remediation

Remediation Suggestions

  1. Introduce explicit states such as Unknown, Not tested, Insufficient coverage, and Unsupported diplotype.
  2. Track expected and observed SNP coverage independently for every gene.
  3. Infer a reference allele only when the relevant loci have valid reference genotype calls; never infer it merely because a record is absent.
  4. Return Unknown rather than Normal (inferred) when a diplotype is not present in the phenotype mapping.
  5. Remove the default normal_metabolizer fallback from phenotype_to_key(). Return None or raise a controlled validation error for unknown values.
  6. Do not generate medication recommendations for genes with insufficient coverage or unsupported phenotypes.
  7. Replace the standard-dose fallback in lookup_drugs() with an explicit “No recommendation available” classification.
  8. Add prominent per-gene coverage and confidence fields to the report.
  9. Reject wholly unknown formats and files that do not meet a documented minimum coverage threshold.
  10. Add negative tests for empty files, partial files, malformed genotype calls, unsupported diplotypes, and missing recommendation keys.

T09 · Insecure Skill Coding Practices

Warning
Location
pharmgx_reporter.py:750
Finding

Unescaped Input Values Permit Markdown Report Injection and Unsafe Reproducibility Commands

Content
View full analysis

Vulnerability Details

File Location: pharmgx_reporter.py:750-773, 928-998, 1041-1044
Vulnerability Type: Untrusted-data injection into generated Markdown
Risk Level: Medium

Vulnerable Code

python
def parse_file(path):
    content = Path(path).read_text()
    lines = content.split("\n")
    fmt = detect_format(lines)

    snps = {}
    for line in lines:
        if line.startswith("#") or line.strip() == "":
            continue
        if "rsid" in line.lower() and "chromosome" in line.lower():
            continue
        parts = line.split("\t") if "\t" in line else line.split(",")
        if len(parts) >= 4:
            rsid = parts[0].strip()
            if not rsid.startswith("rs"):
                continue
            if len(parts) == 5:
                genotype = parts[3].strip() + parts[4].strip()
            else:
                genotype = parts[3].strip()
            if genotype and genotype not in ("--", "00"):
                snps[rsid] = genotype.upper()
python
def generate_report(input_path, fmt, total_snps, pgx_snps, profiles, drug_results):
    now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
    checksum = hashlib.sha256(Path(input_path).read_bytes()).hexdigest()
    fname = Path(input_path).name

    lines = []
    lines.append("# ClawBio PharmGx Report")
    lines.append("")
    lines.append(f"**Date**: {now}")
    lines.append(f"**Input**: `{fname}`")
python
for rsid, info in sorted(pgx_snps.items(), key=lambda x: x[1]["gene"]):
    lines.append(f"| {rsid} | {info['gene']} | {info['allele']} | {info['genotype']} | {info['effect']} |")
python
lines.append("## Reproducibility")
lines.append("")
lines.append("```bash")
lines.append(f"python pharmgx_reporter.py --input {fname} --output report")
lines.append("```")

Technical Analysis

The parser accepts arbitrary non-empty ...[truncated 3021 chars]

Remediation
View remediation

Remediation Suggestions

  1. Validate every genotype against a strict, locus-appropriate allowlist before storing or reporting it.
  2. Reject control characters, Markdown delimiters, HTML fragments, and unexpectedly long genotype values.
  3. Implement context-specific Markdown escaping for table cells, inline-code spans, and ordinary text.
  4. Normalize filenames for display or replace them with a safe generated identifier.
  5. Reject filenames containing newline, carriage-return, NUL, or other control characters.
  6. Use shlex.quote(fname) when generating POSIX shell examples. If cross-platform support is required, provide separately escaped commands for each shell.
  7. Consider omitting the untrusted original filename from copyable command examples.
  8. Render reports through a structured Markdown-generation layer rather than direct string interpolation.
  9. If reports are converted to HTML, configure the renderer to disable raw HTML and sanitize links and generated markup.
  10. Add tests using pipes, backticks, brackets, HTML, shell metacharacters, spaces, and newline-bearing filenames to verify that report structure cannot be escaped.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (4)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill processes a user-supplied genetic data file and generates a report, which inherently requires file read/write behavior, but it does not declare any explicit tool scope such as permissions or allowed-tools. In an agent environment, this creates an authorization gap where the runtime may permit broader filesystem access than users or platform policy expect, increasing the risk of unintended access to sensitive data or overwriting files.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The report places highly actionable medication guidance such as 'AVOID,' dose reductions, and alternative therapy suggestions before any prominent safety warning, while the disclaimer appears much later. In a pharmacogenomics context, users may act on the recommendations without reading the later caveats, especially because the report format resembles a clinical decision support document and includes specific drug-by-drug instructions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The tool processes highly sensitive genetic and medication-response data and writes a detailed report to disk by default without an explicit consent step, privacy warning, output protection, or minimization of sensitive contents. In the context of a pharmacogenomics skill, local report generation materially increases risk of unauthorized disclosure through shared systems, backups, sync services, or loose file permissions, even though there is no network exfiltration in the code shown.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
35% confidence
Finding

SQP-3 only covers natural-language policy violations such as forced language or locale. This file is entirely in English and does not offer any language choice, but there is no explicit policy statement or instruction forcing a locale, so evidence is weak.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.