Back to skill

Security audit

bill claw

Security checks for vulnerabilities and agentic risk

Overview

BillClaw is a coherent local bookkeeping skill, but it exposes private financial data through review-worthy dashboard and export risks.

Install only if you are comfortable with a local skill managing a SQLite ledger of financial records. Keep the dashboard bound to 127.0.0.1, avoid using it with untrusted transaction data, review records before delete or merge confirmations, and be cautious when exporting CSV files or choosing output paths.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/dashboard.html:520
Finding

Stored Cross-Site Scripting in Transaction Dashboard

Content
View full analysis
{ const ka = state.sortKey; let va = a[ka], vb = b[ka]; if (ka === 'amount') { va = Number(va); vb = Number(vb); return mult * (va - vb); } return mult * String(va || '').localeCompare(String(vb || ''), 'zh-CN'); }); rows.forEach((r) => { const tr = document.createElement('tr'); const tag = r.type === '收入' ? 'tag-in' : 'tag-out'; tr.innerHTML = `${r.date}${r.type}${r.category}` + `${fmtMoney(r.amount)}${(r.note || '').replace(/`; tb.appendChild(tr); }); } ``` The corresponding CLI accepts database-controlled values without validating the date format or constraining the category: ```python date_str = str(payload["date"]) type_ = str(payload["type"]) category = str(payload["category"]) amount = float(payload["amount"]) ``` ```python if type_ not in ("收入", "支出"): issues.append("type must be income or expense") if type_ == "支出" and category not in expense_defaults: pass # custom categories allowed if type_ == "收入" and category not in income_defaults: pass ``` ### Technical Analysis The dashboard constructs table rows through `innerHTML` and directly interpolates the transaction's `date` and `category` fields. Both fields originate from SQLite records and can contain arbitrary HTML because the record-validation function does not validate dates or restrict custom-category content. A payload such as the following can therefore be s ...[truncated 1943 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/web.py:40
Finding

Unauthenticated Financial Data Exposure When Bound to a Non-Loopback Interface

Content
View full analysis
None: from web import run_server host = str(payload.get("host") or "127.0.0.1") port = int(payload.get("port") or 8000) run_server(host=host, port=port, db_path=db_path) ``` Sensitive APIs have no authentication or authorization checks: ```python @app.route("/api/summary") def api_summary(): date_from, date_to = _date_args() with conn_ctx() as conn: t = totals(conn, date_from=date_from, date_to=date_to) rows = aggregate_by_category( conn, date_from=date_from, date_to=date_to, type_="支出" ) exp_only = [{"category": r["category"], "total": float(r["total"])} for r in rows] inc_rows = aggregate_by_category( conn, date_from=date_from, date_to=date_to, type_="收入" ) inc_only = [{"category": r["category"], "total": float(r["total"])} for r in inc_rows] return jsonify( { "totals": t, "expense_by_category": exp_only, "income_by_category": inc_only, } ) ``` ```python @app.route("/api/transactions") def api_transactions(): date_from, date_to = _date_args() type_ = request.args.get("type") or None if type_ == "": type_ = None page = max(1, int(request.args.get("page", 1))) per_page = min(500, max(10, int(request.args.get("per_page", 100)))) offset = (page - 1) * per_page with conn_ctx() as conn: total = count_transactions( conn, date_from=date_from, date_to=date_to, type_=type_ ) rows = query_transactions( conn, ...[truncated 2133 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/main.py:230
Finding

Spreadsheet Formula Injection in CSV Exports

Content
View full analysis
dict[str, Any]: path = Path(payload.get("path") or "billclaw_export.csv") with get_connection(db_path) as conn: rows = query_transactions( conn, date_from=payload.get("date_from"), date_to=payload.get("date_to"), type_=payload.get("type"), category=payload.get("category"), limit=payload.get("limit", 10000), ) if not rows: path.write_text("", encoding="utf-8") return {"ok": True, "error": None, "data": {"path": str(path.resolve()), "rows": 0}} keys = list(rows[0].keys()) with path.open("w", newline="", encoding="utf-8-sig") as f: w = csv.DictWriter(f, fieldnames=keys) w.writeheader() w.writerows(rows) return {"ok": True, "error": None, "data": {"path": str(path.resolve()), "rows": len(rows)}} ``` ### Technical Analysis `csv.DictWriter` correctly escapes CSV delimiters and quotes, but it does not neutralize spreadsheet formulas. Text fields including `category`, `note`, and `source` can begin with spreadsheet formula markers such as: ```text = + - @ ``` When the resulting CSV file is opened in spreadsheet software, such cells may be interpreted as formulas rather than plain text. Quoting the cell according to CSV syntax does not necessarily prevent formula evaluation. The exact consequences depend on the spreadsheet product and its security configuration. Potential formula behavior includes external references, network requests, misleading displayed values, or dangerous legacy formula features. ### Attack Path 1. An attacker or untrusted data source causes a transaction field to contain a formula-like value, for example: ```text =HYPERLINK("ht ...[truncated 1087 chars]
Remediation
View remediation
Any: if not isinstance(value, str): return value stripped = value.lstrip() if stripped.startswith(("=", "+", "-", "@")): return "'" + value return value safe_rows = [ {key: spreadsheet_safe(value) for key, value in row.items()} for row in rows ] w.writerows(safe_rows) ``` ]]>

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding

Unpinned Python Dependencies Permit Unreviewed Future Versions

Content
View full analysis
=1.2.0 pydantic>=2.0.0 matplotlib>=3.8.0 flask>=3.0.0 ``` The documented installation command resolves these unrestricted versions from the configured package index: ```bash pip install -r requirements.txt ``` ### Technical Analysis Every dependency uses only a lower version bound. An installation performed after the audit may therefore select a substantially newer package than the version originally reviewed or tested. The dependency file also contains no package hashes or lock data. This does not prove that any currently listed package is malicious. The weakness is that the installed dependency set is neither reproducible nor cryptographically constrained. A future compromised release, unexpected major behavior change, or compromise of the configured package source could introduce unreviewed installation or runtime behavior. The declared package names appear consistent with the imported libraries; no typosquatting or dependency-confusion package name was identified in the reviewed file. ### Attack Path 1. A new dependency version is published after this project was audited. 2. The version satisfies the open-ended `>=` constraint. 3. A user runs the documented `pip install -r requirements.txt` command. 4. The package resolver selects the newer, unreviewed release. 5. Package installation hooks or imported runtime code execute in the user's virtual environment. 6. If that release or package source is compromised, the dependency gains the privileges of the installing or running user. ### Impact Assessment A compromised dependency executes with the privileges of the user running installation or BillClaw. Within those privileges, dependency code could access: - The SQLite ledger and report output. - Files available to the Python process. - ...[truncated 274 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (32)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

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).

Content

Scanner excerpt · SETUP.md (reported line 65)May include surrounding context.

md
## 报表图表中的中文

`scripts/report.py` 会自动选用系统里常见的中文字体(如 macOS 的 PingFang SC、Windows 的微软雅黑、Linux 的 Noto CJK 等)。若饼图/标题仍显示为方框,请在系统安装一款 CJK 字体,例如 Debian/Ubuntu:`sudo apt install fonts-noto-cjk`,安装后删除 matplotlib 字体缓存再试:`rm -rf ~/.cache/matplotlib`。

## Web 看板静态资源

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
85% confidence
Finding

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).

Content

Scanner excerpt · SETUP.md (reported line 65)May include surrounding context.

md
## 报表图表中的中文

`scripts/report.py` 会自动选用系统里常见的中文字体(如 macOS 的 PingFang SC、Windows 的微软雅黑、Linux 的 Noto CJK 等)。若饼图/标题仍显示为方框,请在系统安装一款 CJK 字体,例如 Debian/Ubuntu:`sudo apt install fonts-noto-cjk`,安装后删除 matplotlib 字体缓存再试:`rm -rf ~/.cache/matplotlib`。

## Web 看板静态资源

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description covers a full-featured BillClaw bookkeeping application with CLI commands, database interactions, and dashboard/reporting features. The supplied code chunk, however, is merely a static vendor JavaScript asset placeholder/comment for Chart.js. On its own, it does not exhibit the declared primary purpose or capabilities. While Chart.js could support chart rendering in a dashboard, this chunk is only a supporting front-end dependency and not representative of the described skill behavior. Therefore, the description does not accurately represent what this specific supplied code chunk actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

This code chunk does not match the described BillClaw bookkeeping skill behavior. The declaration centers on a Python/SQLite bookkeeping CLI and related local dashboard/report features. The actual code is a static vendor asset for chart zooming and panning in the browser. While interactive chart support could be a supporting detail of a dashboard, this chunk by itself does not perform any of the declared core functions and instead serves a different, narrower purpose as a front-end dependency. Therefore the supplied chunk is materially inconsistent with the declared purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

This code does not implement or expose the declared bookkeeping functionality. Instead, it is a generic vendor-side JavaScript dependency for handling touch gestures in a web interface. While such a library could be a supporting asset of a dashboard, the chunk itself has a materially different purpose and none of the declared BillClaw CLI/SQLite/accounting behaviors are present here. Therefore, the description does not accurately represent what this supplied code chunk actually does.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- Web 看板:本地 Flask + `scripts/dashboard.html` + `scripts/static/vendor` 内 Chart.js,支持时间筛选、按月柱状图、分页明细与图表交互(缩放/平移等),离线可开页

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- Web 看板:本地 Flask + `scripts/dashboard.html` + `scripts/static/vendor` 内 Chart.js,支持时间筛选、按月柱状图、分页明细与图表交互(缩放/平移等),离线可开页

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- Web 看板:本地 Flask + `scripts/dashboard.html` + `scripts/static/vendor` 内 Chart.js,支持时间筛选、按月柱状图、分页明细与图表交互(缩放/平移等),离线可开页

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This markdown file is natural-language documentation, and its headings, descriptions, and command annotations are all presented in Chinese. Under the policy, forcing a specific language without user opt-in can be a natural-language policy violation when no alternative language option or justification is provided.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SETUP.md (reported line 65)May include surrounding context.

md
## 报表图表中的中文

`scripts/report.py` 会自动选用系统里常见的中文字体(如 macOS 的 PingFang SC、Windows 的微软雅黑、Linux 的 Noto CJK 等)。若饼图/标题仍显示为方框,请在系统安装一款 CJK 字体,例如 Debian/Ubuntu:`sudo apt install fonts-noto-cjk`,安装后删除 matplotlib 字体缓存再试:`rm -rf ~/.cache/matplotlib`。

## Web 看板静态资源

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill clearly instructs the agent to read environment variables, read/write local files, operate on a SQLite database, generate PNG/CSV output, and potentially delete files, yet it declares no explicit tool scope or permission boundaries. This increases the chance of over-broad tool access and accidental misuse because the runtime cannot enforce least-privilege constraints from the skill manifest itself.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill advertises activation on broad, everyday phrases about money tracking, categories, charts, exports, and running local scripts, without clear exclusions or tighter triggering conditions. Over-broad routing can cause the agent to invoke a capability-rich local skill in contexts the user did not intend, increasing the risk of unintended database modification, file creation, or local service startup.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

The skill explicitly discusses assisting with cron/launchd-based periodic cleanup, which can introduce persistent scheduled behavior on the host. Although it says not to configure such tasks unless explicitly requested, persistence mechanisms are sensitive because they can be abused to maintain ongoing execution or cause recurring file deletion if the agent misinterprets a request.

Content

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

md
- **目录含义**:`report` 将合图与单图 PNG 写入 `output_dir`。未传时默认为**当前工作目录**下的 `billclaw_output/`(与显式传 `./billclaw_output` 等价,取决于执行 `main.py` 时的 cwd)。这些文件仅为报表缓存/附件,**不替代** SQLite 账本(`db/expenses.db`)。
- **为何要清理**:同一目录下反复生成会堆积同名或带时间戳的文件,长期占用磁盘;对话中若用户关心空间、目录杂乱、或希望「整理报表文件」,应说明该目录用途与可清理性。
- **Agent 行为**:**删除磁盘文件前须征得用户明确同意**;同意后可删除该 `output_dir` 下过期的 PNG(或按用户指定的保留策略,例如只保留最近 N 天/最近一次生成)。删除前用一两句话说明将删的是报表图片、不涉及账本数据。
- **系统级定时清理(可选)**:若用户希望在无人值守时自动清理,可建议用本机 **cron**(Linux/macOS)或 **launchd**(macOS)定期删除指定 `output_dir` 下的 `*.png`(或整目录内旧文件),具体路径与保留天数由用户自行配置;Agent 不擅自替用户配置系统定时任务,除非用户明确要求协助编写命令或 plist 片段。

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The default category labels are hard-coded in Chinese, and later logic also depends on fixed Chinese values for transaction kinds. This imposes a specific language/locale in the skill behavior without any visible opt-in, language selection, or justification in the file.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The function deletes transaction records from the database, which is a destructive operation affecting user data. In this file, there is no confirmation prompt, logging, print statement, or explanatory comment/docstring warning that records will be permanently removed.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

Multiple user-facing strings in this file are hardcoded in Chinese, including errors, hints, and the CLI description, which effectively forces a specific language for all users. The file does not offer language selection or explain that the skill is intentionally limited to a Chinese-language context.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The export command accepts a user-controlled path and writes transaction data directly to that location with no path restriction, overwrite protection, or confirmation step in this file. In an agent-integrated context, this can be abused to overwrite arbitrary files writable by the process or exfiltrate sensitive bookkeeping data to unexpected locations, especially if another component passes untrusted payloads through to the CLI.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

The serve command starts a Flask-backed server based on user-supplied host and port, which is a network-listening action with no safety gating in this file. While it defaults to localhost, allowing arbitrary host values means a caller can expose the bookkeeping dashboard on external interfaces, increasing the attack surface and potentially disclosing sensitive financial data if the web layer lacks authentication or is misconfigured.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The time parser is explicitly restricted to languages=["zh", "en"], which imposes a language/locale constraint in natural-language handling. There is no surrounding user choice, opt-in, or documented justification that this skill is intentionally limited to Chinese and English inputs.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This file hardcodes user-visible chart labels, titles, and messages in Chinese, such as chart titles and empty-state text, without offering a locale choice. That can violate language/locale policy when the skill is expected to adapt to user preference rather than enforce a single language.

Content

No source excerpt is available for this finding.

Unbounded Output

Medium
Category
Output Handling
Confidence
75% confidence
Finding

Output size or generation rate is not bounded. Unbounded output enables denial-of-service through resource exhaustion, log flooding, or context-window stuffing.

Content

Scanner excerpt · scripts/static/vendor/chart.umd.min.js (reported line 19)May include surrounding context.

js
* https://github.com/kurkle/color#readme
 * (c) 2023 Jukka Kurkela
 * Released under the MIT License
 */function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e<i?6:0):e===n?(i-t)/s+2:(t-e)/s+4}(e,i,s,h,n),r=60*r+.5),[0|r,l||0,a]}function zt(t,e,i,s){return(Array.isArray(e)?t(e[0],e[1],e[2]):t(e,i,s)).map(Mt)}function Ft(t,e,i){return zt(Lt,t,e,i)}function Vt(t){return(t%360+360)%360}function Bt(t){const e=Tt.exec(t);let i,s=255;if(!e)return;e[5]!==i&&(s=e[6]?vt(+e[5]):Mt(+e[5]));const n=Vt(+e[2]),o=+e[3]/100,a=+e[4]/100;return i="hwb"===e[1]?function(t,e,i){return zt(Rt,t,e,i)}(n,o,a):"hsv"===e[1]?function(t,e,i){return zt(Et,t,e,i)}(n,o,a):Ft(n,o,a),{r:i[0],g:i[1],b:i[2],a:s}}const Wt={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg"
...[truncated 28 chars]

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This code contains natural-language text exclusively in Chinese, including the module description and the user-facing confirmation message, which indicates the skill is oriented to a specific language/locale without any visible opt-in or alternative. Under the stated policy, forcing a specific language without user choice or documented regional justification is a natural-language policy concern.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The file contains a hard-coded Chinese error message, and nearby API logic also uses Chinese category values such as "支出" and "收入". Because this code does not offer a language choice or document that the skill is intentionally region-specific, it appears to impose a specific locale contrary to the language/locale policy.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency is specified with only a minimum version, which allows future installs to resolve to different releases over time. This weakens reproducibility and can unintentionally introduce vulnerable or incompatible versions through the software supply chain.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
dateparser>=1.2.0
pydantic>=2.0.0
matplotlib>=3.8.0
flask>=3.0.0

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
98% confidence
Finding

Using an unpinned pydantic version means deployments may receive different package versions, including ones with known security advisories. In a skill that parses user-driven bookkeeping data, that uncertainty increases supply-chain and availability risk.

Content

Scanner excerpt · requirements.txt (reported line 2)May include surrounding context.

text
dateparser>=1.2.0
pydantic>=2.0.0
matplotlib>=3.8.0
flask>=3.0.0