T09 · Insecure Skill Coding Practices
- Location
scripts/zeelin_start.py:21- Finding
Sensitive configuration is persisted without restrictive file permissions
- Content
View full analysis
Vulnerability Details
File Location:
scripts/zeelin_start.py:21-49
Vulnerability Type: Plaintext sensitive-data storage with insufficient access-control enforcement
Risk Level: MediumThe Skill stores its ZeeLin API key in
config.jsonand adds messaging recipient information obtained fromZEELIN_TARGET_USERandZEELIN_CHANNEL. The code does not enforce restrictive permissions when writing this file.Relevant code:
python def save_config(config): """保存配置到文件""" with open(CONFIG_FILE, "w") as f: json.dump(config, f, ensure_ascii=False, indent=2) def ensure_target_config(): """确保配置中有 target_user 和 channel,如果没有则尝试从环境变量获取并保存""" config = load_config() # 检查是否需要更新 needs_save = False # 尝试从环境变量获取 target_user = os.environ.get("ZEELIN_TARGET_USER", "") channel = os.environ.get("ZEELIN_CHANNEL", "") if target_user and not config.get("target_user"): config["target_user"] = target_user needs_save = True print(f"自动配置 target_user: {target_user}") if channel and not config.get("channel"): config["channel"] = channel needs_save = True print(f"自动配置 channel: {channel}") if needs_save: save_config(config) print(f"配置已保存到 {CONFIG_FILE}")The documented configuration also places the API credential in the same file:
json { "api_key": "your API key" }Technical Analysis
Python's ordinary
open(path, "w")call does not guarantee owner-only permissions. For a newly created file, its effective permissions depend on the process umask. For an existing file, the existing mode is retained. The implementation neither creates the file with mode0600nor checks whether an existing configuration file is accessible by group or other users.Consequently, the API key, target user identifier, and messaging channel can ...[truncated 1626 chars]
- Remediation
View remediation
Remediation Suggestions
- Store the API key in the platform's secret-management facility or retrieve it from a dedicated environment variable instead of
config.json. - Avoid persisting
target_userandchannelunless persistence is operationally required. - If file-based storage is unavoidable, create the file atomically with owner-only mode:
python fd = os.open(CONFIG_FILE, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=2) - Check existing file permissions with
statbefore loading secrets and reject or repair files accessible by group or other users. - Use atomic replacement through a securely created temporary file in the same directory to prevent partial writes and race conditions.
- Remove recipient identifiers from informational log output.
- Ensure
config.jsonis excluded from source control, published packages, backups not designed for secrets, and broadly accessible diagnostic bundles. - Document that the API key and recipient metadata are sensitive and provide a credential-rotation procedure.
- Store the API key in the platform's secret-management facility or retrieve it from a dedicated environment variable instead of
