security_box 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +58 -2
- data/lib/security_box/assets/security_box.wasm +2 -2
- data/lib/security_box/guest/main.rb +4 -0
- data/lib/security_box/guest/rspec.rb +59 -0
- data/lib/security_box/guest_ext/Gemfile +5 -0
- data/lib/security_box/guest_ext/Gemfile.lock +21 -0
- data/lib/security_box/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b5972e4ef120906a25c58bd1783e11091948929bd3fbdbd8aa4f738cd57a8819
|
|
4
|
+
data.tar.gz: b50d68cb20da70bcae520b0c5828391d11cf34c3f81839c7e9d458dde82d4f86
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0221d3c37d4d5cb19c985b51088724ddd213d99ae52a9e3af305e1ed924e527c842e05bdebd0b5cfa330564bffea4571a6d28dc95f2fbed3ad8deb6a403f7bef
|
|
7
|
+
data.tar.gz: f5a71c1643e9543424e6b2767960d6cbb6d836ae8fa979dd653fcffe4bc14498c92352658a0de5c49f9263f45c5c6e37959f64ac8f6ed64268fe38424ced054f
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.0] - 2026-11-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- RSpec is bundled into the sandbox image (`lib/security_box/guest_ext`), so
|
|
13
|
+
guest code can run spec suites from mounted projects. `SB.run_rspec` loads
|
|
14
|
+
RSpec lazily, runs it with `HOME`/cwd set to a writable directory and
|
|
15
|
+
returns the exit status instead of exiting the guest. The image grows by
|
|
16
|
+
~2MB (about 52MB total); boot time is unchanged because RSpec is only
|
|
17
|
+
required on first use.
|
|
18
|
+
- `samples/rspec_sandbox`: a runnable sample that mounts a `ruby_src/` folder
|
|
19
|
+
read-only, runs its existing spec with `/work` as cwd through
|
|
20
|
+
`SB.run_rspec`, and includes a deliberately failing example to show how
|
|
21
|
+
failures reach the caller (`Result#value` + `Result#stdout`).
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- `Rakefile`: the guest image freshness check now includes
|
|
26
|
+
`lib/security_box/guest_ext/Gemfile.lock`.
|
|
27
|
+
|
|
8
28
|
## [0.6.0] - 2026-09-21
|
|
9
29
|
|
|
10
30
|
### Added
|
data/README.md
CHANGED
|
@@ -20,7 +20,8 @@ tries to escape.
|
|
|
20
20
|
then packed with the guest entrypoint (`lib/security_box/guest/main.rb`)
|
|
21
21
|
plus a hardening prelude (`lib/security_box/guest/prelude.rb`). The
|
|
22
22
|
`sb_rpc` gem declares the `sb`/`call` wasm import used by the host RPC
|
|
23
|
-
channel (see "Host RPC (code mode)" below)
|
|
23
|
+
channel (see "Host RPC (code mode)" below), and RSpec is bundled so guest
|
|
24
|
+
code can run spec suites (see "Running RSpec inside the sandbox").
|
|
24
25
|
2. On every `#eval`, the host creates an exclusive tmpdir, writes the user code to it, and
|
|
25
26
|
mounts it read-write as `/work` inside the sandbox. It also generates a per-eval
|
|
26
27
|
random token and passes it to the guest via `SB_TOKEN`.
|
|
@@ -53,7 +54,7 @@ gem "security_box"
|
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
The gem ships a prebuilt sandbox image (`lib/security_box/assets/security_box.wasm`,
|
|
56
|
-
about
|
|
57
|
+
about 52MB), so no network access or build tools are needed at install or at
|
|
57
58
|
runtime.
|
|
58
59
|
|
|
59
60
|
### Rebuilding the image (development only)
|
|
@@ -269,6 +270,50 @@ Security notes:
|
|
|
269
270
|
- Mounted content can also be **written by the guest** in `mount_rw` mounts —
|
|
270
271
|
treat a writable mount as part of the guest's blast radius.
|
|
271
272
|
|
|
273
|
+
### Running RSpec inside the sandbox
|
|
274
|
+
|
|
275
|
+
The image bundles RSpec (`~> 3.13`), so guest code can run a spec suite from a
|
|
276
|
+
mounted project. `SB.run_rspec` loads RSpec lazily, runs it with `HOME` and cwd
|
|
277
|
+
set to a writable directory, and returns the exit status (`0` = all examples
|
|
278
|
+
passed) instead of exiting the guest:
|
|
279
|
+
|
|
280
|
+
```ruby
|
|
281
|
+
box = SecurityBox.spawn(
|
|
282
|
+
timeout_ms: 30_000,
|
|
283
|
+
fuel_ms: 20_000,
|
|
284
|
+
mounts: [{ host: "./project", guest: "/project", mode: :read_only }]
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
result = box.eval(<<~RUBY)
|
|
288
|
+
SB.run_rspec("--no-color", "-I", "/project/lib", "/project/spec",
|
|
289
|
+
chdir: "/project")
|
|
290
|
+
RUBY
|
|
291
|
+
|
|
292
|
+
result.value # => 0
|
|
293
|
+
result.stdout # => RSpec output
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`chdir:` defaults to `/work` (the sandbox tmpdir). Use it when the suite needs
|
|
297
|
+
`.rspec`, relative paths or status files from the mounted project; mount the
|
|
298
|
+
project read-write if RSpec must persist them.
|
|
299
|
+
|
|
300
|
+
You can also call RSpec directly:
|
|
301
|
+
|
|
302
|
+
```ruby
|
|
303
|
+
require "/bundle/setup.rb"
|
|
304
|
+
require "rspec/core"
|
|
305
|
+
RSpec::Core::Runner.run(["--no-color", "/work/spec"], $stderr, $stdout)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Notes:
|
|
309
|
+
|
|
310
|
+
- Specs need a larger `timeout_ms`/`fuel_ms` than trivial evals; size them for
|
|
311
|
+
the whole suite, not for a single expression.
|
|
312
|
+
- The guest has no process APIs, so RSpec features that spawn processes
|
|
313
|
+
(external formatters, `--bisect`-style workflows) are unavailable.
|
|
314
|
+
- `/work` is the sandbox's own writable tmpdir and cannot be used as a mount
|
|
315
|
+
target; mount projects at another path (for example `/project`).
|
|
316
|
+
|
|
272
317
|
### Configuration
|
|
273
318
|
|
|
274
319
|
`SecurityBox::Configuration` is immutable; use `.build` to create and `#with` to derive:
|
|
@@ -433,6 +478,17 @@ bundle exec ruby bin/spike.rb
|
|
|
433
478
|
LLM agent built with [RubyLLM](https://github.com/crmne/ruby_llm) whose code-execution
|
|
434
479
|
tool runs model-generated Ruby inside the sandbox, demonstrating folder mounts
|
|
435
480
|
(read-only vs read-write) end to end.
|
|
481
|
+
- [`samples/ruby_llm/context7_code_mode`](samples/ruby_llm/context7_code_mode/README.md) —
|
|
482
|
+
a RubyLLM code-mode agent whose sandbox code reaches the hosted
|
|
483
|
+
[Context7](https://context7.com) MCP server through `SB.call`: host RPC
|
|
484
|
+
handlers proxy the MCP tools (streamable HTTP + bearer auth), so the API
|
|
485
|
+
key never crosses the sandbox boundary. Includes a no-LLM smoke script
|
|
486
|
+
that exercises the RPC↔MCP wiring.
|
|
487
|
+
- [`samples/rspec_sandbox`](samples/rspec_sandbox/README.md) — runs an existing
|
|
488
|
+
RSpec suite inside the sandbox: the library and its spec live in `ruby_src/`,
|
|
489
|
+
are mounted read-only and run with `/work` as cwd via `SB.run_rspec`. The
|
|
490
|
+
suite includes one deliberately failing example, showing how failures reach
|
|
491
|
+
the caller through `Result#value` and `Result#stdout`.
|
|
436
492
|
|
|
437
493
|
## Documentation
|
|
438
494
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/security_box/assets/security_box.wasm
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 53871847 bytes
|
|
4
|
+
sha256: a866ea3c387155f5bce26a4a3f109b54c778297a1ad45680bbf63d38001fc636
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Guest-side RSpec runner (loaded by main.rb before user code).
|
|
2
|
+
#
|
|
3
|
+
# The image bundles RSpec (see lib/security_box/guest_ext/Gemfile), so guest
|
|
4
|
+
# code can run a spec suite written to /work or mounted at another path:
|
|
5
|
+
#
|
|
6
|
+
# SB.run_rspec("--no-color", "/work/spec")
|
|
7
|
+
# SB.run_rspec("-I", "/project/lib", "/project/spec", chdir: "/project")
|
|
8
|
+
#
|
|
9
|
+
# RSpec is required lazily on the first call, so ordinary evals do not pay
|
|
10
|
+
# the load cost. The runner returns RSpec's exit status (0 = all examples
|
|
11
|
+
# passed) instead of exiting the process, keeping the envelope channel intact.
|
|
12
|
+
module SB
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Runs RSpec with `args` (an argv-style list) and returns its exit status.
|
|
16
|
+
# The suite runs with cwd and HOME set to `chdir` (default /work) so
|
|
17
|
+
# relative paths, `.rspec` discovery and RSpec's status/cache files stay
|
|
18
|
+
# inside a writable directory.
|
|
19
|
+
def run_rspec(*args, chdir: "/work")
|
|
20
|
+
previous_home = ENV["HOME"]
|
|
21
|
+
previous_pwd = Dir.pwd
|
|
22
|
+
load_rspec!
|
|
23
|
+
|
|
24
|
+
work = if File.directory?(chdir)
|
|
25
|
+
chdir
|
|
26
|
+
elsif File.directory?("/work")
|
|
27
|
+
"/work"
|
|
28
|
+
else
|
|
29
|
+
previous_pwd
|
|
30
|
+
end
|
|
31
|
+
ENV["HOME"] = work
|
|
32
|
+
Dir.chdir(work)
|
|
33
|
+
|
|
34
|
+
RSpec::Core::Runner.run(args.flatten, $stderr, $stdout)
|
|
35
|
+
ensure
|
|
36
|
+
Dir.chdir(previous_pwd) if previous_pwd && File.directory?(previous_pwd)
|
|
37
|
+
if previous_home.nil?
|
|
38
|
+
ENV.delete("HOME")
|
|
39
|
+
else
|
|
40
|
+
ENV["HOME"] = previous_home
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def load_rspec!
|
|
45
|
+
require "/bundle/setup.rb"
|
|
46
|
+
require "rspec/core"
|
|
47
|
+
# Hide the embedded guest protocol frames (/src/main.rb, /src/rspec.rb,
|
|
48
|
+
# ...) from RSpec's printed failure backtraces; the sandbox reserves /src,
|
|
49
|
+
# so this cannot hide user files.
|
|
50
|
+
RSpec.configure do |config|
|
|
51
|
+
config.backtrace_exclusion_patterns << %r{\A/src/}
|
|
52
|
+
end
|
|
53
|
+
rescue LoadError => e
|
|
54
|
+
raise LoadError,
|
|
55
|
+
"rspec is not available in this image (#{e.message}); " \
|
|
56
|
+
"rebuild it with `bundle exec rake security_box:build_image`"
|
|
57
|
+
end
|
|
58
|
+
private_class_method :load_rspec!
|
|
59
|
+
end
|
|
@@ -8,3 +8,8 @@ gem "ruby_wasm", "~> 2.10.1"
|
|
|
8
8
|
# Rakefile). Only guest-safe dependencies belong here — host-only gems
|
|
9
9
|
# (wasmtime) must stay in the project Gemfile.
|
|
10
10
|
gem "sb_rpc", path: "sb_rpc"
|
|
11
|
+
|
|
12
|
+
# Bundled so guest code can run spec suites mounted at /work (see
|
|
13
|
+
# lib/security_box/guest/rspec.rb and SB.run_rspec). RSpec is pure Ruby and
|
|
14
|
+
# adds no native extensions, so it does not change the Ruby build cache key.
|
|
15
|
+
gem "rspec", "~> 3.13"
|
|
@@ -6,7 +6,21 @@ PATH
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
8
8
|
specs:
|
|
9
|
+
diff-lcs (1.6.2)
|
|
9
10
|
logger (1.7.0)
|
|
11
|
+
rspec (3.13.2)
|
|
12
|
+
rspec-core (~> 3.13.0)
|
|
13
|
+
rspec-expectations (~> 3.13.0)
|
|
14
|
+
rspec-mocks (~> 3.13.0)
|
|
15
|
+
rspec-core (3.13.6)
|
|
16
|
+
rspec-support (~> 3.13.0)
|
|
17
|
+
rspec-expectations (3.13.5)
|
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
19
|
+
rspec-support (~> 3.13.0)
|
|
20
|
+
rspec-mocks (3.13.8)
|
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
22
|
+
rspec-support (~> 3.13.0)
|
|
23
|
+
rspec-support (3.13.7)
|
|
10
24
|
ruby_wasm (2.10.1)
|
|
11
25
|
logger
|
|
12
26
|
ruby_wasm (2.10.1-x86_64-linux)
|
|
@@ -17,12 +31,19 @@ PLATFORMS
|
|
|
17
31
|
x86_64-linux
|
|
18
32
|
|
|
19
33
|
DEPENDENCIES
|
|
34
|
+
rspec (~> 3.13)
|
|
20
35
|
ruby_wasm (~> 2.10.1)
|
|
21
36
|
sb_rpc!
|
|
22
37
|
|
|
23
38
|
CHECKSUMS
|
|
24
39
|
bundler (4.0.20) sha256=7978a8ac648767f5e635bc522445b79e80a52b907a39a36c2d8085ed6bc762ae
|
|
40
|
+
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
25
41
|
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
42
|
+
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
|
|
43
|
+
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
|
|
44
|
+
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
|
45
|
+
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
|
|
46
|
+
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
|
26
47
|
ruby_wasm (2.10.1) sha256=874e0acb783a8326bfb0db892004816c26963a4276a9f719791a8828e53a05f1
|
|
27
48
|
ruby_wasm (2.10.1-x86_64-linux) sha256=728e29dc688246572b56dcef1c7b131431d07865320c9b8434dd1ccdfd63db2b
|
|
28
49
|
sb_rpc (0.1.0)
|
data/lib/security_box/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: security_box
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Marcelo Junior
|
|
@@ -45,6 +45,7 @@ files:
|
|
|
45
45
|
- lib/security_box/guest/main.rb
|
|
46
46
|
- lib/security_box/guest/prelude.rb
|
|
47
47
|
- lib/security_box/guest/rpc.rb
|
|
48
|
+
- lib/security_box/guest/rspec.rb
|
|
48
49
|
- lib/security_box/guest_ext/Gemfile
|
|
49
50
|
- lib/security_box/guest_ext/Gemfile.lock
|
|
50
51
|
- lib/security_box/guest_ext/sb_rpc/ext/sb_rpc/extconf.rb
|