Skip to content

Commit df765ef

Browse files
pluskymimi1vx
authored andcommitted
refactor(commands)!: remove the lrun local-execution command
lrun ran an arbitrary command in mtui's local shell. It is removed from both surfaces as a design choice: - Over MCP it never made sense: the client already has its own local execution, which is exactly why lrun was permanently deny-listed there from the start. A tool that exists only to be denied is dead surface. - In the REPL the tester is already sitting at a shell; prefixing a local command with 'lrun' bought nothing over running it directly (or over 'run' for the reference hosts). Removal, not denial: the command is gone from the registry, so neither the REPL nor MCP can reach it, and the MCP deny-list shrinks to the interactive/REPL-only set. A new registry test pins the removal (absent from the registry AND from the deny-list, so no stale entry lingers). MCP tests that used lrun as their deny-list specimen now use shell, which keeps the same assertions meaningful. The MCP tool surface is unchanged - lrun was never synthesised - so downstream LLM configs are unaffected. cli.md regenerated via cargo xtask gen-docs; help-listing snapshot refreshed. BREAKING CHANGE: the lrun REPL command no longer exists. Use your shell directly, or 'run' for commands on the reference hosts.
1 parent d8ebc5f commit df765ef

17 files changed

Lines changed: 83 additions & 229 deletions

File tree

AGENTS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,9 @@ see phase progress. Phase 0 (workspace bootstrap) is already complete (closed).
131131
converting the command's `clap` arg spec to a JSON schema, reconstructing argv
132132
from tool kwargs, and dispatching through the **same engine** as the REPL.
133133
REPL-only commands (`quit`, `exit`, `EOF`, `edit`, `shell`, `help`, `terms`,
134-
`switch`) and local process execution (`lrun`) are deny-listed; the deny-list ∩
135-
registry is consistency-tested and drift is warned about at boot.
134+
`switch`) are deny-listed; the deny-list ∩ registry is consistency-tested and
135+
drift is warned about at boot. Local process execution has no command at all —
136+
`lrun` was removed by design; do not reintroduce it.
136137

137138
## Contracts (do not break without intent — these enable ecosystem interop)
138139
- **RRID grammar** `project:kind:maintenance_id:review_id` and its parse errors.

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ _The entries below document the final Python releases, retained for history._
9999
immediately instead of each repeating the doomed download, so a down
100100
server costs one transport timeout, not one per caller.
101101

102+
### Removed
103+
104+
- The `lrun` command (run a command in the local shell) has been removed from
105+
both the REPL and the MCP server. It never made sense over MCP — the client
106+
already has its own local execution, which is why it was permanently
107+
deny-listed there — and in the REPL a tester is already at a shell. Use the
108+
shell directly, or `run` for commands on the reference hosts.
109+
102110
## 19.0.1 - 2026-07-17
103111

104112
### Added

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ ecosystem.
3737

3838
### MCP security boundary
3939

40-
`lrun` executes commands as the local process user. It remains available through
41-
direct `mtui` use and trusted callers of the core engine, but MCP synthesis and
42-
routing never expose it over stdio or HTTP. This permanent deny applies under
43-
every MCP profile and cannot be reversed with `[mcp] tools_allow`.
40+
Interactive/REPL-only commands (`shell`, `edit`, `terms`, …) are permanently
41+
deny-listed: MCP synthesis and routing never expose them over stdio or HTTP,
42+
under every MCP profile, and the deny cannot be reversed with
43+
`[mcp] tools_allow`. Local process execution is not exposed at all — the former
44+
`lrun` command was removed from mtui entirely.
4445

4546
MCP profiles reduce the advertised tool surface; they are not authentication or
4647
authorization. HTTP session isolation is likewise not caller authentication.

crates/mtui-core/src/commands/localrun.rs

Lines changed: 0 additions & 145 deletions
This file was deleted.

crates/mtui-core/src/commands/mod.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//! [`Session`](crate::Session).
77
//!
88
//! Ported in port-order "waves" (`PLAN-phase5.md §5.5`). Wave 1 — the core
9-
//! workflow that gates the phase — lands here: `run`, `lrun`, `update`,
9+
//! workflow that gates the phase — lands here: `run`, `update`,
1010
//! `install`/`uninstall`, `prepare`, `downgrade`, `reboot`, `set_repo`,
1111
//! `show_update_repos`.
1212
@@ -15,7 +15,6 @@ pub mod support;
1515
mod perform;
1616

1717
mod downgrade;
18-
mod localrun;
1918
mod prepare;
2019
mod reboot;
2120
mod run;
@@ -82,7 +81,6 @@ mod help;
8281
mod terms;
8382

8483
pub use downgrade::Downgrade;
85-
pub use localrun::LocalRun;
8684
pub use prepare::Prepare;
8785
pub use reboot::Reboot;
8886
pub use run::Run;

crates/mtui-core/src/registry.rs

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,9 @@ impl Registry {
117117
/// Commands that must not be synthesised into MCP tools.
118118
///
119119
/// Ports and hardens upstream's MCP deny-list (`AGENTS.md`): these commands drive
120-
/// the interactive shell, require a controlling terminal, or execute locally
121-
/// with the `mtui-mcp` process user's authority. The Phase-7 `mtui-mcp` tool
120+
/// the interactive shell or require a controlling terminal. (Local process
121+
/// execution used to be the third category; its `lrun` command was removed
122+
/// outright rather than merely denied.) The Phase-7 `mtui-mcp` tool
122123
/// synthesiser skips every registry command whose name or alias appears here,
123124
/// and warns at boot if a deny-list entry no longer resolves (so a
124125
/// renamed/removed command does not drift silently). Kept here, beside
@@ -132,7 +133,6 @@ pub const MCP_DENYLIST: &[&str] = &[
132133
"quit", "exit", "EOF", // session exit (Wave 2)
133134
"switch", // active-template pointer, REPL-only (Wave 2)
134135
"shell", // interactive PTY attach, Phase 6 (Wave 2)
135-
"lrun", // arbitrary local execution as the mtui-mcp process user
136136
"help", // registry listing / per-command help, REPL-only (Phase 6)
137137
"edit", // $EDITOR spawn on the controlling TTY, REPL-only (Phase 6)
138138
"terms", // spawn terminal-launcher scripts to hosts, REPL-only (Phase 6)
@@ -152,7 +152,6 @@ pub fn register_all() -> Registry {
152152
let mut registry = Registry::new();
153153
// Wave 1 — core workflow (gates the phase).
154154
registry.register(Arc::new(commands::Run));
155-
registry.register(Arc::new(commands::LocalRun));
156155
registry.register(Arc::new(commands::Update));
157156
registry.register(Arc::new(commands::Install));
158157
registry.register(Arc::new(commands::Uninstall));
@@ -269,7 +268,6 @@ mod tests {
269268
// Wave 1 — core workflow.
270269
for name in [
271270
"run",
272-
"lrun",
273271
"update",
274272
"install",
275273
"uninstall",
@@ -362,12 +360,13 @@ mod tests {
362360

363361
#[test]
364362
fn register_all_command_count() {
365-
// 10 Wave 1 + 14 Wave 2 + 17 Wave 3 + 12 Wave 4 (incl. request_review)
366-
// + 4 P5 follow-ups (export, list_refhosts, load_template, list_locks)
367-
// + 2 openQA-holder follow-ups (reload_openqa, set_workflow:
363+
// 9 Wave 1 (10 originally; `lrun` was later removed outright) + 14
364+
// Wave 2 + 17 Wave 3 + 12 Wave 4 (incl. request_review) + 4 P5
365+
// follow-ups (export, list_refhosts, load_template, list_locks) + 2
366+
// openQA-holder follow-ups (reload_openqa, set_workflow:
368367
// mtui-rs-zs4/plt) + 3 Phase 6 (help: mtui-rs-lhz.9; edit:
369-
// mtui-rs-lhz.10; terms: mtui-rs-lhz.11) = 62 canonical commands.
370-
assert_eq!(register_all().len(), 62);
368+
// mtui-rs-lhz.10; terms: mtui-rs-lhz.11) = 61 canonical commands.
369+
assert_eq!(register_all().len(), 61);
371370
}
372371

373372
#[test]
@@ -397,11 +396,18 @@ mod tests {
397396
}
398397

399398
#[test]
400-
fn mcp_denylist_blocks_local_execution() {
401-
assert!(MCP_DENYLIST.contains(&"lrun"));
399+
fn lrun_is_fully_removed() {
400+
// `lrun` (arbitrary local execution) was removed as a design choice —
401+
// not merely MCP-denied. It must be absent from the registry (so
402+
// neither the REPL nor MCP can reach it) and absent from the deny-list
403+
// (which would otherwise imply it still exists somewhere).
402404
assert!(
403-
register_all().contains("lrun"),
404-
"lrun remains a REPL command"
405+
!register_all().contains("lrun"),
406+
"lrun must not be a registered command"
407+
);
408+
assert!(
409+
!MCP_DENYLIST.contains(&"lrun"),
410+
"a removed command must not linger on the deny-list"
405411
);
406412
}
407413

@@ -419,8 +425,8 @@ mod tests {
419425
let _reserved_or_registered = r.contains(name);
420426
}
421427
// Sanity: the currently-registered deny-listed commands are the Wave 2
422-
// REPL-only set (quit+aliases, switch, shell), local execution (`lrun`),
423-
// plus `help`, `edit`, and `terms` (Phase 6).
428+
// REPL-only set (quit+aliases, switch, shell) plus `help`, `edit`, and
429+
// `terms` (Phase 6).
424430
let registered_denied: Vec<&str> = MCP_DENYLIST
425431
.iter()
426432
.copied()
@@ -429,7 +435,7 @@ mod tests {
429435
assert_eq!(
430436
registered_denied,
431437
vec![
432-
"quit", "exit", "EOF", "switch", "shell", "lrun", "help", "edit", "terms"
438+
"quit", "exit", "EOF", "switch", "shell", "help", "edit", "terms"
433439
]
434440
);
435441
}

crates/mtui-core/tests/snapshots/help_listing__help_listing.snap

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ get help install list_bugs
1111
list_history list_hosts list_locks list_metadata
1212
list_packages list_products list_refhosts list_sessions
1313
list_templates list_timeout list_update_commands list_versions
14-
load_template lock lrun openqa_jobs
15-
openqa_overview prepare put quit
16-
reboot regenerate reject reload_openqa
17-
reload_products remove_host request_review run
18-
set_host_state set_log_level set_repo set_timeout
19-
set_workflow shell show_diff show_log
20-
show_update_repos switch terms unassign
21-
uninstall unload unlock update
22-
updates whoami
14+
load_template lock openqa_jobs openqa_overview
15+
prepare put quit reboot
16+
regenerate reject reload_openqa reload_products
17+
remove_host request_review run set_host_state
18+
set_log_level set_repo set_timeout set_workflow
19+
shell show_diff show_log show_update_repos
20+
switch terms unassign uninstall
21+
unload unlock update updates
22+
whoami

crates/mtui-mcp/src/deny.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
//! Commands the MCP server must not expose as tools.
22
//!
3-
//! Hardening of upstream `mtui/mcp/deny.py`. Each entry either cannot
4-
//! meaningfully run outside an interactive terminal session or crosses a local
5-
//! process trust boundary, and is filtered out when [`crate::tools`] synthesises
6-
//! tools from the command [`Registry`].
3+
//! Hardening of upstream `mtui/mcp/deny.py`. Each entry cannot meaningfully run
4+
//! outside an interactive terminal session, and is filtered out when
5+
//! [`crate::tools`] synthesises tools from the command [`Registry`]. (Upstream's
6+
//! third category — local process execution via `lrun` — no longer needs
7+
//! denying: the command was removed from mtui entirely.)
78
//!
89
//! The deny surface is **not** re-declared here: it is the single
910
//! [`mtui_core::MCP_DENYLIST`], which sits beside `register_all` so the list and
@@ -19,8 +20,6 @@
1920
//! (P7.8) operate on the loaded report file directly instead.
2021
//! - `shell`: opens an interactive root PTY on a refhost and needs a TTY the MCP
2122
//! transports do not provide.
22-
//! - `lrun`: executes arbitrary local commands with the `mtui-mcp` process user's
23-
//! authority. It remains available to a human through the direct REPL.
2423
//! - `help`: prints argparser help to stdout; the MCP protocol already
2524
//! advertises tool descriptions.
2625
//! - `terms`: launches local terminal-emulator scripts on the operator's
@@ -48,7 +47,7 @@ mod tests {
4847
#[test]
4948
fn blocked_commands_are_denied() {
5049
for name in [
51-
"quit", "exit", "EOF", "edit", "shell", "lrun", "help", "terms", "switch",
50+
"quit", "exit", "EOF", "edit", "shell", "help", "terms", "switch",
5251
] {
5352
assert!(is_denied(name), "{name} must be denied");
5453
}

crates/mtui-mcp/src/profiles.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
//! An operator selects a profile with `[mcp] profile` and can fine-tune with
1313
//! `[mcp] tools_allow` / `[mcp] tools_deny` (see [`apply_profile`]).
1414
//! Profiles only filter the surface remaining after the permanent MCP deny-list;
15-
//! `tools_allow` cannot restore a command such as `lrun` that was never
15+
//! `tools_allow` cannot restore a command such as `shell` that was never
1616
//! synthesised.
1717
//!
1818
//! The default is `full` so existing deployments are unchanged; slimming the tool

0 commit comments

Comments
 (0)