Skip to content

Mcp(fix[safety]): Narrow on a typo instead of widening - #7

Open
tony wants to merge 1 commit into
masterfrom
mcp-safety-fail-closed
Open

Mcp(fix[safety]): Narrow on a typo instead of widening#7
tony wants to merge 1 commit into
masterfrom
mcp-safety-fail-closed

Conversation

@tony

@tony tony commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

TMUX_MCP_SAFETY fell back to the default tier on any value it did not recognise, and that default is mutating. An operator who wrote read_only or ro got a server advertising send_keys and every creating tool on a socket they had meant to be read-only, with nothing saying so.

--safety refuses an unknown name outright. The variable cannot, because it is read from wherever the process was started — but falling back and falling back upward are different decisions, and only the first was intended. Safety::from_env's own documentation said widening on a typo would be the wrong way to fail while the code did exactly that.

The two directions do not cost the same. Narrowing costs a restart, and the startup line names the tier in force, so the typo is visible. Widening hands an agent authority nobody granted and says nothing.

What changed

  • Safety::from_value resolves a tier from an optional value the way an environment supplies one. Unset takes the default; set and unrecognised takes Safety::ReadOnly.
  • Safety::from_env reduces to reading the variable and calling it — the split CallerIdentity::from_env already uses, so the fallback is testable without a process-wide environment no test can hold alone.
  • --safety is unchanged. A flag is typed on purpose, so a wrong one stays a Stop::Misuse.
  • The tmux-mcp README says how the variable and the flag differ, and where a typo becomes visible.

Tests

an_unrecognised_tier_narrows_rather_than_widens covers read only, read_only, ro, yolo, and the empty string. an_unset_tier_takes_the_default and a_tier_is_read_by_name pin the other two paths.

Note that read-only was never affected: Safety::parse has always accepted it as an alias for readonly.

Scope

tmux-mcp only. crates/libtmux/docs/public-api.txt records the libtmux surface and is untouched.

why: TMUX_MCP_SAFETY fell back to the default on any value it did not
recognise, and the default is mutating. An operator who wrote read_only
or ro got a server offering send_keys and every creating tool on a
socket they had meant to be read-only. The flag refuses such a name;
the variable cannot, because it is read from wherever the process was
started -- but falling back and falling back *upward* are different
decisions, and only the first was intended. Safety::from_env's own
documentation said widening on a typo would be the wrong way to fail,
while the code did exactly that.

The two directions do not cost the same. Narrowing costs a restart and
is visible in the startup line, which names the tier in force.
Widening hands an agent authority nobody granted and says nothing.

what:
- Add Safety::from_value, resolving a tier from an optional value; unset
  takes the default, set-and-unrecognised takes ReadOnly
- Reduce Safety::from_env to reading the variable and calling it, the
  split CallerIdentity::from_env already uses so parsing is testable
  without a process-wide environment
- Cover the fallback in both directions, including the empty string
- Correct the cli.rs test comment that described the old fallback
- Say in the README how the variable and the flag differ, and where a
  typo becomes visible
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