diff --git a/CHANGELOG.md b/CHANGELOG.md index fb1469a7..e7ae98f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,7 +56,8 @@ breaking entries are marked **BREAKING**. - **Wrapped commands** (`kaish_kernel::tools::wrapped`, `subprocess` feature): register an external program as a tool with a declared grammar. Verbs and flags are deny-by-default, refused with exit 2 before any spawn; the kernel renders - argv. Runs with `allow_external_commands` off. See `docs/wrapped_command.md`. + argv. It narrows rather than widens: `allow_external_commands = false` plus + named wrappers is the intended shape. See `docs/wrapped_command.md`. - **`ValidationIssue::command`** — the command an issue concerns, when one is genuinely known (`UndefinedCommand`'s name, a builtin's own regex/schema failure), so an embedder can route on it instead of parsing `message`. diff --git a/crates/kaish-kernel/src/tools/wrapped.rs b/crates/kaish-kernel/src/tools/wrapped.rs index 6daf61a3..d72fbfed 100644 --- a/crates/kaish-kernel/src/tools/wrapped.rs +++ b/crates/kaish-kernel/src/tools/wrapped.rs @@ -5,6 +5,14 @@ //! renders the argv itself, so a value is never parsed as a flag by the child //! unless the declaration put it in flag position. //! +//! This narrows what a kernel can run; it does not widen it. +//! `allow_external_commands` chooses between "no word spawns anything" and +//! "every program on `$PATH` spawns, with any arguments." A wrapped command is +//! the setting between: the executable is pinned at registration, so a changed +//! `$PATH` changes nothing, and an undeclared verb or flag is refused before +//! any spawn. Registering wrappers with the switch left `false` is the intended +//! shape, not a way around it. +//! //! ```no_run //! use kaish_kernel::tools::wrapped::{Flag, Positional, Verb, WrappedCommand}; //! diff --git a/docs/wrapped_command.md b/docs/wrapped_command.md index 701cddb2..47a521c9 100644 --- a/docs/wrapped_command.md +++ b/docs/wrapped_command.md @@ -430,9 +430,13 @@ and the two names that collide: optional slot without it. - A relative `path_under(root)` — the root must be an absolute path. -A wrapped command is a registered tool, not an external command. It runs when -`allow_external_commands` is `false`. It needs the `subprocess` feature; a -sandbox build has no `wrapped` module. +A wrapped command is a registered tool, not an external command, so +`allow_external_commands` does not gate it. That switch is not a lock a wrapper +picks: it decides whether an arbitrary word becomes a `$PATH` lookup. A wrapper +is the narrower grant beside it — one pinned executable, one declared grammar. +Leaving the switch `false` and registering a wrapper is the point. + +It needs the `subprocess` feature; a sandbox build has no `wrapped` module. ## What the kernel owns, and what the embedder owns