Back to skill

Security audit

Aholo 3DGS reconstruction and generation

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its Aholo 3D upload purpose, but it sends authenticated requests to an undeclared beta API and trusts server-provided upload destinations too broadly.

Install only if you are comfortable sending your Aholo API key, task metadata, prompts, and selected images/videos to Aholo infrastructure, including the current undeclared beta world API endpoint. Avoid AHOLO_INSECURE_SKIP_VERIFY except for controlled testing, and prefer a version that validates upload domains and pins dependencies.

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
aholo_reconstruct.py:32
Finding
API Key Transmitted to an Undeclared Beta Gateway## Vulnerability Details **File Location**: `aholo_reconstruct.py`, lines 32–33, 106–107, 205–206, 470–477, 505–512, 528–531, and 577–579 **Vulnerability Type**: Credential disclosure to an unexpected service endpoint **Risk Level**: Medium ### Vulnerable Code ```python SITE_CONFIG = { "base_url": "https://api-beta.aholo3d.com", "token_base_url": "https://api.aholo3d.com", "path_prefix": "/global", "viewer_url_template": "https://studio.aholo3d.com/3dgs-model/{world_id}", "api_keys_url": "https://labs.aholo3d.com/api-keys", "skill_script_path": ".cursor/skills/aholo-3dgs-reconstruction-global/aholo_reconstruct.py", } ``` ```python class AholoClient: BASE_URL = SITE_CONFIG["base_url"] TOKEN_BASE_URL = SITE_CONFIG["token_base_url"] ``` ```python def _auth_headers(self) -> Dict[str, str]: return {"Authorization": self.api_key, "Content-Type": "application/json"} ``` The raw API-key header is subsequently used for authenticated requests to `BASE_URL`, including: ```python resp = self.session.post( url, headers=self._create_task_headers(), json=body, timeout=60, verify=self.verify_ssl ) ``` ```python resp = self.session.get(url, headers=self._auth_headers(), timeout=30, verify=self.verify_ssl) ``` ```python resp = self.session.post( url, headers=self._auth_headers(), json=body, timeout=30, verify=self.verify_ssl ) ``` ### Technical Analysis `SKILL.md` identifies `https://api.aholo3d.com` as the gateway that receives authenticated OpenAPI requests. The implementation instead assigns `https://api-beta.aholo3d.com` to `base_url`. Create, generation, status, poll, and list operations derive their URLs from this value and send the raw `AHOLO_API_KEY` in the `Authorization` header. Although both domains appear to belong to Aholo, the beta endpoint is not disclosed in the declared behavior. Users therefore authorize credential transmission t ...[truncated 1629 chars]
Remediation
## Remediation Suggestions 1. Replace the beta gateway with the documented production endpoint: ```python "base_url": "https://api.aholo3d.com", ``` 2. If the beta endpoint is genuinely required, disclose it explicitly in `SKILL.md` and require an intentional configuration option rather than silently selecting it. 3. Enforce a strict allowlist for every endpoint that receives `AHOLO_API_KEY`. 4. Parse and validate configured URLs before sending credentials, requiring: - HTTPS. - An exact approved hostname. - No user information in the URL. - An approved port. 5. Use separate, scoped credentials for beta and production environments where supported. 6. Avoid logging the `Authorization` header and review beta infrastructure for historical API-key retention. 7. Rotate affected API keys if the beta endpoint was not intended to receive production credentials.

T09 · Insecure Skill Coding Practices

Warning
Location
aholo_reconstruct.py:231
Finding
Unvalidated Server-Controlled Upload Destination Can Redirect Sensitive Media## Vulnerability Details **File Location**: `aholo_reconstruct.py`, lines 221–239, 258–266, 291–303, and 331–388 **Vulnerability Type**: Unrestricted external upload destination **Risk Level**: Medium ### Vulnerable Code The upload destination is accepted directly from the token response: ```python def get_upload_token(self) -> Dict[str, Any]: url = f"{self.TOKEN_BASE_URL}{PATH_UPLOAD_TOKEN}" with step_timer("fetch upload token"): try: resp = self.session.get(url, headers=self._auth_headers(), timeout=30, verify=self.verify_ssl) payload = self._parse_open_api_json(resp) err = self._check_open_api_response(resp, payload) if err: return err data = payload if isinstance(payload, dict) else {} self.ous_token = data.get("ousToken") self.global_domain = data.get("globalDomain") self.block_size = int(data.get("blockSize") or self.block_size) if not self.ous_token or not self.global_domain: return {"success": False, "error": "Upload token response missing ousToken or globalDomain."} return { "success": True, "ousToken": self.ous_token, "globalDomain": self.global_domain, "blockSize": self.block_size, } ``` The unvalidated value is then used as the destination for file uploads: ```python def _upload_file_single(self, file_path: str) -> Dict[str, Any]: if not self.global_domain: return {"success": False, "error": "Missing globalDomain."} path = Path(file_path) md5_value = self._calculate_md5(file_path) mime_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream" url = f"{self.global_domain}/ous/api/v2/single/upload" with step_timer(f"single-file upload: {path.name}"): try: with ...[truncated 3687 chars]
Remediation
## Remediation Suggestions 1. Parse `globalDomain` with `urllib.parse.urlparse` before storing or using it. 2. Require all of the following: - Scheme exactly equal to `https`. - A non-empty normalized hostname. - No URL user information. - No query string or fragment. - Default HTTPS port or an explicitly approved port. - Hostname matching an exact allowlist or carefully defined provider suffix. 3. Maintain an explicit list of approved upload domains supplied by the service provider. Do not use a broad substring check. 4. Reject IP-literal destinations, loopback addresses, link-local addresses, private network ranges, and localhost names unless they are explicitly required and securely configured. 5. Disable automatic redirects for upload operations or validate every redirect target before following it: ```python resp = self.session.post(..., allow_redirects=False) ``` 6. Apply the same validation to upload-status polling, single uploads, multipart initialization, and multipart part uploads. 7. Keep the existing one-time-token behavior, as it limits credential reuse. 8. Preserve TLS verification as the default and avoid `AHOLO_INSECURE_SKIP_VERIFY` for sensitive media uploads. 9. Consider binding upload tokens server-side to an approved origin, account, file digest, maximum size, and short expiration period.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill description says it uses api.aholo3d.com for upload/create/status, but the analyzed behavior includes undeclared task listing and use of api-beta.aholo3d.com as the main base URL. This mismatch is dangerous because it can silently route user data and API keys to a different environment than promised, undermining trust boundaries, change-control expectations, and potentially exposing production inputs to a beta service.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill instructs the agent to use environment variables, local file paths, and network access, but it does not declare an explicit tool/permission scope. That creates a governance gap: reviewers and enforcement systems cannot easily constrain what resources the skill may access, increasing the chance of overbroad data access or unintended external calls.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
### TLS (script default behavior)

- Script **enables** SSL verification by default (secure by default).
- Corporate/self-signed certs: set `AHOLO_INSECURE_SKIP_VERIFY=1` (or `true` / `yes` / `on`) to explicitly disable verification; prefer `REQUESTS_CA_BUNDLE` to point at your enterprise CA.
- On `CERTIFICATE_VERIFY_FAILED`, configure CA certs first; use `AHOLO_INSECURE_SKIP_VERIFY` only when you accept the risk.

### Responses & errors
Confidence
88% confidence
Finding
The skill documents an environment flag that disables TLS certificate verification. Even though it says this is not recommended, normalizing an easy insecure bypass can enable man-in-the-middle attacks against API traffic, exposing API keys and user-uploaded media if operators use it in the wrong environment.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
| 11 | **Multiple videos:** ask before create (**do not** choose for user): **A** one 3DGS (one create, all in `videoPaths`); **B** one 3DGS per video (see #9). Only 1 video → skip question |
| 9 | **Create POST (high cost)** — **Default:** one user **single** 3D intent → at most **one** create per conversation round; no retry on same intent after fail/timeout/missing `worldId` unless user **explicitly re-orders**. **Pre-upload failure** (POST not sent) → one first create after fix. **Charged but no worldId** → task list / status/list, not another create. **Multi-video B:** user chose separate 3DGS → create **per video** (`videoPaths` one each), warn N tasks/charges upfront; **no** duplicate create for same video; failed video → no retry, continue with remaining. Use `forbidCreate` only to block accidental **duplicate for the same completed task**, not the next video in B |
| 10 | `projectName` only if user explicitly asks; never invent from folder name or timestamp |
| 5–6 | After each `worldId` → **ask** wait or not; if wait → **sync** `poll` (`intervalSeconds=60`, `timeoutSeconds=14400`); if not → link only. **No** background poll + "I'll notify you"; **no** poll without asking |

### `taskQuality` display names (API values unchanged)
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The manifest describes the skill as limited to global 3D tasks for upload, create, and poll/status, but the code also defines and later uses a `/global/world/v1/list` endpoint for enumerating worlds. Listing existing worlds is a distinct management capability not reflected in the stated description and broadens the skill's operational scope.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
insecure_skip = str(os.environ.get("AHOLO_INSECURE_SKIP_VERIFY", "")).strip().lower()
        if insecure_skip in {"1", "true", "yes", "on"}:
            # Explicit opt-out only (e.g. corporate self-signed certs).
            self.verify_ssl = False
            urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
        else:
            self.verify_ssl = True
Confidence
96% confidence
Finding
The client allows TLS certificate verification to be disabled via `AHOLO_INSECURE_SKIP_VERIFY`, and then suppresses related warnings. If enabled, an attacker on the network path could intercept API keys, upload tokens, media uploads, and task metadata via man-in-the-middle attacks, which is especially sensitive because this skill handles credentialed uploads of user files.

Cloud Storage Exfiltration

Medium
Category
Data Exfiltration
Content
results.append({"success": True, "url": item, "originalPath": item, "isUrl": True})
            else:
                print(f"Uploading {input_label}: {item}")
                up = self.upload_file(item)
                if up.get("success"):
                    print(f"{input_label} upload succeeded: {item}")
                else:
Confidence
55% confidence
Finding
Data is uploaded to cloud storage (S3 / GCS / Azure Blob). This may be a legitimate backup or exfiltration to an external bucket. Manual review is recommended.

Cloud Storage Exfiltration

Medium
Category
Data Exfiltration
Content
results.append({"success": True, "url": item, "originalPath": item, "isUrl": True})
            else:
                print(f"Uploading {input_label}: {item}")
                up = self.upload_file(item)
                if up.get("success"):
                    print(f"{input_label} upload succeeded: {item}")
                else:
Confidence
55% confidence
Finding
Data is uploaded to cloud storage (S3 / GCS / Azure Blob). This may be a legitimate backup or exfiltration to an external bucket. Manual review is recommended.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The create flows upload local cover files, videos, and images and may send prompts to external Aholo/OUS services without an explicit disclosure or confirmation step at the point of transmission. In an agent environment, this is dangerous because users may provide local paths assuming local processing, leading to unintended transfer of sensitive media or prompts to third-party infrastructure.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The script implements a `list` action that enumerates worlds despite the declared skill scope being limited to upload/create/poll/status. In an agent setting, this expands accessible data beyond user expectation and can expose metadata about other projects/tasks tied to the API key, creating an information-disclosure and overprivilege issue.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
The markdown hardcodes Chinese labels alongside the display names, which introduces a language/locale preference in the user-facing prompt content. There is no indication that the skill is region-specific or that the user can choose a preferred language, so this is a natural-language policy concern.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0
urllib3>=2.0.0
Confidence
98% confidence
Finding
The dependency is specified with a lower bound only (`requests>=2.28.0`), which makes builds non-reproducible and can silently pull in vulnerable or breaking versions over time. Because the exact installed version is unknown, security posture cannot be reliably assessed or controlled.

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
93% confidence
Finding
`requests` has known advisories, and because the manifest does not pin an exact version, there is no way to verify whether deployment will use a fixed or affected release. In a skill that performs API uploads and polling over the network, insecure HTTP client behavior could expose credentials, leak request metadata, or weaken transport protections if an affected version is installed.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0
urllib3>=2.0.0
Confidence
98% confidence
Finding
The dependency is unpinned (`urllib3>=2.0.0`), allowing future installs to resolve to different versions without review. This creates supply-chain and patch-management risk because a vulnerable or incompatible release could be introduced unexpectedly.

Unverifiable Dependency: urllib3 has 16 known advisory(ies) (CVE-2025-66471 (urllib3 streaming API improperly handles highly compressed data); CVE-2024-37891 (urllib3's Proxy-Authorization request header isn't stripped during cross-origin ); CVE-2026-21441 (Decompression-bomb safeguards bypassed when following HTTP redirects (streaming ) +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
`urllib3` has multiple known advisories, but the unpinned requirement prevents verification of whether the installed version is safe. Given this skill’s reliance on remote gateway communication, an affected HTTP transport library could increase risk from proxy handling, redirect behavior, or compression-related denial of service issues.

Static analysis

No suspicious patterns detected.