Commit 07cc5b5
authored
Feat execute bash (#119)
* feat: add execute_bash tool — sandboxed bash interpreter via just-bash
Adds a new execute_bash tool that runs bash commands inside the
Hyperlight micro-VM using just-bash (pure TypeScript bash interpreter).
Commands run in hardware-isolated QuickJS with no host process access.
40+ Unix commands available: ls, cat, grep, jq, sed, awk, sort, find,
tree, diff, head, tail, wc, cut, tr, xargs, tee, cp, mkdir, touch,
base64, md5sum, date, env vars, pipes, redirects, and more.
Architecture:
- just-bash bundled via esbuild with node: API stubs (1.4 MB)
- Polyfills for URL, Buffer, process, AbortController, crypto, setTimeout
- Auto-registered internal handler (sys-bash-runner)
- Stateless: fresh Bash instance per call
- Heap auto-expanded to 64 MB on first use
- Rebuilds automatically as part of npm run build:modules
Key design decisions:
- Tool, not module: LLM calls execute_bash({command}) directly
- No in-memory FS: all file ops go through host:fs-read/host:fs-write
- curl requires fetch plugin enabled (actionable error if not)
- Unsupported commands (rm, mv, python3, sqlite3) return clear errors
- Command list in tool description for strong LLM guidance
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* feat: add execute_bash to system message workflow guidance
The LLM didn't know about execute_bash because the system prompt
only mentioned register_handler + execute_javascript. Added bash
tool guidance between WORKFLOW and DIRECT FILE I/O sections.
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* feat: wire execute_bash to show-code and code log
Shows '$ command' inline when show-code is enabled and writes
bash commands to the code log file alongside JS handler code.
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* fix: add execute_bash to ALLOWED_TOOLS gating list
Without this, the tool exists but the SDK blocks the LLM from
calling it. Absolute rookie mistake.
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* fix: add execute_bash to all skill allowed-tools lists
When a skill is active, the SDK only exposes tools listed in the
skill's allowed-tools. execute_bash was missing from all 9 skills,
so the LLM couldn't see or use it when any skill was active.
Also adds a pattern-integrity test that verifies every defineTool
in index.ts has a corresponding entry in ALLOWED_TOOLS — catches
this class of bug automatically.
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* chore: gitignore output dirs and temp bundle file
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* fix: add execute_bash to availableTools — the THIRD allowlist
The SDK has THREE places that control tool visibility:
1. tools[] — implementation registration
2. ALLOWED_TOOLS — pre-execution gate (tool-gating.ts)
3. availableTools[] — tells SDK what to show the model
execute_bash was in 1 and 2 but not 3, so the model literally
couldn't see it. Added test that verifies every defineTool has
entries in BOTH ALLOWED_TOOLS AND availableTools.
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* feat: native RDRAND-backed crypto and Math.random for sandbox
Adds hardware random number generation to the Hyperlight QuickJS
runtime using x86_64 RDRAND instruction:
- crypto.getRandomValues(Uint8Array) — fills array with HW random bytes
- crypto.randomUUID() — generates RFC 4122 v4 UUIDs with proper
version/variant bits, backed by RDRAND (not a fake hex generator)
- Math.random() — overrides QuickJS built-in with RDRAND-backed
implementation, enabling awk rand() in just-bash
Also fixes execute_bash:
- Auto-configures heap (64MB) and buffers (2048KB) at startup when
ha:bash module is present (not lazily on first call)
- Stubs sql.js to reduce bundle size
- Removes JS crypto polyfill (now native)
- Fixes curl listed as 'NOT AVAILABLE' in tool description
(curl is supported when fetch plugin is enabled)
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* fix: isolate bash from JS sandbox — dedicated sandbox + blocked from JS APIs
- Bash runs in its own dedicated sandbox (getBashSandbox) with only
ha:bash loaded — no pptx/pdf/xlsx memory competition
- Excluded ha:bash from main JS sandbox module loading
- Filtered bash from list_modules output (LLM won't see it)
- module_info('bash') returns clear error: 'use execute_bash tool'
- Fixed contradictory system message ('No bash' vs bash tool section)
- JS handlers cannot import ha:bash — it only exists in the bash sandbox
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* fix: actionable memory error guidance for both execute_javascript and execute_bash
- onPostToolUse now handles execute_bash memory errors (was JS only)
- Clearly tells LLM to increase SCRATCH for 'Out of physical memory'
and HEAP for 'malloc failed' — with specific configure_sandbox call
- configure_sandbox description now lists common memory errors and
which setting to change for each
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* feat: crypto.subtle.digest + Buffer.toString(encoding) for bash sandbox
- Add sha1/sha2 RustCrypto crates (no_std, audited) to native-globals
- Implement crypto.subtle.digest('SHA-1'|'SHA-256', data) → Promise<ArrayBuffer>
matching the Web Crypto API — fixes sha256sum/sha1sum in just-bash
- Fix Buffer polyfill: HaBuffer subclass with .toString(encoding) supporting
base64, latin1/binary, hex, utf-8 — fixes silent data corruption in
curl (Basic auth), yq, and unguarded Buffer.from().toString() paths
- Restore md5sum/sha256sum to BASH_SUPPORTED_COMMANDS (were removed
when crypto.subtle was missing — now backed by RustCrypto)
- Add POSIX-only flag caveat to execute_bash description
- Update large-output guidance to mention execute_bash as alternative
to handler-based file processing
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* fix: use format! instead of alloc::format! for clippy without hyperlight cfg
The CI runs clippy without the hyperlight cfg flag, so 'extern crate alloc'
is not active and alloc::format! fails with E0433. Plain format! works in
both std and no_std+alloc contexts via the prelude.
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* fix: address all Copilot PR review comments (#119)
- lib.rs: update module docs — RDRAND not xorshift128+ PRNG
- lib.rs: add cfg(target_arch=x86_64) guard on rdrand64 with fallback
- lib.rs: use #[macro_use] extern crate alloc for format! in no_std
- lib.rs: fix crypto.subtle.digest to respect byteOffset/byteLength
on ArrayBufferViews (subarray would hash wrong data)
- node-path-stub.mjs: fix normalize('foo/..') returning 'foo' instead
of '.' — pop when out.length > 0, preserve '..' for relative paths
- index.ts: sync plugins to bashSandbox via syncPluginsToSandbox()
so curl/fs-read/fs-write/MCP work in execute_bash
- index.ts: reset bashSandbox on reset_sandbox (was leaking old sandbox)
- index.ts: separate memory error guidance for bash vs JS sandbox
(bash has fixed limits, configure_sandbox doesn't affect it)
- system-message.ts: fix 'No curl' → 'same plugins', add crypto to
AVAILABLE GLOBALS (getRandomValues, randomUUID, subtle.digest)
- Remove unused scripts/bash-bundle/build-ha-bash.mjs
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* fix: add curl to bash commands, improve /sessions and /resume UX
- Add 'curl' to BASH_SUPPORTED_COMMANDS — it was in just-bash but
missing from the allowlist, causing 'command not found'
- /sessions now shows full session UUIDs (not truncated) so users
can copy-paste into /resume
- /sessions -all now works as alias for --all
- /resume without an ID shows interactive numbered picker (top 20
most recent sessions) — type a number to resume, like az CLI
subscription selector
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* fix: wire fetch plugin into bash curl + fix arrow keys in REPL
- curl was in BASH_SUPPORTED_COMMANDS but just-bash's curl needs a
fetch function to make HTTP requests. Build a Web Fetch adapter
over host:fetch (fetchText/post) and pass it to the Bash constructor.
curl now works when the fetch plugin is enabled.
- Fix arrow keys printing ^[[A after slash commands: use rl.prompt()
instead of raw process.stdout.write() in questionCapturingPaste()
so readline properly manages terminal raw mode
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* fix: native module discovery, curl, FS wiring, SIGILL, docs, tests
Native modules (ha:html, ha:markdown, ha:image, ha:ziplib):
- Add NATIVE_MODULES to sandbox getAvailableModules() so validator
accepts imports (was rejecting 'ha:html is not available')
- listModules() and loadModule() now find native Rust modules via
.json metadata + .d.ts exports (no .js file needed)
- 8 new tests: native module visibility, metadata, handler registration
Bash curl:
- Replace broken lazy-loaded curl with defineCommand custom command
that bridges host:fetch (just-bash's lazy loader breaks in QuickJS)
- Fix FetchResult type: Record<string,string> not Map for headers
Bash filesystem:
- Wire host:fs-read/fs-write into bash via hybrid IFileSystem adapter
(delegates to plugins, falls back to InMemoryFs)
- Add cross-tool file access guidance to tool description
- Remove html-to-markdown from bash (no proper no_std crate available)
Build fixes:
- build.mjs: fix require() in ESM — use imported unlinkSync
- build-modules.js: move bash bundle before hash update step
- Export defineCommand from entry.mjs for custom bash commands
Process fixes:
- Clean up bashSandbox before exit (prevents SIGILL on shutdown)
- Add 'Shutting down — please wait...' message on exit/SIGINT
- /sessions -all alias, full UUID display, interactive /resume picker
Docs:
- Update README, ARCHITECTURE, SECURITY, USAGE, HOW-IT-WORKS for
execute_bash tool (SDK bash still blocked, sandboxed bash available)
Commands:
- Add missing commands: strings, split, sha1sum, gzip/gunzip/zcat,
xan, pwd, false
- Remove rm/mv/ln/rmdir (no delete/rename in security model)
- Custom 'commands' command lists actual available commands
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
* fix: native module tests fail on CI — copy .d.ts/.json in beforeAll
Tests for native module discovery (html, markdown, image, ziplib)
assumed ~/.hyperagent/modules/ already had metadata files from a
previous agent run. On CI the directory is empty. Added beforeAll
that copies .json and .d.ts from builtin-modules/ to the modules
dir before testing, matching what loadAllModules() does at startup.
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
---------
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>1 parent 22256d1 commit 07cc5b5
46 files changed
Lines changed: 3036 additions & 153 deletions
File tree
- builtin-modules
- docs
- scripts
- bash-bundle
- skills
- api-explorer
- data-processor
- mcp-services
- pdf-expert
- pptx-expert
- report-builder
- research-synthesiser
- web-scraper
- xlsx-expert
- src
- agent
- sandbox
- runtime
- modules/native-globals
- src
- tests
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
55 | | - | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
148 | 148 | | |
149 | 149 | | |
150 | 150 | | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
151 | 155 | | |
152 | 156 | | |
153 | 157 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
125 | 125 | | |
126 | 126 | | |
127 | 127 | | |
| 128 | + | |
128 | 129 | | |
129 | 130 | | |
130 | 131 | | |
| |||
235 | 236 | | |
236 | 237 | | |
237 | 238 | | |
238 | | - | |
| 239 | + | |
239 | 240 | | |
240 | 241 | | |
241 | 242 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
54 | | - | |
55 | | - | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
108 | | - | |
109 | | - | |
110 | | - | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
111 | 112 | | |
112 | 113 | | |
113 | 114 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
| 16 | + | |
15 | 17 | | |
16 | 18 | | |
17 | 19 | | |
18 | 20 | | |
19 | 21 | | |
20 | 22 | | |
21 | | - | |
| 23 | + | |
22 | 24 | | |
23 | 25 | | |
24 | 26 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
108 | 108 | | |
109 | 109 | | |
110 | 110 | | |
111 | | - | |
112 | | - | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
113 | 114 | | |
114 | 115 | | |
115 | 116 | | |
116 | 117 | | |
117 | 118 | | |
| 119 | + | |
118 | 120 | | |
119 | 121 | | |
120 | 122 | | |
| |||
0 commit comments