feat(workstation): escalating shell gate + terminal watch TUI with approve/deny hotkeys - #679
Open
peycheff-com wants to merge 17 commits into
Open
feat(workstation): escalating shell gate + terminal watch TUI with approve/deny hotkeys#679peycheff-com wants to merge 17 commits into
peycheff-com wants to merge 17 commits into
Conversation
|
Strix is installed on this repository, but we couldn't run this PR security review because this workspace's trial has ended. Add a card to resume code reviews here. |
There was a problem hiding this comment.
Pull request overview
Adds terminal-native operator ergonomics to the kernel CLI by introducing (1) a workstation “escalating shell gate” that can turn blocked commands into pending approvals in the dev profile, and (2) a watch Bubble Tea TUI for live approval queue review with approve/deny hotkeys.
Changes:
- Implement shell command-name extraction + gating decisions, plus a user-editable allowlist store with seeding and mtime/size caching.
- Add
helm-ai-kernel workstation gateto evaluate commands (and optionally create approval ceremonies on the server). - Add
helm-ai-kernel watch(TUI + snapshot modes) and an HTTP client for the approval API, with unit tests.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| core/pkg/workstation/shellgate.go | New shell command parsing/extraction and gating logic (allow/deny/pending_approval). |
| core/pkg/workstation/shellgate_test.go | Unit tests for extraction, allowlist blocking, and gate profile behaviors. |
| core/pkg/workstation/shellallowlist.go | JSON allowlist store (seed defaults, parse multiple formats, cache by mtime+size). |
| core/go.mod | Adds Bubble Tea as a direct dependency (plus new indirect deps). |
| core/go.sum | Adds checksums for Bubble Tea and its transitive dependencies. |
| core/cmd/helm-ai-kernel/workstation_gate_cmd.go | New workstation gate CLI subcommand + optional approval-ceremony creation. |
| core/cmd/helm-ai-kernel/workstation_gate_cmd_test.go | Tests for gate command exit codes, fail-closed behavior, and ceremony creation. |
| core/cmd/helm-ai-kernel/workstation_cmd.go | Wires workstation gate into the workstation command dispatcher/usage. |
| core/cmd/helm-ai-kernel/watch_model.go | Bubble Tea model for approval watching, including fail-closed behavior on refresh errors. |
| core/cmd/helm-ai-kernel/watch_model_test.go | Model tests for filtering/sorting, guards, transitions, rendering, and fail-closed behavior. |
| core/cmd/helm-ai-kernel/watch_cmd.go | New watch command (TUI vs snapshot modes; API key resolution; polling interval). |
| core/cmd/helm-ai-kernel/watch_client.go | HTTP client for approval list/transition/create operations (fail closed on errors). |
| core/cmd/helm-ai-kernel/watch_client_test.go | HTTP client tests (auth header, 401, transitions, bad URL, missing key). |
Comments suppressed due to low confidence (2)
core/pkg/workstation/shellgate.go:124
- sanitizeCommandToken only trims quotes today, so tokens like "pwd)/x" (from
$(pwd)/x) remain intact and get treated as command names. After avoiding splitting on)this still needs to truncate at the first)and trim leading(/ trailing)so command substitutions and subshell parens don’t pollute the extracted command set.
func sanitizeCommandToken(token string) string {
return strings.ToLower(strings.Trim(strings.TrimSpace(token), `'"`))
}
core/pkg/workstation/shellgate.go:108
- unwrapWrappedCommands skips wrapper flags but not their separate values. For wrappers like
env -u PATH rmorsudo -u root rm, the value token (e.g.PATH/root) is currently treated as the wrapped command and the real command (rm) is never extracted. If that value happens to be allowlisted (e.g.env -u ls rm), the gate can become fail-open and miss a blocked command.
if strings.HasPrefix(token, "-") {
// Bare wrapper flag (e.g. `sudo -E`, `time -p`). Flags that take a
// separate value are not unwrapped; the value may surface as a
// command name, which fails closed.
continue
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Port Rowboat's extractCommandNames/isBlocked semantics (Apache-2.0,
apps/cli/src/application/lib/command-executor.ts) to Go as the parsing
front-end of workstation enforcement, with fail-closed hardening:
- ExtractCommandNames: robust to &&, ||, |, ;, &, backticks, $(...)
subshells, ENV= prefixes, and sudo/env/time/command wrappers
(recursive unwrap, skip ENV= and flags after wrappers).
- BlockedCommandNames: empty allowlist blocks everything, '*' allows all.
- GateShellCommand: blocked commands escalate to PENDING APPROVAL in the
dev profile; production (and any unknown profile) stays fail-closed deny.
- ShellAllowlistStore: user-editable JSON allowlist (bare array /
{allowedCommands} / truthy map) with mtime+size cache; seeds the
Rowboat default set on first use; corrupt files fail closed (no
silent fallback to defaults).
Tests: full extraction table (28 cases), blocked semantics, profile
matrix, mtime reload (same-mtime+size cached, bumped-mtime reload),
fail-closed store errors, and the dev escalation round-trip.
Signed-off-by: Mindburn Labs <mindburnlabs@users.noreply.github.com>
(a) helm-ai-kernel watch — terminal-native live approval watcher
(bubbletea TUI, adapted from Rowboat's Ink ui.tsx, Apache-2.0):
- Pending items always derive from server state (GET /api/v1/approvals);
a failed refresh clears the list and disables actions (fail-closed).
- a/d approve/deny hotkeys POST /api/v1/approvals/{id}/{approve,deny}
with the operator actor; r refresh, arrows/jk navigate, q quit.
- --once/--json snapshot modes; automatic snapshot fallback on non-TTY.
- Auth via HELM_ADMIN_API_KEY env or --api-key-file (0600); no argv
secrets. URL via --url or HELM_KERNEL_URL (default 127.0.0.1:8080).
(b) helm-ai-kernel workstation gate — CLI front-end for the escalating
shell gate: --profile dev|production (unknown profiles fail closed to
production), --allowlist path, exit codes 0/3/126 for
allow/pending_approval/deny, and --request-approval which turns a dev
escalation into a real pending approval ceremony on the server (drainable
from watch).
New dep: github.com/charmbracelet/bubbletea v1.3.10 (only direct
addition; no TUI framework existed in go.mod). Rendering is plain-text
to keep the transitive footprint minimal.
Tests: httptest client coverage (auth header, 401, transition paths,
fail-closed without key), TUI model unit tests (pending filter/sort,
fail-closed actions on fetch error, approve/deny flow, transition error
surfacing, quit, view rendering, snapshot renderer), and gate command
tests (allow/deny/escalate exits, unknown profile fail-closed, corrupt
allowlist fail-closed, ceremony creation, server-down error).
Signed-off-by: Mindburn Labs <mindburnlabs@users.noreply.github.com>
peycheff-com
force-pushed
the
tui-shell-gate
branch
from
July 24, 2026 13:24
b40ef98 to
8a8e6cf
Compare
mindburnlabs
previously approved these changes
Jul 24, 2026
Signed-off-by: Mindburn Labs <mindburnlabs@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wave 3 / P1-12 of the Rowboat×HELM program — terminal-native ops ergonomics for the kernel CLI. Adapts two mechanisms from the Rowboat deep study (
.tmp-research/findings/cli.md, recommendations 2–3) with original Go implementations and attribution comments. No Rowboat code is copied verbatim.(b) Escalating shell gate —
core/pkg/workstationPorts Rowboat's
extractCommandNames/isBlockedsemantics (apps/cli/src/application/lib/command-executor.ts,config/security.ts) as the parsing front-end of workstation enforcement:ExtractCommandNames— robust to&&,||,|,;,&, backticks,$(...),()subshells,ENV=prefixes, andsudo/env/time/commandwrappers.GateShellCommand— blocked commands become pending approvals, not hard failures, in the dev profile only; production (and any unknown profile) stays fail-closed deny.ShellAllowlistStore— user-editable JSON allowlist (bare array /{allowedCommands}/ truthy map) with mtime+size cache; seeds the minimal Rowboat default set on first use (0600).Fail-closed hardening deviations (documented in-file): recursive wrapper unwrapping,
ENV=/flag skipping after wrappers, corrupt allowlist = error (never silent fallback to defaults), unknown profile ⇒ production.(a)
watchsubcommand —core/cmd/helm-ai-kernelTerminal-native live approval watcher (bubbletea TUI, adapted from Rowboat's Ink
ui.tsx):GET /api/v1/approvals); a failed refresh clears the list and disables actions — fail-closed on API errors.a/dapprove/deny hotkeys →POST /api/v1/approvals/{id}/{approve,deny};rrefresh, arrows/j/knavigate,qquit.--once/--jsonsnapshot modes; automatic snapshot fallback on non-TTY stdout.HELM_ADMIN_API_KEYenv or--api-key-file(0600) — no argv secrets.workstation gateCLIhelm-ai-kernel workstation gate --profile dev|production --command "..."— exit codes0/3/126for allow/pending_approval/deny;--request-approvalturns a dev escalation into a real pending approval ceremony on the server, drainable fromwatch.New dependency
github.com/charmbracelet/bubbletea v1.3.10— the only direct addition; go.mod had no TUI framework. Rendering is plain-text (no lipgloss import) to keep the transitive footprint minimal.Test plan
GOWORK=off go test ./pkg/workstation/ ./cmd/helm-ai-kernel/— greenGOWORK=off go vet— clean;golangci-lint— zero findings in new files (61 pre-existing baseline issues elsewhere untouched)Follow-ups (not in this PR)
/stream-style SSE endpoint sowatchcan stream instead of poll (rec. 2 second half); kernel currently has no run-event bus surface.workstation enforce(currently a separategatesubcommand to avoid changing enforce behavior).watchonce a server-side run listing/stream surface exists.