Back to skill

Security audit

Pharmaclaw Market Intel Agent

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a public pharma data tool, but it can write generated files outside the chosen output folder when given path-like drug input.

Review this skill before installing. It queries public biomedical APIs and writes local result files, but avoid using untrusted or path-like drug names until filename sanitization is fixed, and run it only in a workspace where generated file overwrites would not damage important data.

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

Error
Location
scripts/query_faers.py:73
Finding
User-Controlled Path Traversal and File Overwrite in FAERS Output Generation## Vulnerability Details **File Location**: `scripts/query_faers.py`, lines 73–156 **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: High The user-controlled `--drug` argument is incorporated into output filenames without filename sanitization or verification that the resulting paths remain inside the requested output directory. ### Vulnerable Code ```python os.makedirs(args.output, exist_ok=True) drug_name = args.drug.strip() # Resolve SMILES if looks like one if re.search(r'^[A-Za-z0-9#\(\)=\[\]@+\-%/.]+$', drug_name) and len(drug_name) > 3 and not drug_name.isalpha(): print(f"Resolving SMILES '{drug_name}' to drug name...", file=sys.stderr) resolved = get_drug_name_from_smiles(drug_name) if resolved: drug_name = resolved print(f"Resolved to: {drug_name}", file=sys.stderr) else: print(f"Failed to resolve SMILES '{args.drug}', using as-is.", file=sys.stderr) ``` The unsanitized value is subsequently used in multiple output paths: ```python yearly_counts.to_json( os.path.join(args.output, f'{drug_name}_yearly_trends.json'), orient='records', indent=2 ) plt.savefig( os.path.join(args.output, f'{drug_name}_yearly_trends.png'), dpi=150, bbox_inches='tight' ) ``` It is also used in the recent-events and unconditional summary writes: ```python events_path = os.path.join(args.output, f'{drug_name}_recent_events.json') with open(events_path, 'w') as f: json.dump(events_data, f, indent=2) # Summary JSON summary_path = os.path.join(args.output, f'{drug_name}_summary.json') with open(summary_path, 'w') as f: json.dump(results, f, indent=2) ``` ### Technical Analysis `os.path.join()` does not enforce containment within its first argument. If the filename component is absolute, the preceding output directory is discarded. Relative values containing directory separators a ...[truncated 2554 chars]
Remediation
## Remediation Suggestions 1. Never use the raw drug name as a filesystem component. Create a dedicated filename slug that permits only a narrow set of characters, such as ASCII letters, digits, underscores, and hyphens. 2. Replace all separators, traversal components, control characters, and leading dots. Use a stable hash when the sanitized value would be empty or ambiguous. 3. Apply the same sanitization to names returned by PubChem because remote API data must not be trusted as a safe filename. 4. Resolve and validate every destination before writing: ```python from pathlib import Path import hashlib import re def safe_label(value): label = re.sub(r'[^A-Za-z0-9_-]+', '_', value).strip('._-') if not label: label = hashlib.sha256(value.encode()).hexdigest()[:16] return label[:100] output_root = Path(args.output).resolve() output_root.mkdir(parents=True, exist_ok=True) label = safe_label(drug_name) def output_path(suffix): destination = (output_root / f"{label}{suffix}").resolve() if output_root not in destination.parents: raise ValueError("Output path escapes the configured directory") return destination ``` 5. Use `output_path("_summary.json")` and equivalent validated destinations for every JSON and PNG write. 6. Consider exclusive file creation or an explicit overwrite option when existing files must not be replaced silently. 7. Run the Skill under a least-privileged account with write access limited to a dedicated output directory. 8. Add regression tests covering absolute paths, `../` traversal, nested separators, leading dots, Unicode separators, empty labels, and hostile names returned by PubChem.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (19)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
There is a clear mismatch between the declared description and the actual code behavior. The description claims biomedical data retrieval and analysis functionality using external APIs and chart generation, but the code only packages a directory into a compressed .skill archive and deletes a local cache directory. Its primary purpose is build/packaging, not FAERS analysis. The triggers related to adverse events, safety reports, and clinical trials are unrelated to this code chunk’s behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description focuses on fetching and analyzing FAERS data from openFDA, with SMILES resolution and adverse-event outputs. However, the code shown is not limited to FAERS: it explicitly supports a second major capability, querying ClinicalTrials.gov through query_trials.py, including condition, status, and phase filters, and presents itself as a 'Pharma Market Intel unified entry point.' That is a materially broader purpose than the declared FAERS-centric description. The triggers also include clinical-trial-related terms, which align with the code, but those capabilities are not accurately represented in the main declared description. There is no evidence in this chunk about SMILES resolution, JSON/chart generation, or direct openFDA access; those may exist in other files, but this chunk primarily demonstrates orchestration of FAERS plus trials. Therefore this is a description/behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
There is a clear description-behavior mismatch. The description presents a concrete pharmacovigilance skill for retrieving and analyzing FDA adverse event data and producing structured outputs and charts. The actual code is a generic placeholder script with a print statement and TODO comments only. It does not access any external APIs, process inputs, analyze data, or generate files. The declared triggers also suggest broad clinical/pharma functionality that is not represented in the code. Therefore the description does not accurately represent the supplied code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The core declared functionality matches the code closely: FAERS querying, SMILES resolution through PubChem, event listing, yearly trends, reaction/outcome summaries, and PNG/JSON generation are all implemented. However, the declared triggers include several unrelated clinical-trial-oriented terms ('clinical trial', 'clinicaltrials', 'trial pipeline', 'recruiting trial'). The code has no clinical trials functionality and only accesses FAERS/openFDA drug event data plus PubChem. This makes the trigger set materially broader than the actual behavior, so there is a description/behavior mismatch, albeit limited to triggers rather than the main purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
This is a clear material mismatch. While the code does support one declared implementation detail—SMILES resolution via PubChem—the primary purpose is not FAERS adverse event analysis. It calls the ClinicalTrials.gov studies endpoint, parses study metadata such as NCT ID, trial phase, recruitment status, sponsor, enrollment, and conditions, computes aggregate trial stats, and outputs trial-focused charts. It does not access openFDA or FAERS, does not retrieve safety reports or adverse event cases, and does not compute reactions/outcomes from pharmacovigilance data. The trigger list also includes clinical trial terms, which align with the actual code but conflict with the declared FAERS description. Therefore the description does not accurately represent the supplied code chunk.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The manifest describes a skill that fetches and analyzes FAERS/openFDA and PubChem data and generates JSON and chart outputs. This file instead packages a directory into a .skill tarball and deletes local cache directories, which is unrelated to adverse-event analysis and indicates behavior outside the described skill functionality.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The file header and usage text show the skill has been broadened from FAERS adverse-event analysis into ClinicalTrials.gov querying, which is a material capability expansion beyond the stated purpose. Scope expansion increases attack surface, can surprise operators, and may cause the agent to access/process external data sources that were not expected during review or deployment.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
This conditional path actually executes trial-search workflows when metrics include trials or clinical_trials, confirming the undocumented expansion is live rather than dead code. In an agent setting, hidden or under-declared capabilities are risky because policy, user consent, and downstream reviewers may assume a narrower operating scope than the code really has.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill documentation advertises command execution, network access, file output, and asset generation, but it does not declare any explicit tool scope or permission boundaries. In an agent environment, undeclared shell/network/file-write capability increases the chance of overbroad execution, misuse of external resources, and unintended writes because callers and policy layers cannot easily constrain what the skill is allowed to do.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The manifest constrains the skill to FAERS/openFDA analysis, while the body documents broader ClinicalTrials.gov querying and combined workflows. This discrepancy can bypass governance or policy decisions made from manifest metadata alone, causing the agent to invoke network actions and data processing outside the reviewed scope.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger list contains broad terms such as clinical trial, adverse event, and safety report that can match ordinary user conversations and cause unexpected activation. In a skill with shell, network, and file-write capabilities, accidental invocation increases risk because the agent may perform external queries and generate artifacts when the user did not intend to run this tool.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"--drug", drug, "--output", output,
           "--limit-events", str(limit_events)]
    print(f"[chain] Running FAERS query...", file=sys.stderr)
    result = subprocess.run(cmd, capture_output=True, text=True)
    print(result.stderr, file=sys.stderr, end='')
    print(result.stdout, end='')
    return result.returncode == 0
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"--drug", drug, "--output", output,
           "--limit-events", str(limit_events)]
    print(f"[chain] Running FAERS query...", file=sys.stderr)
    result = subprocess.run(cmd, capture_output=True, text=True)
    print(result.stderr, file=sys.stderr, end='')
    print(result.stdout, end='')
    return result.returncode == 0
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The `run_trials` function orchestrates a distinct capability: querying trial data by drug, condition, status, and phase. While trial intelligence may be useful generally, it is not an obvious requirement of a skill whose stated purpose is FAERS/openFDA post-market adverse-event analysis.

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

Medium
Category
Data Flow
Content
# Get synonyms
        syn_url = f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/{cid}/synonyms/JSON"
        syn_resp = requests.get(syn_url, timeout=10)
        if syn_resp.status_code != 200:
            return None
        info = syn_resp.json().get('InformationList', {}).get('Information', [{}])[0]
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.

External Transmission

Medium
Category
Data Exfiltration
Content
def query_faers(drug_query, count_field=None, limit=100):
    """Query openFDA FAERS API."""
    base_url = "https://api.fda.gov/drug/event.json"
    params = {
        'search': f'patient.drug.medicinalproduct:"{drug_query}"',
        'limit': limit
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The skill manifest describes a tool that fetches and analyzes FAERS adverse event data from openFDA, but this file is dedicated to querying ClinicalTrials.gov trial data and generating trial-focused summaries and plots. That is a distinct data source and use case from post-market adverse event analysis, so the implemented behavior materially exceeds the described scope.

Intent-Code Divergence

Medium
Confidence
89% confidence
Finding
The module documentation explicitly states that the script queries the ClinicalTrials.gov API and generates related outputs, while the skill's declared purpose is FAERS/openFDA adverse event analysis. This is an active documentation-to-skill-intent contradiction rather than a mere omission, because the file advertises a separate capability inconsistent with the overall stated skill intent.

Intent-Code Divergence

Low
Confidence
91% confidence
Finding
The document header labels this as a 'FAERS Query Skill' and the overview focuses on post-market safety data. The same file later documents ClinicalTrials.gov API usage, trial searches by phase/status, trial timeline plots, and a combined chain entrypoint, which contradicts the narrower framing of the skill's stated intent in its own documentation.

Static analysis

No suspicious patterns detected.