UNIFIED FINANCE — link the accounts, feed the fleet safely (Proposal v2 · draft for weekend review)
For: Byron · Host: Mia · Nature: read-only aggregation · Sensitivity: maximum (bank data + bearer tokens + LLM exposure)
Scope assumption (correct me): one read-only view of balances, transactions, holdings, net worth — that you and the fleet can query, and that drives budget cards/alerts. No money movement. Transfers/bill-pay = a different, higher-risk proposal.
Executive summary
What. Link your institutions once, aggregate balances + transactions + holdings into an encrypted store on Mia, and expose it to the fleet through a read-only, aggregates-only interface plus a tailnet-only dashboard — so "travel spend this month," "net-worth trend," "flag charges over $X" become one query, wired as real actions.
Two things a hard review changed, and they're load-bearing:
Plaid may be the wrong tool. Plaid production access requires a company/app review, privacy policy, and security questionnaire — "one person's Mac Mini fleet" isn't a sanctioned use case, and getting shut off would brick the project. SimpleFIN Bridge is built for exactly personal, read-only aggregation: clean ToS for individuals, no production charade, and a far smaller attack surface (no Plaid keys, no webhook infra). Phase 0's first job is Plaid-vs-SimpleFIN for your actual institutions — coverage is the only axis where Plaid clearly wins.
The real risk isn't strangers — it's the agents. The original draft secured tokens from outsiders and then handed the data to the least predictable actors in the system (LLMs) with no controls. Financial PII flowing into a cloud LLM context would ship your finances to a third-party inference provider on every budgeting question — silently breaking "private." And transaction memos are attacker-controllable strings (anyone who can cause a charge writes text your agents read) → a prompt-injection/exfiltration path. So this v2 is built around token isolation, an aggregates-only agent interface, an explicit LLM data-flow policy, and per-agent authorization — not just "encrypt and tailnet."
Decision sought. Approve the direction (read-only, provider TBD, safety-first) and Phase 0, which resolves the four gating questions below before any account is linked.
The four Phase-0 gating questions (must answer before Phase 1)
- Provider: SimpleFIN vs Plaid for your real banks/cards/brokerage — coverage + ToS + cost, measured.
- LLM data-flow policy: which models may ever see finance data. Default stance: raw rows never leave Mia; only aggregates may reach a model, and only a local model unless you opt in. The MCP tool returns summaries, not transaction dumps.
- Token isolation boundary: how bearer tokens are made architecturally unreachable from the agent runtime (below).
- Webhook vs poll: default poll-only — a webhook needs a public endpoint, which contradicts tailnet-only. Poll keeps zero public surface.
Architecture (safety-first shape)
Link (one-time, you) → tokens held ONLY by a dedicated `finance-sync` OS user
finance-sync: talks to provider (read-only products), writes finance.db
│ (agents CANNOT read tokens or the provider API — different user, no access)
▼
finance.db — SQLCipher-encrypted; key in the sync-user's keychain, not on disk plaintext
│ ingest FILTER: account masks only, no /auth, no full numbers, field allowlist
▼
finance-api (read-only socket) — returns AGGREGATES ONLY (never raw rows to agents)
│ per-agent allowlist · tiered access · rate limit · full audit log · freshness stamp
▼
finance.arnao.ai (TAILNET-ONLY dashboard, raw view for YOU only) · minimized alerts
The agent runtime and the token vault are separate OS users on the same box — an agent RCE or MCP bug is one privilege boundary away from tokens, not zero.
Security posture (the crux — corrected)
- Read-only products only — no payment/transfer scopes. Can see, never move.
- Token isolation — a
finance-syncuser owns tokens + provider API; agents reach only a socket serving derived data. Tokens are unreachable from the MCP layer by construction. - Do NOT back up tokens — they're cheap to recreate (re-link ~10 min). Backing them up copies bank credentials into every backup location. Back up finance.db (encrypted) only; token loss is a drilled re-link runbook, a non-event. (Corrects v1, which had this backwards.)
- LLM data-flow policy — raw transactions never enter model context; agents get aggregates; cloud models only with explicit opt-in per query class; a local model is the default for anything touching finance.
- Transaction text is untrusted — treated as data, not instructions; the finance tool's outputs are constrained; no single agent turn combines finance-read with unconstrained outbound tools (kills the memo-injection → exfil path).
- Per-agent authz + tiered access + audit + rate limit — most agents get "budget summary"; almost none get raw; every query logged (who/what/when); anomalous-query alerting is the compromise detector.
- finance.db encryption specified — SQLCipher (not FileVault-only, which protects only a powered-off Mac).
- Minimized alerts — Telegram is not E2E; alerts say "large charge detected — check dashboard," never the merchant/amount. No PII in the notification layer.
- Item health is a first-class citizen — items rot constantly (
ITEM_LOGIN_REQUIRED, OAuth re-consent, MFA changes). Every agent answer carries a freshness stamp; a stale feed silently answering "runway" with month-old data is worse than none. Staleness → re-link alert. - Transaction lifecycle handled — pending→posted mutation, removals, sync-cursor, dedup — or "spend this month" is simply wrong.
- Exit plan + retention — documented provider-abandonment/export path; a transaction-history retention/aging policy, not indefinite accumulation.
What it enables (wired actions, per your rule — not passive links)
- Spend/subscription queries, net-worth trend, budget cards (categorize / set cap / snooze — real buttons, owner-gated).
- Alerts that fire (large charge, low balance, duplicate subscription) → minimized Telegram push. Honest caveat: poll-based alerts lag; your bank's native real-time fraud alerts stay the front line — this is insight, not fraud detection.
- Agent-composable via the aggregates-only tool: "runway at current burn?" answered from summaries, with freshness.
Phases
| Phase | Scope | Effort |
|---|---|---|
| 0 | Gating decisions: provider (SimpleFIN vs Plaid, tested on your institutions) · LLM data-flow policy · token-isolation design · poll-vs-webhook · pricing confirm · encryption mechanism | 1 day |
| 1 | Link accounts (you, once) · isolated token store · SQLCipher finance.db · ingest filter · initial pull | 1–2 days |
| 2 | Sync (poll) w/ txn-lifecycle handling · aggregates-only finance-api · per-agent authz + audit + rate limit · minimized alert rules | 2–3 days |
| 3 | Tailnet dashboard (raw for you) + action-board budget cards · freshness everywhere | 2 days |
What I need from you
- Confirm read-only aggregation scope (or say if transfers are wanted — different tier).
- Which institutions (banks/cards/brokerage) — so Phase 0 tests provider coverage.
- Your call on the LLM policy: local-model-only for finance, or allow aggregates to a cloud model on specific query classes?
- Agree finance.db is tailnet-only + in the backup rotation (tokens are NOT backed up).
Self-critique — the Fable adversarial pass
Draft 1 was run through Claude Fable 5 as a hostile fintech-security review; it called v1 "a security vibe, not a security design." The load-bearing catches, all now fixed:
- "Plaid production isn't a hobbyist signup — personal use may be rejected/shut off, bricking the project." → v2 leads with SimpleFIN vs Plaid as the Phase-0 gate; SimpleFIN is built for exactly this.
- "The biggest hole: financial PII → LLM context; cloud LLM = shipping your finances to a third party; and transaction memos are attacker-controllable prompt-injection." → v2 adds an explicit LLM data-flow policy (aggregates-only, local-model default), untrusted-text handling, and no finance-read + outbound-tool in one agent turn.
- "Tokens co-located with the agent runtime = one RCE hop." →
finance-syncuser isolation; tokens architecturally unreachable from agents. - "Backing up tokens is backwards — they're regenerable bearer creds; you'd copy bank keys into every backup." → v2 does not back up tokens; re-link is a drilled runbook; only encrypted finance.db is backed up.
- "No per-agent authz/audit/rate-limit; 'the fleet can query' = a compromised low-trust agent gets full history." → allowlist + tiered access + audit + rate limit + anomalous-query alerting.
- "Telegram isn't E2E — 'large charge at [merchant] $X' leaks PII." → minimized alerts, no PII in notifications.
- "Webhook contradicts tailnet-only." → poll-only default.
- "Item rot is the norm; stale feed answering 'runway' is worse than none." → freshness stamp on every answer + re-link alerts; transaction-lifecycle handling.
- "'SQLite encrypted at rest' is a vibe; 'consider excluding account numbers' is waffle." → SQLCipher named; a hard field allowlist at ingest, not "consider."
- Also folded in: pricing verified not asserted, compromise detection, retention policy, provider-exit plan.
- Endorsed: read-only-only, no-money-movement boundary, tailnet intent, treating the token as a bearer credential, decision-first structure.
Fable's verdict on v1: "do not approve Phase 1 as written." v2 answers all seven MUST-fixes and reframes around the agent-data-flow risk it flagged as the biggest gap. This is a draft for your weekend review — the four Phase-0 gating questions are where your input decides the shape.