Low Risk — Risk Score 28/100
Last scan:21 hr ago Rescan
28 /100
snarky-expense-butler
毒舌记账管家,支持记账、查询、预算提醒、毒舌消费分析、地域统计、趋势图
Skill functions as a legitimate local expense tracker but contains undocumented network code (OpenRouter API call) that contradicts the SKILL.md declaration of 'no external dependencies, no API key'. The network functionality degrades gracefully to matplotlib and poses no data exfiltration risk.
Skill Namesnarky-expense-butler
Duration59.3s
Enginepi
Safe to install
Update SKILL.md to document the optional OpenRouter API integration for trend chart generation. Remove the credential-reading code for ~/.openclaw/openclaw.json (keychain approach can't extract the key anyway). Declare network:READ if the API feature is retained.

Findings 3 items

Severity Finding Location
Medium
SKILL.md claims no external dependencies but code makes network calls Doc Mismatch
SKILL.md explicitly states '纯本地 JSON 存储的个人消费记录系统,无外部依赖,无需 API Key'. However, scripts/expense_trends.py contains a generate_with_llm() function that makes an HTTP POST request to https://openrouter.ai/api/v1/chat/completions. This is a doc-to-code mismatch on a core security-relevant claim.
response = requests.post("https://openrouter.ai/api/v1/chat/completions", headers={"Authorization": f"Bearer {api_key}", ...}, json={...})
→ Update SKILL.md to document that the trend chart feature optionally uses OpenRouter API if OPENROUTER_API_KEY is set, and that matplotlib is used as a fallback.
scripts/expense_trends.py:126
Low
Credential config file access attempt Sensitive Access
get_openrouter_api_key() attempts to read ~/.openclaw/openclaw.json to extract an OpenRouter API key. However, the key is stored in the system keychain, not directly accessible from the JSON, so no actual credential is exfiltrated. The attempt is benign but reveals knowledge of credential storage patterns.
config_path = os.path.expanduser('~/.openclaw/openclaw.json')
with open(config_path, 'r') as f:
    config = json.load(f)
→ Remove the ~/.openclaw/openclaw.json reading logic since it cannot extract the key anyway. Rely solely on the OPENROUTER_API_KEY environment variable.
scripts/expense_trends.py:36
Low
SKILL.md budget defaults contradict script hardcoded values Doc Mismatch
SKILL.md documents default budget as 日 ¥50/周 ¥350/月 ¥1500, but expense_budget.py hardcodes defaults of ¥500/¥3000/¥12000. This minor inconsistency could mislead users configuring budgets.
daily_budget = budget.get('daily', 500)  # vs SKILL.md default ¥50
→ Align default budget values between SKILL.md and code, or make them configurable via environment variables.
scripts/expense_budget.py:13
ResourceDeclaredInferredStatusEvidence
Filesystem READ WRITE ✓ Aligned All scripts write to ./expense_records.json — declared as data path in SKILL.md
Network NONE READ ✗ Violation scripts/expense_trends.py:126 — HTTP POST to openrouter.ai
Shell NONE NONE No subprocess/os.system calls found
Environment NONE READ ✓ Aligned scripts/expense_trends.py:36 — reads OPENROUTER_API_KEY env var
Skill Invoke NONE NONE N/A
Clipboard NONE NONE N/A
Browser NONE NONE N/A
Database NONE NONE N/A
1 findings
🔗
Medium External URL 外部 URL
https://openrouter.ai/api/v1/chat/completions
scripts/expense_trends.py:126

File Tree

8 files · 55.9 KB · 1621 lines
Python 7f · 1564L Markdown 1f · 57L
├─ 📁 scripts
│ ├─ 🐍 add_expense.py Python 215L · 7.9 KB
│ ├─ 🐍 expense_analysis.py Python 265L · 9.0 KB
│ ├─ 🐍 expense_budget.py Python 178L · 5.8 KB
│ ├─ 🐍 expense_location.py Python 203L · 6.2 KB
│ ├─ 🐍 expense_query.py Python 151L · 5.2 KB
│ ├─ 🐍 expense_report.py Python 280L · 10.6 KB
│ └─ 🐍 expense_trends.py Python 272L · 9.3 KB
└─ 📝 SKILL.md Markdown 57L · 2.0 KB

Dependencies 2 items

PackageVersionSourceKnown VulnsNotes
requests unpinned import No Imported in expense_trends.py but not listed in any dependency file; version not pinned
matplotlib unpinned import No Used as fallback for chart generation; not listed in dependency file

Security Positives

✓ No base64-encoded execution, obfuscation, or anti-analysis techniques found
✓ No reverse shell, C2 communication, or data exfiltration to external servers
✓ No access to sensitive paths (~/.ssh, ~/.aws, .env) beyond the benign openclaw config read
✓ No credential harvesting beyond the failed openclaw config read (key is in keychain)
✓ No subprocess/shell execution beyond standard Python file I/O
✓ File locking (fcntl) used for concurrent write safety
✓ Network call gracefully degrades to matplotlib if API fails
✓ No supply chain risks — no external dependencies declared or used
✓ All data stays in local JSON file as documented