Back to skill

Security audit

Generate images using Runware API

Security checks for vulnerabilities and agentic risk

Overview

This image-generation skill mostly matches its purpose, but it gives the agent broad, automatic authority to send prompts to Runware and write files locally without enough user control.

Review before installing. Use a dedicated Runware API key with limited billing exposure, avoid sensitive prompts, and run the skill only where local file writes are acceptable. Prefer changing the skill so it asks before sending prompts, pins dependencies, confines output to a dedicated image directory, and refuses to overwrite existing files unless explicitly requested.

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

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Unpinned Third-Party Dependencies Create a Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-3`; installation occurs in `.github/workflows/ci.yml:18-20` and is documented in `SKILL.md:40-42` **Vulnerability Type**: T08: Insecure Dependencies **Risk Level**: Medium ### Vulnerable Code `requirements.txt:1-3`: ```text requests>=2.28.0 python-dotenv pytest>=7.0.0 ``` `.github/workflows/ci.yml:18-20`: ```yaml - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt ``` `SKILL.md:40-42`: ```text 1. Install dependencies - pip install -r requirements.txt (The script uses requests and python-dotenv; keep requirements minimal.) ``` ### Technical Analysis The project installs packages directly from the configured Python package index without exact version pins or integrity hashes. `python-dotenv` has no version constraint, while `requests>=2.28.0` and `pytest>=7.0.0` permit any later release satisfying the lower bound. Consequently, the code reviewed during this audit is not sufficient to determine the code that will execute during a future installation. A newly published, compromised, or otherwise unsafe dependency version could be selected automatically. Python package installation may execute build-system code, and imported dependency code executes with the privileges of the user or CI runner. No evidence was found that the currently named packages are malicious or that dependency confusion or typosquatting is presently occurring. The vulnerability is the absence of reproducible dependency resolution and artifact integrity controls. ### Attack Path 1. An upstream dependency release or its distribution account is compromised, or an unsafe future release is published. 2. A user or CI job runs `pip install -r requirements.txt`. 3. Pip resolves the mutable constraints to the affected release because exact versions and hashes are absent. 4. Malicious build hooks may execute during installation, or malicious package cod ...[truncated 749 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin all direct and transitive dependencies to reviewed versions using a lockfile generated by a tool such as `pip-tools`, Poetry, or uv. 2. Install with hash verification, for example: ```bash pip install --require-hashes -r requirements.lock ``` 3. Separate runtime dependencies from development dependencies so end users do not need to install `pytest`. 4. Use automated dependency scanning and controlled update pull requests. 5. Review dependency release notes and artifact provenance before updating pins. 6. In CI, install from the reviewed lockfile rather than directly from mutable constraints. 7. Restrict CI token permissions and avoid exposing secrets to dependency-installation steps. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_image.py:157
Finding
Unrestricted Output Path Allows Overwriting Arbitrary User-Writable Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_image.py:157-193` **Vulnerability Type**: T09: Insecure Skill Coding Practices **Risk Level**: Medium ### Vulnerable Code ```python if args.outfile: # Expand user (~) and environment variables (%VAR%) outfile_expanded = os.path.expandvars(args.outfile) outfile_expanded = os.path.expanduser(outfile_expanded) out = Path(outfile_expanded) if not out.is_absolute(): base_dir = Path(last_dir) if last_dir else Path(default_dir).expanduser() base_dir = base_dir.expanduser() base_dir.mkdir(parents=True, exist_ok=True) out = base_dir / out else: # if absolute path, ensure parent exists out.parent.mkdir(parents=True, exist_ok=True) # remember the directory for next time try: cfg["last_output_dir"] = str(out.parent) CONFIG_PATH.write_text(json.dumps(cfg, indent=2)) except Exception: pass else: out_dir = Path(last_dir).expanduser() if last_dir else Path(default_dir).expanduser() out_dir.mkdir(parents=True, exist_ok=True) # create filename from prompt slug = slugify(prompt) from datetime import datetime ts = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"{slug}_{ts}.{ext}" out = out_dir / filename # remember the directory for next time try: cfg["last_output_dir"] = str(out_dir) CONFIG_PATH.write_text(json.dumps(cfg, indent=2)) except Exception: pass save_output_image(b64, out) ``` The final write is implemented at `scripts/generate_image.py:44-47`: ```python def save_output_image(b64_data: str, out_path: Path): img = base64.b64decode(b64_data) out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_bytes(img) ``` ### Technical Analysis The `--outfile` argument accepts absolute paths and relative paths containing traversal components such as `../`. Environment-variable and home-directory expan ...[truncated 1951 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve all output paths against a dedicated output directory and reject paths that escape it: ```python base_dir = Path(default_dir).expanduser().resolve() candidate = (base_dir / args.outfile).resolve() try: candidate.relative_to(base_dir) except ValueError: raise ValueError("Output path must remain inside the configured output directory") ``` 2. Reject absolute paths unless an explicit, trusted-user-only option enables them. 3. Reject `..` traversal components before creating directories. 4. Avoid silent replacement by opening files in exclusive-creation mode or requiring an explicit `--overwrite` option. 5. Validate decoded content using an image parser and enforce an approved format and extension. 6. Consider generating the output filename internally and exposing only a sanitized basename to agent-controlled input. 7. Do not persist untrusted output directories to `skill-config.json`. 8. Add tests covering absolute paths, traversal paths, symlink escapes, existing-file replacement, invalid base64, and non-image response data. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (20)

Credential Access

High
Category
Privilege Escalation
Content
HERE = Path(__file__).resolve().parent.parent
CONFIG_PATH = HERE / "skill-config.json"
ENV_PATH = HERE / ".env"

load_dotenv(ENV_PATH)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill documents behavior that requires environment access, local file reads/writes, and outbound network access, but it declares no explicit tool scope or permissions boundary. This is dangerous because an agent may invoke the skill with broader capabilities than users expect, reducing transparency and making external transmission and local file creation happen without clear consent controls.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger phrases are extremely broad and map to common natural-language requests like 'generate an image' or 'draw something,' making unintended invocation likely. In this skill's context, accidental invocation matters because it can cause immediate outbound API calls and local file writes, potentially exposing user prompts to a third party without a clear opt-in moment.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
Provide a secure, documented, and testable integration for generating images via the Runware.ai Image Inference API.

IMPORTANT INSTRUCTIONS FOR AGENT:
1. **Do NOT ask the user for the RUNWARE_API_KEY.** The script automatically loads it from the `.env` file in the skill directory.
2. **Do NOT ask clarifying questions** (style, size, etc.) unless the user's prompt is extremely vague. For requests like "generate a man on the moon", use your best judgment for the prompt and run the script immediately.
3. **Execute the script directly.** Do not propose it.
Confidence
92% confidence
Finding
The skill instructs the agent not to ask clarifying questions and to execute the script directly using its own judgment. In this context, that creates unsafe autonomy because the agent may send unintended prompts to a third-party API, choose parameters the user did not approve, and save files locally without an explicit confirmation step.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The description explains what the skill does but omits a clear warning that user prompts are transmitted to Runware and that generated files are written to the Downloads folder by default. This undermines informed consent and can lead users to share sensitive content or unintentionally create local artifacts on shared machines.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The privacy note implies the skill does not collect or store prompts or keys on the server, but the skill explicitly sends prompts to Runware's external API. This is dangerous because it may mislead users about data handling, causing them to submit sensitive prompts under a false assumption that no external service receives them.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The module docstring says the script will 'save to Downloads', implying a fixed and limited output location. In reality, the code defaults to a configurable directory from skill-config.json and also accepts --outfile, including absolute paths, allowing writes outside Downloads.

Intent-Code Divergence

Medium
Confidence
91% confidence
Finding
The file-level docstring and usage text frame the script as saving generated images to Downloads. However, lines L141-L190 implement output to skill-config-defined directories, remembered prior directories, or arbitrary absolute paths, which directly contradicts that stated behavior.

External Transmission

Medium
Category
Data Exfiltration
Content
def call_runware_generate(api_key: str, prompt: str, size: str = "1024x1024", output_format: str = "PNG", sync: bool = True, number_results: int = 1) -> dict:
    """Call Runware task API and return the parsed JSON response."""
    url = "https://api.runware.ai/v1/tasks"
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
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
"numberResults": number_results,
    }
    payload = [task_obj]
    resp = requests.post(url, json=payload, headers=headers, timeout=120)
    resp.raise_for_status()
    return resp.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: 'original_cfg' from pathlib.Path.read_text (line 18, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
assert out_file.stat().st_size > 0
    finally:
        if original_cfg is not None:
            cfg_path.write_text(original_cfg)
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.

Missing User Warnings

Low
Confidence
88% confidence
Finding
The markdown states that images are saved locally by default and that the script remembers the last used output directory, which affects user data on disk. While functionality is described, there is no explicit warning that running the skill will create directories and persist both images and configuration state locally.

Intent-Code Divergence

Low
Confidence
91% confidence
Finding
The document says the script automatically loads the API key from a .env file, while later saying the key must be provided via the environment or a secure secret manager. Contradictory secret-handling instructions can cause unsafe operator behavior, such as placing secrets in local files when the execution environment does not expect that, increasing the chance of accidental exposure.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0
python-dotenv
pytest>=7.0.0
Confidence
98% confidence
Finding
`requests>=2.28.0` allows a broad range of future versions rather than a fixed audited release. That weakens build reproducibility and can expose the skill to newly introduced vulnerable or compromised package versions at install time.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding
`requests` has known advisories, and because the manifest does not pin an exact version, it is impossible to verify from this file alone whether the installed version is patched. In a network-facing image generation skill that likely calls external APIs, uncertainty around HTTP client security is more relevant because request handling bugs can affect credentials, redirects, or TLS behavior.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0
python-dotenv
pytest>=7.0.0
Confidence
98% confidence
Finding
The dependency `python-dotenv` is unpinned, so installs may resolve to different versions over time. This creates supply-chain and reproducibility risk because a future vulnerable or malicious release could be pulled without any code change in the skill.

Unverifiable Dependency: python-dotenv has 2 known advisory(ies) (CVE-2026-28684 (python-dotenv: Symlink following in set_key allows arbitrary file overwrite via ); CVE-2026-28684 (python-dotenv reads key-value pairs from a .env file and can set them as environ)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
87% confidence
Finding
`python-dotenv` has known advisories, but the requirements file does not specify an exact version, so the deployed package could be vulnerable. This is mainly a configuration and supply-chain concern; if the skill loads `.env` files in unsafe contexts, vulnerable versions could affect local file handling or secret management.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0
python-dotenv
pytest>=7.0.0
Confidence
97% confidence
Finding
`pytest>=7.0.0` is also unpinned, which permits uncontrolled version drift. Even though it is typically a development dependency, unpinned packages still increase supply-chain uncertainty and may introduce vulnerable behavior in CI or test environments.

Unverifiable Dependency: pytest has 2 known advisory(ies) (CVE-2025-71176 (pytest has vulnerable tmpdir handling); CVE-2025-71176 (pytest has vulnerable tmpdir handling)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
84% confidence
Finding
`pytest` has known advisories, and without an exact pinned version there is no assurance that CI or development environments avoid affected releases. While this is less dangerous than a runtime dependency, compromised or vulnerable test tooling can still impact developer systems or pipeline integrity.

Missing User Warnings

Low
Confidence
82% confidence
Finding
This code file makes a real external API request with `api_key` and `prompt`, which can transmit user-provided or system-derived data. The file has an internal comment, but no user-facing warning, prompt, or visible disclosure in the test output about the external transmission.

Static analysis

No suspicious patterns detected.