Back to skill

Security audit

Cs Qweather Jwtgen

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed QWeather JWT token generator, with some avoidable credential-handling and logging risks users should understand.

Install only if you are comfortable letting the skill read your QWeather private key and QWeather identifiers, overwrite ~/.myjwtkey/last-token.dat, and create masked logs under /tmp/cslog. Keep ~/.openclaw/.env and ~/.myjwtkey/ed25519-private.pem private and user-owned; avoid storing unrelated secrets in the shared .env file if this skill will run unchanged.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (2)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/generateJWTtoken.py:22
Finding
Shared credential file is loaded without variable-level least-privilege controls<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generateJWTtoken.py:22-29` **Vulnerability Type**: Excessive credential access and environment-variable override **Risk Level**: Medium ### Vulnerable Code ```python # 尝试加载 dotenv(标准方式读取 .env 文件) try: import dotenv # 优先加载 ~/.openclaw/.env(OpenClaw 标准配置位置) # 若 OpenClaw 已将 env 注入了当前进程,此调用不会覆盖已存在的变量 dotenv.load_dotenv(os.path.expanduser("~/.openclaw/.env"), override=True) except ImportError: # 未安装 dotenv 时跳过,完全依赖环境继承 pass ``` The same behavior is documented in `SKILL.md:28`. ### Technical Analysis The Skill only needs `QWEATHER_SUB` and `QWEATHER_KID`, but `load_dotenv()` loads every variable found in the shared `~/.openclaw/.env` file into the process environment. This can expose unrelated credentials to the script and all code executing in the same process. The use of `override=True` also replaces existing process-environment values with values from the file. This contradicts the adjacent comment claiming that existing variables will not be overwritten. Consequently, caller-supplied QWeather identifiers or other environment settings may be silently replaced. No code in the audited project sends these values over the network or directly reads unrelated variables. The risk is instead an unnecessary expansion of the credential exposure boundary. Imported packages and future code changes can access all loaded values through `os.environ`, despite the declared operation requiring only two variables. ### Attack Path 1. The user's shared `~/.openclaw/.env` contains QWeather configuration and unrelated service credentials. 2. The user invokes the JWT-generation Skill. 3. The script loads every entry from that file into `os.environ`, using file values to override inherited values. 4. A compromised or substituted dependency executing later in the process—such as code reached through `jwt.encode()`—enumerates `os.environ`. 5. The dependency obtains unrelated credentials that wer ...[truncated 1032 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not inject the entire shared credential file into `os.environ`. 2. Parse the file without globally mutating the process environment, then select only the required variables: ```python from dotenv import dotenv_values config = dotenv_values(os.path.expanduser("~/.openclaw/.env")) sub = os.environ.get("QWEATHER_SUB") or config.get("QWEATHER_SUB") kid = os.environ.get("QWEATHER_KID") or config.get("QWEATHER_KID") ``` 3. Apply an explicit allowlist containing only `QWEATHER_SUB` and `QWEATHER_KID`. 4. Prefer inherited environment variables over file values so that explicit caller configuration is not unexpectedly replaced. 5. If `load_dotenv()` must remain, use `override=False`; however, this still loads unrelated entries and is less secure than allowlisted parsing. 6. Correct the documentation and comments so that the stated precedence matches the implementation. 7. Consider storing QWeather credentials in a dedicated configuration file with mode `0600`, rather than a shared file containing credentials for multiple services. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generateJWTtoken.py:41
Finding
Predictable log file in a shared temporary directory permits symlink-based file access<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generateJWTtoken.py:41-57` **Vulnerability Type**: Unsafe temporary-file handling **Risk Level**: Medium ### Vulnerable Code ```python def _get_log_path() -> str: """获取日志文件路径,每天一个文件,放在 /tmp/cslog/ 目录中。""" log_dir = "/tmp/cslog" os.makedirs(log_dir, exist_ok=True) today = datetime.now().strftime("%Y%m%d") return os.path.join(log_dir, f"generateJWTtoken-{today}.log") def _log(msg: str) -> None: """写入日志文件并在 stderr 打印。""" ts = datetime.now().strftime("%H:%M:%S") line = f"[{ts}] {msg}" log_path = _get_log_path() with open(log_path, "a", encoding="utf-8") as f: f.write(line + "\n") print(line, file=sys.stderr) ``` ### Technical Analysis The script uses the globally predictable directory `/tmp/cslog` and a date-derived filename. It creates and opens the file without checking: - Whether the directory is owned by the invoking user. - Whether the log path is a symbolic link. - Whether the target is a regular file. - Whether an existing file has safe ownership and permissions. Python's normal `open(..., "a")` follows symbolic links. If an attacker can pre-create `/tmp/cslog` or write into an existing insecurely configured instance of that directory, the attacker can place a symbolic link at the expected daily filename. A subsequent invocation then appends log messages to the link target with the privileges of the invoking process. The file is not created with an explicit `0600` mode. Its initial permissions depend on the process umask, potentially making masked identifiers and operational metadata visible to other local users. ### Attack Path 1. A local attacker predicts the filename, such as `/tmp/cslog/generateJWTtoken-20260916.log`. 2. Before the victim runs the Skill, the attacker creates `/tmp/cslog` if it does not already exist and places a symbolic link at the predicted log path. 3. The symbolic link points to a file writable by the victim ...[truncated 1295 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store logs in a private, user-owned state directory rather than a shared `/tmp` path. For example, use an application directory beneath `$XDG_STATE_HOME` or `~/.local/state`. 2. Create the log directory with mode `0700` and verify that it is owned by the current user. 3. Create log files with mode `0600`. 4. Reject symbolic links and non-regular files. On supported Unix systems, open the file using `os.open()` with `O_APPEND`, `O_CREAT`, `O_WRONLY`, and `O_NOFOLLOW`, then wrap the descriptor with `os.fdopen()`. 5. Validate the opened file with `os.fstat()` and confirm that it is a regular file owned by the expected user. 6. If temporary storage is unavoidable, create a private directory using `tempfile.mkdtemp()` and ensure it cannot be reused across trust boundaries. 7. Do not run the JWT generator with elevated privileges, because its declared functionality only requires access to the invoking user's key and configuration files. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (8)

Credential Access

High
Category
Privilege Escalation
Content
"""
生成和风天气 JWT Token 的工具。

环境变量(支持从 ~/.openclaw/.env 自动加载):
    QWEATHER_SUB  和风账户的用户标识(sub 字段)
    QWEATHER_KID  和风账户的密钥 ID(kid 字段)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
"""
生成和风天气 JWT Token 的工具。

环境变量(支持从 ~/.openclaw/.env 自动加载):
    QWEATHER_SUB  和风账户的用户标识(sub 字段)
    QWEATHER_KID  和风账户的密钥 ID(kid 字段)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
"""
生成和风天气 JWT Token 的工具。

环境变量(支持从 ~/.openclaw/.env 自动加载):
    QWEATHER_SUB  和风账户的用户标识(sub 字段)
    QWEATHER_KID  和风账户的密钥 ID(kid 字段)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
"""
生成和风天气 JWT Token 的工具。

环境变量(支持从 ~/.openclaw/.env 自动加载):
    QWEATHER_SUB  和风账户的用户标识(sub 字段)
    QWEATHER_KID  和风账户的密钥 ID(kid 字段)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
import dotenv
    # 优先加载 ~/.openclaw/.env(OpenClaw 标准配置位置)
    # 若 OpenClaw 已将 env 注入了当前进程,此调用不会覆盖已存在的变量
    dotenv.load_dotenv(os.path.expanduser("~/.openclaw/.env"), override=True)
except ImportError:
    # 未安装 dotenv 时跳过,完全依赖环境继承
    pass
Confidence
83% confidence
Finding
The script loads secrets from '~/.openclaw/.env' with override=True, which forcibly replaces existing environment variables with values from a local file. If that file is writable by another local user/process or symlinked, the script could be tricked into using attacker-controlled identifiers, causing token generation under unexpected identities or operational misuse; automatic secret loading also broadens the trusted input surface.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill documents and invokes capabilities that access environment variables, read a private key from disk, write a token to disk, and execute a shell command, but it does not declare any tool scope or permission boundaries. In an agent environment, this weakens least-privilege controls and can allow broader-than-expected access to secrets and filesystem state, increasing the blast radius if the skill is misused or compromised.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
必须位于 `~/.myjwtkey/ed25519-private.pem`,权限应为 `600`。

```bash
chmod 600 ~/.myjwtkey/ed25519-private.pem
```

### Token 输出文件
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Tainted flow: 'token_file' from os.environ.get (line 125, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
"""将 token 写入 ~/.myjwtkey/last-token.dat,返回文件路径。"""
    token_file = os.path.expanduser("~/.myjwtkey/last-token.dat")
    os.makedirs(os.path.dirname(token_file), exist_ok=True)
    with open(token_file, "w", encoding="utf-8") as f:
        f.write(token)
    os.chmod(token_file, 0o600)
    return token_file
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Static analysis

No suspicious patterns detected.