Skip to content

v4.5.0 — Keychain-only credential storage

Choose a tag to compare

@jtalk22 jtalk22 released this 21 Jul 21:53
9a7efac

Keychain-only credential storage — zero plaintext on disk, every failure loud

Closes #162. Slack credentials can now live exclusively in the macOS Keychain — no plaintext token file is ever written, while --setup, slack_refresh_tokens, and automatic refresh keep working unchanged.

Token storage backends

Three storage modes (SLACK_MCP_TOKEN_STORAGE, or choose once in --setup)

  • auto (default) — token file (chmod 600) + Keychain backup, exactly the previous behavior
  • keychain-only — macOS Keychain only. Writes verified by read-back; a legacy token file is migrated in and removed only after both entries verify. Failures are loud and structured (keychain_write_failed, plaintext_removal_failed with the exact cleanup command) — never a silent fallback to plaintext
  • file — token file only; the Keychain is never touched (no prompts — shared machines, CI)

On macOS, --setup asks where credentials should live and remembers the answer in ~/.slack-mcp-meta.json, so the server, CLI, and any LaunchAgent follow the same choice with no env-var plumbing. Unrecognized mode values fail closed at startup — a typo can never downgrade to plaintext.

Reliability hardening (from the adversarial review on #163)

  • In-memory last-known-good tokens — a refresh that extracts working tokens but can't persist them (locked Keychain) keeps them available in-process, so the auth retry uses the fresh pair instead of the stale stored credentials. Surfaced as storage.unpersisted_fresh_tokens.
  • Honest auto-heal — persistence failures are recorded in telemetry (stuck-detection can trip) and reported as persisted: false, never as a clean heal.
  • Cross-process write lock — token file + metadata writes are serialized across server/CLI/LaunchAgent (O_EXCL lock, stale-holder takeover), ending silent lost-updates; proven by a two-process regression test.
  • Keychain writes use -U update-in-place (all modes), removing the delete-then-add window that could lose the entry.
  • Hardened setup wizard (platform gate, strict prompts, friendly save errors) and token CLI (tokens:clear exits non-zero on incomplete cleanup; tokens:status flags a lingering plaintext file even when no credentials load).

Observability

slack_token_status now reports the active backend under storage: { mode, mode_source, keychain_available, plaintext_file_present, unpersisted_fresh_tokens }. --doctor and npm run tokens:status print the storage mode with its origin and warn on pending migration.

Tests

44 tests (20 new since 4.4.3), including a two-process metadata-race regression, undeletable-plaintext fail-loud coverage, memory-fallback lifecycle, and five end-to-end tests that boot the real MCP server over stdio.

Credit: adversarial test pass by @TyceHerrman surfaced four of the hardening items; the pre-existing Chrome extraction timeout found during that pass is tracked in #168.

Full changelog: https://github.com/jtalk22/slack-mcp-server/blob/main/CHANGELOG.md