Back to skill

Security audit

grsai nano-banana 生图技能

Security checks for vulnerabilities and agentic risk

Overview

This image-generation skill does what it claims, but its handling of API keys, network destinations, downloads, and output files is too loosely controlled for automatic trust.

Install only if you trust the publisher and are comfortable sending prompts, reference image URLs, and a grsai API key to the configured endpoint. Avoid putting real API keys in command-line history, do not use --base-url unless you fully trust the destination, avoid sensitive or internal image URLs, and save output only to a controlled directory where overwrites are acceptable.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
generate.py:87
Finding
API Key and User Content Can Be Sent to an Arbitrary Server## Vulnerability Details **File Location**: `generate.py`, lines 87-89, 124-143, and 166-176 **Vulnerability Type**: Unrestricted credential destination **Risk Level**: High ### Vulnerable Code ```python parser.add_argument("--base-url", default="https://grsai.dakka.com.cn", help="grsai API 基础 URL(默认:https://grsai.dakka.com.cn)") ``` ```python def submit_task(args): """提交生图任务""" url = f"{args.base_url}/v1/draw/nano-banana" payload = { "model": args.model, "prompt": args.prompt, "imageSize": args.resolution, "aspectRatio": args.aspect_ratio, "webHook": "-1", "shutProgress": False } if args.input_images: payload["urls"] = args.input_images headers = { "Authorization": f"Bearer {args.api_key}", "Content-Type": "application/json" } try: response = requests.post(url, json=payload, headers=headers, timeout=30) ``` ```python def poll_result(args, task_id): """轮询生图结果""" url = f"{args.base_url}/v1/draw/result" headers = { "Authorization": f"Bearer {args.api_key}", "Content-Type": "application/json" } for attempt in range(1, args.max_retries + 1): try: response = requests.post( url, json={"id": task_id}, headers=headers, timeout=30 ) ``` ### Technical Analysis The user-controlled `--base-url` value is directly incorporated into both API request URLs. No hostname allowlist, scheme restriction, certificate policy beyond the library default, or destination validation is applied before attaching the bearer token. Consequently, the script can send the following sensitive information to any destination selected through `--base-url`: - The grsai bearer API key - The complete im ...[truncated 1658 chars]
Remediation
## Remediation Suggestions 1. Remove `--base-url` from normal user-facing operation and use a fixed, reviewed API endpoint. 2. If custom deployments are necessary, enforce an explicit allowlist of approved HTTPS hostnames. 3. Parse the URL with `urllib.parse.urlparse` and reject: - Non-HTTPS schemes - Embedded credentials - Unexpected ports - IP literals - Private, loopback, link-local, or reserved addresses 4. Disable automatic redirects for credential-bearing requests with `allow_redirects=False`, or validate every redirect destination before following it. 5. Scope API keys to the minimum service permissions and support rapid key revocation. 6. Clearly disclose which endpoint receives prompts, reference URLs, and credentials before submission.

T09 · Insecure Skill Coding Practices

Warning
Location
generate.py:56
Finding
API Key Is Required on the Command Line## Vulnerability Details **File Location**: `generate.py`, line 56 **Vulnerability Type**: Sensitive credential exposure through process arguments **Risk Level**: Medium ### Vulnerable Code ```python parser.add_argument( "--api-key", "-k", required=True, help="grsai API Key" ) ``` The documented invocation pattern also places the secret directly in the command: ```bash uv run generate.py \ --prompt "可爱柴犬头像" \ --aspect-ratio "1:1" \ --api-key "sk-xxx" ``` ### Technical Analysis Command-line arguments are not an appropriate primary channel for secrets. Depending on the operating system and execution environment, arguments can be exposed through: - Shell history files - Process inspection interfaces - Job-control and orchestration logs - Agent execution traces - Monitoring and telemetry systems - Debug output or copied command transcripts Although `SKILL.md` describes environment or OpenClaw configuration as possible key-storage methods, the script does not read those sources and instead marks `--api-key` as required. The implementation therefore contradicts the safer documented configuration approach. ### Attack Path 1. The victim follows the documented command and supplies a real key with `--api-key`. 2. The shell stores the complete command in its history, or an execution framework records it. 3. Another local user, administrator, support operator, log consumer, or compromised process reads the recorded command. 4. The observer extracts the API key. 5. The key is used to consume credits or invoke the external API without authorization. ### Impact Assessment Exposure is generally limited to principals that can inspect process metadata, command history, or execution logs. A captured key may provide all external-service privileges assigned to that key, including consumption of paid credits. This issue does not independently provide local privilege escalation.
Remediation
## Remediation Suggestions 1. Read the key from a dedicated environment variable such as `GRSAI_API_KEY`. 2. Integrate with the OpenClaw credential configuration described by the Skill metadata. 3. If no configured key is available, request it interactively with `getpass.getpass()` so it is not echoed or stored in shell history. 4. Retain `--api-key` only as a deprecated compatibility option, accompanied by a clear warning. 5. Remove command examples that contain credentials in process arguments. 6. Ensure error messages and diagnostic logging never print the Authorization header or API key. 7. Recommend narrowly scoped, revocable keys and periodic rotation.

T09 · Insecure Skill Coding Practices

Warning
Location
generate.py:194
Finding
Untrusted Response URLs Are Downloaded Without Destination or Size Validation## Vulnerability Details **File Location**: `generate.py`, lines 194-208 and 274-275 **Vulnerability Type**: Unrestricted remote resource retrieval and unbounded download **Risk Level**: Medium ### Vulnerable Code ```python def download_image(image_url, output_path): """下载图片""" try: response = requests.get(image_url, timeout=120) response.raise_for_status() output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, 'wb') as f: f.write(response.content) return True except Exception as e: print(f"❌ 下载失败:{e}", file=sys.stderr) return False ``` ```python print(f"📥 下载图片:{image_url}") if download_image(image_url, output_path): ``` ### Technical Analysis The image URL originates in the remote API response and is passed directly to `requests.get()`. The script does not validate: - The URL scheme - The destination hostname or resolved IP address - Redirect targets - Whether the destination is private, loopback, link-local, or reserved - The response `Content-Type` - The response size - Whether the response is actually an image Because `response.content` buffers the complete response before writing it, a sufficiently large response can exhaust memory. The content is then written without a maximum file-size limit, creating a disk-exhaustion risk. The API is expected to return an image location, so a network download is necessary. However, unrestricted destination selection and unbounded buffering are not necessary for image generation. ### Attack Path 1. The API endpoint, an allowed custom endpoint, or an attacker who compromises that service returns a crafted `results[0].url`. 2. The script passes that URL to `requests.get()` without validation. 3. The URL points to an internal network service, a redirect chain, or an attacker-controlled oversized response. ...[truncated 720 chars]
Remediation
## Remediation Suggestions 1. Require HTTPS for downloaded result URLs. 2. Allowlist the documented image-delivery domains. 3. Resolve the hostname and reject loopback, private, link-local, multicast, and reserved IP ranges. 4. Disable redirects or validate the scheme, hostname, port, and resolved address of every redirect target. 5. Use streaming downloads: ```python requests.get(image_url, stream=True, timeout=120, allow_redirects=False) ``` 6. Enforce a strict maximum download size using both `Content-Length` and a running byte counter. 7. Require an approved image media type and verify the file signature before retaining the output. 8. Delete partial files after failed or oversized downloads. 9. Apply network-level egress controls so the Skill cannot reach internal metadata services or unrelated private networks.

T09 · Insecure Skill Coding Practices

Warning
Location
generate.py:220
Finding
User-Controlled Output Paths Can Overwrite Arbitrary Writable Files## Vulnerability Details **File Location**: `generate.py`, lines 220-223 and 274-275 **Vulnerability Type**: Unrestricted file-write destination **Risk Level**: Medium ### Vulnerable Code ```python args = parse_args() filename = generate_filename(args) output_path = Path(args.output_dir) / filename ``` ```python print(f"📥 下载图片:{image_url}") if download_image(image_url, output_path): ``` The resulting path is written as follows: ```python output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, 'wb') as f: f.write(response.content) ``` ### Technical Analysis Both `--output-dir` and `--filename` are user-controlled. The path is constructed without canonicalization or containment checks. A filename containing an absolute path or traversal components can escape the intended `generated` directory. Existing files are opened with `wb`, which truncates and replaces their contents. The write remains subject to the invoking process's operating-system permissions. Nevertheless, arbitrary writes anywhere within those permissions exceed the normal requirement to save a generated image in a designated output directory. ### Attack Path 1. An attacker influences an agent or user to invoke the Skill with a crafted filename or output directory. 2. The supplied path uses an absolute location or traversal components such as `../`. 3. `Path(args.output_dir) / filename` resolves outside the expected output directory. 4. The API-supplied response body is downloaded. 5. `open(output_path, 'wb')` truncates and overwrites the targeted file if the process can write to it. ### Impact Assessment The attacker may overwrite or corrupt files writable by the Skill process. Depending on the execution account and selected target, this could affect user documents, application configuration, workspace files, or scripts. The vulnerability does not bypass operating-system access controls and does no ...[truncated 144 chars]
Remediation
## Remediation Suggestions 1. Resolve the approved output directory and candidate output path with `Path.resolve()`. 2. Verify that the candidate path remains beneath the approved directory using `Path.is_relative_to()` or an equivalent containment check. 3. Reject absolute filenames, `..` components, path separators in `--filename`, and empty filenames. 4. Restrict generated filenames to a safe basename and an approved image extension. 5. Avoid silently replacing existing files; use exclusive creation mode or require explicit overwrite confirmation. 6. Detect and reject symbolic-link targets where practical. 7. Run the Skill as a minimally privileged user with write access only to the intended output directory.

T08 · Insecure Dependencies

Note
Location
generate.py:4
Finding
Dependency Version Is Not Reproducibly Pinned## Vulnerability Details **File Location**: `generate.py`, lines 4-7 **Vulnerability Type**: Unlocked third-party dependency **Risk Level**: Low ### Vulnerable Code ```python # /// script # requires-python = ">=3.10" # dependencies = [ # "requests>=2.28.0", # ] # /// ``` ### Technical Analysis The PEP 723 dependency specification accepts any available `requests` release at or above version 2.28.0. The project contains no lockfile or integrity hashes. Consequently, installations performed at different times can resolve different package versions. No evidence indicates that the declared `requests` package is malicious or typosquatted. The issue is a supply-chain hardening weakness: future resolution occurs outside the reviewed artifact and is not reproducible. ### Attack Path 1. A user invokes the script through `uv run`. 2. The package resolver selects a version satisfying `requests>=2.28.0`. 3. A future compromised, vulnerable, or otherwise unsafe compatible release is retrieved from the configured package index. 4. The dependency is installed or imported in the user's execution context. 5. Any malicious package behavior would execute with the same privileges as the Skill process. Exploitation depends on compromise of the package source, resolver configuration, or a future accepted release; no such compromise was identified during this audit. ### Impact Assessment A compromised dependency could execute code with the permissions of the user running the Skill and access the same files, environment variables, network connectivity, and API credentials. The present likelihood is low because the dependency name is legitimate and no malicious package was identified.
Remediation
## Remediation Suggestions 1. Pin `requests` to a reviewed exact version. 2. Commit a lockfile generated by the selected package manager. 3. Use integrity hashes where the deployment workflow supports them. 4. Configure installation to use a trusted package index. 5. Add automated dependency vulnerability and update monitoring. 6. Review and deliberately update the pinned dependency rather than accepting unreviewed future versions automatically.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (10)

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
The entire skill description and usage guidance are presented only in Chinese, including the user interaction instruction in L19, with no indication that other languages are supported or that the Chinese-only constraint is required for a region-specific use case. This can violate language/locale policy when a skill implicitly mandates a specific language without user opt-in.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill documents network usage and external API calls, but it does not declare any tool scope such as permissions or allowed-tools. This creates a mismatch between what the skill can do and what users or the platform can review upfront, increasing the risk of silent external access and weakening least-privilege controls.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill does not prominently warn users that prompts, image URLs, and authentication-backed requests are sent to a third-party service. Users may unknowingly disclose sensitive text, proprietary images, or internal URLs to an external provider, creating privacy and data-governance risk.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The natural-language invocation is framed broadly enough that ordinary conversation like 'help me generate an image' could trigger the skill without a clearly bounded command pattern. That increases the chance of accidental activation and unintentional transmission of user prompts or referenced images to the external service.

External Transmission

Medium
Category
Data Exfiltration
Content
**提交任务:**
```bash
curl -X POST "https://grsai.dakka.com.cn/v1/draw/nano-banana" \
  -H "Authorization: Bearer sk-xxx" \
  -H "Content-Type: application/json" \
  -d '{
Confidence
97% confidence
Finding
The documented curl examples explicitly transmit user prompts and bearer-token-authenticated requests to an external domain. While expected for an image-generation integration, this is still a real external-transmission risk because sensitive prompts, image references, and API credentials may be exposed to a third party or mishandled in logs and shell history.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill sends prompts, optional input image URLs, and an API key to a third-party service, but it provides no explicit warning or consent mechanism about external data transmission. In agent or automation contexts, users may unknowingly transmit sensitive prompts, proprietary URLs, or credentials to an external provider, creating privacy and compliance risk.

External Transmission

Medium
Category
Data Exfiltration
Content
}
    
    try:
        response = requests.post(url, json=payload, headers=headers, timeout=30)
        response.raise_for_status()
        result = response.json()
Confidence
80% 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
}
    
    try:
        response = requests.post(url, json=payload, headers=headers, timeout=30)
        response.raise_for_status()
        result = response.json()
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Tainted flow: 'task_id' from requests.post (line 153, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
print(f"🔄 第 {attempt}/{args.max_retries} 次轮询...")
        
        try:
            response = requests.post(url, json={"id": task_id}, headers=headers, timeout=30)
            response.raise_for_status()
            result = response.json()
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'image_url' from requests.post (line 194, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
def download_image(image_url, output_path):
    """下载图片"""
    try:
        response = requests.get(image_url, timeout=120)
        response.raise_for_status()
        
        output_path.parent.mkdir(parents=True, exist_ok=True)
Confidence
93% confidence
Finding
The script downloads a URL returned by the remote API without validating its scheme, host, or IP range. If the configured service or a compromised upstream endpoint returns an attacker-controlled URL, the local environment can be induced to make arbitrary outbound requests, creating SSRF-style behavior and potentially accessing internal-only resources.

Static analysis

No suspicious patterns detected.