Security: User ID exposure in Beancount ledger accounts #8

Open
opened 2026-01-19 12:43:30 +00:00 by padreug · 0 comments
Owner

Problem

Castle currently uses LNbits user_id (truncated to 8 chars) in Beancount account names:

  • Assets:Receivable:User-c32bb2c8
  • Liabilities:Payable:User-c32bb2c8

Security risk: LNbits allows login with just the user_id (no password required by default). Anyone with read access to the ledger file could potentially extract user IDs and gain access to those LNbits accounts.

Current Exposure Points

  1. Beancount account names in ledger file
  2. Journal entry metadata
  3. API responses (balance endpoints, user lists)
  4. Frontend display (though truncated)

Options to Consider

  • LNbits setting: LNBITS_ALLOW_URL_LOGIN=false
  • Pros: Quick fix, no code changes to Castle
  • Cons: Requires server configuration, users must use other auth methods

Option 2: Use a derived pseudonymous identifier

  • Generate a Castle-specific identifier: hash(user_id + castle_salt)
  • Store mapping in Castle database
  • Pros: User IDs not exposed, reversible internally
  • Cons: Adds complexity, need to manage mapping

Option 3: Use usernames instead of IDs

  • Account names like Liabilities:Payable:User-alice
  • Pros: Human readable, doesn't expose internal IDs
  • Cons: Usernames can change, may not be unique, requires migration

Option 4: Use wallet_id instead

  • Already partially done in some places
  • Pros: Slightly less sensitive than user_id
  • Cons: Similar exposure risk, wallet_id also grants access

Option 5: Encrypt/obfuscate identifiers in ledger

  • Store encrypted references, decrypt only when needed
  • Pros: Ledger file is safe to share
  • Cons: Breaks Beancount tooling compatibility, complex

Recommendation

Short-term: Enable LNBITS_ALLOW_URL_LOGIN=false on production servers

Long-term: Implement Option 2 (derived pseudonymous identifier) with:

  • One-way derivation for account names
  • Internal mapping table for reverse lookup
  • Migration path for existing accounts
  • account_utils.py - Account naming logic
  • fava_client.py - User ID usage in queries
  • crud.py - Database operations with user IDs
  • views_api.py - API endpoints exposing user data
## Problem Castle currently uses LNbits `user_id` (truncated to 8 chars) in Beancount account names: - `Assets:Receivable:User-c32bb2c8` - `Liabilities:Payable:User-c32bb2c8` **Security risk**: LNbits allows login with just the `user_id` (no password required by default). Anyone with read access to the ledger file could potentially extract user IDs and gain access to those LNbits accounts. ## Current Exposure Points 1. Beancount account names in ledger file 2. Journal entry metadata 3. API responses (balance endpoints, user lists) 4. Frontend display (though truncated) ## Options to Consider ### Option 1: Disable user_id login in LNbits (Recommended short-term) - LNbits setting: `LNBITS_ALLOW_URL_LOGIN=false` - Pros: Quick fix, no code changes to Castle - Cons: Requires server configuration, users must use other auth methods ### Option 2: Use a derived pseudonymous identifier - Generate a Castle-specific identifier: `hash(user_id + castle_salt)` - Store mapping in Castle database - Pros: User IDs not exposed, reversible internally - Cons: Adds complexity, need to manage mapping ### Option 3: Use usernames instead of IDs - Account names like `Liabilities:Payable:User-alice` - Pros: Human readable, doesn't expose internal IDs - Cons: Usernames can change, may not be unique, requires migration ### Option 4: Use wallet_id instead - Already partially done in some places - Pros: Slightly less sensitive than user_id - Cons: Similar exposure risk, wallet_id also grants access ### Option 5: Encrypt/obfuscate identifiers in ledger - Store encrypted references, decrypt only when needed - Pros: Ledger file is safe to share - Cons: Breaks Beancount tooling compatibility, complex ## Recommendation **Short-term**: Enable `LNBITS_ALLOW_URL_LOGIN=false` on production servers **Long-term**: Implement Option 2 (derived pseudonymous identifier) with: - One-way derivation for account names - Internal mapping table for reverse lookup - Migration path for existing accounts ## Related Files - `account_utils.py` - Account naming logic - `fava_client.py` - User ID usage in queries - `crud.py` - Database operations with user IDs - `views_api.py` - API endpoints exposing user data
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: aiolabs/castle#8
No description provided.