Back to skill

Security audit

Model Setup

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but it handles API keys in ways that can expose them through command lines, backups, and unvalidated API endpoints.

Review before installing. Use this only with model-provider keys you are comfortable storing in local OpenClaw config files, avoid testing untrusted or plaintext Base URLs, and rotate any key previously passed through these command examples. The skill is not malicious based on the artifacts, but its current credential handling is risky enough that users should treat installation and use as requiring careful review.

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/test_model.py:70
Finding

API Credentials Exposed Through Process Command-Line Arguments

Content
View full analysis

Vulnerability Details

File Location: scripts/test_model.py:70-76, 193-199; documented invocation at SKILL.md:39-42
Vulnerability Type: Sensitive information exposure through process arguments
Risk Level: High

Vulnerable Code

python
curl_cmd = [
    "curl", "-s", "-X", "POST", test_url,
    "-H", "Content-Type: application/json",
    "-H", f"Authorization: Bearer {api_key}",
    "-d", json.dumps(payload),
    "--connect-timeout", "10",
    "--max-time", "30"
]
python
if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("用法: test_model.py <provider_config_json> <model_id> [--test-tool-calling] [--test-streaming]")
        sys.exit(1)

    provider_config = json.loads(sys.argv[1])
    model_id = sys.argv[2]

The documented invocation also places the credential-bearing provider configuration directly in the command line:

bash
python3 scripts/test_model.py '<provider_config_json>' '<model_id>' [--test-tool-calling] [--test-streaming]

Equivalent credential-bearing curl argument construction is also present in the optional tool-calling and streaming tests at scripts/test_model.py:122-128 and scripts/test_model.py:164-170.

Technical Analysis

The provider configuration contains the API key, but the interface requires that configuration to be supplied through sys.argv. Command-line arguments can be retained in shell history and may be visible through process inspection, auditing systems, monitoring agents, crash reports, or command telemetry.

The script subsequently creates a curl child process and embeds the API key in an Authorization header passed as another command-line argument. This creates a second exposure window in which the bearer credential is present in the child process's argument vector.

Although access to another process's command line depends on operating-system protections, user identity, and process-monitoring permissions, secrets should not be ...[truncated 1045 chars]

Remediation
View remediation

Remediation Suggestions

  • Do not accept credential-bearing JSON through command-line arguments.
  • Read provider configuration from standard input or from a file with mode 0600.
  • Prefer obtaining the API key from a protected environment variable or operating-system secret manager while ensuring it is not printed or logged.
  • Replace the curl subprocess with an in-process HTTP client so authorization headers are not copied into a child process's argument vector.
  • If curl must be retained, provide headers through a protected temporary configuration file or standard input rather than -H command-line arguments.
  • Update SKILL.md so examples never instruct users to place secrets directly in shell commands.
  • Redact credentials from exception messages, diagnostic output, telemetry, and debug logs.
  • Recommend revocation and rotation of keys that have already been used through the current interface.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/test_model.py:54
Finding

API Keys Can Be Sent to Unvalidated or Plaintext Endpoints

Content
View full analysis

Vulnerability Details

File Location: scripts/test_model.py:54-76
Vulnerability Type: Unvalidated destination for sensitive credentials and missing transport-security enforcement
Risk Level: High

Vulnerable Code

python
# 尝试调用模型进行简单测试
# 这里使用 curl 进行测试
base_url = provider_config.get("baseUrl", "")
api_type = provider_config.get("api", "openai-completions")

if not base_url:
    result["error"] = "baseUrl 为空"
    return result

# 构建测试请求
test_url = f"{base_url}/chat/completions"

# 准备测试 payload
payload = {
    "model": model_id,
    "messages": [
        {"role": "user", "content": "测试"}
    ],
    "max_tokens": 10
}

# 使用 curl 测试
curl_cmd = [
    "curl", "-s", "-X", "POST", test_url,
    "-H", "Content-Type: application/json",
    "-H", f"Authorization: Bearer {api_key}",
    "-d", json.dumps(payload),
    "--connect-timeout", "10",
    "--max-time", "30"
]

Technical Analysis

The only validation applied to baseUrl is a nonempty-string check. The code does not:

  • Require HTTPS.
  • Validate the URL scheme and hostname.
  • Reject embedded URL credentials.
  • Distinguish trusted provider domains from arbitrary custom endpoints.
  • Warn the user before disclosing a bearer token to an unrecognized host.
  • Restrict plaintext HTTP to an explicit local development mode.

Because the API key is automatically attached as a bearer token, control over or influence on baseUrl is effectively control over the destination to which the credential is disclosed. Use of an http:// endpoint additionally exposes the credential to interception or modification by parties on the network path.

Custom endpoints are a legitimate feature of the Skill, so arbitrary-host support is not inherently malicious. The vulnerability is the absence of security validation and explicit informed confirmation before transmitting a reusable secret.

Attack Path

  1. An attacker supplies, recommends, or causes the user to enter an attacker-controlled Base URL, or a use ...[truncated 1137 chars]
Remediation
View remediation

Remediation Suggestions

  • Parse baseUrl with a strict URL parser.
  • Allow only https by default and reject unsupported schemes, malformed hosts, fragments, and embedded credentials.
  • Permit plaintext HTTP only through an explicit development option restricted to loopback addresses.
  • Display the normalized destination hostname and require explicit confirmation before sending a key to a custom or previously untrusted host.
  • Provide an optional allowlist for approved provider domains.
  • Prevent redirects from forwarding authorization headers to a different origin, or disable redirects for validation requests.
  • Use TLS certificate verification and do not expose an option that silently disables verification.
  • Consider separate credentials for custom endpoints instead of encouraging reuse of production provider keys.
  • Avoid logging response bodies when they may contain sensitive provider diagnostics.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/add_model.py:17
Finding

Plaintext Credential Backups and Predictable Temporary Files

Content
View full analysis

Vulnerability Details

File Location: scripts/add_model.py:17-48; additional predictable temporary-file writes at scripts/add_model.py:159-163 and scripts/add_model.py:179-183
Vulnerability Type: Unsafe temporary-file handling and excessive retention of plaintext secrets
Risk Level: Medium

Vulnerable Code

python
def backup_config(config_path):
    """
    备份配置文件

    Args:
        config_path: 配置文件路径

    Returns:
        Path: 备份文件路径
    """
    backup_path = config_path.with_suffix(f".json.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}")
    shutil.copy2(config_path, backup_path)
    return backup_path


def load_config(config_path):
    """
    加载配置文件

    Args:
        config_path: 配置文件路径

    Returns:
        dict: 配置内容
    """
    if not config_path.exists():
        return {"providers": {}}

    with open(config_path, 'r', encoding='utf-8') as f:
        return json.load(f)


def save_config(config_path, config):
    """
    保存配置文件

    Args:
        config_path: 配置文件路径
        config: 配置内容
    """
    # 先写入临时文件
    temp_path = config_path.with_suffix('.json.tmp')
    with open(temp_path, 'w', encoding='utf-8') as f:
        json.dump(config, f, indent=2, ensure_ascii=False)

    # 原子性替换
    temp_path.replace(config_path)

The same fixed-name pattern is used when modifying default and agent configuration:

python
temp_path = default_config_path.with_suffix('.json.tmp')
with open(temp_path, 'w', encoding='utf-8') as f:
    json.dump(default_config, f, indent=2, ensure_ascii=False)
temp_path.replace(default_config_path)
python
temp_path = agent_config_path.with_suffix('.json.tmp')
with open(temp_path, 'w', encoding='utf-8') as f:
    json.dump(agent_config, f, indent=2, ensure_ascii=False)
temp_path.replace(agent_config_path)

Technical Analysis

The model configuration is expected to contain plaintext API keys. Every update copies the existing configuration to a timestamped backup. These backups have ...[truncated 2477 chars]

Remediation
View remediation

Remediation Suggestions

  • Store API credentials in an operating-system keychain or secret manager and keep only secret references in models.json.
  • Create temporary files in the destination directory with tempfile.NamedTemporaryFile or mkstemp, using unique unpredictable names and mode 0600.
  • Use exclusive creation and reject symbolic links.
  • Verify that the destination directory is not writable by untrusted users.
  • Flush and fsync the temporary file before atomic replacement; where durability is required, also synchronize the parent directory.
  • Explicitly enforce restrictive permissions on configuration files and backups rather than relying on the process umask.
  • Implement a defined backup-retention policy and securely remove obsolete backups.
  • Avoid placing secrets in backups where possible; otherwise encrypt backups using a separately protected key.
  • Preserve ownership and permissions intentionally after replacement.
  • Apply the hardened temporary-file mechanism consistently to model, default, and agent configuration writes.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The skill presents itself as a safe configuration manager for models.json, but the finding indicates it may not manage that file as described and may also perform undocumented behavior such as streaming tests. Undocumented side effects and missing promised protections increase the risk of unintended network requests, configuration drift, and user consent failures.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The skill presents itself as a safe configuration manager for models.json, but the finding indicates it may not manage that file as described and may also perform undocumented behavior such as streaming tests. Undocumented side effects and missing promised protections increase the risk of unintended network requests, configuration drift, and user consent failures.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The skill presents itself as a safe configuration manager for models.json, but the finding indicates it may not manage that file as described and may also perform undocumented behavior such as streaming tests. Undocumented side effects and missing promised protections increase the risk of unintended network requests, configuration drift, and user consent failures.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill instructs use of shell commands plus file read/write operations, but it does not declare any explicit tool scope or permission boundaries. That makes the skill capable of modifying sensitive local configuration and invoking scripts without transparent guardrails, increasing the risk of over-privileged execution or accidental misuse.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file's user-facing instructions and workflow are entirely in Chinese, which imposes a specific language on users. Under the policy, language constraints should either offer user choice or be clearly justified as region-specific, neither of which appears here.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 18)May include surrounding context.

md
**必需参数:**
- **Provider ID**: 提供商标识符(如 `openai`, `anthropic`, 或自定义 ID)
- **Base URL**: API 基础 URL(如 `https://api.openai.com/v1`)
- **API Key**: API 密钥(格式通常为 `key_id:secret`)
- **Model ID**: 模型 ID(如 `gpt-4`, `claude-3-opus`)
- **Model Name**: 显示名称(如 `GPT-4 (OpenAI)`)

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 134)May include surrounding context.

md
**必需参数:**
- **Provider ID**: 提供商标识符(如 `openai`, `anthropic`, 或自定义 ID)
- **Base URL**: API 基础 URL(如 `https://api.openai.com/v1`)
- **API Key**: API 密钥(格式通常为 `key_id:secret`)
- **Model ID**: 模型 ID(如 `gpt-4`, `claude-3-opus`)
- **Model Name**: 显示名称(如 `GPT-4 (OpenAI)`)

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 162)May include surrounding context.

md
{
  "provider_id": "anthropic",
  "provider_config": {
    "baseUrl": "https://api.anthropic.com/v1",
    "apiKey": "sk-ant-xxx",
    "api": "anthropic-completions"
  },

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This Python file contains user-facing natural-language content entirely in Chinese, including the module docstring, function docstrings, validation errors, usage text, and status messages. Under the policy, forcing a specific language without user opt-in or a documented justification is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script's docstrings, status messages, and CLI usage text are written in Chinese, which imposes a specific language on users without any opt-in or alternative locale handling. The policy explicitly calls for flagging language or locale constraints when the skill forces a specific language without user choice.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script automatically sends the supplied API key in an Authorization header and transmits user-controlled prompts to whatever baseUrl is configured, without any trust check, confirmation, or restriction to expected providers. In the context of a model-setup skill, this makes it easy to exfiltrate credentials to a malicious or mistyped endpoint during 'testing', especially because the feature is specifically designed to validate arbitrary new model configurations.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/test_model.py (reported line 82)May include surrounding context.

python
]

        try:
            output = subprocess.check_output(curl_cmd, stderr=subprocess.PIPE, text=True)
            response = json.loads(output)

            # 检查响应

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/test_model.py (reported line 130)May include surrounding context.

python
"--max-time", "30"
                ]

                tool_output = subprocess.check_output(tool_curl_cmd, stderr=subprocess.PIPE, text=True)
                tool_response = json.loads(tool_output)

                # 检查是否有 tool_calls

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/test_model.py (reported line 170)May include surrounding context.

python
"--max-time", "30"
                ]

                stream_output = subprocess.check_output(stream_curl_cmd, stderr=subprocess.PIPE, text=True)

                # 检查是否是 SSE 格式(以 "data: " 开头的多行)
                lines = stream_output.strip().split('\n')

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

This Python file contains natural-language strings such as the module docstring, error messages, and formatted output entirely in Chinese. Under the policy rule for language or locale constraints, forcing a specific language without user opt-in is a natural-language policy concern unless the tool is clearly documented as region-specific.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.