Skip to content

Stop storing API keys as plaintext - #77

Closed
dyk1454683243-sudo wants to merge 1 commit into
mtiluk:mainfrom
dyk1454683243-sudo:cursor/hash-api-keys-491c
Closed

dyk1454683243-sudo wants to merge 1 commit into
mtiluk:mainfrom
dyk1454683243-sudo:cursor/hash-api-keys-491c

Conversation

@dyk1454683243-sudo

Copy link
Copy Markdown

Fixes #67.

Issue #67 is labeled documentation / good-first-issue, but the live secret still sits in SQLite as plaintext. Hashing is the right server-side fix (the CLI must keep a retrievable key, so it stays in the OS keyring). This does not invent a cipher: it uses crypto/sha256 for high-entropy tokens and leaves password hashing on bcrypt.

Why hashing, not keyring, on the server

  • The client already stores the live API key with zalando/go-keyring. That cannot be hashed: potok push / pull / doctor have to send the original bearer token.
  • The server only needs to verify a presented key. A database leak should not yield a usable credential.
  • Generated keys are 256-bit crypto/rand values, so a fast cryptographic hash is enough. This is the same approach GitHub uses for personal access tokens. User passwords remain on bcrypt.

Changes

Server

  • CreateUser stores sha256: + hex(SHA-256(key)) in users.api_key_hash and returns the plaintext key once.
  • UserByAPIKey hashes the presented bearer token and looks up the digest. Lookups no longer return a key or hash.
  • Migration 0004 renames api_key → api_key_hash. On startup, any leftover potok_… (or other non-hash) values are hashed in place. The migrate step is idempotent.
  • POST /register now returns JSON {id,email,api_key,created_at} so the live key is shown once without dumping password_hash.
  • GET /me is tested to omit api_key.

Client

  • Leftover api_key fields in config.json are moved into the OS keyring on the next potok command, then stripped from the file. An existing keyring entry wins.

Docs

  • README now describes the split: keyring on the client, SHA-256 at rest on the server.

Compatibility

  • New servers hash keys at insert time. Existing databases are rewritten on the next potokd start.
  • Already-issued client keys keep working; only the stored form changes.
  • Rolling back the schema rename cannot un-hash values. Re-register if you downgrade past this change.

Test plan

  • go build ./...
  • go test ./...
  • CreateUser stores a sha256: digest and the returned key still authenticates
  • Startup migrate hashes leftover plaintext potok_ keys and stays idempotent
  • Leftover config.json api_key moves into the keyring and is stripped from the file

Hash server-side API keys with SHA-256 before they are written to SQLite,
migrate existing plaintext potok_ values on startup, and move leftover
config.json api_key fields into the OS keyring.

Fixes mtiluk#67

Co-authored-by: David <dyk1454683243-sudo@users.noreply.github.com>
@mtiluk mtiluk closed this Sep 18, 2026
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.

Stop storing API Keys as plaintext

3 participants