This release includes 1 breaking change for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
Affected surfaces
Summary
AI summaryUpdates What landed, Enforcement layers, and new across a mixed release.
Full changelog
This is a breaking change. Default behavior changes from "any
binary on PATH reachable via shell(...)" to "only operator-
allowlisted binaries reachable; default-deny when unset." Existing
deployments using shell() must set SKILLSCRIPT_SHELL_ALLOWLIST in
their .env before upgrade, or shell() ops will refuse at first
dispatch with ShellBinaryNotAllowedError.
Migration — run BEFORE upgrade
skillfile shell-audit
Scans $SKILLSCRIPT_HOME/skills/ for shell() ops, emits the union
of binaries used, and prints a ready-to-paste SKILLSCRIPT_SHELL_ALLOWLIST=...
line. Paste into .env, review/narrow, THEN upgrade. Per Perry's
pre-upgrade-not-post-break framing.
Why default-deny
Per Scott + Perry decision (memory 7aab6f3f, 2026-06-06): skill
authors are agents → weak trust anchor (hallucination, prompt-
injection, no human-in-loop at scale). Default-open shell relies on
author trust, which doesn't hold for agent-authored skills. Default-
deny converts a discipline-only control ("a human reviews every skill")
into an enforced constraint at the language level. The constraint
IS the safety story.
Pre-v0.18.8 deployments had no operator-side binary scope: any
shell(command="rm -rf /") in a # Autonomous: true skill ran
unchallenged. This ring closes that gap.
Two independent axes
| Operator switch | Controls |
|---|---|
| SKILLSCRIPT_SHELL_ALLOWLIST (new) | Which binaries are reachable (binary scope) |
| SKILLSCRIPT_ENABLE_UNSAFE_SHELL (existing) | Whether bash interpretation is permitted (syntax scope) |
Per Perry's two-axes-decoupled rule: do NOT overload unsafe. An
off-list binary refuses regardless of unsafe. An on-list binary +
unsafe syntax requires BOTH gates to pass.
Unsafe path is all-or-nothing
For shell(..., unsafe=true), the runtime invokes bash -c <body>;
the literal first token is bash. So permitting any unsafe shell at
all requires bash on the allowlist (in addition to
ENABLE_UNSAFE_SHELL=true). Per-binary scope within unsafe-mode bodies
is not implemented and will not be — parse-based enumeration is
unsound against agent-author threat model (e=curl; $e ...,
$(printf cur)l, eval, xargs all defeat it). The proper layer for
binary-scope on unsafe shell is OS-level (restricted PATH,
execve/seccomp, container) — documented in docs/adopter-playbook.md
§ "Shell binary allowlist."
Enforcement layers
- Runtime (authoritative) — refuses off-list binaries at dispatch.
ThrowsShellBinaryNotAllowedErrorwith actionable remediation:
names the binary, names the env var, points atskillfile shell-audit. - Lint (local advisory) — new tier-1 rule
shell-binary-not-allowed
fires whenLintOptions.shellAllowlistis supplied. Author env may
differ from production; lint is for immediate authoring feedback,
not the boundary. Skips silently when allowlist not supplied. - Trace observability — refused ops emit
blocked_reason: "binary-not-allowed"on the trace record for dashboard filtering
(operator's observe→promote loop: "what binaries did skills try to
invoke off the allowlist; do I want to add any?").
What landed
SKILLSCRIPT_SHELL_ALLOWLISTenv var +shellAllowlist?: string[]
config field +BootstrapOpts.shellAllowlist+ Scheduler threadingExecuteContext.shellAllowlist(CLI cascade: env > config > default-deny)
- Runtime gate in
runtime.tscase "shell" — applies to both safe
and unsafe paths - New
ShellBinaryNotAllowedError(OpError subclass) - New tier-1 lint rule
shell-binary-not-allowed - New
TraceOpRecord.blocked_reasonfield - New CLI command
skillfile shell-audit(pre-upgrade migration helper) .env.exampledocumentationdocs/adopter-playbook.mdnew "Shell binary allowlist" section with
full migration walkthrough + trust model + scope caveat (don't oversell)docs/configuration.mdenv-var table extended
Tests
16 new in v0.18.8-shell-allowlist.test.ts (runtime safe/unsafe gates
- lint rule + trace observability). 1745 tests total; typecheck clean.
Narrow-core LOC ceiling 10800 → 11100.
Pre-existing tests using shell() were updated to declare
shellAllowlist in their test ctx — the canary for adopter-side
migration discipline.
Future direction (NOT in this ring)
Per-skill capability declaration: # Shell: curl, jq frontmatter
where operator policy validates declared ∩ allowlist. Makes each
skill's shell footprint self-documenting + auditable. Slated for a
future ring once chokepoint + observability surfaces ship.
Breaking Changes
- Default behavior for `shell()` changes from allowing any binary on `PATH` to denying all binaries unless listed in the new env var/config `SKILLSCRIPT_SHELL_ALLOWLIST`. Existing deployments must set this allowlist before upgrading, otherwise calls will raise `ShellBinaryNotAllowedError`.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About Skillscript
All releases →Related context
Related tools
Earlier breaking changes
- v0.35.0 Editing `# Deadline:` on an approved skill drops it to Draft (signature invalidates).
Beta — feedback welcome: [email protected]