Back to skill

Security audit

VVMAI Image Generator

Security checks for vulnerabilities and agentic risk

Overview

This image skill mostly matches its image-generation purpose, but it has underdocumented edit/upload behavior and weak safeguards that could expose API keys, user content, or local files.

Review before installing. Use this only if you trust VVMAI and the configured endpoint, keep VVMAI_BASE_URL pinned to the intended HTTPS service, avoid sensitive prompts or images, avoid remote --input URLs, and do not use path components in --filename unless the script is fixed to validate destinations and download limits.

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/gen.py:60
Finding

Configurable API Endpoint Can Expose the API Key and User Content

Content
View full analysis
dict: """POST /images/generations""" url = f"{base_url.rstrip('/')}/images/generations" payload = { "model": model, "prompt": prompt, "n": count, "response_format": response_format, } if size: payload["size"] = size if aspect_ratio: payload["aspect_ratio"] = aspect_ratio body = json.dumps(payload).encode("utf-8") req = urllib.request.Request( url, method="POST", headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, data=body, ) ``` ```python api_key = os.environ.get("VVMAI_API_KEY", "").strip() base_url = os.environ.get("VVMAI_BASE_URL", "https://api.vvmai.com/v1").strip() ``` The same endpoint construction and bearer-token transmission pattern is also used by `request_edit`, where an input image is included in the request. ### Technical Analysis The Skill legitimately needs to transmit an API key and prompt to the declared VVMAI image-generation service. However, `VVMAI_BASE_URL` is accepted without validating its scheme or destination host. Consequently, the bearer credential and request content can be sent to any configured host, including a plaintext HTTP endpoint. This exceeds the minimum network privilege needed for the default functionality. A Skill intended to access VVMAI only needs access to VVMAI's authenticated HTTPS endpoint. Supporting arbitrary endpoints without an explicit trust boundary allows a modified environment or configuration file to redirect sensitive requ ...[truncated 1370 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/gen.py:31
Finding

Arbitrary Input-Image URL Fetching Enables SSRF and Unbounded Downloads

Content
View full analysis
Path: """Download remote image to a temp file with chunked read.""" import tempfile suffix = ".jpg" if ".jpg" in url.lower() else ".png" tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix) req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) with urllib.request.urlopen(req, timeout=60) as resp: with open(tmp.name, "wb") as f: while True: chunk = resp.read(65536) if not chunk: break f.write(chunk) return Path(tmp.name) ``` ```python if args.input: # Edit mode input_path = args.input.strip() if input_path.startswith("http"): print(f"Downloading input image...", file=sys.stderr) local_input = download_to_temp(input_path) else: local_input = Path(input_path) ``` ### Technical Analysis Edit mode treats any string beginning with `http` as a remotely downloadable image. The implementation does not: - Restrict requests to public HTTPS destinations. - Reject loopback, private, link-local, multicast, or reserved IP ranges. - Block cloud instance-metadata addresses. - Revalidate destinations after DNS resolution or redirects. - Verify that the response is actually an image. - Limit the total number of downloaded bytes. - Remove the temporary file after the edit request completes. The 60-second timeout limits an individual network operation but does not impose a maximum response size. A sufficiently fast endpoint can therefore write a large amount of data to disk within the timeout. Redirect handling by the URL library may also move the request from an apparently public URL to an internal destination unless explicitly ...[truncated 2057 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gen.py:145
Finding

Unsanitized Output Filename Allows Writes Outside the Selected Directory

Content
View full analysis
None: for idx, item in enumerate(data): image_url = item.get("url") image_b64 = item.get("b64_json") if response_format == "url" and image_url: print(f"MEDIA:{image_url}") continue # Save mode: decode b64 or fallback to url download if not image_b64 and image_url: # API returned url even though we asked for b64 — download it ts = datetime.now().strftime("%Y%m%d-%H%M%S") fn = filename or f"{ts}-{slugify(prompt)}{f'-{idx+1}' if count > 1 else ''}.png" fp = out_dir / fn urllib.request.urlretrieve(image_url, fp) elif image_b64: ts = datetime.now().strftime("%Y%m%d-%H%M%S") fn = filename or f"{ts}-{slugify(prompt)}{f'-{idx+1}' if count > 1 else ''}.png" fp = out_dir / fn fp.write_bytes(base64.b64decode(image_b64)) ``` ```python ap.add_argument("--filename", default="", help="Custom output filename.") ``` ### Technical Analysis The value supplied through `--filename` is used directly as a path component. It is not restricted to a basename and is not checked for absolute paths or parent-directory traversal. With `pathlib`, joining `out_dir` to an absolute `fn` discards `out_dir`. A relative filename containing `../` can similarly resolve outside the intended directory. Both `Path.write_bytes` and `urllib.request.urlretrieve` may replace an existing writable file. The file contents are generated-image bytes returned by the configured API. Although this limits precise content control in ordinary use, an attacker who con ...[truncated 1521 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The documented purpose understates actual behavior: the skill can perform image edits, fetch remote input images, and save files in ways that contradict the stated default behavior. This mismatch is dangerous because operators may approve or invoke the skill under false assumptions, leading to unexpected external data transmission, local file creation, and broader attack surface than disclosed.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill declares no explicit tool scope even though its documented behavior requires environment access, network access, and file writes. That is dangerous because consumers and enforcement layers cannot clearly constrain what the skill is allowed to do, increasing the chance of overbroad execution and abuse if the implementation changes or is invoked unexpectedly.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · README.md (reported line 21)May include surrounding context.

md
args = ap.parse_args()

    api_key = os.environ.get("VVMAI_API_KEY", "").strip()
    base_url = os.environ.get("VVMAI_BASE_URL", "https://api.vvmai.com/v1").strip()
    if not api_key:
        print("Error: VVMAI_API_KEY not set.", file=sys.stderr); sys.exit(1)
    if not base_url:

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

md
args = ap.parse_args()

    api_key = os.environ.get("VVMAI_API_KEY", "").strip()
    base_url = os.environ.get("VVMAI_BASE_URL", "https://api.vvmai.com/v1").strip()
    if not api_key:
        print("Error: VVMAI_API_KEY not set.", file=sys.stderr); sys.exit(1)
    if not base_url:

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/gen.py (reported line 197)May include surrounding context.

python
args = ap.parse_args()

    api_key = os.environ.get("VVMAI_API_KEY", "").strip()
    base_url = os.environ.get("VVMAI_BASE_URL", "https://api.vvmai.com/v1").strip()
    if not api_key:
        print("Error: VVMAI_API_KEY not set.", file=sys.stderr); sys.exit(1)
    if not base_url:

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/gen.py (reported line 201)May include surrounding context.

python
args = ap.parse_args()

    api_key = os.environ.get("VVMAI_API_KEY", "").strip()
    base_url = os.environ.get("VVMAI_BASE_URL", "https://api.vvmai.com/v1").strip()
    if not api_key:
        print("Error: VVMAI_API_KEY not set.", file=sys.stderr); sys.exit(1)
    if not base_url:

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The code forces local saving for any model whose name starts with 'gpt-image' by setting should_save = args.save or args.oss or is_gpt_image, which contradicts the stated behavior that images are not saved locally by default. This can cause unexpected persistence of generated content on disk, creating privacy and data-handling risks if users believe outputs will only be returned as URLs.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

When --input is used, the script uploads the provided local image to an external VVMAI endpoint without an explicit runtime warning or confirmation that user content is being transmitted off-host. In a skill context, users may supply sensitive images and assume local-only processing, making undisclosed third-party transmission a meaningful privacy issue.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The README explicitly documents that generated images may be written to local disk via --save, --oss, and always for gpt-image models, but it does not clearly warn users at the point of use that local files will be created. In an agent context, silent or insufficiently disclosed file writes can surprise users, create persistence of sensitive/generated content, and increase the risk of unintended storage on shared systems.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The top-level docstring frames the script as an image generation and editing client against VVMAI endpoints, but the implementation also downloads remote input images and persists output images to local filesystem paths. This is not merely omitted detail in one place: the docstring presents the script's behavior in a narrower way than the implemented local file handling.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

Prompts are sent to a remote API as part of normal service operation, but the script does not explicitly disclose this data sharing at runtime. This is a lower-severity privacy/transparency issue because prompts may contain sensitive text even though remote transmission is expected for hosted image generation.

Content

No source excerpt is available for this finding.