Back to skill

Security audit

庄的配图创作 Master

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its image-prompting purpose, but its optional renderer handles third-party generation, local prompt logs, plaintext API-key configuration, and remote image downloads with weak safeguards.

Install only if you are comfortable sending prompt text and reference image URLs to the configured image provider. Prefer using an environment variable or secret manager instead of putting a live API key in config.json, avoid sensitive or proprietary prompts unless your provider policy allows it, and clean up run folders when they contain confidential material.

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
config.json:3
Finding

Provider API Key Stored in Plaintext Project Configuration

Content
View full analysis

Vulnerability Details

File Location: config.json:3-10, SKILL.md:116, scripts/render_image.py:147-149
Vulnerability Type: Plaintext credential storage
Risk Level: Medium

Vulnerable Code

config.json:3-10:

json
"channels": {
  "fal": {
    "provider": "fal.ai",
    "base_url": "https://fal.run",
    "model_id": "openai/gpt-image-2",
    "edit_model_id": "openai/gpt-image-2/edit",
    "api_key": ""
  }
}

SKILL.md:116:

text
The fallback script reads `config.json`. The user must fill the channel `api_key` before using that channel.

scripts/render_image.py:147-149:

python
api_key = (channel.get("api_key") or "").strip()
if not api_key:
    raise SystemExit("Missing API key. Fill config.json channels.fal.api_key.")

Technical Analysis

The documented configuration process requires users to place the fal.ai API key directly in config.json, a regular file inside the project. The implementation then reads the secret from that file without checking or enforcing restrictive file permissions.

Although the distributed configuration contains an empty key and therefore does not expose a credential by itself, normal use causes a live credential to be persisted in plaintext. Project files are commonly copied into archives, committed to source control, synchronized to shared storage, included in support bundles, or exposed to other local users. This creates a credential-disclosure risk that is avoidable through environment-based or secret-store-based configuration.

The key is also copied into the process environment as FAL_KEY when fal_client is available:

python
os.environ["FAL_KEY"] = api_key

Environment-based use is appropriate when the value originates from a securely provisioned environment variable, but it does not mitigate the original plaintext storage in config.json.

Attack Path

  1. A user follows the documented inst ...[truncated 946 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove api_key from the operational project configuration and read FAL_KEY directly from the environment:

    python
    api_key = os.environ.get("FAL_KEY", "").strip()
    if not api_key:
        raise SystemExit("Missing FAL_KEY environment variable.")
    
  2. Keep only non-sensitive provider settings in config.json.

  3. If file-based secrets must be supported, use a separate ignored secrets file and require restrictive permissions such as 0600.

  4. Add secret-bearing configuration files to .gitignore and provide a sanitized example file.

  5. Add automated secret scanning to the development and release process.

  6. Update SKILL.md to instruct users to provision credentials through environment variables or an operating-system secret manager.

  7. Rotate any API key that has previously been stored in a committed or shared copy of config.json.

  8. Use provider-side spending limits, narrowly scoped credentials, and regular key rotation to reduce the impact of disclosure.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/render_image.py:211
Finding

Unrestricted Download of Provider-Controlled Image URLs

Content
View full analysis

Vulnerability Details

File Location: scripts/render_image.py:211-220, scripts/render_image.py:235-246, scripts/render_image.py:271-279
Vulnerability Type: Server-side request forgery and unbounded remote-file download
Risk Level: Medium

Vulnerable Code

scripts/render_image.py:211-220 accepts any string beginning with http when it appears under selected response keys or resembles an image URL:

python
def extract_image_urls(value: Any) -> list[str]:
    urls: list[str] = []

    def walk(node: Any, key_hint: str = "") -> None:
        if isinstance(node, dict):
            for key, child in node.items():
                walk(child, key)
        elif isinstance(node, list):
            for child in node:
                walk(child, key_hint)
        elif isinstance(node, str):
            if node.startswith("http") and (
                key_hint in {"url", "image_url", "content_url"}
                or re.search(r"\.(png|jpg|jpeg|webp)(\?|$)", node, re.IGNORECASE)
            ):
                urls.append(node)

scripts/render_image.py:235-246 downloads the accepted URL without validating its destination, redirect chain, size, or decoded file type:

python
def download_image(url: str, destination: Path) -> Path:
    log(f"downloading image to {destination}")
    request = urllib.request.Request(url, headers={"User-Agent": "zhuang-infochart-master/1.0"})
    with urllib.request.urlopen(request, timeout=300) as response:
        content_type = response.headers.get("Content-Type", "")
        suffix = mimetypes.guess_extension(content_type.split(";")[0].strip()) if content_type else None
        final_path = destination
        if suffix and destination.suffix.lower() != suffix.lower():
            final_path = destination.with_suffix(suffix)
        with final_path.open("wb") as handle:
            shutil.copyfileobj(response, handle)
    return final_
...[truncated 3865 chars]
Remediation
View remediation

Remediation Suggestions

  1. Require https URLs and reject malformed URLs or embedded credentials.
  2. Maintain an explicit allowlist of trusted fal.ai image-delivery domains.
  3. Resolve every hostname before connecting and reject all loopback, private, link-local, multicast, reserved, and unspecified IP ranges for both IPv4 and IPv6.
  4. Disable redirects or validate the scheme, hostname, port, and resolved address after every redirect.
  5. Restrict connections to standard HTTPS ports unless another port is explicitly required.
  6. Enforce a strict maximum download size using both Content-Length and a streaming byte counter.
  7. Require an expected image MIME type and reject generic or executable content types.
  8. Decode the completed file with a trusted image library and reject data that is not a valid PNG, JPEG, or WebP image.
  9. Derive the final extension from the verified image format rather than untrusted headers or URL text.
  10. Delete partial files when validation or download fails.
  11. Apply filesystem quotas and write downloads through securely created temporary files before atomically moving validated images into the run directory.
  12. Log rejected destinations without exposing credentials or sensitive URL query parameters.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (9)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill instructs the agent to read files, write run artifacts, and potentially invoke a networked fallback renderer, but it does not declare any explicit tool scope such as allowed tools or permissions. This creates an overbroad execution surface where a host agent may grant more capabilities than are necessary, increasing the chance of unintended file, environment, or network access.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The workflow explicitly requires giving the user a Chinese translation or explanation of the final prompt, and later the reporting section requires a 中文理解版. This imposes a specific language on all users without documenting user choice or a justified locale constraint.

Content

No source excerpt is available for this finding.

Unbounded Output

Medium
Category
Output Handling
Confidence
80% confidence
Finding

Output size or generation rate is not bounded. Unbounded output enables denial-of-service through resource exhaustion, log flooding, or context-window stuffing.

Content

Scanner excerpt · SKILL.md (reported line 112)May include surrounding context.

--channel fal

text

The fallback script reads `config.json`. The user must fill the channel `api_key` before using that channel. All channels should expose the same command shape: mode, prompt input, channel, output run directory, image size, quality, number of images, output format, and edit reference images when needed.

## Bundled Resources

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Line L53 instructs that every visible text string be rendered verbatim in straight English double quotes. This is a natural-language policy concern because it enforces an English-specific punctuation/locale convention regardless of the user's language or regional formatting needs, and the file does not offer a choice or justify the constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The prompt explicitly instructs the model to produce "one final English image-generation prompt," which imposes a language choice regardless of user preference. This is a natural-language policy issue because the file does not offer a language option or explain why English is required.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The instruction 'exact visible text strings in straight English double quotes' imposes an English-language constraint in the skill's natural-language behavior. This is a locale/language policy issue because the file does not provide user opt-in, alternatives, or a documented justification for forcing English formatting.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The file’s instructions and user-facing guidance are entirely in Chinese and include directive behavior for how to present styles to the user, but they do not offer any language choice or state that Chinese is a justified locale requirement. This can violate the language/locale policy for skills that should not force a specific language without user opt-in.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script sends the user prompt and provider arguments to an external image provider over the network, but there is no user-facing disclosure or consent mechanism indicating that prompt contents and possibly reference image URLs will leave the local environment. In this skill, users may submit sensitive text, concepts, or proprietary visual materials for infographic generation, so silent third-party transmission creates a real confidentiality and compliance risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script persistently writes the full prompt and provider arguments to files in the run directory, which can expose sensitive user content, proprietary data, or confidential business information to other local users, backups, logs, or later unintended access. In this skill’s context, prompts may contain unpublished article content, internal data, or image-editing references, so undisclosed storage meaningfully increases privacy and data-handling risk.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.