Skip to content

feat(workstation): escalating shell gate + terminal watch TUI with approve/deny hotkeys - #679

Open
peycheff-com wants to merge 17 commits into
mainfrom
tui-shell-gate
Open

feat(workstation): escalating shell gate + terminal watch TUI with approve/deny hotkeys#679
peycheff-com wants to merge 17 commits into
mainfrom
tui-shell-gate

Conversation

@peycheff-com

@peycheff-com peycheff-com commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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/workstation

Ports Rowboat's extractCommandNames/isBlocked semantics (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, and sudo/env/time/command wrappers.
  • 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) watch subcommand — core/cmd/helm-ai-kernel

Terminal-native live approval watcher (bubbletea TUI, adapted from Rowboat's Ink ui.tsx):

  • Pending items always derive from server state (GET /api/v1/approvals); a failed refresh clears the list and disables actions — fail-closed on API errors.
  • a/d approve/deny hotkeys → POST /api/v1/approvals/{id}/{approve,deny}; r refresh, arrows/j/k navigate, q quit.
  • --once/--json snapshot modes; automatic snapshot fallback on non-TTY stdout.
  • Auth: HELM_ADMIN_API_KEY env or --api-key-file (0600) — no argv secrets.

workstation gate CLI

helm-ai-kernel workstation gate --profile dev|production --command "..." — exit codes 0/3/126 for allow/pending_approval/deny; --request-approval turns a dev escalation into a real pending approval ceremony on the server, drainable from watch.

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/ — green
  • GOWORK=off go vet — clean; golangci-lint — zero findings in new files (61 pre-existing baseline issues elsewhere untouched)
  • Extraction table: 28 cases covering every construct above; mtime reload; fail-closed store errors; dev escalation round-trip
  • TUI model unit tests: pending filter/sort, fail-closed actions on fetch error, approve/deny flow, transition error surfacing, view/snapshot rendering
  • httptest client coverage: auth header, 401, transition paths, fail-closed without key
  • Gate command tests incl. ceremony creation against httptest server

Follow-ups (not in this PR)

  • Server-side /stream-style SSE endpoint so watch can stream instead of poll (rec. 2 second half); kernel currently has no run-event bus surface.
  • Wire the shell gate into workstation enforce (currently a separate gate subcommand to avoid changing enforce behavior).
  • Run-state panel in watch once a server-side run listing/stream surface exists.

@peycheff-com
peycheff-com requested a review from a team as a code owner July 24, 2026 12:56
Copilot AI review requested due to automatic review settings July 24, 2026 12:56
@strix-security

Copy link
Copy Markdown

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 gate to 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 rm or sudo -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.

Comment thread core/pkg/workstation/shellgate.go Outdated
Comment thread core/pkg/workstation/shellgate_test.go Outdated
Comment thread core/pkg/workstation/shellallowlist.go Outdated
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>
Copilot AI review requested due to automatic review settings July 24, 2026 13:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.

Comment thread core/cmd/helm-ai-kernel/watch_cmd.go
Comment thread core/pkg/workstation/shellallowlist.go Outdated
Comment thread core/pkg/workstation/shellgate.go
mindburnlabs
mindburnlabs previously approved these changes Jul 24, 2026
Signed-off-by: Mindburn Labs <mindburnlabs@users.noreply.github.com>
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.

3 participants