Kassiber is local-first. The database and all computation stay on your machine. There is no telemetry, crash reporter, update check, license check, or analytics. Outbound traffic is limited to the requests listed below.
Kassiber is pre-release (0.1.x) — treat this as a description of
current behavior, not a long-term contract.
Out of the box, Kassiber ships three built-in named backends:
mempool→esplora→https://mempool.bitcoin-austria.at/api— the default for Bitcoin wallets, operated by Bitcoin Austria.fulcrum→electrum→ssl://index.bitcoin-austria.at:50002— Bitcoin-Austria-operated Electrum/Fulcrum indexer.liquid→electrum→ssl://les.bullbitcoin.com:995— a third-party Liquid Electrum endpoint operated by BullBitcoin.
Every wallets sync against any of these sends your addresses (or
scripthashes, or gap-limit-scanned descriptor scripts) to whoever
operates that endpoint. They can link the queries to your IP and the
identifying User-Agent: kassiber/<version> header. "Bitcoin Austria
operates it" is still third-party from your machine's point of view;
"BullBitcoin operates it" for Liquid especially so.
Descriptor wallets are worse than address wallets here: gap-limit discovery leaks a contiguous run of receive + change scripts, so the backend sees the wallet cluster rather than just individual addresses.
Mitigations, in order of effect:
- Run your own Bitcoin Core and use a
bitcoinrpcbackend (traffic stays on-box). - Run your own Esplora / Electrs / Fulcrum and use it as an
esploraorelectrumbackend. - Torify the process (
torsocks python3 -m kassiber ...) or route through a VPN. Kassiber has no built-in SOCKS support yet. - Prefer
address-kind wallets overdescriptor-kind wallets when you only care about a fixed set of addresses. - Skip
rates syncand userates setfor manual rate upserts.
All HTTP(S) requests send User-Agent: kassiber/<version>. This is not
configurable.
| Trigger | Destination | Transport | What the other side learns |
|---|---|---|---|
wallets sync against the built-in mempool default |
https://mempool.bitcoin-austria.at/api (Bitcoin Austria) |
Esplora over HTTPS | IP, User-Agent, scripthashes, query timing, descriptor scan shape |
wallets sync against the built-in fulcrum default |
ssl://index.bitcoin-austria.at:50002 (Bitcoin Austria) |
Electrum JSON-RPC over TLS | IP, queried scripthashes, query timing |
wallets sync against the built-in liquid default |
ssl://les.bullbitcoin.com:995 (BullBitcoin) |
Electrum JSON-RPC over TLS | IP, queried Liquid scripthashes, query timing |
wallets sync against a user-configured Esplora backend |
your configured URL | Esplora over HTTP(S) | same categories as mempool above |
wallets sync against a user-configured Electrum backend |
your configured ssl:// or tcp:// URL |
Electrum JSON-RPC over raw TCP/TLS | IP, queried scripthashes, query timing |
wallets sync against a bitcoinrpc backend |
your configured URL | HTTP(S) POST with Basic auth | nothing leaves your machine if the node is local |
rates sync (only) |
https://api.coingecko.com/api/v3/coins/bitcoin/market_chart |
unauthenticated HTTPS GET | IP, User-Agent, which fiat pair and window |
ai models, ai chat, ai.test_connection against a configured remote/TEE provider |
your configured provider URL or CLI provider | OpenAI-compatible HTTP(S) or the configured local CLI's own transport | prompt/tool context, model request metadata, IP/provider account context according to that provider |
Nothing else makes network calls. rates set, rates latest,
rates range, rates pairs, journal processing, metadata CRUD, and all
reports are fully offline unless the user explicitly invokes an AI provider
that itself contacts a remote service.
~/.kassiber/data/kassiber.sqlite3— default SQLite DB. Contains descriptors, xpubs, addresses, transactions, metadata, rates cache, backend definitions/defaults, and any stored backend credentials.~/.kassiber/config/backends.env— default backend config file. May contain Bitcoin Core RPC credentials and backend tokens.~/.kassiber/config/settings.json— managed state manifest for the active path layout. Not secret by itself, but it reveals where the rest of the local state lives.~/.kassiber/attachments/— managed attachment store for copied local files. URL attachments are stored as literal references in the database and are not fetched.- Liquid descriptor wallets embed private SLIP77 blinding keys in
wallets.config_json. Anyone who can read the DB can unblind your confidential outputs. - Older installs may still resolve to
~/.local/share/kassiber,~/.local/share/satbooks, or a legacy<data-root>/.env; runkassiber statusto see the active paths. - Keep backend config out of version control. Prefer
COOKIEFILEover inlineUSERNAME/PASSWORD.
The SQLite database is now optionally encrypted via SQLCipher 4. After
running kassiber secrets init, every subsequent invocation needs a
passphrase: type it interactively, or pass --db-passphrase-fd <FD>
from a parent process.
~/.kassiber/data/kassiber.sqlite3— when encrypted, contents are protected by SQLCipher 4 with stock PBKDF2-HMAC-SHA512 (kdf_iter = 256000). Recoverable with the upstreamsqlcipherbinary using only the passphrase.- The pre-migration plaintext file is preserved as
kassiber.pre-encryption.sqlite3.baksomvrolls back the change. Kassiber refuses to overwrite an existing rollback backup at that path. ~/.kassiber/config/backends.envand~/.kassiber/attachments/are not inside the SQLCipher boundary. They are outside the encrypted database file and remain plaintext on disk. URLs, kinds, chain, and network metadata are not secrets and may stay in the dotenv. Tokens, passwords, auth headers, and basic-auth usernames must move into the encrypted DB — usekassiber secrets migrate-credentialsto lift any pre-existing entries inbackends.envinto the encryptedbackendstable, or seed new credentials directly with--token-stdin/--token-fd FD. Until that runs, every Kassiber command warns to stderr that the dotenv still carries plaintext secrets.- A wrong passphrase produces the structured
unlock_failedenvelope rather than a partial open. The daemon refuses to start without a passphrase when the file is encrypted. kassiber secrets change-passphraserotates the key in place viaPRAGMA rekeyand verifies withcipher_integrity_checkwhen the bundled SQLCipher build supports it.- A
.kassiberbackup file does not recover a forgotten passphrase. The DB inside the backup is encrypted under whatever passphrase was active when the backup was produced.
OS keychain is not the perimeter. The SQLCipher passphrase is the perimeter. Pick a long passphrase from a password manager and treat the loss of that passphrase as data loss — there is no recovery path. Desktop macOS builds can optionally remember the database passphrase in Keychain behind a local user-presence prompt for Touch ID-style unlock. That is convenience only: disabling it removes Kassiber's saved copy, but it does not change the SQLCipher key, recover a lost passphrase, or move backend/wallet material out of the encrypted database.
Desktop credential stores are a separate boundary, not SQLCipher replacement. Desktop builds can store AI provider API keys in macOS Keychain, Windows user-scope Credential Manager/DPAPI, or Linux Secret Service when platform policy selects a native store. The unlocked Python daemon remains trusted at runtime and receives the key only to call the configured provider. Backend tokens, descriptors, xpubs, blinding keys, and reveal payloads stay SQLCipher-protected and are not migrated to OS credential stores. See docs/plan/10-secret-management.md.
Reveal is a UX gate, not cryptographic separation. Once the daemon
is running with the unlocked DB, it can read every credential. The
auth_required round-trip for wallets reveal-descriptor and
backends reveal-token enforces re-prompting for presence; it does not
add a separate cryptographic tier.
Normal backends ... and wallets ... success output now follows a narrow
safe-to-record contract for secret-bearing config values:
- backend inspection output now uses an allowlisted safe view: raw credential
values and unknown backend config keys are suppressed, while credential
presence is exposed through
has_*flags - wallet inspection output now uses an allowlisted safe view: raw descriptor
material and unknown wallet config keys are suppressed, while callers
should rely on state flags such as
descriptor,change_descriptor, anddescriptor_stateinstead - backend URLs shown in output drop embedded credentials and query strings
This contract is intentionally narrow. It does not mean every CLI surface is safe to paste into a hosted model, issue tracker, or shared log. Addresses, notes, file paths, backend names, and other operational metadata may still be sensitive.
kassiber diagnostics collect is a separate public bug-report surface. Its
report is designed to be postable publicly: it includes version/platform data,
command shape, sanitized error context, stack module/function/line frames, DB
health, and aggregate state counts. It omits raw txids, addresses, descriptors,
xpubs, labels, notes, exact amounts, exact rates, backend hostnames, local
paths, raw config, raw API payloads, imported rows, and stack locals. --save
writes the artifact under exports/diagnostics/ in the active Kassiber state
root. --diagnostics-out auto writes the same public report when a command
fails.
- Secrets on the command line still end up in shell history if you
use the deprecated argv forms.
--token <value>,--auth-header <value>,--password <value>,--username <value>,--descriptor <value>,--change-descriptor <value>, and--api-key <value>are kept for backwards-compatibility with existing scripts and emit a deprecation warning. Prefer the safe replacements:--token-stdin/--token-fd FD(and the matching*-stdin/*-fdvariants for the other secret-bearing fields). Only one--*-stdinoption may be active per invocation; any number of--*-fdoptions may coexist. The SQLCipher passphrase itself never has an argv form: use--db-passphrase-fd FDor the interactive prompt. --debugis outside the safe-to-record contract. Debug stack traces, exception context, and any future private logs may still include sensitive local state. Review before pasting into issues, screenshots, or logs. Usediagnostics collector--diagnostics-out autofor public bug reports.- Normal machine output still carries sensitive operational metadata. Success envelopes now redact secret-bearing backend and wallet config values, but addresses, paths, notes, and infrastructure choices can still be sensitive in hosted-model transcripts or shared logs.
- Cross-wallet linkability. Running
wallets syncfor several wallets in one session ties them to the same IP + timing +User-Agentat the backend. Per-wallet sync calls are not per-wallet privacy. tor_proxyis scaffolded but not wired.backends create --tor-proxyaccepts a value and stores it, but HTTP and Electrum traffic currently ignores it. For now, torify the whole process externally.- No SPV / header verification. Backends are trusted for transaction history, confirmations, and fees. A malicious backend can fabricate or hide transactions.
- No rate-source cross-check. Wrong CoinGecko rates become wrong
cost basis becomes wrong capital-gains. For tax-grade numbers prefer
rates setwith values you trust. - Austrian tax processing is currently unavailable. Kassiber only
supports the generic RP2-backed path today. Future Austrian support is
planned in the Kassiber-maintained RP2 fork at
bitcoinaustria/rp2; until then,tax_country=atshould be treated as unsupported. - Generic tax output is not tax advice. It is accounting software output built on local wallet history and available pricing, not a substitute for jurisdiction-specific review.
- Electrum
INSECURE=1disables TLS verification. Only against servers you fully control — never against a public Electrum server. - Plain HTTP to Bitcoin Core is only safe on localhost. Kassiber
will send RPC credentials over
http://to whatever URL you configure. Tunnel remote nodes over SSH / VPN / TLS proxy. - Fixed, identifying
User-Agent. Every outbound HTTP request advertiseskassiber/<version>. - Legacy data-root fallback. If
~/.kassiberdoes not exist yet but~/.local/share/kassiberor~/.local/share/satbooksdoes, Kassiber keeps using the older directory.kassiber statusshows the effective path. - Lightning node wallet kinds.
corelncan sync through read-only Core Lightning RPC methods. Prefer a commando rune restricted to list, get, andbkpr-list*methods with a rate cap (e.g.restrictions='[["method^list","method^get","method^bkpr-list","method=summary"],["method/listdatastore"],["rate=60"]]'). Kassiber passes the rune through theLIGHTNING_RUNEenvironment variable so it does not appear in/proc/<pid>/cmdline. Locallightning-rpcfile access is also supported but is not least-privilege on its own.lndandnwcremain declared but inactive.
The desktop app and kassiber ai CLI surface speak the OpenAI-compatible
wire format against any provider you configure. The default seeded entry
points at local Ollama (http://localhost:11434/v1); add remote providers
through Settings → AI providers or kassiber ai providers create.
- Prompts are sensitive accounting data. A chat about quarantined
transactions or report prep can include wallet labels, addresses, notes,
imported document contents, backend hostnames, and tax annotations. Any
remote provider sees that content. The provider/model picker tags each
configured endpoint as
local,remote, orteeso you can see at a glance whether a prompt is about to leave the device. - Remote chat only after explicit acknowledgement. Remote providers
start unacknowledged unless they are created or updated with
--acknowledge, or confirmed in Settings → AI providers.ai.chatrefuses to send prompts to an unacknowledged off-device provider withai_remote_ack_required. - AI provider API keys have a narrow desktop native-store path. CLI
callers should use
--api-key-stdin/--api-key-fd FD; the old--api-key <value>form is a warning-on-use shim and storessqlcipher_inline. Desktop Settings writes keys throughai.providers.set_api_keyand can move a provider key withai.providers.move_api_key; provider envelopes return onlyhas_api_keyplussecret_ref.{store_id,state}. No generic keyring get/list API is exposed to the webview or assistant. - No encrypted-while-running claim. Once the Python daemon is unlocked, it can read stored AI keys, backend tokens, descriptors, and blinding keys to do its job. This does not protect against malware, admin/root access, debugger memory inspection, a compromised OS, or a compromised webview process.
- The Tauri shell allowlists exactly the AI daemon kinds. The webview cannot reach Ollama (or any other model API) directly — every call passes through the Python daemon. The provider URL never reaches the webview's CSP/CORS surface. The in-app AI has no shell, raw filesystem, arbitrary CLI, or generic daemon-dispatch access.
- The Vite daemon bridge is development-only.
pnpm --dir ui-tauri run dev:bridgeexposes selected daemon kinds, including AI streaming and consent controls, through the Vite server on loopback for browser testing. Do not bind that server to a LAN address or use it as a REST API; it is only a local development bridge to the same daemon trust boundary. - Streaming Stop is best-effort cooperative cancel, not a billing
guarantee. Pressing Stop sends
ai.chat.cancelto the local daemon and suppresses later streamed UI updates. The Python worker stops forwarding deltas once provider control returns between chunks and marks the terminal responsefinish_reason: "cancelled". Remote providers may still bill for tokens already generated or in flight. No prompt content is exposed beyond what was already sent. - Read-only AI tools send selected local data to the selected provider. When tools are enabled, the assistant may read safe daemon snapshots such as status, overview, filtered transactions, wallet/backend summaries, profiles, journals, quarantine summaries, transfer-pair summaries, cached rate metadata, workspace health, next-action guidance, capital-gains reports, and allowlisted skill references. If the selected provider is remote or TEE, those tool results are sent to that provider as chat context.
- AI read tools use redacted daemon surfaces. They must not expose secrets, descriptors, xpub material, API keys, tokens, cookies, auth headers, exact backend URLs, wallet config JSON, or raw wallet files. Wallet and backend tools return labels, kinds, URL presence flags, credential presence flags, and status-style metadata only.
- Mutating AI tools require explicit consent. The current mutating surface
is limited to
ui.wallets.sync. Each call emits a redacted preview and waits forallow_once,allow_session, ordeny; session consent lasts only for that one chat request and only for the same tool name. If allowed, the tool result is fed back to the selected provider as chat context. Unknown tools still returntool_not_allowedand never execute.
Do not file security-impacting issues in the public tracker. Contact the maintainer privately with a reproduction.