Back to skill

Security audit

Gene Structure Mapper

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly purpose-aligned for gene visualization, but its executable script can let a crafted gene name escape the intended cache directory and read or write local JSON-named files.

Review before installing. The skill does what it claims at a high level and does not show malicious intent, but it should be run only in a sandboxed working directory until the gene identifier is validated or cache keys are hashed. Avoid untrusted gene strings and be careful with --output paths because the script writes files locally.

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/main.py:54
Finding

Unsanitized Gene Identifier Enables Cache Path Traversal

Content
View full analysis

Vulnerability Details

File Location: scripts/main.py, lines 54–65 and 83–84
Vulnerability Type: Path traversal through an attacker-controlled cache filename
Risk Level: Medium

Vulnerable code:

python
def get_cache_path(gene: str) -> Path:
    CACHE_DIR.mkdir(exist_ok=True)
    return CACHE_DIR / f"{gene}_ensembl.json"


def fetch_gene_data(gene: str, species: str) -> Optional[dict]:
    """Fetch gene structure from Ensembl REST API with caching."""
    cache_path = get_cache_path(gene)
    if cache_path.exists():
        with open(cache_path) as f:
            return json.load(f)

The same attacker-controlled path is subsequently used for writing:

python
            data = resp.json()
            with open(cache_path, "w") as f:
                json.dump(data, f)

Technical Analysis

The gene value originates from the user-controlled --gene command-line argument. get_cache_path() directly interpolates it into a path beneath .cache without validating allowed characters, rejecting path separators, resolving the path, or verifying that the resolved destination remains inside the cache directory.

pathlib.Path does not neutralize .. components. Consequently, a value containing traversal segments can cause the resulting path to escape .cache. The forced _ensembl.json suffix limits target names but does not prevent access to files outside the intended directory. If the selected path already exists, the application opens it and parses its contents as JSON. If it does not exist and the Ensembl request succeeds, the response is written to that external path.

The write operation also follows symbolic links. An attacker able to prepare a symlink at the selected cache path could redirect the write to another file accessible to the process.

Attack Path

  1. The attacker invokes the script with a crafted gene identifier containing parent-directory components, fo ...[truncated 1587 chars]
Remediation
View remediation

Remediation Suggestions

  1. Validate gene identifiers before filesystem use. Permit only the documented identifier character set, such as ASCII letters, digits, periods, underscores, and hyphens:

    python
    import re
    
    GENE_PATTERN = re.compile(r"^[A-Za-z0-9._-]+$")
    
    def validate_gene(gene: str) -> str:
        if not GENE_PATTERN.fullmatch(gene):
            raise ValueError(
                "Gene identifiers may contain only letters, digits, periods, "
                "underscores, and hyphens."
            )
        return gene
    
  2. Use a fixed-length derived cache key. Hash both species and gene instead of incorporating raw input into a filename:

    python
    import hashlib
    
    def get_cache_path(gene: str, species: str) -> Path:
        CACHE_DIR.mkdir(parents=True, exist_ok=True)
        key = hashlib.sha256(f"{species}\0{gene}".encode("utf-8")).hexdigest()
        return CACHE_DIR / f"{key}_ensembl.json"
    
  3. Enforce directory containment. Resolve the cache root and candidate path, then reject any candidate outside the root:

    python
    cache_root = CACHE_DIR.resolve()
    candidate = (cache_root / filename).resolve()
    if candidate.parent != cache_root:
        raise ValueError("Invalid cache path")
    
  4. Harden file writes. Write to a temporary file within the verified cache directory and atomically replace the destination. Where supported, reject symbolic links by using no-follow file-opening semantics.

  5. Include species in the cache key. The current implementation keys only on gene, which can cause cache collisions between species. Although this is primarily an integrity and correctness concern, including both inputs prevents cross-species cache confusion.

  6. Handle invalid or corrupted cache data safely. Catch json.JSONDecodeError and report or remove the invalid cache entry rather than terminating with an unhandled exception.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (5)

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
80% confidence
Finding

Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Content

Scanner excerpt · gene-structure-mapper_audit_result_v4.json (reported line 294)May include surrounding context.

json
"observed_in": [
        4
      ],
      "problem": "When --demo and --domains are both specified, the script skips the UniProt fetch and produces a diagram without domain overlays, with no warning to the user.",
      "root_cause": "The condition `if args.domains and not args.demo` explicitly suppresses domain fetching in demo mode without notifying the user.",
      "fix": "Either add hardcoded TP53 domain data to DEMO_DATA for demo+domains mode, or print a warning: 'Warning: --domains is not available in --demo mode. Use --gene TP53 --domains for live domain overlay.'"
    },

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill advertises behavior that requires network access to Ensembl/UniProt and local file writes for caching/output generation, but it does not declare any explicit tool scope such as allowed-tools or permissions. This creates a least-privilege gap: a hosting agent may grant broader capabilities than necessary, and users/reviewers cannot easily verify or constrain what the skill is permitted to do.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The audit text says the script "fetches live Ensembl data" and elsewhere notes caching to .cache/ and PNG generation, which means the skill performs outbound network requests and writes local files. This JSON file does not provide any user warning about those behaviors' privacy/system impact; it only documents them as implementation details.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

This JSON file states that the "Trigger description is precise" but does not include the actual trigger phrases, scope boundaries, or negative examples. In a manifest-like file, omitting the concrete activation conditions makes it impossible to verify that invocation language is narrow enough to avoid unintended activation.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The audit notes that SKILL.md documents the --domains flag, yet the implementation does not provide domain overlays when --demo is set. This is an intent-code divergence because the documented capability implies availability, while the code silently suppresses it in that mode.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.