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## 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
## 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## 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
## 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)

Lp3

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.

Missing User Warnings

Medium
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.

Missing User Warnings

Medium
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.

Natural-Language Policy Violations

Low
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.

Static analysis

No suspicious patterns detected.