Back to skill

Security audit

Calorie Lookup

Security checks for vulnerabilities and agentic risk

Overview

This nutrition lookup skill is mostly purpose-aligned, but it needs Review because it can send food text/photos to external model/API services and may expose API keys in user-visible errors.

Install only if you are comfortable sending meal descriptions and food photos to external nutrition/model services and storing food-query results in a local SQLite cache. Use dedicated low-privilege API keys, avoid submitting sensitive photos or health details, and fix/redact provider error handling before using this in shared chats, logs, or production.

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/usda_fdc.py:22
Finding
API Credentials May Be Disclosed Through Unsanitized HTTP Exceptions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/usda_fdc.py:22-41`, `scripts/spoonacular.py:22-46`, `scripts/spoonacular.py:85-91`, `scripts/core.py:117-119`, and `scripts/core.py:349-350` **Vulnerability Type**: API credential exposure through exception messages **Risk Level**: Medium ### Vulnerable Code ```python # scripts/usda_fdc.py:22-31 def _handle_response(r: requests.Response): if r.status_code == 401: raise USDAError("USDA API key invalid or missing (401)", status=401) if r.status_code == 403: raise USDAError("Insufficient USDA API permissions (403)", status=403) if r.status_code == 429: raise USDAError("USDA API rate limit reached (429)", status=429) if r.status_code >= 500: raise USDAError(f"USDA API server error ({r.status_code})", status=r.status_code) r.raise_for_status() ``` ```python # scripts/usda_fdc.py:33-41 def search_food(query: str) -> List[Dict[str, Any]]: _require_key() url = f"{FDC_BASE}/foods/search" payload = { "query": query, "pageSize": SEARCH_PAGE_SIZE, "dataType": PREFERRED_DATA_TYPES, } r = requests.post( url, params={"api_key": USDA_API_KEY}, json=payload, timeout=HTTP_TIMEOUT_SEC, ) ``` ```python # scripts/spoonacular.py:35-46 def search_ingredient(name: str) -> List[Dict[str, Any]]: _require_key() url = f"{SPOONACULAR_BASE}/food/ingredients/search" params = { "query": name, "number": SPOONACULAR_SEARCH_LIMIT, "apiKey": SPOONACULAR_API_KEY, } r = requests.get(url, params=params, timeout=HTTP_TIMEOUT_SEC) ``` ```python # scripts/core.py:117-119 except SpoonacularError as e: spoon_errors.append(f"Spoonacular query failed ({term}): {e}") except Exception as e: spoon_errors.append(f"Unexpected Spoonacular query error ({term}): {e}") ``` ```python # scripts/core.py:349-350 except Exception as e: return _error(f"USDA query failed ...[truncated 2738 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Never return raw request exceptions to users** - Replace `str(e)` in user-facing errors and notes with fixed, provider-specific messages. - Preserve detailed diagnostics only in protected server-side logs after redaction. 2. **Handle every non-success response explicitly** - Replace the final `raise_for_status()` path with a sanitized custom exception: ```python def _handle_response(r: requests.Response): if r.status_code == 401: raise USDAError("USDA authentication failed", status=401) if r.status_code == 403: raise USDAError("USDA access was denied", status=403) if r.status_code == 429: raise USDAError("USDA rate limit reached", status=429) if r.status_code >= 500: raise USDAError("USDA service is temporarily unavailable", status=503) if not r.ok: raise USDAError( f"USDA request failed with HTTP status {r.status_code}", status=r.status_code, ) ``` 3. **Introduce centralized credential redaction** - Redact values associated with `api_key`, `apiKey`, `Authorization`, `token`, and similar fields before logging or returning exception data. - Apply redaction to both URL query strings and serialized request metadata. 4. **Avoid placing credentials in result notes** - Store only a provider name, sanitized status code, and stable internal error identifier. - Do not expose prepared request URLs, response bodies, headers, or exception representations. 5. **Add regression tests** - Mock unhandled responses such as HTTP 400, 404, and 422. - Assert that neither the USDA key nor the Spoonacular key appears in returned values, notes, exception messages, or captured logs. 6. **Consider safer authentication transport where supported** - If a provider supports authentication headers, prefer them over URL parameters. - Where query-parameter authentication is required by the provider, ensure URLs are always treat ...[truncated 66 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (38)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The code’s core behavior is limited to Spoonacular API access for ingredient search and nutrient retrieval. It validates only SPOONACULAR_API_KEY, makes no USDA API calls, and contains no fallback logic. It also does not include any image recognition, cooking coefficient adjustments, or cross-validation functionality. While 'search optimization' is partially supported by candidate selection logic, several prominently declared capabilities are absent, making the description materially broader than the implemented behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The code chunk does not implement the declared primary function of querying nutrition data or estimating calories. Instead, it is a narrow translation helper/cache for Chinese food names. It performs no network access, uses no API keys, does not call Spoonacular or USDA, and includes none of the declared higher-level features such as cooking coefficients, cross-validation, or image recognition. While this translation helper could be a supporting component within a larger nutrition skill, the evaluation is for whether the supplied code chunk accurately matches the declared description; on its own, it materially differs from that description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
声明描述的是一个以 Spoonacular 为主、USDA 为后备的营养查询/热量估算技能,且包含多项高级能力。实际代码片段只是一个辅助性的单位换算模块,作用是把常见单位和份量估算为克重,属于营养计算流程中的底层支持工具,但单独看与声明的主要功能存在明显差距。其主目的并非外部营养数据查询,也未体现任何 API 使用、热量或营养分析、图像识别等核心能力,因此应判定为描述与代码行为不匹配。

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The supplied code chunk is narrowly focused on USDA FDC API operations. It performs HTTP requests to USDA endpoints, handles USDA-specific errors, extracts a small set of nutrient values, and scores candidate search results. There is no Spoonacular API usage, no fallback logic between Spoonacular and USDA, no image recognition, no cooking/yield coefficient processing, and no cross-validation between sources. While search optimization is partially represented by candidate ranking, the declared description materially overstates the implemented behavior and even misstates the primary data source for this code chunk. Therefore this is a clear description-behavior mismatch.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
Line L011 describes the repository domain as 'USDA FoodData Central nutrition lookup + meal calorie estimation,' which implies USDA is the main backing service. Later, L170 explicitly says Spoonacular is the primary data source and USDA is only an automatic fallback, so the documentation gives contradictory statements about the skill’s core behavior.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The guide mandates undisclosed LLM-based decomposer and image-recognizer sub-agents, expanding the skill’s effective behavior beyond a simple API-key nutrition lookup. This creates hidden data flows and capability escalation: non-English text and images may be routed to external model providers without explicit user awareness, increasing privacy, prompt-injection, and supply-chain risk.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
Forcing all non-English input through a mandatory LLM translation/decomposition path removes user choice and can transmit potentially sensitive user content to an external processor unnecessarily. In this skill’s context, food logs may include health-related or lifestyle information, so compulsory routing increases privacy and consent risk even if the intent is functional rather than malicious.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The hook activates on inputs described as 'composite dish / set meal / ambiguous' without defining clear boundaries or exclusion examples. Many ordinary food queries could plausibly fit these labels, which may cause unintended or inconsistent sub-agent invocation.

Vague Triggers

Medium
Confidence
90% confidence
Finding
Items like 'set meals / takeout descriptions' and 'ambiguous descriptions' are high-level categories that do not precisely specify what should or should not trigger the hook. Without tighter constraints, the trigger surface is broad and may overlap with routine user food inputs.

Natural-Language Policy Violations

Medium
Confidence
87% confidence
Finding
The file states that 'LLM translation is the primary path' and that non-English input should be converted rather than handled in the original language. This imposes a language-specific processing choice on users without indicating any option to preserve their language or opt in to translation.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The README states that user food text and food photos may be sent to external services, including an image-recognizer sub-agent backed by third-party models and nutrition APIs, but it does not provide a clear user-facing privacy or data-sharing notice. This creates a real privacy risk because potentially sensitive dietary, health-adjacent, or image data could be transmitted off-system without informed user awareness or consent.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill declares environment variable and external API usage in metadata, but it does not define an explicit tool scope such as permissions or allowed-tools. This creates an authorization ambiguity where a host may grant broader network or secret access than reviewers or users expect, increasing the chance of unintended exfiltration or over-privileged execution.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The workflow states that non-English input is automatically routed to a decomposition/translation sub-agent without user choice or explicit opt-in. This can expose sensitive dietary, health, or image-derived content to additional processing components and external services beyond what the user may reasonably expect, increasing privacy and data-governance risk.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The workflow mandates translating all non-English food names into English before lookup, without documenting user consent, locale handling, or preservation of the original query semantics. This can cause silent meaning drift for culturally specific dishes or ingredients, leading to incorrect nutritional results and privacy/compliance concerns if users reasonably expect processing in their original language.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The README states that when non-English characters are detected, the main agent must invoke this sub-agent, and the output `name` field is forced to English for downstream use. This imposes an English-language transformation by default rather than offering a language choice or opt-in, which matches the language/locale policy violation criteria.

External Transmission

Medium
Category
Data Exfiltration
Content
# Free signup / 免费申请: https://fdc.nal.usda.gov/api-key-signup.html

USDA_API_KEY = os.getenv("USDA_FDC_API_KEY", "").strip()
FDC_BASE = "https://api.nal.usda.gov/fdc/v1"

CACHE_DB_PATH = os.getenv("CALORIE_SKILL_CACHE_DB", "calorie_skill_cache.sqlite3")
HTTP_TIMEOUT_SEC = 15
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
# Free signup / 免费申请: https://fdc.nal.usda.gov/api-key-signup.html

USDA_API_KEY = os.getenv("USDA_FDC_API_KEY", "").strip()
FDC_BASE = "https://api.nal.usda.gov/fdc/v1"

CACHE_DB_PATH = os.getenv("CALORIE_SKILL_CACHE_DB", "calorie_skill_cache.sqlite3")
HTTP_TIMEOUT_SEC = 15
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
Multiple natural-language strings returned to users are written only in Chinese, including warnings and clarification prompts. This imposes a single language/locale choice with no indication of user selection or documented locale constraint.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
This code sends user-provided food names and quantities to external services (Spoonacular and USDA) and later caches meal/query data, but there is no confirmation prompt, visible logging/print, or inline user-facing warning in the file. Because these requests can transmit potentially sensitive dietary information and store it for later reuse, the behavior should be disclosed to users.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The prompt asking the user to restate quantity is presented only in Chinese, with no alternate locale or configurable language handling shown in this file. That creates a locale policy issue unless the skill is explicitly documented as Chinese-only.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
These user-visible messages are emitted only in Chinese and there is no evidence of locale negotiation or opt-in. This can violate language/locale policy for users expecting another language.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The function generates follow-up questions for missing amounts in Chinese only, and those are part of the direct user interaction flow. Without user choice or documented regional scope, this is a natural-language policy violation.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The raised error strings are written in Chinese, which imposes a specific language on users and developers interacting with the skill. The file does not offer a language choice or document a justified region-specific constraint, so this conflicts with the language/locale policy.

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
The docstring specifies that translation notes are returned in Chinese ("翻译:X → Y"), and the implementation returns Chinese-language status strings. This imposes a specific language on downstream user-facing output without opt-in or justification, which matches the locale-policy violation criteria.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The function returns note strings in Chinese for both exact and substring matches, with no option to localize or suppress them. Because this file otherwise performs Chinese-to-English translation, forcing Chinese metadata can violate the requirement not to enforce a specific language without user opt-in.

Static analysis

No suspicious patterns detected.