Skip to content

Commit 9e1997b

Browse files
committed
mcp(fix[middleware]): redact respawn shell payloads
Add `shell` to `_SENSITIVE_ARG_NAMES` so respawn_pane's shell argument is replaced by `{len, sha256_prefix}` in the audit log, matching the existing treatment of `keys`, `text`, `value`, and `content`. An agent that passes `shell="psql -U user -W secret"` would otherwise leak the credential to long-lived audit archives. Extend test_summarize_args_redacts_sensitive_keys to cover both `shell` and `content` (the latter was already in the redaction set in code but the test didn't exercise it). Update the safety doc's redaction list to match the code (was: `keys`, `text`, `value`; now: `keys`, `text`, `value`, `content`, `shell`). Note for the threat model: redaction protects the MCP audit log, not the OS process table or tmux's `pane_current_command` metadata. The new safety subsection (1.C) documents the brief leakage window between respawn-pane invocation and the spawned shell taking over.
1 parent bec9ab1 commit 9e1997b

4 files changed

Lines changed: 24 additions & 6 deletions

File tree

CHANGES

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ _Notes on upcoming releases will be added here_
2828

2929
### Fixes
3030

31+
- Audit log now redacts the ``shell`` argument on
32+
{tooliconl}`respawn-pane` (and ``content`` on {tooliconl}`load-buffer`,
33+
which the code already redacted but the docs did not list). The
34+
``shell`` payload may carry credentials passed to a relaunched
35+
process; redacting the MCP audit log keeps them out of long-lived
36+
log archives. Note: ``shell`` may still appear briefly in the OS
37+
process table and tmux's ``pane_current_command`` metadata until the
38+
spawned shell takes over — do not pass credentials directly even
39+
with redaction.
3140
- {tooliconl}`respawn-pane` now requires an explicit ``pane_id``. Its
3241
signature still accepts ``session_name`` / ``session_id`` /
3342
``window_id`` for backwards-compatibility with the shared pane-target

docs/topics/safety.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Every tool call emits one `INFO` record on the `libtmux_mcp.audit` logger carryi
114114
- `outcome``ok` or `error`, with `error_type` on failure
115115
- `duration_ms`
116116
- `client_id` / `request_id` — from the fastmcp context when available
117-
- `args` — a summary of arguments. Sensitive keys (`keys`, `text`, `value`) are replaced by `{len, sha256_prefix}`; non-sensitive strings over 200 characters are truncated.
117+
- `args` — a summary of arguments. Sensitive keys (`keys`, `text`, `value`, `content`, `shell`) are replaced by `{len, sha256_prefix}`; non-sensitive strings over 200 characters are truncated.
118118

119119
Route this logger to a dedicated sink if you want a durable audit trail; it is deliberately namespaced separately from the main `libtmux_mcp` logger.
120120

src/libtmux_mcp/middleware.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,17 @@ async def on_call_tool(
100100

101101
#: Argument names that carry user-supplied payloads we never want in logs.
102102
#: ``keys`` (send_keys), ``text`` (paste_text), ``value`` (set_environment),
103-
#: and ``content`` (load_buffer) can contain commands, secrets, or
104-
#: arbitrary large strings. Matched by exact name, case-sensitive, to
105-
#: mirror the tool signatures.
106-
_SENSITIVE_ARG_NAMES: frozenset[str] = frozenset({"keys", "text", "value", "content"})
103+
#: ``content`` (load_buffer), and ``shell`` (respawn_pane) can contain
104+
#: commands, secrets, or arbitrary large strings. Matched by exact name,
105+
#: case-sensitive, to mirror the tool signatures.
106+
#:
107+
#: Note on ``shell`` redaction: this redacts the MCP audit log only.
108+
#: ``respawn_pane(shell="env SECRET=... bash")`` may briefly expose the
109+
#: argument via the OS process table and tmux's ``pane_current_command``
110+
#: metadata until the spawned shell takes over — see ``docs/topics/safety.md``.
111+
_SENSITIVE_ARG_NAMES: frozenset[str] = frozenset(
112+
{"keys", "text", "value", "content", "shell"}
113+
)
107114

108115
#: String arguments longer than this get truncated in the log summary to
109116
#: keep records bounded. Non-sensitive strings only — sensitive ones are

tests/test_middleware.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,13 @@ def test_summarize_args_redacts_sensitive_keys() -> None:
149149
"keys": "rm -rf /",
150150
"text": "hello world",
151151
"value": "supersecret",
152+
"content": "buffer payload",
153+
"shell": "psql -U user -W secret123 mydb",
152154
"pane_id": "%1",
153155
"bracket": True,
154156
}
155157
summary = _summarize_args(args)
156-
for sensitive in ("keys", "text", "value"):
158+
for sensitive in ("keys", "text", "value", "content", "shell"):
157159
assert isinstance(summary[sensitive], dict)
158160
assert "len" in summary[sensitive]
159161
assert "sha256_prefix" in summary[sensitive]

0 commit comments

Comments
 (0)