🚧 Glyx is pre-release software. APIs may change before v1.0. Get started →
Documentation
Roadmap
Shell Execution

Shell Execution

Two deliberately different trust tiers, not one capability with a toggle. shellExec (shipped) is a small, explicit binary allowlist for apps that know in advance which commands they need — a Git GUI, a media tool wrapping ffmpeg. shellAgent (planned) is open-ended execution for apps that can't enumerate commands ahead of time, like an AI coding assistant — and because it has no allowlist doing the safety work, it needs a fundamentally different guardrail.


shellExec — scoped exec (shipped)

capabilities: { shellExec: { allow: ['git', 'ffmpeg'] } }
import { shell } from '@glyx-dev/react'
 
const { stdout, exitCode } = await shell.run('git', ['status', '--porcelain'])
  • bin must exactly match an allow entry — no globs, no PATH search (resolved to an explicit path before spawning, closing PATH-hijacking on shared machines).
  • Arguments are always passed as a real argv array via the OS process API — never through a shell interpreter (sh -c / cmd /c). That's the actual injection defense; metacharacter rejection on top is defense-in-depth, not the primary guard.
  • 30s timeout, 8 MiB per-stream output cap.

See the capabilities reference for the full guardrail list.

⚠️

Streaming execution (shell.spawn/shell.poll) is designed but not implemented. shell.run buffers all output and resolves once the process exits — fine for short commands, not for a long-running ffmpeg transcode where you want progress as it happens.


shellAgent — open-ended exec (planned)

capabilities: { shellAgent: { scopeDir: './workspace' } }

For apps that decide what to run at runtime rather than declare it ahead of time. No binary allowlist — any command runs — so the guardrail moves from which binaries to blast radius:

  • Every spawned process's cwd is hard-scoped to scopeDir (canonicalized; ../absolute-path escapes rejected before spawn, not just discouraged). Shipped — the capability declaration and cwd-scoping enforcement already exist in glyx-security.
  • Not yet built: a native, JS-independent activity overlay that shows every command as it runs, drawn directly by the renderer rather than the app's own JS. This is a hard requirement for shellAgent to ship, not optional polish — without an unsuppressable view of what's actually executing, the capability would just be a trust-me boolean with no runtime accountability. It reuses the same "native chrome survives even if the app's JS misbehaves" principle already used elsewhere in Glyx: a decorations: false window whose JS crashes before rendering its own titlebar gets a native (not JS-drawn) fallback close control, so it's never strandable — the same idea, applied to command visibility instead of window chrome.
  • Not yet built: the JS-facing shellAgent binding itself (currently only shellExec's allowlisted path is wired up).
⚠️

Do not declare shellAgent in a real app yet — the capability check exists, but nothing enforces the native-overlay requirement, so there is currently no way to actually satisfy the trust model this tier is meant to provide.


Why two tiers instead of one

An app declaring "I need to shell out to ffmpeg" and an app declaring "I need to run arbitrary developer commands" are asking for wildly different amounts of trust. A single boolean (or even a single allowlist field) can't express that difference — either it's too permissive for the ffmpeg case or too restrictive for the agent case. Both tiers spawn through the identical primitive (std::process::Command, argv-only, never a shell interpreter); shellAgent is shellExec with the allowlist removed and the native activity overlay added, not a separate implementation.