Atlas / MCP servers / assaf / expense

expenseBLOCK

mcp/assaf/expense

Forward a receipt to your inbox and it's filed automatically.

Verdict
BLOCK
Grade
D
Trust score
62 /100
Exposed tools
12 10r · 2w · 0d
Transport
streamable-http
License
—
Stars
6
01

Overview

From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.

Expense is a seamless receipt tracking solution: receipts are automatically sent via email, mileage uses the tax rate provided by the IRS, and a smart AI assistant does your data entry so that you can forget about the process altogether.

PRODUCT.md is the map: what the product is, how it works, the pages a person uses, the architecture and data model, and the vocabulary. This file is the front door; that one is the orientation.

Screenshots

What it does

Receipts come in from a photo, a screenshot, a PDF, the clipboard, a connected Fastmail or Gmail mailbox, or an MCP assistant; each one is read for merchant, amount and category, filed under an IRS Schedule C line, and kept with its image. Drives are mapped and priced at the IRS rate for the trip date and type. The year leaves as a PDF per report, with the receipts attached, or as one ZIP of everything. A warranty document dropped on the warranties page becomes a record you can find by how soon coverage ends.

Free, no card, no ads, no paid tier. Not built for corporate expense policy, approvals, or double-entry bookkeeping. The full tour, with the pages and the rules behind each one, is in PRODUCT.md.

AI assistants (MCP)

The app speaks the Model Context Protocol at https://expense.labnotes.org/mcp. An assistant connects with OAuth — you sign in and click Allow — so there are no API keys. A connected assistant can capture a receipt, log a drive, answer questions about your spending, manage reports, and match a bank statement.

// Claude — .mcp.json (no headers needed: the client discovers OAuth)
{
"mcpServers": {
"expense": {
"ty
Read from source at commit f2f89e1a167cOBSERVED · 2026-10-10
Advertisement
02

Connect

Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control. Replace the environment placeholders with a token scoped to the least it needs.

claude-code
claude mcp add pdfkit-standard-fonts --env EMAIL_TOKEN_ENCRYPTION_KEY=${EMAIL_TOKEN_ENCRYPTION_KEY} --env PUSH_AUTH=${PUSH_AUTH} --env PUSH_PRIVATE_KEY=${PUSH_PRIVATE_KEY} --env SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN} -- npx -y @expense/[email protected]
claude-desktop
{
  "mcpServers": {
    "pdfkit-standard-fonts": {
      "command": "npx",
      "args": [
        "-y",
        "@expense/[email protected]"
      ],
      "env": {
        "EMAIL_TOKEN_ENCRYPTION_KEY": "${EMAIL_TOKEN_ENCRYPTION_KEY}",
        "PUSH_AUTH": "${PUSH_AUTH}",
        "PUSH_PRIVATE_KEY": "${PUSH_PRIVATE_KEY}",
        "SENTRY_AUTH_TOKEN": "${SENTRY_AUTH_TOKEN}"
      }
    }
  }
}
03

Exposed tools (12)

10 read · 2 write · 0 destructive.

ToolRiskDescription
add_to_reportwrite
capture_receiptread
close_reportread
create_reportwrite
export_reportread
get_settingsread
list_categoriesread
list_merchantsread
log_mileageread
plan_expensereadPlan an expense.
plan_mileagereadPlan a mileage trip.
reconcileread
04

Trust audit

BLOCKgrade D · trust 62/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.

LayerWhat it checksResult
L0Provenance & inventoryWARN
L1Static analysis of the codeWARN
L2Instruction surface (what it tells the agent)FAIL
L3Class-specific surfacePASS
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
declared (7 observation(s))
Network
declared (5 observation(s))
Shell
none-observed
Dependencies
not all pinned
Secrets in source
found

Findings (25)

HIGHPrompt injection · prompt.zero_width · CWE-94, CWE-1427
test/fixtures/emails/spectrum.html:44
</style></head><body><div><br></div><div><br></div><div id="sig155209607"><div class="signature">— REDACTED</div></div><div><br></div><div>----- Original message -----</div><div>From: REDACTED &lt;<a
Why it matters. invisible characters in instruction text
Fix. strip non-printing characters
HIGHPrompt injection · prompt.zero_width · CWE-94, CWE-1427
test/fixtures/emails/verizon.html:25
</style></head><body><div><br></div><div><br></div><div id="sig155835519"><div class="signature">— REDACTED</div></div><div><br></div><div>----- Original message -----</div><div>From: Verizon Wireless
Why it matters. invisible characters in instruction text
Fix. strip non-printing characters
MEDIUMInventory / provenance · inv.binary · CWE-1104
public/demo-receipt.webm
demo-receipt.webm
Why it matters. a compiled or binary member cannot be reviewed from source
Fix. ship source, or explain the binary in the README
MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
.agents/skills/prisma-postgres/references/console-and-connections.md:55
DATABASE_URL="postgres://identifier:[email protected]:5432/postgres?sslmode=require"
MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
.github/workflows/deployment-checks.yml:347
export DATABASE_URL="postgres://$PGUSER:[email protected]:6433/$DB_NAME?sslmode=disable"
LOWInventory / provenance · inv.binary · CWE-1104
test/fixtures/statements/amex.xlsx
amex.xlsx
Why it matters. a compiled or binary member cannot be reviewed from source
Fix. ship source, or explain the binary in the README
LOWInventory / provenance · inv.hidden_file · CWE-1104
.secretlintrc.json
.secretlintrc.json
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
.vercelignore
.vercelignore
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
app/lib/db/email-log.ts:9
import type { Contract } from "../../../prisma/contract.d";
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
app/lib/db/reconcile.ts:4
import type { Contract } from "../../../prisma/contract.d";
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
app/lib/prisma.server.ts:4
import type { Contract } from "../../prisma/contract.d";
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
app/lib/prisma.server.ts:5
import contractJson from "../../prisma/contract.json" with { type: "json" };
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
test/helpers/legacyClient.ts:14
import type { Contract } from "../../prisma/contract.d";
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
test/inbound.test.ts:631
expect(isPrivateHost("169.254.169.254")).toBe(true);
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
test/inbound.test.ts:654
await fetchRemoteImageImpl("http://169.254.169.254/latest/meta-data/"),
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
test/jmap-server.test.ts:100
session({ apiUrl: "http://169.254.169.254/latest/meta-data/" }),
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
test/jmap-server.test.ts:354
headers: { location: "http://169.254.169.254/latest/meta-data/" },
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
test/mcp.test.ts:675
url: "http://169.254.169.254/latest/meta-data/",
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
test/account-close.test.ts:29
const BASE_URL = "http://127.0.0.1:5199";
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
test/change-email.test.ts:218
const settings = await fetch("http://127.0.0.1:5199/settings", {
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
test/change-password.test.ts:30
const BASE_URL = "http://127.0.0.1:5199";
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
test/cross-site-post.test.ts:15
return new Request("http://127.0.0.1:4582/login", {
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
test/cross-site-post.test.ts:40
rejectCrossSitePost(post({ Origin: "http://127.0.0.1:4582" })),
LOWObfuscation / stealth · obf.rtl_override · CWE-506, CWE-94
test/fixtures/statements/amex.pdf:1573
�P�������i��=qF�㧶z��LQ�x;FW8�q����8鞸�(�>��j�v��q��=qF:q�=q@P@}1F���\��z�8t�z†��b����2�����p8��L��@��W��es��08�q�q�=q@U��=q�6�j�s��|��Lс��fm����Q�x;W+����\zf�8gt�\P@(g�\b���ڹ\�8.z��4`q��8�z@8z�m^���i��
LOWObfuscation / stealth · obf.zero_width · CWE-506, CWE-94
test/fixtures/emails/quicksilver.yaml:5
View posted transaction details.

Gates applied: no_behavioural_pass.

Audited 2026-10-10 · audit v0.4.1 · source sha f2f89e1a167cfull audit observations/trust-audit/mcp-server/assaf__expense.json · Report an issue / request a re-scan
05

Audit history

Every audit this server has had. A grade with a past is a grade somebody is still checking.

DateSourceVerdictGradeScoreChange
2026-10-10f2f89e1a167cBLOCKD62first audit
06

Questions

What is the expense MCP server?

Forward a receipt to your inbox and it's filed automatically.

What tools does expense expose?

12 in total: 10 read-only, 2 that write, and 0 that can delete or overwrite. Every one is listed on this page with its risk.

Is expense safe to connect to an agent?

No — not without reading the findings first. The audit graded it D (62/100) and found 2 critical or high issues in the source. Each one is listed on this page with the file and line it is on.

What credentials does expense need?

It reads EMAIL_TOKEN_ENCRYPTION_KEY, PUSH_AUTH, PUSH_PRIVATE_KEY and SENTRY_AUTH_TOKEN from the environment. Give it a token scoped to the least it needs — an agent that can be talked into calling a tool can be talked into calling it with your credentials.

How does expense run?

It speaks streamable-http, so it runs as a service you connect to over the network. It is published on npm as @expense/pdfkit-standard-fonts at 1.0.0.

How current is this page?

The grade is for one exact copy of the source (f2f89e1a167c), read on 2026-10-10. The repository is watched and re-audited when it changes.

Advertisement