T09 · Insecure Skill Coding Practices
- Location
scripts/aggregate_llm_usage.py:441- Finding
Spreadsheet Formula Injection in Generated Excel Reports
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This is a local LLM usage report generator, but its generated Excel files may contain sensitive user identifiers and should be handled carefully.
Install only if you are comfortable processing LLM usage logs locally and storing the resulting workbook. Treat input logs and outputs as sensitive because raw user identifiers can appear in the report. Avoid using untrusted CSV/XLS files, or sanitize values beginning with =, +, -, or @ before opening or sharing the generated Excel workbook.
scripts/aggregate_llm_usage.py:441Spreadsheet Formula Injection in Generated Excel Reports
The code substantially matches the declared purpose: it aggregates LLM usage data, distinguishes internal/external users, summarizes by provider/model/user, calculates costs from pricing_config.json, and exports Excel output. However, there is a notable description/behavior mismatch in the report structure: the description says it outputs a 'multi-sheet Excel report' for the aggregations, which implies separate sheets per dimension, while the code creates one summary sheet ('汇总报表') containing four sections and only one additional sheet for pricing details. Also, the code supports Excel input as well as CSV, which is an undeclared extra capability, though relatively minor. Primary purpose remains aligned, so this is a limited but real mismatch.
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.
detail['unit_price_completion'] = completion_price
prompt_cost = prompt / 1_000_000 * prompt_price
completion_cost = completion / 1_000_000 * completion_price
return prompt_cost + completion_cost, detail
# 图片类型:按张计价,单价 × generated_image_count
if detail['model_type'] == '图片':
Without declared permissions the skill's intent is opaque and cannot be validated.
The primary description is written entirely in Chinese and presents the skill behavior and usage in that language without indicating that users may choose another language or that the locale restriction is intentional for a region-specific tool. Under the language/locale policy, forcing a specific language without opt-in can be a natural-language policy violation.
The skill processes direct identifiers such as email and phone and produces user-level summaries, but provides no privacy notice, minimization guidance, masking behavior, or retention boundaries. This can lead to unnecessary exposure of personal data in generated reports, especially if outputs are shared broadly or stored insecurely.
The script constructs a combined user identifier from email, phone, or user_id and then exports it into both summary and detail Excel sheets. This can disclose personally identifiable information to anyone with access to the generated workbook, especially since the tool aggregates operational logs and makes broad per-user reporting easy to share or redistribute.
This JSON file includes user-facing natural-language values such as the note field and model_type in Chinese (for example, "维护价" and "文本"). Because the file does not document that it is region-specific or provide any language/locale opt-in, it appears to enforce a specific language in a general configuration artifact.
Later entries continue the same pattern of Chinese-only descriptive text in note/model_type fields, indicating the locale restriction is systemic rather than isolated. Under the policy, fixed language/locale content should either be optional for users or clearly justified as region-specific.
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
for row_idx, row_data in enumerate(grouped.itertuples(), header_row + 1):
ws.cell(row=row_idx, column=1, value=row_data[1]).border = thin_border
for col_idx, field in enumerate(agg_fields, 2):
cell = ws.cell(row=row_idx, column=col_idx, value=getattr(row_data, field))
cell.border = thin_border
cost_val = getattr(row_data, cost_field)
cell = ws.cell(row=row_idx, column=cost_col_idx, value=round(cost_val, 4))
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
for col_idx, field in enumerate(agg_fields, 2):
cell = ws.cell(row=row_idx, column=col_idx, value=getattr(row_data, field))
cell.border = thin_border
cost_val = getattr(row_data, cost_field)
cell = ws.cell(row=row_idx, column=cost_col_idx, value=round(cost_val, 4))
cell.number_format = '#,##0.0000'
cell.border = thin_border
No suspicious patterns detected.