Back to skill

Security audit

Nano Banana Image T8

Security checks for vulnerabilities and agentic risk

Overview

The skill largely matches its image-generation purpose, but its bundled script handles API keys in ways that could expose them to unintended URLs.

Install only if you are comfortable with this skill storing and reusing a Nano Banana API key on disk. Use a limited, revocable key, avoid shared machines, do not override the base URL, and rotate/delete the saved key if you stop using the skill. The publisher should validate the fixed API host, use an unauthenticated client for image downloads, and offer explicit key deletion or non-persistent mode before this should be treated as low risk.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/test_nano_banana_2.py:303
Finding
Caller-Controlled Base URL Can Exfiltrate the API Key<![CDATA[ ## Vulnerability Details **File Location**: `scripts/test_nano_banana_2.py:303-307, 362` **Vulnerability Type**: Unrestricted authenticated endpoint override **Risk Level**: High ### Vulnerable Code ```python parser.add_argument("--api-key", default=os.getenv("NANO_BANANA_API_KEY", "")) parser.add_argument("--check-key", action="store_true") parser.add_argument("--set-default-model", default="") parser.add_argument("--show-default-model", action="store_true") parser.add_argument("--base-url", default="https://ai.t8star.cn") ``` ```python with httpx.Client(headers=_build_headers(api_key), follow_redirects=True) as client: ``` The authorization header is constructed as follows: ```python def _build_headers(api_key: str) -> dict[str, str]: return {"Authorization": f"Bearer {api_key}"} ``` The supplied base URL is subsequently used for authenticated requests: ```python resp = client.post(f"{base_url}/v1/images/generations", json=payload, timeout=300) ``` ### Technical Analysis The documentation states that the API base address must remain fixed at `https://ai.t8star.cn`, but the implementation exposes `--base-url` without validating its scheme, hostname, port, or origin. All requests made through the client carry the global `Authorization: Bearer <API key>` header. Consequently, anyone who can influence the script invocation can replace the legitimate API endpoint with an attacker-controlled HTTPS server and receive the user's API key in the initial request. This violates the documented endpoint restriction and exceeds the minimum privileges needed for image generation. Redirect processing is also enabled globally, increasing the number of destinations the client may contact, although the direct arbitrary base URL is sufficient to exploit the issue. ### Attack Path 1. A user supplies a valid Nano Banana API key, or the script loads a previously saved key. 2. An attacker-controlled instruction causes the script to be invoked with: ```b ...[truncated 1075 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--base-url` option from production use if endpoint substitution is not required. 2. Otherwise, parse and validate the URL before creating any request: - Require the `https` scheme. - Require the exact hostname `ai.t8star.cn`. - Reject user information, fragments, alternate ports, and ambiguous host encodings. - Compare normalized origins rather than using string-prefix validation. 3. Disable automatic redirects for authenticated API requests: ```python httpx.Client(headers=_build_headers(api_key), follow_redirects=False) ``` 4. If redirects are operationally required, validate every redirect target and never forward authorization headers across origins. 5. Add tests proving that attacker-controlled domains, subdomains, alternate ports, non-HTTPS schemes, and deceptive URLs such as `ai.t8star.cn.attacker.example` are rejected. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/test_nano_banana_2.py:88
Finding
API-Controlled Image URLs Are Fetched with the Bearer Credential<![CDATA[ ## Vulnerability Details **File Location**: `scripts/test_nano_banana_2.py:88-95, 120-125, 157-162, 362` **Vulnerability Type**: Credential leakage through authenticated arbitrary-URL retrieval **Risk Level**: High ### Vulnerable Code ```python def _extract_image_bytes(item: dict[str, Any], client: httpx.Client) -> bytes: if "b64_json" in item and isinstance(item["b64_json"], str): return base64.b64decode(item["b64_json"]) url_value = item.get("url") if isinstance(url_value, str) and url_value: resp = client.get(url_value, timeout=60) resp.raise_for_status() return resp.content raise RuntimeError("响应中未包含 b64_json 或 url") ``` The API explicitly requests URL-based responses: ```python payload: dict[str, Any] = { "model": model, "prompt": prompt, "n": 1, "response_format": "url", } ``` The same pattern is used for image editing: ```python form_data: dict[str, str] = { "model": edit_model, "prompt": prompt, "n": "1", "response_format": "url", } ``` The download client has the API key attached globally: ```python with httpx.Client(headers=_build_headers(api_key), follow_redirects=True) as client: ``` ### Technical Analysis The image URL is obtained from a remote API response and is therefore untrusted. `_extract_image_bytes` passes that absolute URL to the same `httpx.Client` used for authenticated API calls. Because the client has a default bearer authorization header, a URL pointing to another origin receives the API key on the direct request. No validation restricts the URL to HTTPS or to an approved image-delivery host. The method also follows redirects, and the downloaded response is accepted as image data without checking its content type or size. This creates two related security problems: - The bearer credential may be disclosed to any host named in the API response. - The remote API can induce outbound GET requests to arbitrary reachable destinations. ...[truncated 1458 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never use the authenticated API client to download returned images. 2. Create a separate client without authorization headers: ```python with httpx.Client(follow_redirects=False) as download_client: response = download_client.get(validated_url, timeout=60) ``` 3. Strictly validate returned URLs: - Permit only HTTPS. - Use an explicit allowlist of trusted image-delivery hostnames. - Reject IP literals, loopback addresses, link-local addresses, private network ranges, and hostnames that resolve to those ranges. - Reject embedded credentials and nonstandard ports unless explicitly required. 4. Disable redirects or validate each redirect destination before following it. 5. Prefer `b64_json` responses if the service supports them securely, avoiding secondary downloads. 6. Apply a maximum response-size limit and stream the download rather than buffering an unlimited body. 7. Verify an expected image MIME type and validate the actual file signature before saving. 8. Use separate output extensions based on validated image formats. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:24
Finding
Long-Lived Plaintext API-Key Storage and Command-Line Secret Exposure<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:24-29, 66, 106`; `scripts/test_nano_banana_2.py:204-216, 219-245, 303` **Vulnerability Type**: Excessive credential persistence and insecure secret transport **Risk Level**: Medium ### Vulnerable Configuration and Code The Skill declares persistent credential storage: ```yaml - key: api_key label: API Key type: api_key required: true prompt: 请提供 Nano Banana API Key aliases: ["apikey", "api key", "key"] env_vars: ["NANO_BANANA_API_KEY"] saved_file: "~/.whaleclaw/credentials/nano_banana_api_key.txt" ``` The instructions state that the key will be persisted and reused: ```text 4. API Key comes from the user's conversation message and is passed through --api-key or an environment variable during execution; the script stores it at ~/.whaleclaw/credentials/nano_banana_api_key.txt with mode 600. ``` The recommended command template places the credential in a command-line argument: ```bash --api-key '<key extracted from the user message, or empty to use the saved key>' \ ``` The script stores the key as plaintext: ```python def _load_saved_api_key() -> str: if not _KEY_FILE.exists(): return "" return _KEY_FILE.read_text(encoding="utf-8").strip() def _save_api_key(api_key: str) -> None: _KEY_FILE.parent.mkdir(parents=True, exist_ok=True) _KEY_FILE.write_text(api_key.strip(), encoding="utf-8") os.chmod(_KEY_FILE, 0o600) ``` Interactive credentials are saved automatically: ```python entered = getpass.getpass("请输入 API Key(输入过程不可见): ").strip() if not entered: raise SystemExit( "缺少 API key,请通过 --api-key / 环境变量 NANO_BANANA_API_KEY / 交互输入提供" ) _save_api_key(entered) print(f"API Key 已保存到: {_KEY_FILE}") return entered ``` Command-line key input is enabled here: ```python parser.add_argument("--api-key", default=os.getenv("NANO_BANANA_API_KEY", "")) ``` ### Technical Analysis Image generation requires the key only while making the remote requ ...[truncated 2520 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make credential use ephemeral by default and do not save keys unless the user explicitly opts in. 2. Prefer passing secrets through a protected environment supplied directly to the child process or through standard input. Avoid command-line arguments. 3. When persistence is requested, use an operating-system credential manager or encrypted secret store rather than a plaintext file. 4. Provide explicit key-management operations: - Show whether a key exists without revealing it. - Delete the saved key. - Replace or rotate the key. - Define an expiry or require periodic reauthorization. 5. If a file fallback is unavoidable: - Create the parent directory with mode `0700`. - Create the credential file atomically with mode `0600` at creation time. - Reject symlinks. - Avoid a write-then-`chmod` sequence. 6. Align the documentation with the actual persistence behavior. 7. Ask for explicit informed consent before saving a key and clearly state its path, retention period, and deletion procedure. 8. Remove the `saved_file` declaration if durable storage is not essential to the Skill's declared functionality. ]]>
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

Tp2

High
Category
MCP Tool Poisoning
Confidence
85% confidence
Finding
Mixing characters from multiple Unicode scripts in a single identifier is a common technique to create visually ambiguous tool names.

Tp2

High
Category
MCP Tool Poisoning
Confidence
85% confidence
Finding
Mixing characters from multiple Unicode scripts in a single identifier is a common technique to create visually ambiguous tool names.

Tp2

High
Category
MCP Tool Poisoning
Confidence
85% confidence
Finding
Mixing characters from multiple Unicode scripts in a single identifier is a common technique to create visually ambiguous tool names.

Tp2

High
Category
MCP Tool Poisoning
Confidence
85% confidence
Finding
Mixing characters from multiple Unicode scripts in a single identifier is a common technique to create visually ambiguous tool names.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes shell scripts, reads environment variables, persists credentials to disk, and performs network calls, but it does not declare an explicit tool/permission scope. That weakens containment and reviewability, making it easier for a broadly capable skill to access sensitive resources without clear policy boundaries.

Vague Triggers

Medium
Confidence
91% confidence
Finding
Broad triggers such as 'image generation test' and 'image edit test' can cause the skill to activate outside the intended Nano Banana workflow. Because this skill can accept, use, and persist API keys, accidental activation increases the chance of unintended credential collection or execution in unrelated conversations.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill explicitly stores API keys in a persistent file and reuses them in future sessions, but the user-facing description does not prominently warn users before that storage occurs. This creates a consent and secret-handling risk, especially in shared or multi-user environments where persisted credentials may outlive the user's expectation.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The file contains user-facing messages in Chinese, and the interactive prompts and errors throughout the script are also Chinese-only. For a general-purpose test utility, this imposes a specific language on users without opt-in or documented justification, which matches the natural-language locale policy violation criteria.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The script persistently stores the API key under the user's home directory and automatically reuses it later. For a test utility, long-term credential retention increases the chance of local disclosure through weak filesystem hygiene, backups, shared accounts, or accidental inclusion in other tooling, even though the file mode is restricted to 0600.

Context-Inappropriate Capability

Low
Confidence
84% confidence
Finding
Beyond testing image generation and editing, the code maintains a saved default model in ~/.whaleclaw/credentials/nano_banana_default_model.txt and exposes commands to show or change it. This is a convenience configuration capability rather than a direct requirement of the declared testing purpose.

Static analysis

No suspicious patterns detected.