Skip to content

feat: Introduce env variables encryption & auto-discover MFP session cookies #3

Open
rencsaridogan wants to merge 8 commits into
AdamWalt:mainfrom
rencsaridogan:main
Open

feat: Introduce env variables encryption & auto-discover MFP session cookies #3
rencsaridogan wants to merge 8 commits into
AdamWalt:mainfrom
rencsaridogan:main

Conversation

@rencsaridogan

@rencsaridogan rencsaridogan commented Apr 28, 2026

Copy link
Copy Markdown

Add Fernet encryption + OS keychain support for credentials

Summary

  • Adds optional at-rest encryption for MFP_USERNAME / MFP_PASSWORD using Fernet symmetric encryption (cryptography library)
  • MFP_SECRET_KEY is resolved at runtime from two sources in order: MFP_SECRET_KEY env var, then OS keychain (mfp-mcp / MFP_SECRET_KEY) — the key never has to appear in the Claude Desktop config
  • Adds get_secret_key() that checks env then calls keyring.get_password(), used by get_decrypted_credential() transparently
  • Adds scripts/store-key.ts — a one-time Node.js CLI (npm run store-key) that generates a Fernet-compatible key, stores it in the OS keychain, and prints the encryption snippet; zero manual keychain commands needed
  • Adds cryptography>=41.0.0 as a Python runtime dependency (keyring was already present)
  • Adds package.json / tsconfig.json for the Node tooling; node_modules/ and package-lock.json are gitignored
  • Updates README with a 3-step encrypted credentials guide, a full Key Management CLI reference, and an updated Project Structure tree
  • Add Chromium browser auto-discovery as a primary auth path on macOS — works with Arc, Chrome, Edge, Brave, Vivaldi, Opera, or any other installed Chromium-based browser
  • Fix authenticate_with_credentials false-positive success check that was poisoning ~/.mfp_mcp/cookies.json with pre-auth garbage on every call

Why

Credentials in claude_desktop_config.json sit in plain text. Encrypting them is only meaningful if the key is stored separately — otherwise a leaked config still exposes everything. By keeping MFP_SECRET_KEY in the OS keychain and out of the config entirely, a leaked config yields useless ciphertext. store-key makes this a one-command setup with no manual keychain interaction.

How it works (env variables encryption)

  1. npm run store-key — generates key, writes to keychain, prints key + Python encrypt snippet
  2. User encrypts MFP_USERNAME / MFP_PASSWORD using the printed snippet
  3. Encrypted values go into Claude Desktop config; key stays in keychain
  4. On startup, get_secret_key() checks env var first, then keyring.get_password("mfp-mcp", "MFP_SECRET_KEY")
  5. get_decrypted_credential() decrypts values before passing to auth flow
  6. If no key is found anywhere, raw env values are used — fully backward compatible

How it works (chromium cookie detection)

1. Chromium auto-discovery (new Method 3 in get_mfp_client)

try_chromium_browsers_for_session_cookies():

  1. Enumerates macOS keychain entries matching * Safe Storage
  2. Derives each browser's AES-128 key (PBKDF2-HMAC-SHA1, salt=saltysalt, 1003 iters — Chromium's os_crypt_mac.mm scheme)
  3. Locates and copies the browser's cookies SQLite DB
  4. Decrypts v10/v11 entries, stripping the 32-byte SHA-256 host_key prefix that modern Chromium prepends to plaintext
  5. Returns the first browser with a real MFP session token (session-token or _mfp_session)

Cookies are only persisted to ~/.mfp_mcp/cookies.json after a verified round-trip to MFP, so a transient failure can't poison the file.

2. Strict success check in authenticate_with_credentials

The old check matched any cookie containing user/session/auth/logged_in as a substring — too loose. Replaced with _has_real_mfp_session() which requires a session-token substring or _mfp_session cookie. On failure, raises with a clear message pointing users at the auto-discovery path.

3. refresh_browser_cookies tool

  • New auto mode (now the default) — runs Chromium discovery
  • Explicit aliases: arc, chrome, chromium, edge, brave, vivaldi, opera
  • Legacy firefox path via browser_cookie3

Test plan

  • npm run store-key generates key and stores it; second run shows the guard prompt
  • npm run store-key -- --show prints the stored key
  • npm run store-key -- --overwrite replaces existing key
  • npm run store-key -- --delete removes it; server falls back to plain-text auth
  • Plain-text auth works with no MFP_SECRET_KEY set anywhere (backward compat)
  • Auth works with key in MFP_SECRET_KEY env var
  • Auth works with key in keychain only (no env var)
  • Wrong key logs Decryption failed and falls through to cookie auth
  • Keychain backend failure logs a warning and falls through gracefully
  • Module imports cleanly (python -c "import mfp_mcp.server")
  • End-to-end: wipe cookies.json → call get_mfp_client() with env creds → form login raises cleanly → Chromium discovery finds Arc → 16 cookies extracted with valid __Secure-next-auth.session-token → diary round-trip succeeds → cookies persisted
  • authenticate_with_credentials no longer overwrites cookies.json on failure

@rencsaridogan rencsaridogan requested a review from AdamWalt as a code owner April 28, 2026 09:41
Renç Sarıdoğan and others added 3 commits May 21, 2026 19:26
…cOS)

MyFitnessPal migrated to a NextAuth backend, which breaks the legacy
form-POST `authenticate_with_credentials` flow this MCP relied on. The
flow returns HTTP 200 with no session token, but the success check matched
any cookie name containing "auth" — including `__Host-next-auth.csrf-token`
in the pre-auth response — so it falsely reported success and overwrote
`~/.mfp_mcp/cookies.json` with useless cookies on every call.

Two changes:

1. Add a Chromium auto-discovery auth path (macOS).

   `try_chromium_browsers_for_session_cookies()` enumerates macOS keychain
   entries matching `* Safe Storage`, derives each browser's AES key
   (PBKDF2-HMAC-SHA1, salt=saltysalt, 1003 iters), reads the cookies
   SQLite, decrypts v10/v11 entries (stripping the 32-byte SHA-256
   host_key prefix that modern Chromium prepends), and returns the first
   browser with a real MFP session token. Works out of the box with Arc,
   Chrome, Edge, Brave, Vivaldi, Opera, and any other installed
   Chromium-based browser — including ones `browser_cookie3` doesn't
   support.

   Wired into `get_mfp_client()` as Method 3, between the stored-cookie
   path and the `browser_cookie3` fallback. Cookies are only persisted
   after a successful round-trip to MFP so a transient failure can't
   poison `cookies.json`.

2. Tighten `authenticate_with_credentials` success check.

   Require a real session token (`session-token` substring or
   `_mfp_session`) before returning success. If absent, raise with a
   clear message pointing users at the Chromium auto-discovery path.
   Prevents the regression where stale-cookie failures clobber valid
   cookies with pre-auth garbage.

The `refresh_browser_cookies` tool gains an `auto` mode (the new default)
that runs the same discovery, plus explicit aliases for `arc`, `chrome`,
`chromium`, `edge`, `brave`, `vivaldi`, `opera`, and the legacy `firefox`
path via `browser_cookie3`.

Verified end-to-end against a logged-in Arc session — auth chain falls
through credential → stored cookies → Chromium auto-discovery → success,
and `cookies.json` is rebuilt with a valid `__Secure-next-auth.session-token`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Seven fixes from the PR #3 review:

1. `_verify_cookies_and_format` now persists only after the live MFP
   round-trip succeeds — matches the anti-poisoning behavior of the
   auto-discovery path so an expired session can't clobber valid
   `cookies.json`.

2. Fix `TypeError` in `refresh_browser_cookies` unsupported-browser
   path: `sorted(_CHROMIUM_BROWSER_ALIASES) | {'firefox'}` would crash
   because `sorted()` returns a list. Build the option set first, then
   sort.

3. `_decrypt_chromium_value_macos` now takes the cookie's `host_key`
   and only strips the 32-byte SHA-256 prefix when it actually matches
   `sha256(host_key)`. Previously, long legacy cookie values that
   happened to decode as UTF-8 after dropping the first 32 bytes were
   silently truncated.

4. Replace `tempfile.mktemp()` with `tempfile.mkstemp()` to eliminate
   the TOCTOU window where another process could replace the chosen
   path between name selection and `shutil.copy`.

5. `refresh_browser_cookies(browser='chrome')` on Linux/Windows now
   falls through to `browser_cookie3.chrome(...)` instead of trying to
   read a macOS-only keychain entry. Other Chromium browsers return a
   clear "macOS only" message on non-macOS.

6. Use the SQLite backup API (read-only source connection) instead of
   `shutil.copy` to snapshot the cookies DB. This captures rows that
   live in the adjacent `-wal` file while the browser is running, so
   recently-set session cookies aren't missed.

7. Tighten the host filter from `host_key LIKE '%myfitnesspal.com%'`
   (which would also match `notmyfitnesspal.com`) to
   `host_key = 'myfitnesspal.com' OR host_key LIKE '%.myfitnesspal.com'`
   — exact match plus genuine subdomains only.

Also extracted the SQLite snapshot into `_snapshot_sqlite_db` for
reuse and added `hashlib` to imports (dropping unused `shutil`).

Verified end-to-end against the live Arc session — auth chain still
works, 16 cookies persisted, real diary round-trip succeeds. Added
self-tests for Fix 3: a 272-byte legacy value (no prefix) now
round-trips intact, and a modern value with a verified
`sha256(host_key)` prefix is correctly stripped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…traction

feat: auto-discover MFP session cookies from any Chromium browser (macOS)
@rencsaridogan rencsaridogan changed the title feat: Introduce env variables encryption feat: Introduce env variables encryption & auto-discover MFP session cookies Jun 2, 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.

1 participant