Back to skill

Security audit

xlsx

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a spreadsheet helper, but its formula recalculation script makes persistent LibreOffice macro changes that can overwrite a user’s existing macros.

Review before installing. Use this only if you are comfortable with a helper that writes into your LibreOffice user macro profile. Prefer running it on copies of spreadsheets, avoid untrusted workbooks, and consider changing recalc.py to use a temporary LibreOffice profile before relying on it for regular work.

Vulnerability Patterns
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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 (1)

T06 · System Persistence

Warning
Location
recalc.py:18
Finding
Persistent LibreOffice Profile Modification Can Overwrite Existing User Macros<![CDATA[ ## Vulnerability Details **File Location**: `recalc.py`, lines 18–42 **Vulnerability Type**: Persistent application-profile modification and unsafe file overwrite **Risk Level**: Medium ### Vulnerable Code ```python def setup_libreoffice_macro(): """Setup LibreOffice macro for recalculation if not already configured""" if platform.system() == 'Darwin': macro_dir = os.path.expanduser('~/Library/Application Support/LibreOffice/4/user/basic/Standard') else: macro_dir = os.path.expanduser('~/.config/libreoffice/4/user/basic/Standard') macro_file = os.path.join(macro_dir, 'Module1.xba') if os.path.exists(macro_file): with open(macro_file, 'r') as f: if 'RecalculateAndSave' in f.read(): return True if not os.path.exists(macro_dir): subprocess.run(['soffice', '--headless', '--terminate_after_init'], capture_output=True, timeout=10) os.makedirs(macro_dir, exist_ok=True) macro_content = '''<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> <script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic"> Sub RecalculateAndSave() ThisComponent.calculateAll() ThisComponent.store() ThisComponent.close(True) End Sub </script:module>''' try: with open(macro_file, 'w') as f: f.write(macro_content) ``` ### Technical Analysis The script installs executable LibreOffice Basic code directly into the current user's persistent LibreOffice profile. It uses the generic module path `Standard/Module1.xba`, which may already contain macros created by the user or another application. The existing-file check only searches for the substring `RecalculateAndSave`. If that substring is absent, the file is opened using mode `w`, which truncates the complete module before writing th ...[truncated 2110 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Use an isolated temporary LibreOffice profile** - Create a unique temporary directory for each recalculation operation. - Launch LibreOffice with an isolated profile using `-env:UserInstallation=file:///absolute/path`. - Install the recalculation macro only in that temporary profile. - Delete the profile after the operation, including on errors and timeouts. 2. **Do not overwrite generic user modules** - Avoid writing to `Standard/Module1.xba` in the user's normal profile. - If persistent installation is unavoidable, use a uniquely named library and module owned by this application. 3. **Preserve existing content** - Check whether the target path already exists before creating it. - Refuse to replace an existing module unless explicit user authorization is obtained. - If modification is required, create a verified backup and restore it in a `finally` block. - Do not use substring matching as the basis for deciding whether an entire XML module may be replaced. 4. **Implement reliable cleanup** - Track every file and directory created by the script. - Remove installed macro artifacts after recalculation. - Ensure cleanup runs after success, failure, exceptions, and subprocess timeouts. 5. **Apply defensive file handling** - Write new files atomically through a temporary file followed by a controlled rename. - Validate that the destination remains inside the intended isolated profile. - Use restrictive permissions for temporary profile files. - Reject symbolic-link destinations before writing to avoid overwriting an unintended file. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description presents a broad, full-featured spreadsheet capability covering creation, editing, formatting, analysis, visualization, and formula recalculation for multiple spreadsheet types. The supplied code chunk is much narrower: it sets up a LibreOffice macro, invokes LibreOffice headlessly to recalculate formulas in a given Excel file, and reports Excel-style error cells and formula counts using openpyxl. While formula recalculation is one declared sub-capability, the actual code does not implement the comprehensive spreadsheet functionality claimed. Its primary purpose is specifically formula recalculation and error scanning for Excel files, so the description materially overstates the behavior.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill instructs use of file read/write operations and shell execution (`python recalc.py`) but declares no explicit tool scope or permission boundaries. In an agent setting, this can enable overbroad access to local files and command execution without clear least-privilege constraints, increasing the chance of unintended file modification or abuse if the skill is invoked on untrusted inputs.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The activation language is extremely broad and can match many ordinary spreadsheet-related requests, causing the skill to trigger in more situations than necessary. Overbroad activation raises security risk because it increases opportunities for this skill to gain access to sensitive spreadsheets or perform file operations in contexts where a narrower skill would not be appropriate.

Behavior Manipulation

Medium
Category
Prompt Injection
Content
## CRITICAL: Use Formulas, Not Hardcoded Values

**Always use Excel formulas instead of calculating values in Python and hardcoding them.** This ensures the spreadsheet remains dynamic and updateable.

### ❌ WRONG - Hardcoding Calculated Values
```python
Confidence
70% confidence
Finding
Subtle instructions detected that may alter agent decision-making or introduce hidden biases.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill provides instructions for modifying existing spreadsheets, including row/column insertion and deletion, without requiring backups, confirmation, or warning about destructive changes. In practice this can lead to silent corruption of formulas, formatting, macros, or business-critical templates, especially when operating on user-supplied files.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The script writes a macro into the user's global LibreOffice profile, creating a persistent modification outside the scope of the requested spreadsheet operation. Persistently altering application-level macro configuration is dangerous because it changes future LibreOffice behavior, broadens trust in a macro-capable environment, and can affect unrelated documents or workflows without clear user consent.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
Invoking external desktop software on untrusted spreadsheet files and installing a macro expands the attack surface well beyond ordinary file parsing. In the context of a spreadsheet skill, recalculation may be legitimate, but doing it through persistent macro installation in LibreOffice is more dangerous because it introduces execution-capable components and reliance on a large external application that may process hostile content.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The script modifies LibreOffice user macro configuration without any explicit warning, confirmation, or visibility to the user. Silent persistent changes to user application settings are dangerous because they violate least surprise, reduce user control, and can leave behind executable configuration that persists after the task completes.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
return True

    if not os.path.exists(macro_dir):
        subprocess.run(['soffice', '--headless', '--terminate_after_init'],
                      capture_output=True, timeout=10)
        os.makedirs(macro_dir, exist_ok=True)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if platform.system() == 'Darwin':
            # Check if gtimeout is available on macOS
            try:
                subprocess.run(['gtimeout', '--version'], capture_output=True, timeout=1, check=False)
                timeout_cmd = 'gtimeout'
            except (FileNotFoundError, subprocess.TimeoutExpired):
                pass
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if timeout_cmd:
            cmd = [timeout_cmd, str(timeout)] + cmd

    result = subprocess.run(cmd, capture_output=True, text=True)

    if result.returncode != 0 and result.returncode != 124:  # 124 is timeout exit code
        error_msg = result.stderr or 'Unknown error during recalculation'
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.