feat: Introduce env variables encryption & auto-discover MFP session cookies #3
Open
rencsaridogan wants to merge 8 commits into
Open
feat: Introduce env variables encryption & auto-discover MFP session cookies #3rencsaridogan wants to merge 8 commits into
rencsaridogan wants to merge 8 commits into
Conversation
chore: Untrack files and enrich .gitignore
feat: Introduce env variables encryption
…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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add Fernet encryption + OS keychain support for credentials
Summary
MFP_USERNAME/MFP_PASSWORDusing Fernet symmetric encryption (cryptographylibrary)MFP_SECRET_KEYis resolved at runtime from two sources in order:MFP_SECRET_KEYenv var, then OS keychain (mfp-mcp/MFP_SECRET_KEY) — the key never has to appear in the Claude Desktop configget_secret_key()that checks env then callskeyring.get_password(), used byget_decrypted_credential()transparentlyscripts/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 neededcryptography>=41.0.0as a Python runtime dependency (keyringwas already present)package.json/tsconfig.jsonfor the Node tooling;node_modules/andpackage-lock.jsonare gitignoredauthenticate_with_credentialsfalse-positive success check that was poisoning~/.mfp_mcp/cookies.jsonwith pre-auth garbage on every callWhy
Credentials in
claude_desktop_config.jsonsit in plain text. Encrypting them is only meaningful if the key is stored separately — otherwise a leaked config still exposes everything. By keepingMFP_SECRET_KEYin the OS keychain and out of the config entirely, a leaked config yields useless ciphertext.store-keymakes this a one-command setup with no manual keychain interaction.How it works (env variables encryption)
npm run store-key— generates key, writes to keychain, prints key + Python encrypt snippetMFP_USERNAME/MFP_PASSWORDusing the printed snippetget_secret_key()checks env var first, thenkeyring.get_password("mfp-mcp", "MFP_SECRET_KEY")get_decrypted_credential()decrypts values before passing to auth flowHow it works (chromium cookie detection)
1. Chromium auto-discovery (new Method 3 in
get_mfp_client)try_chromium_browsers_for_session_cookies():* Safe Storagesaltysalt, 1003 iters — Chromium'sos_crypt_mac.mmscheme)session-tokenor_mfp_session)Cookies are only persisted to
~/.mfp_mcp/cookies.jsonafter a verified round-trip to MFP, so a transient failure can't poison the file.2. Strict success check in
authenticate_with_credentialsThe old check matched any cookie containing
user/session/auth/logged_inas a substring — too loose. Replaced with_has_real_mfp_session()which requires asession-tokensubstring or_mfp_sessioncookie. On failure, raises with a clear message pointing users at the auto-discovery path.3.
refresh_browser_cookiestoolautomode (now the default) — runs Chromium discoveryarc,chrome,chromium,edge,brave,vivaldi,operafirefoxpath viabrowser_cookie3Test plan
npm run store-keygenerates key and stores it; second run shows the guard promptnpm run store-key -- --showprints the stored keynpm run store-key -- --overwritereplaces existing keynpm run store-key -- --deleteremoves it; server falls back to plain-text authMFP_SECRET_KEYset anywhere (backward compat)MFP_SECRET_KEYenv varDecryption failedand falls through to cookie authpython -c "import mfp_mcp.server")cookies.json→ callget_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 persistedauthenticate_with_credentialsno longer overwritescookies.jsonon failure