Back to skill

Security audit

Tax

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent local tax organizer, but it should be reviewed because it stores sensitive tax records and exports CSV files without stronger local protection or spreadsheet-safety controls.

Install only if you are comfortable keeping tax-related records as local plaintext files under ~/.openclaw/workspace/memory/tax/. Use a private account or encrypted disk, check file permissions, avoid syncing that folder to broad backups, and treat generated CSV files as untrusted when opening them in spreadsheet software.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/add_cpa_question.py:16
Finding
Sensitive tax records are created without restrictive filesystem permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/add_cpa_question.py:16-30` **Additional Locations**: `scripts/add_document.py:15-29`; `scripts/archive_year.py:17-31`; `scripts/capture_event.py:21-35`; `scripts/generate_summary.py:21-23, 180, 267`; `scripts/log_notice.py:16-30`; `scripts/prep_meeting.py:19-21, 193`; `scripts/set_year_state.py:29-43`; `scripts/track_expense.py:16-30` **Vulnerability Type**: Insecure permissions for sensitive local data **Risk Level**: Medium ### Vulnerable Code ```python def ensure_base_dir() -> None: os.makedirs(BASE_DIR, exist_ok=True) def load_questions() -> Dict[str, Any]: if os.path.exists(QUESTIONS_FILE): with open(QUESTIONS_FILE, "r", encoding="utf-8") as f: return json.load(f) return {"questions": []} def save_questions(data: Dict[str, Any]) -> None: ensure_base_dir() with open(QUESTIONS_FILE, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) ``` The same creation pattern is used by the other listed scripts for tax documents, expenses, notices, annual state, and generated summaries. ### Technical Analysis The application stores sensitive tax and financial records in the predictable directory `~/.openclaw/workspace/memory/tax/`. It creates the directory with `os.makedirs()` and writes files with ordinary `open(..., "w")` calls, but it does not set or verify restrictive permission modes. Consequently, permissions are determined by the process umask. Under a common umask of `0022`, directories can be created with mode `0755` and files with mode `0644`. On a multi-user system, these modes may allow other local users to traverse the tax-memory directory and read files containing: - Tax document types, issuers, and amounts - Expense amounts and descriptions - Tax-authority notice summaries and deadlines - CPA questions and linked records - Raw natural-language input - Generated Markdown and CSV handoff reports The issue does ...[truncated 1359 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the base directory with owner-only permissions: ```python os.makedirs(BASE_DIR, mode=0o700, exist_ok=True) os.chmod(BASE_DIR, 0o700) ``` 2. Create output files with mode `0600`, rather than relying on the process umask. For example: ```python fd = os.open( QUESTIONS_FILE, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600, ) with os.fdopen(fd, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) ``` 3. Use atomic writes: create a temporary file inside the protected directory with mode `0600`, flush and synchronize it, and then replace the destination with `os.replace()`. 4. Audit and correct existing permissions for the base directory, JSON records, generated Markdown files, and CSV reports. 5. Centralize secure storage operations in one shared helper so every script applies identical permission and atomic-write controls. 6. Consider optional encryption at rest where the threat model includes other privileged local software, shared workstations, or untrusted backup systems. 7. Add automated tests that run with a permissive umask and verify that directories remain `0700` and files remain `0600`. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_summary.py:180
Finding
Spreadsheet formula injection in generated annual CSV summaries<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_summary.py:180-230` **Vulnerability Type**: CSV/spreadsheet formula injection **Risk Level**: Medium ### Vulnerable Code ```python def write_csv( output_path: str, tax_year: int, documents: List[Dict[str, Any]], expenses: List[Dict[str, Any]], expense_summary: List[Dict[str, Any]], notices: List[Dict[str, Any]], questions: List[Dict[str, Any]], year_state: Dict[str, Any], ) -> None: with open(output_path, "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["section", "field_1", "field_2", "field_3", "field_4", "field_5"]) writer.writerow(["overview", "tax_year", tax_year, "", "", ""]) writer.writerow(["overview", "state", year_state.get("state", "unknown"), "", "", ""]) writer.writerow(["overview", "documents_recorded", len(documents), "", "", ""]) writer.writerow(["overview", "expense_records", len(expenses), "", "", ""]) writer.writerow(["overview", "open_notices", count_open_notices(notices), "", "", ""]) writer.writerow(["overview", "open_questions_for_cpa", count_open_questions(questions), "", "", ""]) for doc in documents: writer.writerow([ "document", doc.get("document_type", ""), doc.get("issuer", ""), doc.get("amount", ""), doc.get("date_received", ""), doc.get("status", ""), ]) for row in expense_summary: writer.writerow([ "expense_summary", row.get("category", ""), row.get("count", 0), row.get("total", 0.0), row.get("currency", "USD"), "", ]) for notice in notices: writer.writerow([ "notice", notice.get("authority", ""), notice. ...[truncated 3016 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Apply a centralized CSV-cell sanitizer to every text value that may originate from user input or imported records. ```python def sanitize_csv_cell(value: Any) -> Any: if not isinstance(value, str): return value dangerous_prefixes = ("=", "+", "-", "@") inspected = value.lstrip(" \t\r\n") if inspected.startswith(dangerous_prefixes): return "'" + value return value ``` 2. Invoke the sanitizer for every dynamic field passed to `writer.writerow()`, including document fields, notice fields, question text, status fields, expense categories, currencies, and year-state values. 3. Do not rely on `csv.writer` quoting as a formula-injection defense; quoting only ensures valid CSV serialization. 4. Consider exporting a non-formula-bearing format for professional handoff, such as JSON or a carefully escaped PDF, while retaining CSV only as an explicit optional output. 5. Document that CSV files contain untrusted text and should be imported with formula evaluation disabled where the spreadsheet application supports that option. 6. Add regression tests for values beginning with `=`, `+`, `-`, and `@`, including variants with leading spaces, tabs, carriage returns, and newlines. 7. Ensure sanitization occurs at export time rather than mutating the original stored tax records, preserving the integrity of raw captured data. ]]>
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill describes local file read/write behavior and specific storage paths, but it does not declare an explicit tool scope such as permissions or allowed-tools. That creates an authorization ambiguity where an agent may be able to access filesystem capabilities more broadly than users or platform policy expect, especially given the sensitive nature of tax records stored under a predictable directory.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The file explicitly recommends storing tax records under a local workspace path but does not warn that these records can contain highly sensitive personal and financial data. While local-first storage can improve privacy versus cloud sync, users may still store unencrypted files on shared, backed-up, or poorly secured systems, increasing the risk of unauthorized access or accidental disclosure.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script writes annual tax summaries containing sensitive financial and notice data to predictable local files under the user's home directory without any explicit warning, confirmation, or permission gate at the point of export. In a tax-records skill, this increases the risk of users unintentionally creating plaintext artifacts that may later be exposed through backups, shared accounts, endpoint compromise, or accidental disclosure.

Static analysis

No suspicious patterns detected.