T09 · Insecure Skill Coding Practices
- Location
lib/token_manager.py:42- Finding
Predictable Shared Temporary Token Cache Enables Symlink and File-Overwrite Attacks
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill appears to perform the advertised Ezviz audio broadcast function, but it needs review because it handles device-control credentials and stores access tokens in a risky default cache.
Install only if you expect this skill to control Ezviz audio broadcast-capable devices. Use dedicated least-privilege Ezviz app credentials, prefer environment variables over config-file fallback, avoid passing secrets on the command line, and set EZVIZ_TOKEN_CACHE=0 on shared or high-security machines unless you have hardened the cache location.
lib/token_manager.py:42Predictable Shared Temporary Token Cache Enables Symlink and File-Overwrite Attacks
The skill says credentials must be supplied via environment variables, yet it also reads fallback credentials from local OpenClaw config files. This creates a sensitive capability expansion: the skill can access local secrets beyond what the user may have intended, and the documented security model does not match actual credential sourcing behavior.
The skill says credentials must be supplied via environment variables, yet it also reads fallback credentials from local OpenClaw config files. This creates a sensitive capability expansion: the skill can access local secrets beyond what the user may have intended, and the documented security model does not match actual credential sourcing behavior.
Model output is used without validation or sanitization. Unvalidated output injected into downstream contexts (SQL, shell, HTML) enables injection attacks and arbitrary code execution.
subprocess.run(['espeak', '-w', output_path, text], check=True, capture_output=True) subprocess.run(['ffmpeg', '-i', input, '-acodec', 'libmp3lame', output], ...)
**安全措施** (v1.0.2+):
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
清除缓存:
# 清除所有缓存 Token
rm -rf /tmp/ezviz_global_token_cache/
# 或使用 Token 管理器
python3 lib/token_manager.py clear
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
清除缓存:
# 清除所有缓存 Token
rm -rf /tmp/ezviz_global_token_cache/
# 或使用 Token 管理器
python3 lib/token_manager.py clear
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
⚠️ 早期版本用户必须轮换凭证!
如果您曾经使用过旧版本的 .env 文件:
🚨 您的凭证可能已泄露!
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
⚠️ 早期版本用户必须轮换凭证!
如果您曾经使用过旧版本的 .env 文件:
🚨 您的凭证可能已泄露!
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
⚠️ 早期版本用户必须轮换凭证!
如果您曾经使用过旧版本的 .env 文件:
🚨 您的凭证可能已泄露!
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def get_cached_token(app_key, app_secret, use_cache=True):
"""
Get access token, using cached version if available and valid.
Args:
app_key: Ezviz app key
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def get_cached_token(app_key, app_secret, use_cache=True):
"""
Get access token, using cached version if available and valid.
Args:
app_key: Ezviz app key
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def get_cached_token(app_key, app_secret, use_cache=True):
"""
Get access token, using cached version if available and valid.
Args:
app_key: Ezviz app key
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def get_cached_token(app_key, app_secret, use_cache=True):
"""
Get access token, using cached version if available and valid.
Args:
app_key: Ezviz app key
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def get_cached_token(app_key, app_secret, use_cache=True):
"""
Get access token, using cached version if available and valid.
Args:
app_key: Ezviz app key
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def get_cached_token(app_key, app_secret, use_cache=True):
"""
Get access token, using cached version if available and valid.
Args:
app_key: Ezviz app key
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def get_cached_token(app_key, app_secret, use_cache=True):
"""
Get access token, using cached version if available and valid.
Args:
app_key: Ezviz app key
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def get_cached_token(app_key, app_secret, use_cache=True):
"""
Get access token, using cached version if available and valid.
Args:
app_key: Ezviz app key
This line outputs part of the access token directly to the CLI, creating unnecessary exposure of sensitive authentication material. Because this skill is specifically designed to authenticate to Ezviz services and broadcast to devices, leaked token fragments in operator consoles or logs are more dangerous than in a generic utility, as they relate directly to device-control capabilities.
result = get_cached_token(args.app_key, args.app_secret, use_cache=use_cache)
if result["success"]:
print(f"\nAccess Token: {result['access_token'][:30]}...")
print(f"Expires: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(result['expire_time'] / 1000))}")
print(f"From Cache: {result['from_cache']}")
else:
The skill documentation declares significant capabilities—environment access, reading local config files, writing token caches, network calls, and subprocess-based TTS—but does not define an explicit tool scope such as allowed tools or permissions. In an agent setting, missing scope boundaries increases the chance the runtime grants broader-than-necessary access, making accidental credential access, filesystem interaction, or command execution harder to constrain.
The primary description and usage cue are written as Chinese-only instructions, and the document does not state that the skill supports multiple languages or that Chinese is a required regional constraint. Under the policy, forcing a specific language without user opt-in is a natural-language policy issue.
The CLI prints the first 30 characters of the access token to stdout, which still exposes sensitive credential material. In many environments stdout is captured by shell history, CI logs, terminal recording, or centralized logging systems, allowing partial token disclosure and increasing the risk of credential misuse or correlation with other leaked data.
The usage documentation encourages passing app_key and app_secret on the command line, which exposes secrets through shell history, process listings, job-control logs, and audit systems. Because these are long-lived API credentials, disclosure can allow unauthorized access token generation and use of the EZVIZ API.
The skill metadata and comments emphasize environment-based secrets, but the code silently falls back to reading EZVIZ credentials from unrelated local OpenClaw config files. This broadens the credential trust boundary and can cause unintended use of more privileged local secrets, enabling credential exposure or misuse against the wrong account.
The skill’s stated purpose is sending audio or TTS content to Ezviz devices, but the implementation achieves TTS by spawning local commands such as say, afconvert, espeak, and ffmpeg. Executing host binaries is a materially broader capability than simple API-based broadcasting and is not declared in the manifest context.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# Add pauses to make audio longer (Ezviz requires minimum audio length)
# Say the text with natural pauses
text_with_pauses = f"注意,{text},请注意"
subprocess.run(['say', '-o', temp_aiff, text_with_pauses], check=True, capture_output=True)
# Convert AIFF to WAV using correct format specifier
subprocess.run(['afconvert', '-f', 'WAVE', '-d', 'LEI16@44100', temp_aiff, output_path],
check=True, capture_output=True)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
text_with_pauses = f"注意,{text},请注意"
subprocess.run(['say', '-o', temp_aiff, text_with_pauses], check=True, capture_output=True)
# Convert AIFF to WAV using correct format specifier
subprocess.run(['afconvert', '-f', 'WAVE', '-d', 'LEI16@44100', temp_aiff, output_path],
check=True, capture_output=True)
os.remove(temp_aiff)
elif sys.platform.startswith('linux'):