Back to skill

Security audit

rodin3d-skill

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its stated 3D-generation purpose, but its credential handling and remote-controlled download behavior need review before installation.

Review this skill before installing. Use a dedicated Hyper3D API key with limited billing exposure, avoid passing keys with --api-key or printing them, and treat uploaded images/prompts as shared with Hyper3D. If you run the script, use a disposable output directory and avoid running it in sensitive project or home directories until filename and URL validation are fixed.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate_3d_model.py:170
Finding
API-Controlled Path Components Allow Arbitrary File Writes## Vulnerability Details **File Location**: `scripts/generate_3d_model.py`, lines 170-176 and 184-208 **Vulnerability Type**: Path traversal and unrestricted file write **Risk Level**: High ### Vulnerable Code ```python # If output directory is specified, download model if args.output: # Create task-specific output directory task_output_dir = os.path.join(args.output, task_uuid) print(f"\nDownloading models to: {task_output_dir}") for file_info in file_list: download_model(file_info.get('url'), task_output_dir, file_info.get('name')) ``` ```python def download_model(model_url, output_dir, filename=None): """ Download 3D model to specified directory Args: model_url: Model download link output_dir: Output directory filename: Filename (optional) """ import requests # Ensure output directory exists os.makedirs(output_dir, exist_ok=True) # Get filename if not filename: filename = os.path.basename(model_url.split("?")[0]) output_path = os.path.join(output_dir, filename) # Download file try: response = requests.get(model_url, stream=True, timeout=60) response.raise_for_status() with open(output_path, "wb") as f: for chunk in response.iter_content(chunk_size=8192): if chunk: f.write(chunk) ``` ### Technical Analysis The `task_uuid` and `file_info["name"]` values originate from the remote API response. They are passed to `os.path.join()` without validation or canonical containment checks. `os.path.join()` does not enforce confinement beneath its first argument. A filename containing traversal components such as `../../target` can escape the output directory. On supported platforms, an absolute second path can also cause the intended base path to be discarded. The API-controlled `task_uuid` creates an additi ...[truncated 1313 chars]
Remediation
## Remediation Suggestions - Do not use API-provided identifiers directly as local path components. Generate local filenames and task-directory names using trusted UUID generation. - Reduce API-provided filenames to a safe basename and reject absolute paths, parent-directory components, path separators, empty names, and platform-specific alternate separators. - Resolve both the output root and candidate destination with `pathlib.Path.resolve()`, then verify that the destination is a descendant of the output root before opening it. - Open newly created files with exclusive creation where overwriting is unnecessary. - Maintain an explicit allowlist of expected filename extensions. - Apply restrictive permissions to output directories and files. - A suitable containment pattern is: ```python from pathlib import Path import uuid root = Path(args.output).resolve() task_dir = (root / str(uuid.uuid4())).resolve() task_dir.mkdir(parents=True, exist_ok=True) supplied_name = Path(filename).name if supplied_name != filename or supplied_name in {"", ".", ".."}: raise ValueError("Unsafe filename") destination = (task_dir / supplied_name).resolve() if task_dir not in destination.parents: raise ValueError("Destination escapes output directory") ```

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_3d_model.py:170
Finding
Unvalidated Remote Download URL Enables Server-Side Request Forgery## Vulnerability Details **File Location**: `scripts/generate_3d_model.py`, lines 170-176 and 184-205 **Vulnerability Type**: Server-side request forgery through an untrusted download URL **Risk Level**: Medium ### Vulnerable Code ```python if args.output: # Create task-specific output directory task_output_dir = os.path.join(args.output, task_uuid) print(f"\nDownloading models to: {task_output_dir}") for file_info in file_list: download_model(file_info.get('url'), task_output_dir, file_info.get('name')) ``` ```python def download_model(model_url, output_dir, filename=None): """ Download 3D model to specified directory Args: model_url: Model download link output_dir: Output directory filename: Filename (optional) """ import requests # Ensure output directory exists os.makedirs(output_dir, exist_ok=True) # Get filename if not filename: filename = os.path.basename(model_url.split("?")[0]) output_path = os.path.join(output_dir, filename) # Download file try: response = requests.get(model_url, stream=True, timeout=60) response.raise_for_status() ``` ### Technical Analysis The model URL is taken from the API response and passed directly to `requests.get()`. The code does not validate the URL scheme, hostname, port, resolved IP address, or redirect chain. Consequently, a manipulated API response could direct the client toward loopback services, private network hosts, link-local cloud metadata endpoints, or other resources reachable from the user's network context. The default redirect behavior of `requests` also means an initially acceptable URL could redirect to a prohibited destination unless every hop is validated. This is an SSRF condition contingent on an attacker being able to manipulate the API response or compromise the remote service or associated delivery i ...[truncated 1127 chars]
Remediation
## Remediation Suggestions - Accept only `https` download URLs. - Maintain an explicit allowlist of documented Hyper3D download and CDN hostnames. - Resolve the hostname and reject loopback, private, link-local, multicast, unspecified, and reserved address ranges for both IPv4 and IPv6. - Disable automatic redirects or manually validate the scheme, hostname, port, and resolved address of every redirect target. - Protect against DNS rebinding by connecting only to a validated resolved address while preserving correct TLS hostname validation. - Reject URLs containing unexpected credentials or ports. - Set strict response-size limits while streaming and validate expected content types. - Prefer an API design in which the trusted Hyper3D endpoint proxies downloads or returns an opaque identifier rather than an arbitrary URL.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_3d_model.py:66
Finding
API Keys Can Be Exposed Through Terminal Output and Process Arguments## Vulnerability Details **File Location**: `SKILL.md`, lines 14-19, 78-89, and 120-145; `scripts/generate_3d_model.py`, lines 66 and 79-88 **Vulnerability Type**: Sensitive credential exposure **Risk Level**: Medium ### Vulnerable Code and Instructions ```bash echo $HYPER3D_API_KEY ``` ```bash python <skill_dir>/scripts/generate_3d_model.py --image path/to/image.jpg --geometry-file-format glb --quality medium --output path/to/output_dir --api-key $HYPER3D_API_KEY ``` ```python parser.add_argument("--api-key", help="Hyper3D API key (overrides environment variable)") ``` ```python # Check Hyper3D API key env_api_key = os.environ.get("HYPER3D_API_KEY") # If user provided API key via command line argument, use it directly if args.api_key: print("Using API key from command line argument") client = Hyper3DAPIClient(api_key=args.api_key) elif env_api_key: # Found API key in environment variable, ask user if they want to use it print(f"Found API key in environment variable: {env_api_key[:5]}...{env_api_key[-5:]}") ``` ### Technical Analysis The documentation instructs users to print the complete API key with `echo`. Terminal sessions can be recorded, copied, streamed, or collected by logging systems. The examples also encourage supplying the key through `--api-key`, placing the expanded secret in the process argument list. Process arguments may be visible to other local users, process-monitoring software, telemetry agents, job logs, or command wrappers. The script additionally prints the first and last five characters of an environment-provided key. This leaks credential material unnecessarily. For keys of ten characters or fewer, the slices overlap or collectively disclose the entire key. ### Attack Path 1. A user follows the documentation and displays the environment variable or invokes the script with `--api-key`. 2. A terminal recorder, process observer, monitoring agent, j ...[truncated 670 chars]
Remediation
## Remediation Suggestions - Replace `echo $HYPER3D_API_KEY` with a presence-only check that does not reveal the value, such as: ```bash test -n "$HYPER3D_API_KEY" && echo "API key is configured" || echo "API key is missing" ``` - Remove command examples that expand credentials into `--api-key`. - Prefer environment variables, a credential manager, or a permissions-restricted configuration file. - If interactive entry is needed, use Python's `getpass.getpass()` rather than `input()` so the key is not echoed. - Remove the API-key prefix and suffix from log output. Log only whether a key source was found. - Consider deprecating the `--api-key` option. If compatibility requires retaining it, emit a warning and document that it is unsafe on shared systems. - Rotate any key that may already have appeared in terminal recordings, shell wrappers, process telemetry, or CI logs.

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding
Unpinned Third-Party Dependencies Prevent Reproducible and Verifiable Installation## Vulnerability Details **File Location**: `requirements.txt`, lines 1-2 **Vulnerability Type**: Unpinned third-party dependencies **Risk Level**: Low ### Vulnerable Code ```text requests Pillow ``` ### Technical Analysis Neither dependency has a fixed version or integrity hash. Each installation can therefore resolve to a different release depending on repository state and resolver behavior. This prevents reproducible review and makes it harder to ensure that the exact installed artifacts were previously assessed. The package names are legitimate and no malicious dependency was identified in the audited files. The risk arises from unconstrained future dependency resolution rather than evidence that the current project intentionally installs a malicious package. ### Attack Path 1. A user or automated environment installs the dependencies from `requirements.txt`. 2. The resolver selects whatever compatible versions are available at installation time. 3. A newly released vulnerable or compromised version is selected. 4. The dependency is imported by the project and its code executes in the Python process. 5. Any malicious dependency behavior receives the same operating-system privileges and accessible data as the script. ### Impact Assessment A compromised dependency could execute arbitrary Python code with the privileges of the user running the skill and access the same files, environment variables, network connections, and API key available to that process. A merely vulnerable release may have narrower impact based on the affected functionality. No specific compromised release or currently exploitable dependency vulnerability was established by this static audit.
Remediation
## Remediation Suggestions - Pin each dependency to a reviewed version rather than allowing unconstrained resolution. - Generate and commit a lock file containing transitive dependency versions and cryptographic hashes. - Install with hash verification, for example through a hash-locked requirements file and `pip --require-hashes`. - Use a trusted package index and restrict unexpected alternate indexes. - Run dependency vulnerability and license scans in continuous integration. - Establish a controlled update process that reviews release notes, security advisories, and lock-file changes before upgrades.
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (21)

Credential Access

High
Category
Privilege Escalation
Content
1. **Get a key**: Go to https://hyper3d.ai/api-dashboard → Click **"Create New API Key"** → Create Secret Key
2. **Save to `.env`** (recommended for persistence):
   ```bash
   echo 'HYPER3D_API_KEY=your_api_key_here' >> .env
   echo '.env' >> .gitignore  # Don't commit secrets
   ```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
2. **Save to `.env`** (recommended for persistence):
   ```bash
   echo 'HYPER3D_API_KEY=your_api_key_here' >> .env
   echo '.env' >> .gitignore  # Don't commit secrets
   ```

### Code Examples
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
88% confidence
Finding
The skill clearly instructs access to environment variables and outbound network communication, but it does not declare any explicit tool scope or permissions boundaries. In an agent environment, missing scope declarations reduce transparency and can allow broader-than-expected execution behavior, especially when handling secrets and external API calls.

Ssd 3

Medium
Confidence
99% confidence
Finding
Instructing users to fall back to a shared/free API key encourages use of a credential not uniquely tied to the user or deployment. Shared credentials undermine accountability, can be revoked unexpectedly, may violate provider policy, and create a path for untrusted third parties to induce unauthorized usage or quota exhaustion.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The documentation tells users to supply the API key via a command-line argument, which can expose the secret through shell history, process listings, logs, and telemetry. This is especially risky in shared systems, CI runners, or agent platforms where invocation details may be captured automatically.

External Transmission

Medium
Category
Data Exfiltration
Content
def __init__(self, api_key=None):
        self.api_key = api_key or os.environ.get("HYPER3D_API_KEY")
        self.api_url = "https://api.hyper3d.com/api/v2/rodin"
        self.status_url = "https://api.hyper3d.com/api/v2/status"
        self.download_url = "https://api.hyper3d.com/api/v2/download"
        self.headers = {
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
def __init__(self, api_key=None):
        self.api_key = api_key or os.environ.get("HYPER3D_API_KEY")
        self.api_url = "https://api.hyper3d.com/api/v2/rodin"
        self.status_url = "https://api.hyper3d.com/api/v2/status"
        self.download_url = "https://api.hyper3d.com/api/v2/download"
        self.headers = {
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
def __init__(self, api_key=None):
        self.api_key = api_key or os.environ.get("HYPER3D_API_KEY")
        self.api_url = "https://api.hyper3d.com/api/v2/rodin"
        self.status_url = "https://api.hyper3d.com/api/v2/status"
        self.download_url = "https://api.hyper3d.com/api/v2/download"
        self.headers = {
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
def __init__(self, api_key=None):
        self.api_key = api_key or os.environ.get("HYPER3D_API_KEY")
        self.api_url = "https://api.hyper3d.com/api/v2/rodin"
        self.status_url = "https://api.hyper3d.com/api/v2/status"
        self.download_url = "https://api.hyper3d.com/api/v2/download"
        self.headers = {
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
def __init__(self, api_key=None):
        self.api_key = api_key or os.environ.get("HYPER3D_API_KEY")
        self.api_url = "https://api.hyper3d.com/api/v2/rodin"
        self.status_url = "https://api.hyper3d.com/api/v2/status"
        self.download_url = "https://api.hyper3d.com/api/v2/download"
        self.headers = {
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
def __init__(self, api_key=None):
        self.api_key = api_key or os.environ.get("HYPER3D_API_KEY")
        self.api_url = "https://api.hyper3d.com/api/v2/rodin"
        self.status_url = "https://api.hyper3d.com/api/v2/status"
        self.download_url = "https://api.hyper3d.com/api/v2/download"
        self.headers = {
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The client uploads user-supplied images and prompt text to a third-party service, but this code provides no consent gate, disclosure, or data-minimization check at the point of transmission. In a skill that may handle private design assets or proprietary prompts, silent external transfer can expose sensitive data to the vendor unexpectedly.

External Transmission

Medium
Category
Data Exfiltration
Content
dict: Task status information
        """
        try:
            response = requests.post(
                self.status_url,
                headers=self.headers,
                json={"subscription_key": subscription_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.

External Transmission

Medium
Category
Data Exfiltration
Content
dict: Download links list
        """
        try:
            response = requests.post(
                self.download_url,
                headers=self.headers,
                json={"task_uuid": task_uuid},
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
The script reveals part of the environment-stored API key to the console and asks the user to confirm use of that specific secret. Exposing even a partial credential unnecessarily increases the chance of credential leakage through logs, terminal capture, screenshots, or shared execution environments, and this behavior is not required to generate 3D models.

Natural-Language Policy Violations

Low
Confidence
79% confidence
Finding
The instructions say that if the user does not specify a tier, the skill should use `Sketch` by default. This is a natural-language behavior constraint that overrides user preference granularity without opt-in, which can be a policy concern because it forces a specific output setting instead of prompting or offering a choice.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests
Pillow
Confidence
96% confidence
Finding
The dependency 'requests' is unpinned, so installs may resolve to different versions over time, including versions with known security defects or breaking changes. In a skill that performs external API communication, this increases supply-chain uncertainty and makes builds non-reproducible, which can expose the runtime to avoidable library vulnerabilities.

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
Because 'requests' is not version-pinned, it is impossible to verify whether the deployed environment uses a release affected by known advisories. This matters more in this skill because 'requests' is central to outbound network communication with a third-party API, so flaws in HTTP handling, redirect behavior, TLS verification state, or credential usage could directly affect confidentiality or integrity.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests
Pillow
Confidence
96% confidence
Finding
The dependency 'Pillow' is unpinned, allowing package resolution to drift to arbitrary versions at install time. Because this skill processes images, using an unpinned image library is particularly risky: older or vulnerable Pillow releases have had memory corruption and resource-consumption issues that could be triggered by crafted image input.

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 does not pin 'Pillow', so there is no assurance that deployment will avoid versions with known vulnerabilities. In this skill, that is more dangerous than a generic packaging issue because Pillow will likely handle user-supplied images, and malformed images have historically been used to trigger denial-of-service, crashes, or worse in image parsers.

Missing User Warnings

Low
Confidence
78% confidence
Finding
The client automatically reads HYPER3D_API_KEY from the environment and uses it for authenticated API requests. While this is common practice, the file provides no user-facing notice that a credential from the environment will be accessed and attached to outbound requests.

Static analysis

No suspicious patterns detected.