Back to skill

Security audit

mopng-api

Security checks for vulnerabilities and agentic risk

Overview

This looks like a real MoPNG image workflow skill, but it needs Review because it can send an API key and image references to configurable remote services and can auto-approve low-cost paid runs.

Review this skill before installing. Use it only with a MoPNG API key you are willing to use for remote image processing, keep MOPNG_AGENT_BASE_URL at the trusted HTTPS MoPNG Agent endpoint unless you operate the replacement service, and prefer --no-auto-approve or a zero auto-approve threshold if you want explicit approval before paid execution. Do not pass private/internal URLs or URLs containing credentials as reference images.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/mopng_agent.py:20
Finding
API Key Disclosure Through an Unrestricted Configurable Base URL<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mopng_agent.py`, lines 20–55 **Vulnerability Type**: Unrestricted credential-bearing network destination **Risk Level**: High ### Vulnerable Code ```python class AgentClient: def __init__(self, base_url: str, api_key: str, timeout: float = 90) -> None: self.base_url = base_url.rstrip("/") self.api_key = api_key self.timeout = timeout def call(self, method: str, path: str, body: dict | None = None) -> dict: headers = {"Accept": "application/json", "X-API-Key": self.api_key} data = None if body is not None: data = json.dumps(body, ensure_ascii=False).encode("utf-8") headers["Content-Type"] = "application/json" req = request.Request( f"{self.base_url}{API_PREFIX}{path}", data=data, headers=headers, method=method, ) try: with request.urlopen(req, timeout=self.timeout) as response: # nosec B310 — base URL is operator configuration raw = response.read().decode("utf-8") except error.HTTPError as exc: detail = exc.read().decode("utf-8", "replace") raise RuntimeError(f"motu-agent HTTP {exc.code}: {detail}") from exc except error.URLError as exc: raise RuntimeError(f"motu-agent unavailable: {exc.reason}") from exc if not raw: return {} try: return json.loads(raw) except json.JSONDecodeError as exc: raise RuntimeError("motu-agent returned non-JSON data") from exc def _client(args: argparse.Namespace) -> AgentClient: key = os.getenv("MOPNG_API_KEY") if not key: raise ValueError("MOPNG_API_KEY is required") return AgentClient(os.getenv("MOPNG_AGENT_BASE_URL", DEFAULT_BASE_URL), key, args.timeout) ``` ### Technical Analysis The client obtains `MOPNG_AGENT_BASE_URL` directly from the environment and u ...[truncated 1765 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse `MOPNG_AGENT_BASE_URL` before constructing the client. 2. Require the `https` scheme in production. 3. Reject URLs containing a username, password, query string, or fragment. 4. Allowlist `agent-api.mopng.cn` by default. 5. If custom deployments are required, introduce an explicit allowlist such as `MOPNG_ALLOWED_AGENT_HOSTS`. 6. Reject loopback, private, reserved, multicast, link-local, and cloud metadata addresses. 7. Resolve hostnames and validate every resolved address to prevent private-address resolution. 8. Ensure redirects are either disabled or revalidated before forwarding authentication headers. 9. Never forward `X-API-Key` across an origin-changing redirect. 10. Add tests covering HTTP URLs, arbitrary external hosts, embedded credentials, private IPs, metadata endpoints, and redirects. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mopng_agent.py:63
Finding
Reference Image URLs Are Forwarded Without Required SSRF and Credential Validation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mopng_agent.py`, lines 63–97 **Vulnerability Type**: Unvalidated remote resource reference **Risk Level**: Medium ### Vulnerable Code ```python def _brief(args: argparse.Namespace) -> dict: references = args.reference_url or [] if len(references) > 14: raise ValueError("at most 14 --reference-url values are allowed") reference = references[0] if references else "prompt-only" subject_type = "image" if references else "text" style = {} if args.style_constraint: style["constraint"] = args.style_constraint if args.avoid: style["avoid"] = args.avoid brief = { "user_intent": args.intent, "spec": { "goal": args.goal, "usage": args.usage, "subject": {"type": subject_type, "reference": reference}, "style": style, "format": args.format, }, "budget": { "cost_mode": args.cost_mode, "max_cost_points": args.max_cost_points, "max_time_sec": args.max_time_sec, }, } if references: brief["spec"]["subject"]["references"] = references if args.width or args.height: if not args.width or not args.height: raise ValueError("--width and --height must be provided together") brief["spec"]["size"] = {"width": args.width, "height": args.height} return brief ``` ### Technical Analysis The implementation limits the number of reference URLs but does not validate their contents. Every `--reference-url` value is inserted into the Brief and transmitted to the remote MoPNG Agent. The client does not enforce the security policy documented in `SKILL.md`, which requires HTTPS and prohibits URLs containing credentials, internal addresses, or cloud metadata destinations. Missing checks include: - HTTPS enforcement - Rejection of embedded usernames and passwords - Hostname validity checks - Reject ...[truncated 1774 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate every `--reference-url` before constructing the Brief. 2. Require an absolute HTTPS URL with a valid hostname. 3. Reject URLs with embedded usernames or passwords. 4. Reject loopback, private, reserved, multicast, and link-local IP literals. 5. Explicitly block cloud metadata names and addresses, including `169.254.169.254` and `metadata.google.internal`. 6. Resolve hostnames and reject the URL if any resolved address is non-public. 7. Normalize hostnames before applying validation to prevent trailing-dot and alternate-notation bypasses. 8. Apply equivalent validation server-side immediately before fetching the resource. 9. Revalidate every redirect destination and limit the number of redirects. 10. Add tests for all prohibited URL classes and verify consistency with the requirements in `SKILL.md`. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/mopng_api.py:110
Finding
Legacy Result Downloader Permits Redirect-Based SSRF and Unbounded Response Reads<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mopng_api.py`, lines 110–128 and 342–347 **Vulnerability Type**: Incomplete SSRF mitigation and unbounded download **Risk Level**: Low ### Vulnerable Code ```python def _validate_result_download_url(url: str) -> None: """Validate URLs returned by MoPNG before downloading (HTTPS + no obvious SSRF targets).""" u = url.strip() p = parse.urlparse(u) if p.scheme.lower() != "https": raise ValueError("Download URL must use https.") if not p.hostname: raise ValueError("Download URL must include a hostname.") if p.username is not None or p.password is not None: raise ValueError("Download URL with credentials is not allowed.") if _host_is_ssrf_risk(p.hostname): raise ValueError("Download URL host is not allowed.") ``` ```python def _download_image(url: str, output_path: Path) -> None: """Download image from URL to local path""" _validate_result_download_url(url) req = request.Request(url, method="GET") with request.urlopen(req, timeout=60) as resp: # nosec B310 — URL validated in _validate_result_download_url output_path.write_bytes(resp.read()) ``` ### Technical Analysis The downloader validates only the textual hostname in the initial URL. It does not resolve the hostname and inspect the resulting addresses, and it relies on `urllib`'s automatic redirect handling without revalidating redirect destinations. Consequently, an initially acceptable public hostname can resolve to a private address or redirect to a blocked destination after passing `_validate_result_download_url()`. The response body is also consumed through an unrestricted `resp.read()`. `MAX_BYTES` applies to input image validation but is not enforced for downloaded results. A server can therefore return a very large response, causing excessive memory and disk consumption. The declared Skill workflow uses `mopng_agent.py` and explicitly deprecates this di ...[truncated 1768 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the deprecated direct API client if it is no longer part of supported functionality. 2. Resolve the result hostname and reject the URL if any resolved address is private, loopback, reserved, multicast, or link-local. 3. Disable automatic redirects or process redirects manually. 4. Reapply scheme, credential, hostname, DNS, and address validation to every redirect target. 5. Pin the validated address carefully while preserving TLS hostname verification to reduce DNS-rebinding exposure. 6. Stream downloads in bounded chunks rather than using `resp.read()` without a limit. 7. Enforce a strict maximum result size and abort when `Content-Length` or streamed bytes exceed it. 8. Validate the response media type and image signature before accepting the file. 9. Write to a temporary file and atomically rename it only after successful validation. 10. Add tests for redirects to private addresses, hostnames resolving to private addresses, excessive responses, and invalid content types. ]]>
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (24)

Tainted flow: 'req' from os.getenv (line 298, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req = request.Request(url, method=method, headers=headers)
    
    try:
        with request.urlopen(req, timeout=timeout) as resp:  # nosec B310 — URL is fixed API host + path
            return json.loads(resp.read().decode())
    except error.HTTPError as e:
        body = e.read().decode("utf-8", "ignore")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.getenv (line 298, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
"""Download image from URL to local path"""
    _validate_result_download_url(url)
    req = request.Request(url, method="GET")
    with request.urlopen(req, timeout=60) as resp:  # nosec B310 — URL validated in _validate_result_download_url
        output_path.write_bytes(resp.read())
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Credential Access

High
Category
Privilege Escalation
Content
outputs/
__pycache__/
.cursor/
.env
.omc/
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
声明把该技能描述为一个基于 qise-studio/motu-agent 的 MoPNG Agent 工作流技能,重点在 agent 协商和任务规划/批准式执行;但代码实际只是一个本地 CLI API client,向 mo-api.mopng.cn 发起请求并下载结果。两者在主要实现形态和核心能力上有明显差异。图像生成/修图的大方向部分吻合,且文生图、图生图、抠图、扩图、放大等能力基本存在;但声明中的关键特征——agent 协商、Brief/Plan 处理、多步工作流 orchestration——均未实现,属于实质性描述不符。此外,代码还提供了图片文字翻译和模型列表查询这两项声明未提到的功能。综合判断为存在明显 mismatch。

Cloud Metadata Access

High
Category
Server-Side Request Forgery
Content
"127.0.0.1",
        "0.0.0.0",  # nosec B104 — blocklisted hostname for SSRF checks, not socket bind
        "::1",
        "169.254.169.254",
        "metadata.google.internal",
    }
)
Confidence
90% confidence
Finding
Code accesses a cloud instance metadata endpoint (e.g. 169.254.169.254). A single request can return temporary IAM credentials, making this a high-value SSRF target for credential theft.

Cloud Metadata Access

High
Category
Server-Side Request Forgery
Content
"0.0.0.0",  # nosec B104 — blocklisted hostname for SSRF checks, not socket bind
        "::1",
        "169.254.169.254",
        "metadata.google.internal",
    }
)
Confidence
90% confidence
Finding
Code accesses a cloud instance metadata endpoint (e.g. 169.254.169.254). A single request can return temporary IAM credentials, making this a high-value SSRF target for credential theft.

Known Vulnerable Dependency: pytest==9.0.2 — 2 advisory(ies): CVE-2025-71176 (pytest has vulnerable tmpdir handling); CVE-2025-71176 (pytest has vulnerable tmpdir handling)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The skill documentation is written entirely in Chinese and does not indicate that users may choose another language or locale. Under the policy criteria, forcing a specific language without opt-in is a natural-language policy violation unless the locale restriction is clearly documented and justified.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
--goal '文生图' --usage '海报' --watch
```

低于 `MOPNG_AGENT_AUTO_APPROVE_COST_POINTS`(默认 5 点)的方案会自动批准;其他方案停在 Plan 阶段,需显式执行 `approve`。

## 手动协商
Confidence
88% confidence
Finding
The README documents an automatic approval path for plans below a configurable cost threshold, which weakens human review over agent-selected actions. In a skill that negotiates and executes image-generation/editing workflows against a remote API, auto-approval can allow unintended processing, policy-bypassing prompts, or unexpected external requests to proceed without explicit operator confirmation.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill declares executable capabilities involving environment access, file I/O, network, and shell, but does not constrain them with an explicit permission or allowed-tools scope. In an agent setting, this widens the blast radius: a prompt-induced or implementation-level misuse could access secrets, read/write unintended files, or make arbitrary outbound requests beyond the documented image workflow.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
The skill description and operational instructions are entirely written in Chinese and present the workflow as the required interaction mode, with no indication that users may choose another language. This can violate language/locale policy where skills must not force a specific language unless the constraint is explicitly justified or optional.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
|---|---:|---|
| `MOPNG_API_KEY` | 是 | MoPNG 用户 API Key,格式通常为 `ak_...`;仅放在宿主私密配置中。 |
| `MOPNG_AGENT_BASE_URL` | 否 | `motu-agent` 地址,默认 `https://agent-api.mopng.cn`;不要重复填写 `/api/v1/open/agent`。 |
| `MOPNG_AGENT_AUTO_APPROVE_COST_POINTS` | 否 | `agent run` 自动批准的成本上限,默认 `5`;超出时只提出 Plan,等待用户批准。 |

鉴权使用 `X-API-Key: $MOPNG_API_KEY`。服务端同时支持 `Authorization: Bearer $MOPNG_API_KEY`,但客户端默认使用前者。
Confidence
87% confidence
Finding
The documented AUTO_APPROVE behavior allows the skill to approve billable remote operations based on a configured threshold rather than explicit per-request user confirmation. Even with a low cost cap, this creates a real risk of unauthorized spending or unintended processing of user-supplied images, especially in an agent environment where requests may be ambiguous or manipulated.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
### 一键模式的自动批准规则

`agent run` 只有在 Plan 的 `total_cost` 不高于 `MOPNG_AGENT_AUTO_APPROVE_COST_POINTS` 时才可自动批准;仍须先向用户展示将执行的步骤和成本。高于阈值或用户要求高质量/指定模型时,停在 Plan 阶段等待批准。需要强制批准可使用 `--no-auto-approve`。

## OpenAPI 契约
Confidence
90% confidence
Finding
The auto-approve mechanism at this location authorizes execution without a mandatory explicit consent step for each operation. In the context of an image-generation/editing skill that can consume API credits and process user media, that is a meaningful security and safety risk because user intent, sensitive content handling, and billing consequences can be misapplied automatically.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
### 一键模式的自动批准规则

`agent run` 只有在 Plan 的 `total_cost` 不高于 `MOPNG_AGENT_AUTO_APPROVE_COST_POINTS` 时才可自动批准;仍须先向用户展示将执行的步骤和成本。高于阈值或用户要求高质量/指定模型时,停在 Plan 阶段等待批准。需要强制批准可使用 `--no-auto-approve`。

## OpenAPI 契约
Confidence
90% confidence
Finding
The auto-approve mechanism at this location authorizes execution without a mandatory explicit consent step for each operation. In the context of an image-generation/editing skill that can consume API credits and process user media, that is a meaningful security and safety risk because user intent, sensitive content handling, and billing consequences can be misapplied automatically.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
parser = argparse.ArgumentParser(description="Call motu-agent MoPNG Agent OpenAPI")
    sub = parser.add_subparsers(dest="command", required=True)

    run = sub.add_parser("run", help="create a Brief and optionally auto-approve a cheap Plan")
    _add_common(run)
    _add_brief_args(run)
    run.add_argument("--no-auto-approve", action="store_true")
Confidence
85% 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.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
parser = argparse.ArgumentParser(description="Call motu-agent MoPNG Agent OpenAPI")
    sub = parser.add_subparsers(dest="command", required=True)

    run = sub.add_parser("run", help="create a Brief and optionally auto-approve a cheap Plan")
    _add_common(run)
    _add_brief_args(run)
    run.add_argument("--no-auto-approve", action="store_true")
Confidence
85% 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.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
if args.command in {"run", "brief"}:
            result = client.call("POST", "/session", _brief(args))
            _print(result)
            if args.command == "run" and not args.no_auto_approve:
                plan_data = result.get("plan", {})
                threshold = int(os.getenv("MOPNG_AGENT_AUTO_APPROVE_COST_POINTS", "5"))
                llm_billing = plan_data.get("llm_billing") or {}
Confidence
92% confidence
Finding
The run command will automatically approve and potentially execute a returned plan unless the operator opts out, allowing an external service to influence actions with limited human review. In this skill context, the agent is specifically designed to transform user requests into plans and execute image workflows, so default auto-approval increases the risk of unintended spend, undesired processing, or acting on manipulated server responses.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
_print(result)
            if args.command == "run" and not args.no_auto_approve:
                plan_data = result.get("plan", {})
                threshold = int(os.getenv("MOPNG_AGENT_AUTO_APPROVE_COST_POINTS", "5"))
                llm_billing = plan_data.get("llm_billing") or {}
                total_estimated_cost = plan_data.get("total_cost", threshold + 1) + llm_billing.get("cost_virtual", 0)
                if total_estimated_cost <= threshold:
Confidence
94% confidence
Finding
The approval gate relies only on a cost threshold from environment configuration plus billed virtual cost, which is an incomplete policy for deciding whether a remote-generated plan is safe to execute. A malicious or compromised service could return a cheap but harmful or privacy-invasive workflow, and the client would approve it automatically without requiring human confirmation.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This code issues a DELETE request to remove a session, but there is no confirmation prompt, user-facing log/print, or explanatory comment/docstring warning that the operation is destructive. For a code file, irreversible operations like deletion should include some form of disclosure unless clearly warned elsewhere.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill uploads local images and forwards prompts to a third-party service, but the code provides no user-visible consent, warning, or policy gate before transferring potentially sensitive content off-platform. In an agent skill context, users may assume processing is local unless clearly told otherwise, creating a real privacy and data-handling risk.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Import should work, but main should fail
            import subprocess
            result = subprocess.run(
                [sys.executable, "-m", "scripts.mopng_api", "list-models"],
                capture_output=True,
                text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Low
Confidence
73% confidence
Finding
The default base URL is fixed to a .cn endpoint, which imposes a specific regional service selection without any explicit user opt-in or documented locale justification in this file. The policy requires avoiding language or locale constraints unless the user is given a choice or the constraint is clearly documented and justified.

Context-Inappropriate Capability

Low
Confidence
90% confidence
Finding
The manifest focuses on converting user requests into briefs/plans and executing image generation/editing tasks such as text-to-image, image-to-image, background removal, outpainting, and upscaling. The separate list-models command queries and reveals available backend AI models, which is a discovery/introspection feature not clearly justified by that stated purpose.

Context-Inappropriate Capability

Low
Confidence
84% confidence
Finding
The manifest describes a workflow-oriented image generation/editing skill, but does not mention handling secrets or reading local environment state. Accessing MOPNG_API_KEY is an additional capability beyond the user-facing image processing purpose, even though it supports the API integration technically.

Static analysis

Detected: suspicious.obfuscated_code

Potential obfuscated payload detected.

Warn
Code
suspicious.obfuscated_code
Location
tests/test_mopng_api.py:25