Back to skill

Security audit

Pokeinfo

Security checks for vulnerabilities and agentic risk

Overview

This Pokémon lookup skill is mostly aligned with its stated purpose, but needs review because its voice feature uses unsafe temporary files and it trusts URLs returned by an external API.

Review before installing, especially on shared or sensitive machines. The skill does not request credentials and its main behavior is Pokémon lookup, but voice mode should be used cautiously until URL allowlisting, bounded downloads, and secure per-run temporary files are added.

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)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/pokeinfo.py:303
Finding
Unrestricted Requests to API-Provided URLs## Vulnerability Details **File Location**: `scripts/pokeinfo.py:303-321`, `scripts/pokeinfo.py:323-330`, `scripts/pokeinfo.py:336-350`, `scripts/pokeinfo.py:404-406`, and `scripts/pokeinfo.py:444-455` **Vulnerability Type**: Server-Side Request Forgery and Unrestricted Resource Retrieval **Risk Level**: Medium ### Vulnerable Code ```python def get_localized_type_name(type_url, lang): """Get type name in specified language.""" try: type_data = fetch_url(type_url) for name_entry in type_data.get("names", []): if name_entry["language"]["name"] == lang: return name_entry["name"] except Exception: pass return None def get_localized_ability_name(ability_url, lang): """Get ability name in specified language.""" try: ability_data = fetch_url(ability_url) for name_entry in ability_data.get("names", []): if name_entry["language"]["name"] == lang: return name_entry["name"] except Exception: pass return None def download_cry(cry_url, output_path): """Download cry audio file.""" req = urllib.request.Request(cry_url, headers={ 'User-Agent': 'Mozilla/5.0 (compatible; Pokeinfo/1.0)' }) with urllib.request.urlopen(req, timeout=15) as response: with open(output_path, 'wb') as f: f.write(response.read()) ``` ```python for t_entry in data['types']: type_name = get_localized_type_name(t_entry['type']['url'], lang) if not type_name: type_name = t_entry['type']['name'].title() types.append(type_name) abilities = [] for a in data['abilities']: ability_name = get_localized_ability_name(a['ability']['url'], lang) if not ability_name: ability_name = a['ability']['name'].replace('-', ' ').title() ``` ```python cry_url = data['cries'].get('latest') if cry_url: lines.ap ...[truncated 3079 chars]
Remediation
## Remediation Suggestions 1. Implement a centralized URL-validation function before every outbound request. 2. Permit only `https` URLs with an explicit allowlist of required PokéAPI-controlled hostnames, such as the API and approved static asset hosts. 3. Reject URLs containing credentials, fragments, unexpected ports, or unsupported schemes. 4. Resolve destination hostnames and reject loopback, private, link-local, multicast, reserved, and unspecified IP address ranges. 5. Validate every redirect destination rather than validating only the initial URL, or disable automatic redirects. 6. Apply separate allowlists for API metadata and cry assets. 7. Stream responses in bounded chunks and enforce strict maximum sizes for JSON and audio. 8. Verify `Content-Type` and reject unexpected content before parsing. 9. Validate downloaded audio structure and duration before passing it to native audio libraries. 10. Return a controlled error when validation fails rather than silently accepting an untrusted destination.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/pokeinfo.py:438
Finding
Predictable Shared Temporary Files Permit Symlink-Based File Overwrite## Vulnerability Details **File Location**: `scripts/pokeinfo.py:323-330` and `scripts/pokeinfo.py:438-455` **Vulnerability Type**: Insecure Temporary File Creation and Symlink Following **Risk Level**: Medium ### Vulnerable Code ```python def download_cry(cry_url, output_path): """Download cry audio file.""" req = urllib.request.Request(cry_url, headers={ 'User-Agent': 'Mozilla/5.0 (compatible; Pokeinfo/1.0)' }) with urllib.request.urlopen(req, timeout=15) as response: with open(output_path, 'wb') as f: f.write(response.read()) ``` ```python temp_dir = tempfile.gettempdir() vorbis_path = os.path.join(temp_dir, f"pokeinfo_cry_{pokemon_id}_vorbis.ogg") opus_path = os.path.join(temp_dir, f"pokeinfo_cry_{pokemon_id}_opus.ogg") try: download_cry(cry_url, vorbis_path) success = convert_cry_to_opus(vorbis_path, opus_path) if os.path.exists(vorbis_path): os.remove(vorbis_path) if success and os.path.exists(opus_path): return opus_path except Exception: pass return None ``` ### Technical Analysis Cry files are created in the system-wide temporary directory using names derived solely from the public Pokémon ID. These names are deterministic and can be predicted before the skill runs. The download operation uses ordinary `open(output_path, 'wb')`, which truncates an existing file and follows symbolic links. There is no exclusive file creation, ownership check, private temporary directory, or no-follow protection. The output path supplied to `soundfile.write` is similarly predictable. On a multi-user system, another local account or less-trusted process with access to the shared temporary directory can pre-create either path as a symbolic link. When the victim invokes voice conversion, the application may follow the link and overwrite a file writable by the victim. Concurrent invocations for the same Pokémon also sha ...[truncated 1643 chars]
Remediation
## Remediation Suggestions 1. Create a unique private directory for each invocation with `tempfile.TemporaryDirectory()`. 2. Generate unpredictable files using `tempfile.NamedTemporaryFile`, `mkstemp`, or securely randomized names. 3. Use exclusive creation so an existing path causes failure instead of being overwritten. 4. Where supported, use `O_NOFOLLOW` and verify with `fstat` that the opened object is a regular file owned by the current user. 5. Pass already-open file descriptors to processing libraries where their APIs permit it. 6. Set restrictive permissions, such as `0600` for files and `0700` for the temporary directory. 7. Keep all temporary artifacts inside the private directory and remove the directory in a `finally` block or context manager. 8. Avoid returning a path that can be replaced after validation; if another component must consume the file, preserve ownership and verify it immediately before use. 9. Add tests covering pre-existing files, symbolic links, concurrent invocations, failed conversions, and cleanup behavior.
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (4)

Lp3

Medium
Category
MCP Least Privilege
Confidence
97% confidence
Finding
The skill performs network access and writes configuration and temporary audio files, but it does not declare any explicit tool scope or permissions. This weakens least-privilege controls and can allow the agent runtime to grant broader capabilities than users expect, increasing risk if the skill is modified, misused, or invoked in an unexpected context.

Vague Triggers

Medium
Confidence
96% confidence
Finding
The trigger language is very broad, including phrases like 'what is [pokemon]' and 'any query related to Pokémon / Pokemon data retrieval,' which can cause unintended invocation on ambiguous user requests. Over-broad activation can route conversations to this skill when the user did not intend it, leading to unnecessary network/file actions and confusing or policy-bypassing behavior in multi-skill environments.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The instructions say to display the text "AS-IS" and "do not add translations" because the script localizes output based on the user's language setting. This can force a specific language/locale behavior without checking the current user's preference at interaction time or offering an opt-in choice.

Missing User Warnings

Low
Confidence
86% confidence
Finding
This code path creates files in the local temp directory and downloads remote audio content into them when `--voice` is used. Although the operation is part of the feature, there is no explicit user-facing notice in code output or usage text that local files will be created as part of voice generation.

Static analysis

No suspicious patterns detected.