You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Treat stdio env values as secrets, like HTTP headers
A config entry's `env` routinely holds API tokens — directly or via `${VAR}`
substitution — but only `headers` were protected. Resolved `env` values were
written to `sessions.json` in plaintext, printed verbatim by `mcpc --json
@session` / `connect --json`, and passed to the bridge in its command line,
where `ps` exposed them.
They now follow the exact same path as headers: stored in the OS keychain
(`session:<name>:env`), redacted to `<redacted>` in `sessions.json` and all
`--json` output, and delivered to the bridge over IPC after spawn. The bridge
merges them back into the stdio transport config, so servers still get their
environment unchanged. Sessions written before this change keep working —
their on-disk plaintext is used as is; recreate a session to move its values
into the keychain.
Rebuilt on main after the draft branch was squashed in as #316.
Fixes#341
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rmdd458q2Bx68bPxuUtQGa
Copy file name to clipboardExpand all lines: CHANGELOG.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
16
16
17
17
-`mcpc help tools/list` and other MCP method names now show the command's help instead of failing with "Unknown command" — they already worked as aliases everywhere else.
18
18
19
+
### Security
20
+
21
+
- A stdio server's `env` values are no longer stored in plaintext: they are kept in the OS keychain (like HTTP headers), shown as `<redacted>` in `sessions.json` and `--json` output, and passed to the bridge over IPC so they never appear in the process list. Sessions created before this change keep working; recreate them to move their already-stored values out of `sessions.json`.
Copy file name to clipboardExpand all lines: CLAUDE.md
+6-4Lines changed: 6 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -276,7 +276,7 @@ Implements [MCP security best practices](https://modelcontextprotocol.io/specifi
276
276
277
277
- Credentials stored in OS keychain (encrypted by system), with `0600` fallback file
278
278
- No credentials logged even in verbose mode — only log presence/absence (e.g., `refreshToken: present`)
279
-
- Headers sent to bridge via IPC after socket connect, never as command-line arguments (visible in `ps`)
279
+
- Headers and stdio `env` values sent to bridge via IPC after socket connect, never as command-line arguments (visible in `ps`)
280
280
-`sessions.json` and `profiles.json` file permissions: `0600` (user-only)
281
281
282
282
**Transport security:**
@@ -307,7 +307,7 @@ When making changes, follow these rules to maintain the security posture:
307
307
- Always use `ensureDir()` for creating directories (defaults to `0700`); use `mode: 0o600` for files containing secrets
308
308
- Use `execFile()` (array args) instead of `exec()` (shell string) when spawning processes
309
309
- Escape any user-controlled or server-controlled data before embedding in HTML responses
310
-
- Send sensitive data (headers, tokens) via IPC socket, never via CLI arguments or environment variables
310
+
- Send sensitive data (headers, stdio `env` values, tokens) via IPC socket, never via CLI arguments or environment variables
311
311
- Read all keychain values needed to start a bridge in the CLI **before**`spawn()`. After spawn the bridge arms a short IPC-credential timeout; on macOS a Keychain password dialog can block longer than that timeout, so a post-spawn keychain read races the bridge timer and causes ENOENT (#55). The CLI is the only process attached to a TTY and can show the dialog without the user wondering why a background process is asking. Bridge-side keychain access is permitted only on the OAuth token refresh paths (the `oauth-token-manager` callbacks and the id-jag provider callbacks in `src/bridge/index.ts`), where it is needed to persist rotated refresh tokens for long-running sessions
312
312
- Validate and sanitize all external input (URLs, session names, profile names) before use
313
313
- Default to HTTPS; only allow HTTP for localhost/127.0.0.1
- Bearer tokens passed via `--header "Authorization: Bearer ${TOKEN}"` are NOT stored as profiles
470
470
- All session headers are stored in the OS keychain as one JSON blob per session (keychain account: `session:<name>:headers`)
471
-
- Bridge loads them automatically when making requests (delivered over IPC after spawn, never via argv)
471
+
- A stdio server's `env` values get the same treatment (keychain account: `session:<name>:env`) — config `env` routinely holds API tokens, directly or via `${VAR}` substitution
472
+
- Bridge loads both automatically when connecting (delivered over IPC after spawn, never via argv)
472
473
473
474
**CLI Commands:**
474
475
@@ -576,7 +577,8 @@ On failure, the error message includes instructions on how to login. This ensure
0 commit comments