Back to skill

Security audit

Chanjing Text To Digital Person

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it says, but it handles API secrets and downloads files with weaker runtime scoping than its manifest implies.

Review before installing. Use only trusted environment variables, avoid setting CHANJING_OPENAPI_BASE_URL or CHANJING_API_BASE outside controlled testing, protect ~/.chanjing/credentials.json with restrictive permissions, and only run downloads for trusted result URLs into a dedicated output directory.

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/_auth.py:20
Finding
Unvalidated API Base URL Can Exfiltrate Chanjing Credentials and Access Tokens<![CDATA[ ## Vulnerability Details **File Location**: `scripts/_auth.py:20-25, 93-101`; `scripts/_task_api.py:6, 21-45` **Vulnerability Type**: Unrestricted credential destination and insecure transport **Risk Level**: High ### Complete Code Snippet ```python # scripts/_auth.py:20-25 def openapi_base_url() -> str: return ( os.environ.get("CHANJING_OPENAPI_BASE_URL") or os.environ.get("CHANJING_API_BASE") or _DEFAULT_OPENAPI_BASE ).rstrip("/") ``` ```python # scripts/_auth.py:93-101 url = API_BASE + "/open/v1/access_token" req = urllib.request.Request( url, data=json.dumps({"app_id": app_id, "secret_key": secret_key}).encode("utf-8"), headers={"Content-Type": "application/json"}, method="POST", ) try: with urllib.request.urlopen(req, timeout=30) as resp: ``` ```python # scripts/_task_api.py:6 API_BASE = (__import__("os").environ.get("CHANJING_OPENAPI_BASE_URL") or __import__("os").environ.get("CHANJING_API_BASE") or "https://open-api.chanjing.cc").rstrip("/") ``` ```python # scripts/_task_api.py:21-45 def api_get(token, path, query=None): query = query or {} suffix = "" if query: suffix = "?" + urllib.parse.urlencode(query) req = urllib.request.Request( f"{API_BASE}{path}{suffix}", headers={"access_token": token}, method="GET", ) with urllib.request.urlopen(req, timeout=30) as resp: body = json.loads(resp.read().decode("utf-8")) if body.get("code") != 0: raise RuntimeError(body.get("msg", body)) return body.get("data") def api_post(token, path, payload): req = urllib.request.Request( f"{API_BASE}{path}", data=json.dumps(payload).encode("utf-8"), headers={"access_token": token, "Content-Type": "application/json"}, method="POST", ) with urllib.request.urlopen(req, timeout=30) as resp: ``` ### Technical Analysis The API base URL is taken directly from either `CHANJING_OPENAPI_BASE_URL ...[truncated 1936 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the configured endpoint with `urllib.parse.urlparse`. 2. Require the `https` scheme and reject URLs containing user information, fragments, or unexpected ports. 3. Compare the normalized hostname against an explicit allowlist, preferably only `open-api.chanjing.cc` in production. 4. Apply identical validation to both current and legacy environment variables. 5. Disable endpoint overrides by default. If development overrides are necessary, require an explicit development mode and never load production credentials in that mode. 6. Validate the final destination after every redirect or disable redirects for credential-bearing requests. 7. Enforce the manifest network allowlist at runtime rather than treating it as documentation only. 8. Add tests proving that HTTP URLs, deceptive subdomains, user-information tricks, and redirects to unapproved hosts are rejected before any secret is transmitted. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/_auth.py:67
Finding
Credential File Is Persisted Without Explicit Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/_auth.py:67-70` **Vulnerability Type**: Insecure storage of sensitive credentials **Risk Level**: Medium ### Complete Code Snippet ```python def write_config(data): CONFIG_DIR.mkdir(parents=True, exist_ok=True) with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) ``` ### Technical Analysis The configuration file contains the application ID, secret key, access token, and expiration information. The code creates the directory and opens the file without specifying or enforcing restrictive filesystem permissions. For a newly created file, effective permissions depend on the process umask. A permissive umask may create a file readable by other local users. If the file already has insecure permissions, opening it for writing does not correct those permissions. The directory permissions and ownership are likewise not checked. Token persistence is declared in the manifest and is functionally necessary for the selected credential model, but sensitive values should not be stored using ambient default permissions. ### Attack Path 1. The Skill runs on a multi-user host under a permissive umask, or `credentials.json` already exists with broad permissions. 2. A token refresh invokes `write_config()`. 3. The file containing `secret_key` and `access_token` is created or updated without permission hardening. 4. Another local account or process with filesystem access reads the credentials. 5. The exposed credentials are reused against the Chanjing API. ### Impact Assessment A local attacker may obtain the secret key and access token of the user running the Skill. This can expose the same remote API capabilities as the affected Chanjing account, including task operations, generated content, and paid quota. Exploitation requires local filesystem access and depends on the host umask, directory permissions, and existing file mode; therefore, the iss ...[truncated 67 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the credential directory with mode `0700` and verify that it is owned by the current user. 2. Write credentials to a temporary file in the same directory using exclusive creation and mode `0600`. 3. Flush and synchronize the temporary file, then atomically replace `credentials.json`. 4. Explicitly set the final file mode to `0600`, including when updating an existing file. 5. Reject symlinked credential files and directories to reduce link-based file attacks. 6. Validate ownership before reading or writing existing credentials. 7. Consider using the operating system credential store instead of a plaintext JSON file where available. 8. Avoid printing access tokens to standard output when `_auth.py` is invoked directly. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/download_result.py:39
Finding
Downloader Allows Arbitrary Network Requests and Arbitrary Writable File Overwrites<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download_result.py:39-52` **Vulnerability Type**: Unrestricted URL fetch, unsafe output path, and unbounded download **Risk Level**: High ### Complete Code Snippet ```python default_dir = Path("outputs") / "text-to-digital-person" output_path = Path(args.output) if args.output else default_dir / infer_filename(args.url) output_path.parent.mkdir(parents=True, exist_ok=True) req = urllib.request.Request( args.url, headers={"User-Agent": "chanjing-text-to-digital-person-downloader"}, method="GET", ) try: with urllib.request.urlopen(req, timeout=120) as resp, open(output_path, "wb") as handle: handle.write(resp.read()) ``` ### Technical Analysis The downloader accepts an arbitrary URL and passes it directly to `urllib.request.urlopen`. It does not restrict the scheme or hostname, block local and private network addresses, or validate redirect destinations. This can be used as a server-side request forgery primitive from the execution environment. The optional `--output` value is also converted directly into a filesystem path. There is no requirement that the resolved path remain under `outputs/text-to-digital-person` or even under the workspace. Opening the path in `wb` mode truncates an existing writable file. Parent directories are created automatically, and symlink targets are not rejected. Finally, the entire response is read into memory and written without a maximum size. A malicious or unexpectedly large response can consume substantial memory and disk space. This behavior is broader than the documented purpose of explicitly downloading generated API result URLs. ### Attack Path #### Internal Network Request 1. An attacker causes the Skill to be invoked with a URL targeting a loopback, private-network, link-local, or otherwise sensitive endpoint. 2. `urllib.request.urlopen` sends the request from the host running the Skill. 3. Redirects may move the request to an ...[truncated 1409 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require HTTPS URLs and allow only documented Chanjing API or media CDN hostnames. 2. Resolve hostnames and reject loopback, private, link-local, multicast, reserved, and metadata-service addresses. 3. Validate every redirect destination using the same scheme, hostname, port, and resolved-address rules, or disable automatic redirects. 4. If arbitrary external result hosts are genuinely required, obtain explicit user confirmation and apply robust SSRF controls rather than accepting all URLs. 5. Resolve the requested output path and verify that it remains beneath an approved workspace output root. 6. Reject absolute paths, traversal outside the output root, symlinks, and non-regular-file targets. 7. Use exclusive file creation by default and require a separate explicit option before overwriting an existing file. 8. Stream the response in bounded chunks instead of calling `resp.read()` without a limit. 9. Enforce a maximum response size using both `Content-Length` and an actual byte counter. 10. Write to a temporary file, validate the final size and expected media type, and atomically rename it only after successful completion. 11. Remove partial files after errors and set appropriate file permissions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (33)

Credential Access

High
Category
Privilege Escalation
Content
description: >-
  Use Chanjing text-to-digital-person APIs for AI portraits, talking videos,
  optional LoRA training, polling, and explicit downloads when requested.
credential: credentials.json (app_id/secret_key; access_token persisted on disk)
openclaw_primary_env: false
environment: CHANJING_OPENAPI_CREDENTIALS_DIR, CHANJING_OPENAPI_BASE_URL
legacy_environment: CHANJING_CONFIG_DIR, CHANJING_API_BASE
Confidence
91% confidence
Finding
The skill declares use of credentials.json containing app_id/secret_key and persistence of access_token on disk, which confirms handling of sensitive secrets with local storage. Persistent credential material on disk increases exposure to accidental leakage, overbroad file access by other tools, or theft from a compromised host.

Credential Access

High
Category
Privilege Escalation
Content
本 Skill 与 guard 共用:

* `~/.chanjing/credentials.json`
* `https://open-api.chanjing.cc`

无凭证时,脚本会自动打开蝉镜登录页(若同仓库存在则执行 **`chanjing-credentials-guard/scripts/open_login_page.py`**,否则 **`webbrowser.open`**),并提示本地执行 **`chanjing_config.py`**。
Confidence
95% confidence
Finding
This section states the skill shares ~/.chanjing/credentials.json and may automatically open a login page or execute a helper script when credentials are missing. Auto-launching browser flows or helper subprocesses in response to missing credentials increases the attack surface and can surprise users, especially if path integrity or target URL validation is weak.

Credential Access

High
Category
Privilege Escalation
Content
| 文件名(仓库内) | 说明 |
|------------------|------|
| `_auth.py` | 读 **`credentials.json`**、刷新并 **写回** **`access_token` / `expire_in`**;缺 AK/SK 时尝试 **`open_login_page.py`** |
| `_task_api.py` | 任务 API 共用逻辑(由各 CLI import) |
| `create_photo_task.py` | 创建文生图任务 → `photo_unique_id` |
| `get_photo_task.py` | 单个文生图任务详情 |
Confidence
93% confidence
Finding
The documentation says _auth.py reads credentials.json and writes back access_token/expire_in, confirming local mutation of sensitive auth state by the skill. Writing refreshed tokens to disk can create persistence and exfiltration risks if logs, backups, or other processes can access the file.

Credential Access

High
Category
Privilege Escalation
Content
persistAccessTokenOnDisk: true
  primaryEnvIntentionallyOmitted: true
  doNotCommitToVcs:
    - credentials.json

clientPermissions:
  network:
Confidence
91% confidence
Finding
The manifest explicitly enables persistent storage of access tokens on disk in credentials.json. This creates a real exposure path if the workspace, home directory, backups, or other local processes can read the file, and it is more concerning here because the token is an active API credential rather than a static non-sensitive cache item.

Credential Access

High
Category
Privilege Escalation
Content
read:
      - "${CHANJING_OPENAPI_CREDENTIALS_DIR or CHANJING_CONFIG_DIR or ~/.chanjing}/credentials.json"
    write:
      - "${CHANJING_OPENAPI_CREDENTIALS_DIR or CHANJING_CONFIG_DIR or ~/.chanjing}/credentials.json"
  browser:
    mayOpenForAuth: true
    documentedHosts:
Confidence
80% confidence
Finding
Granting write access to the credentials.json file is riskier than mere read access because it allows modification or replacement of stored credentials and tokens. In combination with persistent token storage, this can enable tampering, credential poisoning, or unintended long-term retention of secrets on disk.

Credential Access

High
Category
Privilege Escalation
Content
#!/usr/bin/env python3
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/usr/bin/env python3
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/usr/bin/env python3
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/usr/bin/env python3
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/usr/bin/env python3
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#!/usr/bin/env python3
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
This markdown file presents the operational instructions and safety-relevant usage guidance almost entirely in Chinese, which effectively imposes a specific language on users. The policy allows locale constraints only when users are given a choice or the constraint is clearly documented and justified, neither of which appears here.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
SQP-3 applies to natural-language policy violations across all file types, including markdown. The trigger section lists activation phrases exclusively in Chinese and does not indicate that other languages are supported, optional, or intentionally restricted for a region-specific skill.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The manifest explicitly states that access tokens are persisted to disk, but the user-facing summary does not warn about this credential-handling behavior. Persisting bearer tokens increases the chance of credential theft from the local filesystem, especially because the same manifest also grants read/write access to the credential store.

Missing User Warnings

Medium
Confidence
82% confidence
Finding
The manifest allows downloading content from API response URLs into the user's environment without making that behavior obvious in the skill summary. Downloading remote content can introduce untrusted files into the workspace and may expose users to malicious or unexpected payloads if the API or response URLs are compromised.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The reference documents a `download_result.py` script that downloads `output_url` to the local filesystem, but it does not warn users that invoking it causes a local file write. In an agent skill, undisclosed filesystem side effects reduce informed consent and can lead to overwriting files, storing untrusted remote content locally, or unexpectedly persisting sensitive/generated media on the host.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
skills_dir = Path(__file__).resolve().parent.parent.parent
        script = skills_dir / "chanjing-credentials-guard" / "scripts" / "open_login_page.py"
        if script.exists():
            subprocess.run([sys.executable, str(script)], check=False, timeout=5)
        else:
            import webbrowser
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The code writes app credentials and access tokens into a JSON file under the user's home/config directory without setting restrictive file permissions or using a secure credential store. If the host is shared or local files are exposed by other software, these secrets can be read and reused to access the remote API.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The code sends app_id and secret_key to a remote API endpoint to obtain an access token. Although this is functionally expected for authentication, this file does not provide an explicit warning or disclosure to the user that local credentials will be transmitted over the network.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Content
import urllib.parse
import urllib.request

API_BASE = (__import__("os").environ.get("CHANJING_OPENAPI_BASE_URL") or __import__("os").environ.get("CHANJING_API_BASE") or "https://open-api.chanjing.cc").rstrip("/")

PHOTO_RUNNING = {"Ready", "Generating", "Queued"}
PHOTO_SUCCESS = {"Success"}
Confidence
75% confidence
Finding
Dynamic __import__() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The natural-language docstring and CLI descriptions are presented entirely in Chinese, including usage and output descriptions, with no indication that users may choose another language or that the tool is intended only for a Chinese-speaking context. Under the stated policy, forcing a specific language without opt-in is a locale-policy concern.

Missing User Warnings

Medium
Confidence
83% confidence
Finding
This code constructs a request body from user-provided attributes and transmits it to a remote API via api_post. While the script's purpose is to create a photo-generation task, the file does not include any explicit user-facing warning, comment, or docstring disclosure that the supplied prompts and reference image URL will be sent to an external service.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
This Python file contains natural-language docstrings, argument descriptions, and error messages exclusively in Chinese. Because the skill does not provide an opt-in language choice or state that it is intentionally limited to a Chinese-speaking or region-specific context, it may violate language/locale policy requirements.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The docstring, argparse description/help text, and runtime messages are all hard-coded in Chinese, which imposes a specific language on users. Under the policy, locale constraints should either be optional via user choice or clearly documented as justified for a region-specific tool; neither is present in this file.

Static analysis

No suspicious patterns detected.