Back to skill

Security audit

What to Eat Today | 今天吃什么

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches a food-recommendation purpose, but it includes an optional image-filling script that can download unvalidated remote files and run arbitrary local shell commands.

Review before installing. The normal recommendation command is local and narrow, but avoid running scripts/hydrate_food_images.py unless you accept outbound requests to Bing/Pollinations and local asset writes. Do not use --external-ai-cmd in untrusted workspaces or with untrusted menu/image filenames until it is changed to avoid shell=True and validate downloaded images.

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

Error
Location
scripts/hydrate_food_images.py:78
Finding
Shell Command Injection in the Optional External AI Image Generator<![CDATA[ ## Vulnerability Details **File Location**: `scripts/hydrate_food_images.py:78-88` **Vulnerability Type**: Shell command injection **Risk Level**: High ### Vulnerable Code ```python def external_ai_generate(name: str, out_path: Path, external_ai_cmd: str, timeout: float = 60.0) -> bool: if not external_ai_cmd.strip(): return False cmd = ( external_ai_cmd .replace("{name}", name) .replace("{out_path}", str(out_path)) ) try: result = subprocess.run(cmd, shell=True, check=False, timeout=timeout) ``` ### Technical Analysis The external AI fallback builds a shell command by directly replacing `{name}` and `{out_path}` placeholders with string values and then passes the resulting command to `subprocess.run` with `shell=True`. Because the shell interprets metacharacters such as semicolons, command substitutions, pipes, and redirection operators, placeholder values are treated as executable shell syntax rather than literal command arguments. Dish names normally originate from `assets/menu_db.json`. However, `scripts/expand_menu_db.py` can regenerate database entries from filenames under `assets/foods_image`. Consequently, a crafted filename can become a dish name and later reach this shell command when the external AI fallback is enabled. The `--external-ai-cmd` option is intentionally user-configurable, but this does not make direct shell interpolation safe. Command templates and substituted data must be represented as separate arguments. ### Attack Path 1. An attacker or untrusted archive introduces a file under `assets/foods_image` whose filename stem contains shell metacharacters and an injected command. 2. The operator runs `scripts/expand_menu_db.py`, which imports filename stems into `assets/menu_db.json`. 3. The corresponding image is removed or otherwise becomes missing, causing the dish to be processed by the hydration workflow. 4. The operator runs `scripts/hydrate_food_images.py` ...[truncated 1065 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `shell=True` and invoke the external program through an argument array with `shell=False`. 2. Parse the command template into a controlled argument list before placeholder substitution. Prefer an interface where the executable and each argument are configured separately. 3. Replace placeholders independently in each argument so dish names and paths remain literal values. 4. Validate dish names against a strict allowlist of permitted characters and reject control characters, path separators, and shell metacharacters. 5. Resolve the output path and verify that it remains inside `assets/foods_image`. 6. Consider allowing only explicitly approved external executables rather than accepting an unrestricted command template. 7. Log the executable and sanitized argument list without recording sensitive values. A safer pattern is: ```python import shlex import subprocess template_args = shlex.split(external_ai_cmd) argv = [ arg.replace("{name}", name).replace("{out_path}", str(out_path)) for arg in template_args ] result = subprocess.run( argv, shell=False, check=False, timeout=timeout, ) ``` For stronger protection, avoid parsing a free-form command string altogether and accept a structured executable path and repeated argument options. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/hydrate_food_images.py:24
Finding
Unbounded and Unvalidated Remote Image Downloads<![CDATA[ ## Vulnerability Details **File Location**: `scripts/hydrate_food_images.py:24-56` **Vulnerability Type**: Unsafe remote content download and validation **Risk Level**: Medium ### Vulnerable Code ```python def fetch_url_bytes(url: str, timeout: float = 12.0) -> bytes: req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) with urllib.request.urlopen(req, timeout=timeout) as resp: return resp.read() def search_bing_image_url(query: str) -> str: encoded = urllib.parse.quote(query) search_url = f"https://www.bing.com/images/search?q={encoded}&form=HDRSC2" html = fetch_url_bytes(search_url, timeout=12.0).decode("utf-8", errors="ignore") patterns = [ r'"murl":"(https?://[^"\\]+)"', r"murl&amp;quot;:&amp;quot;(https?://[^&]+)&amp;quot;", r'src="(https?://[^"]+)"', ] for pattern in patterns: match = re.search(pattern, html) if match: url = match.group(1) return bytes(url, "utf-8").decode("unicode_escape") return "" def download_image(url: str, out_path: Path, timeout: float = 15.0) -> bool: try: data = fetch_url_bytes(url, timeout=timeout) if len(data) < 1024: return False out_path.write_bytes(data) return True except Exception: return False ``` ### Technical Analysis The hydrator extracts the first URL matching a permissive regular expression from mutable Bing search-result HTML and downloads it without restricting the destination host. Redirect destinations are not checked. The complete response is loaded into memory through `resp.read()` without a maximum byte limit. A timeout limits elapsed waiting time but does not impose a safe response-size limit. The only content check is a minimum size of 1,024 bytes. The implementation does not verify: - The response `Content-Type`. - Image file signatures or magic bytes. - Whether the data can be decoded as an image. - I ...[truncated 1807 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Permit only HTTPS URLs and reject credentials, unusual ports, and unsupported schemes. 2. Validate every redirect and the final response URL rather than validating only the initial URL. 3. Apply an explicit maximum download size using both `Content-Length` checks and bounded streaming reads. 4. Download into a temporary file instead of loading the complete response into memory. 5. Require an approved image MIME type, while treating the header only as an initial signal rather than proof. 6. Validate file signatures and decode the content with a maintained image-processing library. 7. Enforce maximum width, height, frame count, and total pixel count to mitigate decompression bombs. 8. Re-encode successfully decoded images into a known-safe output format, stripping embedded metadata. 9. Atomically move the validated image into `assets/foods_image` only after all checks succeed. 10. Delete partial or rejected files and report validation failures rather than silently suppressing every exception. 11. Where practical, restrict downloads to approved image hosts or use a trusted image proxy. A bounded streaming implementation should stop immediately when the configured maximum is exceeded and should never save unvalidated response bytes directly as a trusted image. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This finding indicates the skill description masks substantially more powerful behavior: web scraping, external image generation, optional external AI command execution, and arbitrary shell command execution via a user-supplied template. A benign food recommendation facade materially lowers scrutiny while exposing capabilities that can lead to remote data exfiltration, untrusted downloads, and command injection.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This finding indicates the skill description masks substantially more powerful behavior: web scraping, external image generation, optional external AI command execution, and arbitrary shell command execution via a user-supplied template. A benign food recommendation facade materially lowers scrutiny while exposing capabilities that can lead to remote data exfiltration, untrusted downloads, and command injection.

Vague Triggers

High
Confidence
98% confidence
Finding
The skill claims it must be used for essentially any food-related request, creating an overbroad trigger surface for a skill with more capabilities than its description suggests. This is dangerous because broad mandatory invocation increases the frequency with which risky file/network/shell behaviors may be introduced into ordinary conversations.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
This maintenance script exposes a generic external command execution hook through external_ai_cmd, enabling arbitrary shell commands under the guise of image generation. Because the skill's purpose is food suggestion/image hydration, this broad execution capability materially expands the attack surface without strong safeguards.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
.replace("{out_path}", str(out_path))
    )
    try:
        result = subprocess.run(cmd, shell=True, check=False, timeout=timeout)
    except Exception:
        return False
Confidence
99% confidence
Finding
This is a classic tool-parameter abuse issue: externally influenced input is passed into subprocess.run with shell=True, giving the caller a way to execute arbitrary shell metacharacters and commands. In the context of a food skill, such execution is unrelated to intended functionality and therefore especially suspicious and dangerous.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The README instructs users to run image hydration commands that may perform network fetches, invoke third-party image generation services, and even execute an arbitrary external command via --external-ai-cmd, but it does not clearly warn about outbound network activity, privacy implications, cost/API usage, or command-execution risk. In an agent-skill context, this is dangerous because operators may treat the skill as a simple food recommender while unknowingly enabling actions that contact external services or run shell-integrated tooling.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill advertises only a food recommendation use case, yet detected capabilities include shell, network, and file read/write with no declared tool scope or permission boundaries. That combination materially increases risk because the agent may execute code, access local files, or reach external services without explicit limitation or user visibility.

Vague Triggers

Medium
Confidence
90% confidence
Finding
Using vague triggers like 'any preference information' makes the activation boundary unclear and invites accidental invocation in many normal chats. In a skill that appears to have hidden non-recommendation capabilities, ambiguous routing increases operational risk and reduces user expectation alignment.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The documentation instructs copying files into a shared workspace and sending them through external messaging, which extends the skill from recommendation into file movement and outbound content delivery. In context, this creates risk of unintended disclosure of local files or misuse of the messaging channel, especially when combined with filesystem scanning and image-handling behavior noted elsewhere.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
This JSON dataset uses Chinese dish names throughout and provides no accompanying natural-language indication that the skill is Chinese-only or region-specific. Under the language/locale policy rule, a file that effectively constrains outputs or interactions to a single language without documented opt-in can violate organizational locale-choice expectations.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The skill’s natural-language parsing and output are built entirely around Chinese keywords, place names, and Chinese response text. This effectively forces a specific language/locale without any user opt-in or explicit documentation in the file, which matches the language/locale policy violation criteria.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
This file embeds category names and dish names entirely in Chinese, which imposes a specific language on the skill behavior/content without any visible user opt-in or documented locale constraint. The policy allows fixed locale behavior only when it is explicitly justified or when users are offered a choice, neither of which is present here.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The script accepts a command template and executes it with subprocess.run(..., shell=True), which can run arbitrary shell commands on the user's system. While this behavior is optional and somewhat implied by the argument name, there is no explicit runtime warning or confirmation highlighting that arbitrary local commands will be executed.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
.replace("{out_path}", str(out_path))
    )
    try:
        result = subprocess.run(cmd, shell=True, check=False, timeout=timeout)
    except Exception:
        return False
Confidence
98% confidence
Finding
The script builds a shell command from the user-supplied --external-ai-cmd template and then executes it with shell=True. This allows arbitrary command execution if an attacker can influence that argument or the substituted values, and the capability is unrelated to the core food recommendation function, making it an unnecessary high-risk primitive.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script sends dish names from the local menu database to Bing image search and, if enabled, to the Pollinations image API. Although the CLI description says it will fetch images online, there is no explicit warning that local data values are transmitted to third-party services, which is a user-impacting privacy disclosure gap.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The natural-language instructions and usage guidance are written exclusively in Chinese, which can amount to forcing a specific language without user opt-in. The file does not indicate that the skill is intentionally region-specific or provide an alternative language option.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
def merge_overrides(profile, args):
    fields = ["weather", "mood", "mode", "budget", "spicy", "time_slot", "city_tag", "location"]
    for field in fields:
        value = getattr(args, field)
        if value:
            profile[field] = value
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Natural-Language Policy Violations

Low
Confidence
78% confidence
Finding
The generated prompt hard-codes Chinese-language text ('菜品实拍'), which imposes a language choice without user opt-in. This is a natural-language policy concern because the skill does not provide any configurable locale or language selection.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
Several user-facing strings, including the CLI description and help messages, are written only in Chinese. The file does not offer a language selection mechanism or document that it is intentionally limited to Chinese-speaking users.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
The argument help string is presented only in Chinese ("用户原始描述"), while the rest of the CLI uses English identifiers and does not offer any locale selection or opt-in. This creates a language policy issue because the skill imposes a specific language for part of the user experience without documenting or negotiating that choice.

Static analysis

No suspicious patterns detected.