T09 · Insecure Skill Coding Practices
- Location
scripts/_auth.py:13- Finding
Unrestricted API Base Override Can Redirect Credentials and User Data
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill appears to provide the advertised voice-clone TTS workflow, but it has under-scoped credential handling that could send secrets to an environment-controlled API endpoint.
Install only if you trust the publisher and your execution environment. Keep CHANJING_API_BASE unset unless you intentionally use a trusted HTTPS Chanjing endpoint, keep CHANJING_CONFIG_DIR in a private user-owned directory, protect credentials.json from backups or commits, and understand that reference-audio URLs, synthesis text, tokens, and task identifiers are sent to the configured API service.
scripts/_auth.py:13Unrestricted API Base Override Can Redirect Credentials and User Data
scripts/_auth.py:48Credential File Is Written Without Enforced Owner-Only Permissions
The request destination is derived from CHANJING_API_BASE, so environment control can silently redirect app_id and secret_key to an arbitrary server. Because this function posts long-lived credentials to obtain a token, misuse would directly leak secrets and enable unauthorized API access.
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
body = json.loads(resp.read().decode("utf-8"))
except Exception as e:
return None, str(e)
The declared description says the skill's primary purpose is TTS synthesis from text using a user-provided voice. However, this code chunk's actual purpose is voice creation/customized audio cloning from a reference audio URL. It accepts a name, URL, model type, and language; obtains an access token; calls the create_customised_audio API endpoint; and prints the returned voice_id. While some details align with the description—shared credential handling, optional CHANJING_API_BASE, and use of a public URL for reference audio—the core behavior is materially different from text-to-speech synthesis. Therefore this is a description-to-behavior mismatch.
The skill explicitly states that access_token and expire_in are written back into the same credentials.json file, increasing secret persistence on disk and the blast radius if that file is read by another process or accidentally disclosed. Co-locating long-lived app credentials and refreshed bearer tokens in one writable file is riskier than minimizing token persistence or isolating token storage.
## 功能说明
基于用户提供的**参考音频公网 URL**(由蝉镜服务端拉取)创建音色并合成语音;轮询任务并从接口返回 URL 下载结果。脚本**不**依赖 ffmpeg/ffprobe。机器可读凭据声明见篇首 **`metadata.openclaw.credentialModel`**。**`access_token` 与 `expire_in` 会写入同一 `credentials.json`;刷新令牌时由本技能覆盖文件中对应字段,请勿将凭证提交到版本库。**
## 运行依赖
This line confirms that app credentials and refreshed access tokens are both read and written in the same disk file, with overwrite behavior on refresh. Persistent writable storage of bearer tokens alongside root API secrets increases the chance of credential theft, accidental backup leakage, or misuse by other local tooling.
## 登记摘要(英文 · ClawHub / OpenClaw)
**Primary credential (not `primaryEnv`)**: `app_id` + `secret_key` in `~/.chanjing/credentials.json` (or `$CHANJING_CONFIG_DIR/credentials.json`); `access_token` and `expire_in` **read/written** in the same file (**overwritten** on token refresh). **`primaryEnv` omitted** by design. **`metadata.openclaw.credentialModel`** is the machine-readable registry source of truth.
**Required vs optional**: **`CHANJING_API_BASE`** **optional** (default `https://open-api.chanjing.cc`). **`CHANJING_CONFIG_DIR`** optional. **No** `ffmpeg`/`ffprobe` in skill `metadata`.
The runtime scope explicitly includes reading and writing credentials and trusting both user-provided URLs and API-returned URLs. While legitimate for functionality, this combination materially increases exposure because secrets are handled locally and network retrieval is influenced by external inputs.
| # | Topic | Answer |
|---|-------|--------|
| **1** | **Purpose vs implementation; primary credential** | **Aligned**. **Voice-clone TTS** client; **reference audio via user-provided public URL** (server-side fetch). **`CHANJING_API_BASE` not required**. **Primary credential** in **`credentials.json`**; **`primaryEnv` omitted**. **No** `ffmpeg`/`ffprobe` in `metadata`. |
| **2** | **Runtime scope: secrets, URLs** | **In scope**. **Read/write** credentials; **browser** if needed; **HTTPS**; **user reference URL** + **output URLs from API**—trust boundaries include **user-chosen reference URL** and **API host**. |
| **3** | **Env vs file secrets** | **`CHANJING_*`** optional; **`app_id` / `secret_key` / `access_token` / `expire_in` on disk**—sensitive; token fields **overwritten** on refresh. |
| **4** | **Persistence & privilege** | **`always: false`** default; **does not** modify other skills or global agent config. |
The documented behavior persists and overwrites access_token and expire_in in a general credentials file under a configurable directory. Configurable secret locations plus token persistence can be dangerous if the environment variable is manipulated or points to an insecure/shared location.
| Aspect | Details |
|--------|---------|
| **Primary credential** | Same file-based `app_id` / `secret_key` as other Chanjing skills. **Not** `primaryEnv`. |
| **Token** | Refreshed and **written** to `~/.chanjing/credentials.json` (or `CHANJING_CONFIG_DIR`); **`access_token` and `expire_in` are overwritten** when the skill refreshes the token. |
| **Env** | Optional `CHANJING_API_BASE`, `CHANJING_CONFIG_DIR`. |
| **User-supplied URL** | Create Voice API takes a **public URL** to reference audio; the user is responsible for that URL’s safety and legality; Chanjing’s servers retrieve it. |
| **Network / browser** | HTTPS to `https://open-api.chanjing.cc`; may open login when credentials are missing. |
The workflow requires obtaining and then using an access token for multiple subsequent requests, indicating session-bearing credentials are handled across steps. In this skill context, that becomes more sensitive because token persistence is elsewhere documented as on-disk and refreshed automatically.
5. Poll the Query Speech Generation Task Status API until the result is success
6. When the task status is complete, use the corresponding url in API response to download the generated audio file
### Get Access Token API
从 `~/.chanjing/credentials.json` 读取 `app_id` 和 `secret_key`,若无有效 Token 则调用:
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# 鉴权:与 chanjing-credentials-guard 使用同一配置文件(CONFIG_DIR/credentials.json)
# 无 AK/SK 时执行 open_login_page.py 打开注册/登录页
import json
import os
The skill declares significant capabilities in practice—environment access, reading and writing credentials, network access, and shell/script execution—but does not declare an explicit tool scope such as permissions or allowed-tools. This weakens policy enforcement and reviewability, making it easier for an agent runtime to grant broader powers than users or reviewers expect.
The documented workflow requires retaining a voice ID and task ID across polling steps, reflecting session/state persistence. In this skill that state is tied to credentialed API operations and culminates in downloading from API-returned URLs, so weak handling of persisted identifiers can enable unintended reuse, leakage in logs, or confusion across users/jobs.
You should use utf-8 to encode and decode text throughout this task.
1. Obtain an access\_token, which is required for subsequent requests
2. Call the Create Voice API, which accepts a url to an audio file as reference voice
3. Poll the Query Voice API until the result is success; keep the voice ID
4. Call the Create Speech Generation Task API, using the voice ID, record the `task_id`
5. Poll the Query Speech Generation Task Status API until the result is success
Comments, docstrings, and the user-facing NO_CREDENTIALS_MSG are written only in Chinese, which imposes a specific language on users without opt-in or alternative locale support. This matches the language-policy violation category because the file contains user-visible text that assumes a single language.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
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
webbrowser.open(LOGIN_URL)
The function writes sensitive authentication data to credentials.json, and later stores the refreshed access token there as well. In this file there is no confirmation prompt, logging, or warning comment disclosing that credentials and tokens are persisted locally.
The config file path is influenced by the CHANJING_CONFIG_DIR environment variable and then written without validation. In a hostile execution environment, this could redirect credential and token writes to an attacker-chosen location, causing secret exposure, overwriting other files accessible to the process, or unsafe symlink-following behavior.
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)
The code performs an HTTP POST to /open/v1/access_token containing the user's app_id and secret_key. Although this is part of authentication, the file provides no user-facing disclosure, prompt, or logging that credentials will be transmitted to the remote service.
The docstring and usage text require Chinese to understand how to invoke the script and what it does. This imposes a language constraint without any user opt-in or alternative locale, which matches the language/locale policy violation category.
No suspicious patterns detected.