Back to skill

Security audit

Stability Ai

Security checks for vulnerabilities and agentic risk

Overview

This image-generation skill is mostly purpose-aligned, but it needs review because it can send API credentials to an arbitrary configured host and can automatically delete unrelated image files in a chosen output directory.

Install only if you are comfortable sending prompts to Stability AI or a configured API_HOST, and avoid setting API_HOST to anything untrusted or non-HTTPS. Use a dedicated empty output directory because the cleanup routine can delete older PNG, JPG, JPEG, WebP, and same-basename JSON files in that directory. Treat generated JSON metadata as potentially sensitive because it stores prompts and settings.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/core/generate.py:14
Finding
Stability API Credential Disclosure Through an Unrestricted Custom API Host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/core/generate.py:14-15, 68, 103-116, 188, 218-231` **Vulnerability Type**: Credential disclosure to an attacker-controlled network endpoint **Risk Level**: High ### Vulnerable Code ```python API_HOST = os.getenv('API_HOST', 'https://api.stability.ai') API_KEY = os.getenv("STABILITY_API_KEY") ``` ```python url = f"{API_HOST}/v1/generation/{model}/text-to-image" headers = { "Accept": "application/json", "Content-Type": "application/json", "Authorization": f"Bearer {API_KEY}", } response = requests.post(url, headers=headers, json=body, timeout=120) ``` The same behavior is present in the V2 request: ```python url = f"{API_HOST}/v2beta/stable-image/generate/core" headers = { "Authorization": f"Bearer {API_KEY}", } response = requests.post(url, headers=headers, files=files, data=data, timeout=120) ``` ### Technical Analysis The destination host is loaded directly from the `API_HOST` environment variable without validating its scheme or hostname. The script then unconditionally places `STABILITY_API_KEY` in the HTTP `Authorization` header. Although sending the key to Stability AI is necessary for the declared image-generation functionality, allowing an unrestricted destination exceeds the minimum privilege required. A modified `.env` file, inherited environment variable, or unsafe deployment configuration can redirect the request to any server. The implementation also permits a plain HTTP URL, which would expose the key and prompt data to network interception. Both API implementations are affected. In addition to the bearer credential, the requests transmit the user-provided prompt, negative prompt, generation parameters, and other request metadata. ### Attack Path 1. An attacker modifies the Skill's `.env` file or otherwise controls the `API_HOST` environment variable. 2. The attacker sets `API_HOST` to an endpoint under their control, such as `https://attacker.example`. 3 ...[truncated 999 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict the production endpoint to the expected Stability AI origin: ```python API_HOST = "https://api.stability.ai" ``` 2. If custom endpoints are a required feature, parse the URL and enforce: - HTTPS only. - An explicit hostname allowlist. - No embedded URL credentials. - A permitted port list. - No ambiguous or malformed hostnames. 3. Do not send `STABILITY_API_KEY` to custom providers. Use a separate, provider-specific credential for each explicitly configured host. 4. Require explicit user confirmation before sending credentials or prompt data to a non-default endpoint. 5. Consider disabling redirects for authenticated requests with `allow_redirects=False`, or validate every resulting destination before permitting an authorization credential to be forwarded. 6. Document that prompts and credentials are transmitted to the configured endpoint. 7. Rotate the Stability AI API key if the Skill has previously been run with an untrusted or plain HTTP `API_HOST`. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/core/generate.py:313
Finding
Automatic Cleanup Can Delete Unrelated User Images and Metadata<![CDATA[ ## Vulnerability Details **File Location**: `scripts/core/generate.py:313-330, 366` **Vulnerability Type**: Overbroad destructive file cleanup **Risk Level**: Medium ### Vulnerable Code ```python def _cleanup_old_files(output_dir: str, output_format: str) -> None: try: extensions = ["png", "jpg", "jpeg", "webp"] files = [os.path.join(output_dir, f) for f in os.listdir(output_dir) if any(f.endswith(f".{ext}") for ext in extensions)] files.sort(key=os.path.getmtime) retention_limit = 20 if len(files) > retention_limit: removed = files[:len(files) - retention_limit] for f in removed: os.remove(f) json_file = f.replace(os.path.splitext(f)[1], ".json") if os.path.exists(json_file): os.remove(json_file) print(f"Auto-Clean: Removed old file {os.path.basename(f)}") except Exception as e: print(f"Cleanup warning: {e}") ``` The cleanup target is user-controlled: ```python parser.add_argument("--output", default="./assets/generated", help="Output directory") ``` ### Technical Analysis The cleanup routine treats every PNG, JPEG, or WebP file in `output_dir` as if it were owned by the Skill. It does not verify that a file was generated by this script, has the generated filename pattern, or is recorded in a Skill-maintained manifest. The `--output` argument permits the user or invoking automation to select an arbitrary writable directory. Once the directory contains more than 20 matching images, the function deletes the oldest matching files regardless of their origin. For each deleted image, it also deletes a same-basename JSON file if one exists, again without verifying ownership. Automatic retention is consistent with the documented functionality, but deleting unrelated files is not necessary to implement that feature and exceeds the minimum filesystem scope required ...[truncated 1408 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict automatic cleanup to a dedicated, Skill-managed output directory. 2. Maintain a manifest of files created by the Skill and delete only entries whose ownership can be verified. 3. Alternatively, use a unique, non-user-controlled filename prefix and validate the complete generated filename pattern before deletion. 4. Do not infer ownership solely from a file extension or matching JSON basename. 5. Make cleanup opt-in when a custom `--output` directory is supplied. 6. Before deleting files, resolve and validate the output path to ensure it remains inside an approved directory. 7. Consider moving expired files to a recoverable trash or quarantine location rather than deleting them immediately. 8. Warn users when cleanup is enabled and report the exact directory and retention policy before destructive operations occur. ]]>

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding
Unpinned Third-Party Dependencies Create Supply-Chain and Reproducibility Risk<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-3` **Vulnerability Type**: Unconstrained third-party dependencies **Risk Level**: Low ### Vulnerable Code ```text requests python-dotenv pillow ``` ### Technical Analysis The dependency declarations contain no exact versions, upper bounds, lock data, or integrity hashes. Consequently, installations performed at different times can resolve to different package releases. This does not establish that any currently named package is malicious. However, unconstrained resolution prevents reproducible review and causes future installations to trust newly published releases without confirming that they are the versions audited with this Skill. A compromised upstream release, package-index account, index configuration, or unexpected incompatible release could therefore introduce code that was not part of the reviewed artifact. The project documentation also states that dependencies are handled automatically, while the reviewed Python implementation contains no corresponding installation routine. The actual installation mechanism and package source should be documented explicitly. ### Attack Path 1. A user or deployment process installs dependencies from `requirements.txt`. 2. The package resolver selects the newest versions available from its configured package index. 3. A selected release contains compromised, malicious, or unexpectedly unsafe code, or the configured index is untrusted. 4. The package is installed into the Skill's environment. 5. Package code executes during installation or when imported by `generate.py`. This is a supply-chain exposure rather than proof of compromise in the currently reviewed source. ### Impact Assessment A compromised dependency can execute with the privileges of the account performing installation or running the Skill. Depending on those privileges, it could access environment variables such as `STABILITY_API_KEY`, read or alter accessible files, ma ...[truncated 250 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every dependency to a reviewed exact version. 2. Generate and commit a lock file appropriate for the deployment workflow. 3. Use integrity hashes, such as pip's `--require-hashes`, to verify downloaded artifacts. 4. Install packages only from an explicitly configured trusted package index. 5. Run automated dependency vulnerability and provenance checks during release preparation. 6. Review and update pins on a controlled schedule rather than resolving arbitrary latest releases during deployment. 7. Document the real dependency installation procedure and remove the unsupported claim that the reviewed script installs dependencies automatically. 8. Perform dependency installation and Skill execution under a minimally privileged account. ]]>
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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (18)

Tainted flow: 'url' from os.getenv (line 195, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
print(f"Style:      {style_preset}")

    try:
        response = requests.post(url, headers=headers, json=body, timeout=120)
        
        if response.status_code == 401:
            print("Error: Invalid API key. Check your STABILITY_API_KEY.")
Confidence
91% confidence
Finding
The request target is derived from API_HOST, which comes from the environment and is not validated before the code sends the Stability API bearer token and user prompt to it. If an attacker can influence environment variables or the .env file, they can redirect requests to an attacker-controlled host and exfiltrate the API key and prompt data.

Tainted flow: 'url' from os.getenv (line 195, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
print(f"Style:      {style_preset}")

    try:
        response = requests.post(url, headers=headers, files=files, data=data, timeout=120)
        
        if response.status_code == 401:
            print("Error: Invalid API key. Check your STABILITY_API_KEY.")
Confidence
91% confidence
Finding
The V2 request also uses a URL built from the environment-controlled API_HOST while attaching the Authorization bearer token. A modified environment can silently redirect traffic and leak secrets and user content to an attacker-controlled service.

Credential Access

High
Category
Privilege Escalation
Content
if not API_KEY:
    print("Error: STABILITY_API_KEY environment variable not set.")
    print("Tip: Check your .env file or export the variable.")
    sys.exit(1)

ASPECT_RATIO_MAP = {
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
94% confidence
Finding
The skill documents behavior that clearly implies network access, environment-variable access, and local file writes, but it declares no explicit tool scope or permission boundaries. That makes the skill easier to invoke with broader-than-necessary capabilities and weakens reviewability, increasing the chance of unintended data access or misuse if integrated into an agent runtime.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger phrases and description are broad enough to match many ordinary user requests like 'generate image' or 'draw this,' which can cause the skill to activate in contexts the user did not specifically intend. Over-broad routing increases the risk of accidental prompt transmission to an external API and unintended local artifact creation, especially in multi-skill agent environments.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill states that prompts, negative prompts, parameters, and timestamps are stored in local JSON metadata files, but it does not prominently warn users beforehand. Prompts can contain sensitive or proprietary information, so silent persistence creates a privacy and data-retention risk if those files are later accessed, synced, or exfiltrated.

External Transmission

Medium
Category
Data Exfiltration
Content
print(f"Style:      {style_preset}")

    try:
        response = requests.post(url, headers=headers, json=body, timeout=120)
        
        if response.status_code == 401:
            print("Error: Invalid API key. Check your STABILITY_API_KEY.")
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The skill persists prompt metadata and then performs lifecycle management of files, which exceeds a narrowly understood 'generate image' action. Because prompts may contain sensitive user data, storing them in JSON sidecar files increases privacy risk, especially without explicit user consent or retention controls.

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

Medium
Category
Data Flow
Content
print(f"Style:      {style_preset}")

    try:
        response = requests.post(url, headers=headers, files=files, data=data, timeout=120)
        
        if response.status_code == 401:
            print("Error: Invalid API key. Check your STABILITY_API_KEY.")
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: 'filepath' from requests.post (line 146, network input) → open (file write)

Medium
Category
Data Flow
Content
img = Image.open(io.BytesIO(image_data))
        img.save(filepath, "WEBP", quality=95)
    else:
        with open(filepath, "wb") as f:
            f.write(image_data)
    
    return filepath
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.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
Automatic cleanup deletes files in the output directory based solely on extension and age ordering, which is broader than simple image generation. If the output directory contains other important images, this feature can remove user data unexpectedly and cause integrity/availability loss.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script deletes older files automatically without prior warning or confirmation. In an agent-skill context, this is risky because users may reasonably expect generation only, not destructive file operations in the selected output directory.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests
python-dotenv
pillow
Confidence
97% confidence
Finding
The dependency 'requests' is unpinned, so builds are not reproducible and may silently pull in a vulnerable or breaking release over time. In a network-facing skill that likely makes outbound API calls, this increases supply-chain risk and makes it impossible to verify whether known advisories affect the deployed version.

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
89% confidence
Finding
The manifest does not pin a version of 'requests', and the package has multiple known advisories across its release history. That means the deployment could resolve to an affected version without visibility, which is particularly relevant for a skill expected to perform HTTP requests to an external API.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests
python-dotenv
pillow
Confidence
96% confidence
Finding
The dependency 'python-dotenv' is unpinned, which allows arbitrary future releases to be installed without review. Because this package influences environment-variable loading, an unsafe or compromised version could affect secret handling or local file interactions in ways that are hard to audit.

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
84% confidence
Finding
Because 'python-dotenv' is not version-pinned, there is no way to determine from this manifest whether the installed release includes known vulnerabilities. While the package is not the core image-processing component, it can influence configuration and secret-loading behavior, so leaving its version unspecified weakens deployment assurance.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests
python-dotenv
pillow
Confidence
97% confidence
Finding
The dependency 'pillow' is unpinned, creating supply-chain and reproducibility risk for image-processing code. In an image-generation skill, image libraries are especially security-relevant because malformed image inputs or vulnerable decoders have historically led to denial of service and, in some versions, code-execution issues.

Unverifiable Dependency: pillow has 16 known advisory(ies) (CVE-2016-2533 (Pillow buffer overflow in ImagingPcdDecode); CVE-2023-50447 (Arbitrary Code Execution in Pillow); CVE-2021-27922 (Pillow Uncontrolled Resource Consumption) +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
93% confidence
Finding
The manifest leaves 'pillow' unpinned even though the library has a long history of security advisories, including memory-safety and resource-consumption issues. In the context of an image-generation skill, this is more dangerous than average because image handling is a primary function, making a vulnerable Pillow version more likely to be exercised.

Static analysis

No suspicious patterns detected.