Executes a shell command via /bin/sh -c with a configurable timeout.
Arguments:
command(required) — the shell command.timeout_ms(optional, default 120_000, max 600_000).
Returns captured stdout+stderr plus the exit code. Timeouts kill the spawned
process and surface {:error, :timeout} to the loop.
Runs with cd: ctx.cwd, stderr_to_stdout: true. No input redirection.
When the run is confined (ctx.allowed_roots is set), the command is wrapped
in an OS sandbox (ExAthena.Sandbox) that blocks writes outside the roots. If
no sandbox helper is available the command runs unconfined with a logged
warning — never a fake command-string scan.
Summary
Functions
Classify a bash invocation's args as read-only or not. Used by the
permissions layer to allow read-only bash calls (cat, ls, grep, git log,
gh issue view, …) during :plan phase while still denying mutations
(rm, mkdir, redirects, git add/commit, mix ecto.migrate, …).
Functions
Classify a bash invocation's args as read-only or not. Used by the
permissions layer to allow read-only bash calls (cat, ls, grep, git log,
gh issue view, …) during :plan phase while still denying mutations
(rm, mkdir, redirects, git add/commit, mix ecto.migrate, …).
Allowlist-based: a command is read-only only when every shell segment
(split on ;, &&, ||, |, &, newline) starts with a command known
to be read-only. Unknown commands — including interpreter one-liners like
python -c, perl -e, ruby -e, node -e, and editors like ex/ed —
are treated as MUTATING, because arbitrary code execution can never be
proven read-only by a string scan. Command substitution ($(…), backticks)
and file redirects (except 2>&1-style fd duplication and /dev/null
discards) also disqualify a command.
Missing/empty command → false (treat as not read-only so the model gets
a clear phase-gated denial rather than a silent allow).
This classifier gates the :plan phase only; it is advisory next to the
OS sandbox (ExAthena.Sandbox) which enforces write confinement for
confined runs regardless of phase.