Back to skill

Security audit

Zopia Skills

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its Zopia video-creation purpose, but it uses a powerful account token and remote downloads with weak scoping that users should review before installing.

Install only if you trust Zopia with the prompts and project data you send, and use a dedicated ZOPIA_ACCESS_KEY with limited balance where possible. Leave ZOPIA_BASE_URL at the official HTTPS default unless you are intentionally testing, review before deleting episodes or spending credits, and prefer version-pinned install commands instead of copying unpinned npx examples.

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)

T08 · Insecure Dependencies

Warning
Location
README.md:14
Finding
Unpinned npm Packages Are Downloaded and Executed Through npx<![CDATA[ ## Vulnerability Details **File Location**: `README.md:12-22`; `docs/publish-to-clawhub.md:9-15, 29-35` **Vulnerability Type**: Unpinned executable dependency **Risk Level**: Medium ### Vulnerable Code ```bash # README.md npx skills add 11cafe/zopia-skills ``` ```bash # README.md npx clawhub install zopia-skill ``` ```bash # docs/publish-to-clawhub.md npx clawhub login ``` ```bash # docs/publish-to-clawhub.md npx clawhub publish /path/to/zopia-skills \ --slug zopia-skill \ --version <new-version> \ --changelog "change description" ``` ### Technical Analysis The documentation instructs users and maintainers to invoke the `skills` and `clawhub` npm packages through `npx` without specifying reviewed package versions or integrity hashes. When the requested package is not already available locally, `npx` can retrieve the current registry version and execute its entry point. Consequently, the code executed by these commands is not fixed to the version considered during this audit. A compromised npm publisher account, malicious package update, dependency compromise, or package ownership change could cause future installations or publishing operations to execute attacker-controlled code. This issue is especially relevant to `clawhub login` and `clawhub publish`, which may run in an environment containing persistent ClawHub authentication state, repository contents, and other developer credentials. ### Attack Path 1. An attacker compromises the npm package, its publisher account, or one of its runtime dependencies. 2. The attacker publishes a malicious version under the package name used by the documented `npx` command. 3. A user follows the installation or publishing instructions without specifying a version. 4. `npx` resolves and downloads the malicious current version. 5. The package executes with the privileges of the invoking user. 6. The malicious package can access files, environment variables, repository data, and authentication materi ...[truncated 629 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every executable npm package to an exact, reviewed version: ```bash npx --yes skills@<reviewed-version> add 11cafe/zopia-skills npx --yes clawhub@<reviewed-version> install zopia-skill ``` 2. Record the approved package name, npm scope, publisher identity, version, and integrity digest in the documentation. 3. Prefer installation from a lockfile-controlled development environment rather than dynamically executing the latest registry release. 4. Review package provenance and signatures where supported by the registry. 5. Run publishing tools from a restricted environment with only the credentials and repository access required for publication. 6. Re-audit and deliberately update the pinned version when upgrading the CLI. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/_common.py:24
Finding
Configurable API Base URL Can Redirect the Bearer Token to an Arbitrary Host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/_common.py:24-33, 40-69` **Vulnerability Type**: Unrestricted credential destination and optional plaintext transport **Risk Level**: Medium ### Vulnerable Code ```python def _get_access_key() -> str: key = os.environ.get("ZOPIA_ACCESS_KEY", "").strip() if not key: print("Error: environment variable ZOPIA_ACCESS_KEY is not set", file=sys.stderr) sys.exit(1) return key def _get_base_url() -> str: return os.environ.get("ZOPIA_BASE_URL", "https://zopia.ai").rstrip("/") def _build_headers() -> dict[str, str]: return { "Authorization": f"Bearer {_get_access_key()}", "Content-Type": "application/json", } def api_get(path: str, params: dict[str, str] | None = None) -> Any: """Send a GET request and return parsed JSON.""" url = f"{_get_base_url()}{path}" if params: url = f"{url}?{urllib.parse.urlencode(params)}" req = urllib.request.Request(url, headers=_build_headers(), method="GET") return _do_request(req) def api_post(path: str, body: dict[str, Any] | None = None) -> Any: """Send a POST request and return parsed JSON.""" url = f"{_get_base_url()}{path}" data = json.dumps(body or {}).encode("utf-8") req = urllib.request.Request(url, data=data, headers=_build_headers(), method="POST") return _do_request(req) def api_delete(path: str) -> Any: """Send a DELETE request and return parsed JSON.""" url = f"{_get_base_url()}{path}" req = urllib.request.Request(url, headers=_build_headers(), method="DELETE") return _do_request(req) ``` ### Technical Analysis `ZOPIA_BASE_URL` is accepted without validating its scheme, hostname, port, or relationship to the intended Zopia service. `_build_headers()` independently retrieves the production access key and attaches it to every API request. As a result, any process or configuration source capable of influencing `ZOPIA_BASE_URL` can choos ...[truncated 1735 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Allowlist the production API origin, preferably requiring exactly `https://zopia.ai`. 2. Parse the configured URL with `urllib.parse.urlsplit` and validate the scheme, normalized hostname, port, username, password, query, and fragment. 3. Reject plaintext HTTP for all non-loopback destinations. 4. Do not attach a production token to a custom host by default. 5. If local development endpoints are required, place them behind an explicit option such as `ZOPIA_ALLOW_CUSTOM_BASE_URL=1` and require a separate development credential. 6. Display the normalized destination and require confirmation before sending a credential to a non-production origin in interactive use. 7. Add automated tests confirming that malformed URLs, HTTP endpoints, embedded credentials, and unapproved hosts are rejected. 8. Document environment-variable trust boundaries and recommend clearing inherited `ZOPIA_BASE_URL` values before production use. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download_results.py:35
Finding
Untrusted Media URLs Are Downloaded Without Origin Validation or a Reliable Streamed-Size Limit<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download_results.py:35-95` **Vulnerability Type**: Unrestricted remote download and incomplete resource-size enforcement **Risk Level**: Medium ### Vulnerable Code ```python def extract_urls(result: dict) -> list[dict[str, str]]: """Extract all media URLs from a session result.""" urls: list[dict[str, str]] = [] seen: set[str] = set() workspace = result.get("workspace", {}) for entity in workspace.get("entities", []): for url in entity.get("image_urls", []): if url and url not in seen: seen.add(url) urls.append({ "url": url, "type": "image", "source": f"entity:{entity.get('name', '')}", }) for shot in workspace.get("shots", []): for img in shot.get("image_urls", []): if img and img not in seen: seen.add(img) urls.append({ "url": img, "type": "image", "source": f"shot:{shot.get('index', '')}", }) for vid in shot.get("video_urls", []): if vid and vid not in seen: seen.add(vid) urls.append({ "url": vid, "type": "video", "source": f"shot:{shot.get('index', '')}", }) for msg in result.get("messages", []): content = msg.get("content", "") if isinstance(content, str): for match in re.finditer( r'https?://[^\s"\'<>]+\.(?:png|jpg|jpeg|webp|mp4|mov|webm)', content, ): url = match.group(0) if url not in seen: seen.add(url) ext = Path(url.split("?")[0]).suffix.lower() media_type = "video" if ext in VIDEO_EXTS else "image" ...[truncated 4207 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse every media URL before opening it and permit only HTTPS. 2. Allowlist expected Zopia media and storage domains rather than accepting arbitrary hosts. 3. Resolve hostnames and reject loopback, private, link-local, multicast, reserved, and cloud metadata address ranges. 4. Revalidate the destination after every redirect and limit the maximum number of redirects. 5. Maintain a cumulative byte counter while streaming: ```python total = 0 while True: chunk = resp.read(8192) if not chunk: break total += len(chunk) if total > MAX_FILE_SIZE: raise ValueError("Media file exceeds the maximum permitted size") f.write(chunk) ``` 6. Download into a temporary file and atomically rename it only after all checks succeed; delete partial files on failure. 7. Validate `Content-Type` against the expected media category and inspect file signatures before accepting the file. 8. Apply per-file and aggregate download limits, including total bytes, file count, worker count, and elapsed time. 9. Avoid extracting downloadable URLs from unrestricted message text unless the backend marks them as trusted media artifacts. 10. Add tests covering omitted or false `Content-Length`, redirect-to-private-address behavior, oversized chunked responses, invalid MIME types, and partial-file cleanup. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (41)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The declared description presents a content-creation skill focused on generating scripts, characters, storyboards, images, and videos through a systematic AI production workflow. The supplied code does not perform creation or orchestration of any such workflow. Instead, it retrieves an existing session result, parses out media URLs, and downloads image/video assets to disk. This is a materially different primary purpose: asset export/download rather than AI creative generation. While it is related to Zopia media outputs, the code implements an undeclared capability centered on session result extraction and file downloading, which is not represented in the declared description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description focuses entirely on AI-powered video and image creation workflows, including script, character, storyboard, and video generation use cases. The actual code does not implement or support any of those creative functions. Instead, it performs a materially different task: fetching Zopia account balance information and printing it as JSON. This is an unrelated account/financial capability not disclosed in the description, so the description does not accurately represent the code's behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared description emphasizes an AI-powered creative production skill for videos, images, scripts, characters, and storyboards. The provided code chunk does not implement any of those creation capabilities; it only fetches and prints a paginated list of Zopia projects. While project listing could be tangentially related to a broader Zopia ecosystem, this code’s primary purpose is materially different from the declared creative-generation purpose. Therefore, the description does not accurately represent the supplied code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
声明描述的是一个面向终端用户的综合型 AI 视频创作技能,而实际代码只是一个剧集管理 CLI 工具,负责对项目剧集进行增删查。两者主目的存在明显差异:代码没有体现任何生成式 AI 创作能力,也没有实现视频、图片、剧本、角色或分镜相关处理。唯一与声明略有相关的是“多集连续剧制作”中的剧集概念,但代码仅做剧集资源管理,不能支撑声明中的核心创作功能。因此属于明显的描述与行为不一致。

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared description presents a broad AI creative production skill covering script→character→storyboard→video workflows on the Zopia platform. However, the supplied code only reads or writes project settings via CLI arguments for an existing project ID. While these settings are related to video creation, the code does not actually generate media, create scripts/storyboards, or orchestrate the described end-to-end workflow. This is a materially different primary purpose: configuration management rather than AI content creation.

Vague Triggers

High
Confidence
97% confidence
Finding
The trigger conditions are extremely broad and use mandatory activation language for common creative requests. Over-broad routing can cause this skill to capture unrelated user prompts, unnecessarily access the Zopia backend, consume credits, and send user creative content to a third-party service without sufficiently narrow intent verification.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The instruction "Git commit message 用中文" imposes a specific language requirement in natural-language guidance. This is a language-policy constraint without any opt-in, alternative, or region-specific justification, which matches the locale/language policy violation category.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
The README instructs users to execute `npx skills add 11cafe/zopia-skills` without pinning a specific package version. Because `npx` resolves and executes the latest published package state, a compromised upstream package, namespace takeover, or malicious update could cause arbitrary code execution on the user's machine during installation.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
The agent-specific install example again uses `npx skills add ... -a claude-code` without version pinning. This exposes users to the same supply-chain risk as the generic command: remote package code may change over time and execute unexpectedly at install time.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
`npx clawhub install zopia-skill` similarly invokes an unpinned remote CLI package. If the `clawhub` package or one of its dependencies is compromised, users could execute attacker-controlled code merely by following the README installation instructions.

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill declares access to environment variables and relies on a networked backend, but it does not define an explicit tool/permission scope. That makes its operational boundaries unclear and increases the chance of over-broad execution or accidental future privilege expansion, especially since it handles an access key and remote API actions.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The setup example requires `--locale zh-CN`, and later example workflows repeat the same locale choice as the default path. The document does not offer the user a language/locale choice or explain why Chinese locale is mandatory, which conflicts with the policy against forcing a specific locale without opt-in.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill instructs automatic downloading of generated outputs to the local filesystem without clear upfront notice or consent. Unannounced filesystem writes can surprise users, create privacy issues, consume disk space, and persist sensitive or copyrighted media locally even when the user only expected a preview or remote link.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The skill plans to expose project links and media URLs that may enable remote access to generated content, but it does not warn users about the privacy implications. If links are shareable or long-lived, sensitive prompts, creative assets, or unpublished media may be accessible beyond the local session.

Rp1

Medium
Category
MCP Rug Pull
Confidence
94% confidence
Finding
The documentation instructs users to run `npx clawhub` without pinning a specific package version. Because `npx` resolves and executes the latest matching package, a compromised upstream release, typo-squatted package, or unexpected breaking update could cause arbitrary code execution on the publisher's machine during login or publish operations.

Rp1

Medium
Category
MCP Rug Pull
Confidence
94% confidence
Finding
The guide uses `npx clawhub whoami` without a pinned version, which means command execution depends on whatever version npm resolves at runtime. In a supply-chain compromise scenario, an attacker could ship a malicious update that runs code locally and potentially accesses stored ClawHub credentials or repository contents.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The publish step tells operators to execute `npx clawhub publish ...` without constraining the package version. This is especially sensitive because publishing commands often run in privileged developer environments with repository access and release credentials, so a malicious or swapped package could exfiltrate tokens or tamper with releases.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The example publish command also uses unpinned `npx clawhub`, repeating the same supply-chain execution risk. Because this example is likely to be copied verbatim by maintainers, it normalizes unsafe release practices and increases the chance of compromise during real-world publishing.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
The inspection command `npx clawhub inspect zopia-skill` is also unpinned, allowing runtime retrieval and execution of whatever package version is current. Even seemingly read-only commands still execute code locally and can therefore be abused by a malicious upstream package.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
The quick-reference table repeats `npx clawhub whoami` without version pinning. Repetition in a summary section increases the likelihood users will rely on the unsafe form and execute an unintended or malicious package version.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
The quick-reference `inspect` command is unpinned and inherits the same package resolution risk as the rest of the document. Since users often copy commands from summary tables, this creates a practical avenue for unsafe execution in developer environments.

Static analysis

No suspicious patterns detected.