[2.2.0] - 2026-08-01
Security
attach_file_to_transactionno longer reads arbitrary files (#16). The tool passed the caller-suppliedfile_pathstraight toreadFilewith no confinement, and its MIME allow-list checked a caller-suppliedcontent_typerather than the file, so any readable path —~/.ssh/id_rsa, a.env,/etc/passwd— could be uploaded to the LunchMoney attachments endpoint and retrieved again viaget_transaction_attachment_url. Four changes:- The attachment type is now determined by sniffing the file's leading bytes, and the header is checked before the rest of the file is read. Non-media files are rejected without ever being buffered.
- New optional
LUNCHMONEY_ATTACHMENTS_DIRconfines reads to one directory, enforced afterrealpathso..traversal and symlink escapes are both caught. Unset leaves reads unconfined, which preserves existing desktop behaviour; remote/HTTP deployments should always set it. - Size is now checked via
statbefore any bytes are buffered, and non-regular files are rejected — previouslyreadFile("/dev/zero")would grow unbounded and exhaust memory. The file is opened withO_NONBLOCK, because a read-onlyopen(2)on a FIFO blocks until a writer appears; without it a caller could hang a request indefinitely, pin a libuv threadpool thread, and prevent clean shutdown. - Filesystem errors are logged to stderr but reported to the caller generically, so failed reads no longer distinguish
ENOENTfromEACCESand leak filesystem structure.
Added
- Balance history tools — Get and manage monthly balance snapshots across all accounts (used by the Net Worth views). Nine new tools:
get_balance_history,get_account_balance_history,upsert_account_balance_history,delete_account_balance_history,get_crypto_synced_balance_history,upsert_crypto_synced_balance_history,delete_crypto_synced_balance_history,delete_balance_history_entry,update_deleted_account_details. Tool count grows from 41 to 50. - Test suite (
npm test), the project's first. Runs on Node's built-in test runner over the compiled output, so it typechecks as well. 78 tests coveringsrc/attachments.ts— content sniffing, path confinement, resource limits — plus theattach_file_to_transactiontool boundary driven through a real MCP client over an in-memory transport. Every regression described in this release has a test pinning it. Wired into CI via.github/workflows/test.yml. - Automated publishing — pushes to
mainpublish to npm and the MCP Registry whenpackage.jsoncarries a version that is not yet published, so non-release commits are a no-op (.github/workflows/publish-mcp.yml).
Changed
- Attachment reading moved out of
src/tools/transactions.tsintosrc/attachments.ts, so it can be unit-tested without standing up anMcpServer. No behaviour change. attach_file_to_transaction'scontent_typeparameter is now an optional enum of the allowed types and is treated as an assertion: the file's real type always wins, and a mismatch is rejected rather than silently corrected. Previously it selected the declared type outright. HEIC and HEIF are exempt from the mismatch check, since the ISO-BMFF major brand does not reliably distinguish them (many.HEICfiles, iOS ones included, carrymif1).- Updated to TypeScript 7 and refreshed dependencies.
Fixed
%PDF-is no longer required at byte 0. The PDF spec tolerates leading bytes and conforming readers scan for the marker, so PDFs from scanners that emit a preamble were being rejected.- An unset optional
user_configfield in a.mcpbinstall arrives as the literal${user_config.LUNCHMONEY_ATTACHMENTS_DIR}template rather than being omitted. That string is truthy, so it was treated as a configured directory and madeattach_file_to_transactionfail for every Claude Desktop user who left the field blank. Unsubstituted templates are now treated as unset.