feat(mcp): add yt mcp secret rotate #114

Merged
Claude-Run merged 1 commit from feat/YT-56-mcp-secret-rotate into main 2026-07-17 02:28:43 +02:00
Member

Rotating the Hub client secret had no first-class command. Changing the value alone was always survivable (edit mcp.env, restart, yt mcp hub reauth --all), but that path is wrong when the rotation is a security response: it re-mints the stored grants rather than dropping them, so credentials derived from a leaked secret keep working. Nothing cleared hub_tokens, browser sessions are signed cookies with no server-side table to clear, and service install is a full re-declaration that would silently reset --hub-scope (YT-55) while preserving the session key by design. What was left was a hand-edited 0600 env file, a base64 key generated by hand, DELETE statements against an encrypted SQLite database, and a manual restart, with a silent failure mode at every step.

yt mcp secret rotate does the whole response in one command, ordered so nothing local changes until the new secret exists upstream: install a new secret on the Hub service record (authenticating with the active instance's YouTrack token, never with the secret being replaced), patch only YOUTRACK_MCP_HUB_CLIENT_SECRET and YOUTRACK_MCP_SESSION_KEY in mcp.env via temp file plus rename at 0600, purge hub_tokens / access_tokens / refresh_tokens / authorization_codes in one transaction, and systemctl --user restart the unit. A Hub that refuses fails closed: the env is byte-identical and the operator re-runs with --hub-client-secret <value>, which skips the Hub call and applies a value they rotated by hand. --yes skips the confirmation, --no-restart leaves the restart to the caller.

The env write is a patch, never a re-declaration, so no key the operator did not name can be reset. YOUTRACK_MCP_MASTER_KEY is left alone: it protects hub_tokens at rest and the purge deletes every such row.

Tests cover the patch preserving unrelated keys, the purge emptying all four tables, a failing Hub call leaving mcp.env untouched, the restart never reaching for enable --now, and an end-to-end case proving a bearer minted under the old secret dies at once while the same session cookie runs a full Hub login and its tool calls succeed, with no manual cookie clearing.

#YT-56

Rotating the Hub client secret had no first-class command. Changing the value alone was always survivable (edit `mcp.env`, restart, `yt mcp hub reauth --all`), but that path is wrong when the rotation is a security response: it re-mints the stored grants rather than dropping them, so credentials derived from a leaked secret keep working. Nothing cleared `hub_tokens`, browser sessions are signed cookies with no server-side table to clear, and `service install` is a full re-declaration that would silently reset `--hub-scope` (YT-55) while preserving the session key by design. What was left was a hand-edited 0600 env file, a base64 key generated by hand, `DELETE` statements against an encrypted SQLite database, and a manual restart, with a silent failure mode at every step. `yt mcp secret rotate` does the whole response in one command, ordered so nothing local changes until the new secret exists upstream: install a new secret on the Hub service record (authenticating with the active instance's YouTrack token, never with the secret being replaced), patch only `YOUTRACK_MCP_HUB_CLIENT_SECRET` and `YOUTRACK_MCP_SESSION_KEY` in `mcp.env` via temp file plus rename at 0600, purge `hub_tokens` / `access_tokens` / `refresh_tokens` / `authorization_codes` in one transaction, and `systemctl --user restart` the unit. A Hub that refuses fails closed: the env is byte-identical and the operator re-runs with `--hub-client-secret <value>`, which skips the Hub call and applies a value they rotated by hand. `--yes` skips the confirmation, `--no-restart` leaves the restart to the caller. The env write is a patch, never a re-declaration, so no key the operator did not name can be reset. `YOUTRACK_MCP_MASTER_KEY` is left alone: it protects `hub_tokens` at rest and the purge deletes every such row. Tests cover the patch preserving unrelated keys, the purge emptying all four tables, a failing Hub call leaving `mcp.env` untouched, the restart never reaching for `enable --now`, and an end-to-end case proving a bearer minted under the old secret dies at once while the same session cookie runs a full Hub login and its tool calls succeed, with no manual cookie clearing. #YT-56
feat(mcp): add yt mcp secret rotate
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 44s
Create release / Create release from merged PR (pull_request) Has been skipped
e48e8ed298
Rotating the Hub client secret had no first-class command. Changing the value alone was always survivable (edit `mcp.env`, restart, `yt mcp hub reauth --all`), but that path is wrong when the rotation is a security response: it re-mints the stored grants rather than dropping them, so credentials derived from a leaked secret keep working. Nothing cleared `hub_tokens`, browser sessions are signed cookies with no server-side table to clear, and `service install` is a full re-declaration that would silently reset `--hub-scope` (YT-55) while preserving the session key by design. What was left was a hand-edited 0600 env file, a base64 key generated by hand, `DELETE` statements against an encrypted SQLite database, and a manual restart, with a silent failure mode at every step.

`yt mcp secret rotate` does the whole response in one command, ordered so nothing local changes until the new secret exists upstream: install a new secret on the Hub service record (authenticating with the active instance's YouTrack token, never with the secret being replaced), patch only `YOUTRACK_MCP_HUB_CLIENT_SECRET` and `YOUTRACK_MCP_SESSION_KEY` in `mcp.env` via temp file plus rename at 0600, purge `hub_tokens` / `access_tokens` / `refresh_tokens` / `authorization_codes` in one transaction, and `systemctl --user restart` the unit. A Hub that refuses fails closed: the env is byte-identical and the operator re-runs with `--hub-client-secret <value>`, which skips the Hub call and applies a value they rotated by hand. `--yes` skips the confirmation, `--no-restart` leaves the restart to the caller.

The env write is a patch, never a re-declaration, so no key the operator did not name can be reset. `YOUTRACK_MCP_MASTER_KEY` is left alone: it protects `hub_tokens` at rest and the purge deletes every such row.

Tests cover the patch preserving unrelated keys, the purge emptying all four tables, a failing Hub call leaving `mcp.env` untouched, the restart never reaching for `enable --now`, and an end-to-end case proving a bearer minted under the old secret dies at once while the same session cookie runs a full Hub login and its tool calls succeed, with no manual cookie clearing.

#YT-56
Claude-Run deleted branch feat/YT-56-mcp-secret-rotate 2026-07-17 02:28:43 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pandoras-box/youtrack-cli!114
No description provided.