T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/charts.py:234- Finding
Desktop Screenshot Captured and Returned as a Report Image
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This expense-tracking skill is mostly coherent, but it needs Review because its chart code can silently capture and return the user's desktop when image rendering fails.
Review before installing. The ledger behavior is understandable for an expense tracker, but generated reports and exports can contain sensitive financial data, and the chart renderer should be fixed or disabled because it may capture the desktop instead of only the report.
scripts/charts.py:234Desktop Screenshot Captured and Returned as a Report Image
scripts/charts.py:196Stored HTML Injection Through an Unescaped Report Label
scripts/export.py:91CSV Formula Injection in Exported Expense Records
The trigger description is extremely broad, matching common spending-related words and symbols that can appear in ordinary conversation. This can cause the skill to activate unexpectedly and persist sensitive financial data without sufficiently explicit user intent, creating privacy and integrity risks.
The skill reveals a local storage path, but does not clearly warn users up front that their financial records will be written persistently to workspace files. Because the data includes expenses, categories, notes, and member labels, silent persistence raises meaningful privacy and retention concerns.
Telling users to 'just send natural language' makes the boundary between casual discussion and write actions unclear. In a skill that stores persistent ledger entries, this ambiguity increases the chance of accidental data creation, misclassification, or recording of private financial details the user did not intend to save.
The export feature creates shareable copies of sensitive financial data, but the skill does not warn users about the privacy implications of generating CSV or Excel files. Exported files are easier to exfiltrate, mis-share, or leave behind in insecure locations than the primary ledger store.
The title and structure present the taxonomy as a bilingual English/Chinese reference only, which can imply a fixed language/locale expectation for the skill. Under the policy, forcing a specific language without user opt-in is a natural-language policy concern unless the restriction is explicitly justified or alternatives are offered.
The HTML report builder defaults lang to zh, and the main entrypoint also defaults to Chinese via args.get("lang", "zh"), causing the generated report text to be Chinese unless the caller explicitly overrides it. This is a natural-language locale policy issue because the skill forces a specific language by default rather than offering a user choice or clearly documenting a justified locale restriction.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
tmp_png = html_path.with_suffix('.png')
# Try qlmanage first (most reliable on macOS)
try:
r = subprocess.run(
['qlmanage', '-t', '-s', '900', '-o', str(tmp_png.parent), str(html_path)],
capture_output=True, timeout=15
)
The skill launches platform tools to process local files without any user-facing disclosure, and one fallback path uses screencapture. In an agent setting, undisclosed OS tool execution can surprise users and, combined with screen capture behavior, increases privacy and trust risk.
The fallback executes macOS screencapture, which can capture screen contents rather than rendering only the intended chart. If triggered in a real user session, it may unintentionally include other visible windows or sensitive information, creating a privacy leak beyond the chart data itself.
pass
# Fallback: use Safari/webkit
try:
r = subprocess.run(
['screencapture', '-x', '-t', 'png', str(tmp_png)],
capture_output=True, timeout=5
)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
tmp_png = tmp_svg.with_suffix('.png')
tmp_svg.write_text(svg_text, encoding='utf-8')
try:
r = subprocess.run(
['qlmanage', '-t', '-s', str(size), '-o', str(tmp_png.parent), str(tmp_svg)],
capture_output=True, timeout=15
)
The script writes expense reports and chart artifacts to persistent local storage under the user's home directory without warning or retention controls. Because the data contains spending totals, categories, and time-based trends, persistent files may expose sensitive financial information to other local users, backups, or later compromise.
This code writes exported ledger data, including notes, tags, raw input, and timestamps, to files under the user's home directory. While the script's purpose is export, there is no explicit user-facing warning, confirmation, or disclosure in the code that personal financial data will be persisted to disk at a specific path.
The JSON export writes complete entry objects to a file, which may contain sensitive personal finance data. The code does not provide any warning or confirmation about creating a persistent local copy of that data.
The docstring and comment describe export_excel as generating an Excel-compatible CSV by using BOM, but the function calls export_csv({**args, "bom": True}) while export_csv only checks no_bom and ignores bom. If callers pass {"no_bom": true}, the Excel export path will produce a non-BOM file despite the documentation claiming otherwise.
This code creates and writes persistent local files containing expense and account data, and similar writes also occur later via save_ledger and action_config. Although the module docstring states it is an expense tracker, there is no explicit user-facing disclosure in code comments, prompts, or output that running add/config actions will create or overwrite data under ~/.qclaw/workspace/expense-ledger.
No suspicious patterns detected.