Reference · Decisions
0041: Ruby scripts run like shell scripts
A Ruby script is checked and run the same way as a shell script, as the person, through the helper gate.
Status: accepted (main 2026-11-03, sandbox scope as stated below; lead orders 2026-11-03: "Ruby must be supported like .sh scripts", remote execution in a sandbox; drafted by a worker for main). Builds on 0020 (helpers and the gate), 0024 (first-use malware check), 0032 (optional modules), 0036 (Crystal userland).
On this page
Context
WolOS treats a shell script as a first-class program: it has a header rule, a lint pipeline (docs/RULES-system.md, sh -n, shellcheck), CI steps, exec-bit checks, and when it calls wol with WOL_HELPER=<name> it goes through the helper gate (policy, approval sheet, audit log; docs/modules/automation.md). Nothing in the product ran a script on request: there was no wol verb for a script file, and remote runs existed only in developer tooling. Ruby had none of this: tools/check-headers.sh refused a ruby shebang, the rule tests and CI ignored .rb, and no Ruby interpreter was on the system. ADR 0036 makes Crystal the language of apps and tools; Crystal compiles, so it is a poor fit for a file you edit and run, which is what a script is.
Decision
- Ruby is the scripting language beside sh. Crystal stays the language of apps and tools (ADR 0036); Lua stays the plugin sandbox (ADR 0031). A
.rbfile is a script wherever a.shfile is: a bundle entry, a widget source, a plugin or hotkey command, a helper withWOL_HELPER, a file run over ssh. Ruby calls WolOS through thewolCLI with--json, as sh does; there is no Ruby library over libwol (ADR 0036, lock-in). - One runner for every script language:
wol script(crates/controller/src/script.rs).wol script check <file>parses without running (sh -n,bash -n,ruby -wc), then runs the language's linter when it is installed (shellcheck; RuboCop with the explicit cop listRUBOCOP_COPS).wol script run <file> [args]runs it as the person, never as root, with an argv (never a shell string), a clean environment (env -ikeeping onlyKEPT_ENV: who, where, language, terminal and the desktop session; a fixedPATH; no tokens or keys from the calling shell) andWOL_HELPER=<file name>, so every change the script asks ofwolmeets the gate.wol script on <host> <file> [args]sends the file on ssh's stdin (BatchMode, no terminal) and runs it on the other computer the same way: same clean environment, same helper name, so the other WolOS applies its own gate. Each run is one audit record (script runorscript on, the exit code; the script's arguments are not recorded). What differs between languages is one table,KINDS(kind by shebang, else by file name ending). Helpers may list the kinds and check a file (Read);runandonexecute code and stay for people (not inHELPER_VERBS, not offered bywol mcp). - What "sandboxed" means in this release, stated plainly: the clean environment, no root, no shell parsing, the helper gate for every
wolchange, and the audit record. It is not a file-system jail: the script can read and write what the person can. The workspace jail (wol jail run, ADR 0032) is the planned isolation and will wrapwol script runwhen it lands; until then a script is as trusted as the person running it, like a shell script. - The interpreter ships in the image; the linter in the developer module.
rubyfrom the base distribution's repositories at the release snapshot (ADR 0016) in the desktop package list, so a fresh computer and an ssh session can run a.rbfile;rubocopin thedevmodule (files/usr/share/wolos/modules/dev.json). Not from mise: a system script needs the pinned package, not a user-side build. - No Ruby root helpers. Scripts under
files/usr/lib/wolos, units and hooks stay POSIX sh (STANDARDS §2). RB-030 keepsROOT_RUBYempty; adding one needs a new ADR andrubyin wolos-system's depends. - Rules: docs/RULES-ruby.md (
RB-nnn,crates/controller/tests/rules_ruby.rs): header, frozen string literals, argv-only commands, no eval, no object-building decoders, default gems only (STDLIB), timeouts, no fixed/tmp, no world-writable modes,ruby -wcclean, RuboCop with one explicit list (tools/ruby/rubocop.yml,DisabledByDefault,NewCops: disable), an interpreter census per folder. CI installs ruby and rubocop and setsWOLOS_REQUIRE_RUBOCOP=1, so a missing tool fails instead of skipping.tools/check-headers.shaccepts a ruby shebang and parses the file withruby -wc. - Malware check: the background pass looks at
.rbfiles as it looks at.shfiles (xguard::sweep::in_scope). The first-use check scans the interpreter once, not each script it reads; that gap is the same for sh and Ruby (security.md, interpreters).
Consequences
the image grows by the interpreter (about 17 MiB installed for ruby 3.4 plus its rubygems dependency at the time of writing; the measured number belongs in the release notes) and gains a second scripting interpreter that the first-use check does not see into. wol script is the one place to add a language: PowerShell (planned) is one more row in KINDS. Ruby's default gems move between releases; RB-009 names the allowed list, so a snapshot bump that drops one fails loudly. RuboCop releases add cops; the explicit list keeps CI stable and RB-020 fails when a listed cop disappears. Phase 2 (not this ADR): the jail around wol script run, and completions for wol in the shells (there are none for any verb yet).
Related
- Scripts: sh, bash and RubyCheck and run a shell or Ruby script the same way, as you, through the same approval gate as every helper.
- 0031: Plugins, themes and layoutsFolder plugins in a sandbox, themes for every toolkit, and layouts as data.
- 0032: Optional modulesAI, developer, creative and console stacks as removable modules and Kits.
- Optional modulesAI, developer, creative and console stacks, installed after the base and removable.
- Plugin API referenceThe complete manifest, permissions and Lua API for shell plugins.
From the developer documentation, wonderlandos/wolos docs/adr/0041-ruby-scripts.md.