fix(mcp): keep the new secret out of argv on secret rotate #119

Merged
Claude-Run merged 1 commit from fix/YT-62-secret-rotate-no-argv-secret into main 2026-07-17 12:34:54 +02:00
Member

yt mcp secret rotate took its only input as --hub-client-secret <value>, so the replacement for a leaked secret went into the shell's history before the process started, and sat in world-readable /proc/<pid>/cmdline (mode 0444) for the duration of the run, where any local user could read it out of ps. Anything that records commands (sudo logs, shell tracing, CI job logs, session recordings) captured it too. The command exists as the security response to a leaked YOUTRACK_MCP_HUB_CLIENT_SECRET, and its only input channel leaked the replacement. YT-61 made the operator's value the sole source, so that was no longer an edge case but the normal path.

The flag is gone, replaced by three channels resolved in order: --hub-client-secret-stdin (docker login --password-stdin's idiom, stripping exactly one trailing newline and nothing else, so a value ending in whitespace survives), $YOUTRACK_MCP_HUB_CLIENT_SECRET_NEW, then an unechoed rpassword prompt. The prompt is last because it is the only channel an automation cannot drive, so anything passed deliberately outranks it. With no terminal and neither non-interactive channel set, the command exits non-zero naming both, having written nothing.

The environment variable is deliberately not the service's own YOUTRACK_MCP_HUB_CLIENT_SECRET: a shell that has sourced mcp.env already exports that one, holding the value being rotated away from, and reading it here would re-apply the leaked secret while reporting a successful rotation. /proc/<pid>/environ is mode 0400, so unlike argv it is not exposed to other local users.

--hub-client-secret-stdin requires --yes, checked before stdin is read: the secret consumes stdin, so the confirmation's read would hit EOF and abort the rotation, which reads as a hang or a silent no-op rather than a missing flag.

The error text no longer teaches the argv form, and names no secret value on any path, including the empty-value refusal, which names only the channel it came from. The empty check now covers all three channels rather than argv alone.

Removing a flag shipped in 0.11.0 is breaking. It shipped hours ago, its only documented use is a security response, and every existing invocation of it already leaked the secret it passed, so removal beats a deprecation window whose warning would print after the value is already in history and in ps.

no_flag_takes_the_secret_as_a_value pins the removal against the parser rather than a review note, so a value-taking spelling cannot come back unnoticed.

#YT-62

`yt mcp secret rotate` took its only input as `--hub-client-secret <value>`, so the replacement for a leaked secret went into the shell's history before the process started, and sat in world-readable `/proc/<pid>/cmdline` (mode 0444) for the duration of the run, where any local user could read it out of `ps`. Anything that records commands (sudo logs, shell tracing, CI job logs, session recordings) captured it too. The command exists as the security response to a leaked `YOUTRACK_MCP_HUB_CLIENT_SECRET`, and its only input channel leaked the replacement. YT-61 made the operator's value the sole source, so that was no longer an edge case but the normal path. The flag is gone, replaced by three channels resolved in order: `--hub-client-secret-stdin` (`docker login --password-stdin`'s idiom, stripping exactly one trailing newline and nothing else, so a value ending in whitespace survives), `$YOUTRACK_MCP_HUB_CLIENT_SECRET_NEW`, then an unechoed `rpassword` prompt. The prompt is last because it is the only channel an automation cannot drive, so anything passed deliberately outranks it. With no terminal and neither non-interactive channel set, the command exits non-zero naming both, having written nothing. The environment variable is deliberately not the service's own `YOUTRACK_MCP_HUB_CLIENT_SECRET`: a shell that has sourced `mcp.env` already exports that one, holding the value being rotated away from, and reading it here would re-apply the leaked secret while reporting a successful rotation. `/proc/<pid>/environ` is mode 0400, so unlike argv it is not exposed to other local users. `--hub-client-secret-stdin` requires `--yes`, checked before stdin is read: the secret consumes stdin, so the confirmation's read would hit EOF and abort the rotation, which reads as a hang or a silent no-op rather than a missing flag. The error text no longer teaches the argv form, and names no secret value on any path, including the empty-value refusal, which names only the channel it came from. The empty check now covers all three channels rather than argv alone. Removing a flag shipped in 0.11.0 is breaking. It shipped hours ago, its only documented use is a security response, and every existing invocation of it already leaked the secret it passed, so removal beats a deprecation window whose warning would print after the value is already in history and in `ps`. `no_flag_takes_the_secret_as_a_value` pins the removal against the parser rather than a review note, so a value-taking spelling cannot come back unnoticed. #YT-62
fix(mcp): keep the new secret out of argv on secret rotate
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 54s
Create release / Create release from merged PR (pull_request) Has been skipped
fc3a671c90
`yt mcp secret rotate` took its only input as `--hub-client-secret <value>`, so the replacement for a leaked secret went into the shell's history before the process started, and sat in world-readable `/proc/<pid>/cmdline` (mode 0444) for the duration of the run, where any local user could read it out of `ps`. Anything that records commands (sudo logs, shell tracing, CI job logs, session recordings) captured it too. The command exists as the security response to a leaked `YOUTRACK_MCP_HUB_CLIENT_SECRET`, and its only input channel leaked the replacement. YT-61 made the operator's value the sole source, so that was no longer an edge case but the normal path.

The flag is gone, replaced by three channels resolved in order: `--hub-client-secret-stdin` (`docker login --password-stdin`'s idiom, stripping exactly one trailing newline and nothing else, so a value ending in whitespace survives), `$YOUTRACK_MCP_HUB_CLIENT_SECRET_NEW`, then an unechoed `rpassword` prompt. The prompt is last because it is the only channel an automation cannot drive, so anything passed deliberately outranks it. With no terminal and neither non-interactive channel set, the command exits non-zero naming both, having written nothing.

The environment variable is deliberately not the service's own `YOUTRACK_MCP_HUB_CLIENT_SECRET`: a shell that has sourced `mcp.env` already exports that one, holding the value being rotated away from, and reading it here would re-apply the leaked secret while reporting a successful rotation. `/proc/<pid>/environ` is mode 0400, so unlike argv it is not exposed to other local users.

`--hub-client-secret-stdin` requires `--yes`, checked before stdin is read: the secret consumes stdin, so the confirmation's read would hit EOF and abort the rotation, which reads as a hang or a silent no-op rather than a missing flag.

The error text no longer teaches the argv form, and names no secret value on any path, including the empty-value refusal, which names only the channel it came from. The empty check now covers all three channels rather than argv alone.

Removing a flag shipped in 0.11.0 is breaking. It shipped hours ago, its only documented use is a security response, and every existing invocation of it already leaked the secret it passed, so removal beats a deprecation window whose warning would print after the value is already in history and in `ps`.

`no_flag_takes_the_secret_as_a_value` pins the removal against the parser rather than a review note, so a value-taking spelling cannot come back unnoticed.

#YT-62
Claude-Run deleted branch fix/YT-62-secret-rotate-no-argv-secret 2026-07-17 12:34:54 +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!119
No description provided.