Skip to content

feat: add sandboxed Lua backend via :lua VM#45

Merged
mikehostetler merged 5 commits into
agentjido:mainfrom
dclausen:add-lua
Jun 10, 2026
Merged

feat: add sandboxed Lua backend via :lua VM#45
mikehostetler merged 5 commits into
agentjido:mainfrom
dclausen:add-lua

Conversation

@dclausen

@dclausen dclausen commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds Jido.Shell.Backend.Lua — a pure-Elixir Lua 5.3 backend using the latest :lua Hex package from Lua.ex / deflua.com (~> 1.0.0-rc.1), mirrors Jido.Shell.Backend.Bash in structure and all 7 callbacks.
  • Makes :lua a required runtime dependency because the Lua backend is included in the compiled Hex package surface; this avoids optional-dependency compile failures without guarded compatibility code.
  • Lua.Session holder GenServer owns committed %Lua{} state; eval runs in a killable spawned worker so timeout/cancel cannot pin the holder — commit only on clean success, prior state intact on failure.
  • JidoApi bridge exposes command registry under jido.* via use Lua.API, scope: "jido" + deflua; custom print global; arg escaping ports JidoInterop.escape_arg/1 behavior.
  • persistent: false config flag for stateless-per-eval mode (fresh Lua.new() each call).
  • Fixes ShellSessionServer.apply_state_updates to sync {:state_update, %{env: …}} back into backend_state.env for all backends.

Security

Lua.new/0 sandbox disables io, os.execute/exit/getenv, require, load, loadstring, loadfile, dofile, package.loadlib by default. No VfsAdapter needed — file access only through bridged jido.* commands → CommandRunner → VFS.

Validation

  • Verified latest published :lua with mix hex.info lua: 1.0.0-rc.1 published 2026-06-02
  • mix compile --warnings-as-errors
  • mix test (636 passed, 3 excluded)
  • mix hex.build --unpack -o /tmp/jido_shell_pr45_hex
  • MIX_ENV=prod mix deps.get && MIX_ENV=prod mix compile --warnings-as-errors from the unpacked package

dclausen and others added 3 commits June 2, 2026 09:50
Adds `Jido.Shell.Backend.Lua`, a pure-Elixir Lua 5.3 backend backed by
the `:lua` hex package (tv-labs/lua / luerl). Mirrors the Bash backend
in structure and callback contract.

Architecture:
- `Lua.Session` holder GenServer owns committed `%Lua{}` state; the
  holder never evals — each execute spawns a killable worker so
  timeout/cancel cannot pin the holder.
- Commit happens only on clean success; error/timeout/cancel leaves
  prior state intact.
- Per-command context (emit, cwd, env, limits) injected via
  `Lua.put_private` and stripped before commit to prevent stale
  emitter capture across commands.
- `persistent: false` config flag skips the holder and builds a fresh
  `Lua.new()` per eval (stateless mode).

`JidoApi` bridge exposes the command registry under `jido.*` via
`use Lua.API, scope: "jido"` + `deflua`; custom `print` global streams
`{:output, …}` to the session. Arg escaping ports `JidoInterop.escape_arg/1`
so spaces, `;`, `&&`, quotes, and backslashes stay single Jido arguments.

Security: `Lua.new/0` sandbox disables `io`, `os.execute`, `os.exit`,
`os.getenv`, `require`, `load`, `loadstring`, `loadfile`, `dofile`,
`package.loadlib` by default. No VfsAdapter needed; file access only
through bridged `jido.*` commands → `CommandRunner` → VFS.

Also fixes `ShellSessionServer.apply_state_updates` to sync env changes
from `{:state_update, %{env: …}}` back into `backend_state.env` so Lua
(and other backends) see the updated env on subsequent execute calls.

14 tests covering: output streaming, global persistence, jido bridge,
cwd sync, arg escaping, sandbox escape proof, output/runtime limits,
timeout holder survival, cancel reusability, terminate cleanup, and
stateless mode non-persistence.
….API modules

Allows callers to pass extra Lua.API modules via the backend config:

  backend: {Jido.Shell.Backend.Lua, %{apis: [MyApp.LuaApi]}}

The extra modules are loaded after JidoApi in both the persistent session
path (start_lua_session/2) and the stateless eval path (eval_stateless/4).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@mikehostetler mikehostetler left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the substantial backend work here. I don't think this should land as-is because it repeats the optional dependency compile failure tracked in #48.

This PR adds {:lua, "~> 0.4", optional: true}, but the new modules compile-time reference the dependency unconditionally (use Lua.API, %Lua{} patterns, Lua.* calls). In downstream consumers that depend on jido_shell without also depending on :lua, these modules will still be compiled and should fail in the same way Bash.Interop is currently failing for optional :bash.

Before this lands, we need one of these paths:

  1. make :lua a required dependency if Jido.Shell.Backend.Lua is part of the compiled package surface, or
  2. conditionally compile/load the Lua backend modules only when :lua is available and ensure docs/tests cover the consumer-without-lua case.

Given #48 is already forcing us to tighten backend dependency semantics, I would prefer we resolve that package-level policy first and then rebase this PR onto it.

Copy link
Copy Markdown
Contributor

Pushed a maintainer update in de06cb5 to address the dependency-surface blocker from my earlier review:

  • merged current main after fix: exclude bash backend from hex package #49 landed
  • changed :lua from optional to a required runtime dependency because Jido.Shell.Backend.Lua is included in the compiled package surface
  • updated README/PR text so consumers are no longer told to add :lua optionally

I have not run local validation on this updated branch yet.

@mikehostetler mikehostetler merged commit 61c29d8 into agentjido:main Jun 10, 2026
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.

2 participants