Skip to content

security: harden credential handling, SSRF protection, and key storage#1

Open
orsharon7 wants to merge 1 commit into
Shaya16:mainfrom
orsharon7:security/harden-credentials-and-ssrf
Open

security: harden credential handling, SSRF protection, and key storage#1
orsharon7 wants to merge 1 commit into
Shaya16:mainfrom
orsharon7:security/harden-credentials-and-ssrf

Conversation

@orsharon7

Copy link
Copy Markdown

Summary

This PR fixes several security issues identified during a review of the credential handling flow. The app stores bank credentials and API keys, so these are high-priority hardening items.

  • Password masking -- GET /api/integrations/[provider] was returning fully decrypted credentials including passwords. Now returns empty strings for password-type fields. The edit form already had blank=keep logic in the POST handler, so no functionality is lost. Password inputs show "Leave blank to keep current" as placeholder in edit mode, and allValid allows blank passwords when editing.
  • Provider validation -- POST /api/setup/bank now rejects unknown provider values (400) before touching the database.
  • SSRF protection -- Both PUT /api/settings and POST /api/setup/ai now validate that ollamaUrl is a localhost URL before storing it. Previously any URL could be saved and the server would make outbound requests to it.
  • Encryption key location -- The key was stored in data/.encryption-key, the same directory as data/spent.db. Anyone with a copy of that directory could decrypt all credentials. The key now lives at ~/.config/spent/.encryption-key. Existing keys are migrated automatically on first startup.
  • Verbose log warning -- The "Show browser during sync" setting description now explicitly warns that logs may contain bank credentials and should not be shared.

Test plan

  • Edit an existing bank connection -- password field is blank with "Leave blank to keep current" placeholder; saving without retyping keeps the old password
  • POST to /api/setup/bank with an unknown provider name -- should return 400 "Unknown provider"
  • PUT to /api/settings with ollamaUrl pointing to a non-localhost address -- should return 400
  • Confirm ~/.config/spent/.encryption-key is created on first run (mode 600) and data/.encryption-key is absent
  • Existing synced data is still readable after the key migration

🤖 Generated with Claude Code

- Mask password fields in GET /api/integrations/[provider] so decrypted
  credentials are never returned over the wire; edit form uses blank=keep
  logic that was already in the POST handler
- Update bank edit form to allow blank password fields in edit mode and
  show "Leave blank to keep current" placeholder
- Validate provider against BANK_PROVIDERS in POST /api/setup/bank to
  reject unknown provider strings before touching the DB
- Validate ollamaUrl is a localhost URL in both PUT /api/settings and
  POST /api/setup/ai to block SSRF via a crafted Ollama URL
- Move encryption key from data/.encryption-key to
  ~/.config/spent/.encryption-key so a copy of the data directory alone
  cannot decrypt stored credentials; migrates existing keys automatically
- Warn in the Show Browser setting that verbose logs may contain bank
  credentials

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant