kobako 0.26.0-aarch64-linux → 0.28.0-aarch64-linux
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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +103 -0
- data/README.md +156 -118
- data/ROADMAP.md +23 -22
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +38 -0
- data/crates/kobako-runtime/README.md +10 -12
- data/crates/kobako-transport/CHANGELOG.md +35 -0
- data/crates/kobako-transport/README.md +43 -27
- data/crates/kobako-wasmtime/CHANGELOG.md +51 -0
- data/crates/kobako-wasmtime/README.md +8 -10
- data/data/kobako.wasm +0 -0
- data/lib/kobako/3.3/kobako.so +0 -0
- data/lib/kobako/3.4/kobako.so +0 -0
- data/lib/kobako/4.0/kobako.so +0 -0
- data/lib/kobako/capture.rb +1 -4
- data/lib/kobako/catalog/extensions.rb +21 -59
- data/lib/kobako/catalog/flags.rb +41 -0
- data/lib/kobako/catalog/handles.rb +7 -54
- data/lib/kobako/catalog/services.rb +7 -61
- data/lib/kobako/catalog/snippets.rb +42 -66
- data/lib/kobako/catalog.rb +2 -10
- data/lib/kobako/codec/decoder.rb +9 -28
- data/lib/kobako/codec/encoder.rb +3 -22
- data/lib/kobako/codec/error.rb +6 -27
- data/lib/kobako/codec/ext_types.rb +72 -94
- data/lib/kobako/codec/handle_walk.rb +65 -130
- data/lib/kobako/codec/nesting.rb +3 -8
- data/lib/kobako/codec/state.rb +4 -10
- data/lib/kobako/codec/utils.rb +6 -34
- data/lib/kobako/codec.rb +3 -22
- data/lib/kobako/constant_name.rb +8 -0
- data/lib/kobako/context.rb +51 -112
- data/lib/kobako/errors.rb +51 -127
- data/lib/kobako/execution.rb +16 -21
- data/lib/kobako/extension.rb +23 -40
- data/lib/kobako/handle.rb +7 -12
- data/lib/kobako/outcome.rb +51 -80
- data/lib/kobako/payload/arguments.rb +1 -16
- data/lib/kobako/payload.rb +4 -12
- data/lib/kobako/pool.rb +23 -53
- data/lib/kobako/runtime.rb +7 -21
- data/lib/kobako/sandbox.rb +96 -133
- data/lib/kobako/sandbox_options.rb +38 -84
- data/lib/kobako/snippet/binary.rb +1 -16
- data/lib/kobako/snippet/source.rb +1 -16
- data/lib/kobako/snippet.rb +1 -9
- data/lib/kobako/transport/call.rb +1 -14
- data/lib/kobako/transport/dispatcher.rb +112 -195
- data/lib/kobako/transport/error.rb +6 -20
- data/lib/kobako/transport/exposure.rb +5 -10
- data/lib/kobako/transport/reflection.rb +39 -50
- data/lib/kobako/transport/run.rb +17 -71
- data/lib/kobako/transport/yielder.rb +21 -73
- data/lib/kobako/transport.rb +3 -15
- data/lib/kobako/unresolved.rb +4 -11
- data/lib/kobako/usage.rb +2 -6
- data/lib/kobako/version.rb +1 -1
- data/sig/kobako/catalog/extensions.rbs +2 -2
- data/sig/kobako/catalog/flags.rbs +17 -0
- data/sig/kobako/catalog/services.rbs +0 -6
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/codec/ext_types.rbs +10 -8
- data/sig/kobako/codec/handle_walk.rbs +9 -7
- data/sig/kobako/codec/utils.rbs +2 -2
- data/sig/kobako/codec.rbs +2 -2
- data/sig/kobako/constant_name.rbs +3 -0
- data/sig/kobako/context.rbs +2 -3
- data/sig/kobako/errors.rbs +6 -0
- data/sig/kobako/outcome.rbs +6 -8
- data/sig/kobako/runtime.rbs +12 -8
- data/sig/kobako/sandbox.rbs +4 -0
- data/sig/kobako/sandbox_options.rbs +2 -6
- data/sig/kobako/transport/dispatcher.rbs +14 -18
- data/sig/kobako/transport/reflection.rbs +5 -3
- data/sig/kobako/transport/run.rbs +1 -3
- data/sig/kobako/transport/yielder.rbs +3 -3
- data/sig/kobako/transport.rbs +7 -6
- metadata +6 -3
- data/rust-toolchain.toml +0 -11
data/lib/kobako/payload.rb
CHANGED
|
@@ -3,17 +3,9 @@
|
|
|
3
3
|
require_relative "payload/arguments"
|
|
4
4
|
|
|
5
5
|
module Kobako
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
#
|
|
11
|
-
# Keeping the two apart is what lets an endpoint with its own schema
|
|
12
|
-
# replace this layer and keep the envelope, so nothing here may reach for
|
|
13
|
-
# a routing field.
|
|
14
|
-
#
|
|
15
|
-
# +Kobako::Payload::Arguments+ is the wire-symmetric peer of
|
|
16
|
-
# +kobako_codec::payload::Arguments+.
|
|
17
|
-
module Payload
|
|
6
|
+
# What the resolved method receives, kept apart from the envelope so an
|
|
7
|
+
# endpoint with its own schema can replace it; nothing here reads a
|
|
8
|
+
# routing field.
|
|
9
|
+
module Payload # :nodoc:
|
|
18
10
|
end
|
|
19
11
|
end
|
data/lib/kobako/pool.rb
CHANGED
|
@@ -4,29 +4,21 @@ require_relative "errors"
|
|
|
4
4
|
require_relative "sandbox"
|
|
5
5
|
|
|
6
6
|
module Kobako
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# Construction forwards every +Kobako::Sandbox.new+ keyword verbatim
|
|
11
|
-
# and holds the optional block as the per-Sandbox setup hook; a
|
|
12
|
-
# checkout prefers an idle Sandbox and constructs a new one only when
|
|
13
|
-
# none is idle and fewer than +slots+ exist. +#with+ blocks up
|
|
14
|
-
# to +checkout_timeout+ seconds when every slot is held, applies
|
|
15
|
-
# the +TrapError+ discard-and-recreate contract at checkin, and
|
|
16
|
-
# the Pool releases everything with its own reachability — there is no
|
|
17
|
-
# teardown verb.
|
|
7
|
+
# A bounded set of warm, identically set-up Sandboxes, handed out to one
|
|
8
|
+
# holder at a time. Every Sandbox it builds is kept: an invocation leaves
|
|
9
|
+
# nothing behind, whatever ended it.
|
|
18
10
|
class Pool
|
|
19
|
-
# The
|
|
11
|
+
# The default +checkout_timeout+: 5 seconds.
|
|
20
12
|
DEFAULT_CHECKOUT_TIMEOUT_SECONDS = 5.0
|
|
21
13
|
|
|
22
|
-
# Build a Pool of up to +slots+ Sandboxes
|
|
23
|
-
#
|
|
24
|
-
# seconds
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
14
|
+
# Build a Pool of up to +slots+ Sandboxes, built on first demand with the
|
|
15
|
+
# other keywords as Sandbox.new takes them. +checkout_timeout+ bounds how
|
|
16
|
+
# many seconds #with waits, and +nil+ waits indefinitely. The block, if
|
|
17
|
+
# given, sets up each Sandbox once, as with Sandbox#bind and
|
|
18
|
+
# Sandbox#preload.
|
|
19
|
+
#
|
|
20
|
+
# Raises ArgumentError when +slots+ is not a positive Integer or
|
|
21
|
+
# +checkout_timeout+ is not a positive finite number.
|
|
30
22
|
def initialize(slots:, checkout_timeout: DEFAULT_CHECKOUT_TIMEOUT_SECONDS, **sandbox_options, &setup)
|
|
31
23
|
validate_slots!(slots)
|
|
32
24
|
@slots = slots
|
|
@@ -40,29 +32,20 @@ module Kobako
|
|
|
40
32
|
end
|
|
41
33
|
|
|
42
34
|
# Yield one exclusively-held Sandbox to the block and return the
|
|
43
|
-
# block's value
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# +Kobako::TrapError+, in which case the unrecoverable Sandbox is
|
|
47
|
-
# discarded and its slot refills by a fresh construction on next
|
|
48
|
-
# demand.
|
|
35
|
+
# block's value; the Sandbox returns to the pool however the block
|
|
36
|
+
# exits. Raises PoolTimeoutError once every slot has stayed held past
|
|
37
|
+
# +checkout_timeout+.
|
|
49
38
|
def with
|
|
50
39
|
sandbox = acquire
|
|
51
40
|
begin
|
|
52
41
|
yield sandbox
|
|
53
|
-
rescue TrapError
|
|
54
|
-
release_capacity!
|
|
55
|
-
sandbox = nil
|
|
56
|
-
raise
|
|
57
42
|
ensure
|
|
58
|
-
checkin(sandbox)
|
|
43
|
+
checkin(sandbox)
|
|
59
44
|
end
|
|
60
45
|
end
|
|
61
46
|
|
|
62
47
|
private
|
|
63
48
|
|
|
64
|
-
# The idle-first claim loop: an idle Sandbox wins, unclaimed
|
|
65
|
-
# capacity constructs, and a full pool waits for a checkin.
|
|
66
49
|
def acquire
|
|
67
50
|
timeout = @checkout_timeout
|
|
68
51
|
deadline = timeout && (monotonic_now + timeout)
|
|
@@ -73,11 +56,10 @@ module Kobako
|
|
|
73
56
|
end
|
|
74
57
|
end
|
|
75
58
|
|
|
76
|
-
#
|
|
77
|
-
# happens
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
# failure.
|
|
59
|
+
# Waiting happens inside the lock (so a checkin can wake it);
|
|
60
|
+
# construction happens outside (so a slow setup block never holds the
|
|
61
|
+
# lock) — capacity is reserved here and released by +construct_slot+
|
|
62
|
+
# on failure.
|
|
81
63
|
def claim_or_wait(deadline)
|
|
82
64
|
@mutex.synchronize do
|
|
83
65
|
return [:idle, @idle.pop] unless @idle.empty?
|
|
@@ -92,9 +74,7 @@ module Kobako
|
|
|
92
74
|
end
|
|
93
75
|
end
|
|
94
76
|
|
|
95
|
-
#
|
|
96
|
-
# +Kobako::PoolTimeoutError+ once +deadline+ has passed. Must
|
|
97
|
-
# run while holding +@mutex+.
|
|
77
|
+
# Must run while holding +@mutex+.
|
|
98
78
|
def await_slot!(deadline)
|
|
99
79
|
remaining = deadline && (deadline - monotonic_now)
|
|
100
80
|
if remaining && remaining <= 0
|
|
@@ -105,10 +85,8 @@ module Kobako
|
|
|
105
85
|
@slot_freed.wait(@mutex, remaining)
|
|
106
86
|
end
|
|
107
87
|
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
# propagate to the checkout caller unchanged; the reserved
|
|
111
|
-
# capacity is released so a later checkout can retry.
|
|
88
|
+
# A failed construction releases its reserved capacity so a later
|
|
89
|
+
# checkout can retry.
|
|
112
90
|
def construct_slot
|
|
113
91
|
done = false
|
|
114
92
|
sandbox = Sandbox.new(**@sandbox_options)
|
|
@@ -119,7 +97,6 @@ module Kobako
|
|
|
119
97
|
release_capacity! unless done
|
|
120
98
|
end
|
|
121
99
|
|
|
122
|
-
# Return a Sandbox to the idle list and wake one waiting checkout.
|
|
123
100
|
def checkin(sandbox)
|
|
124
101
|
@mutex.synchronize do
|
|
125
102
|
@idle.push(sandbox)
|
|
@@ -127,8 +104,6 @@ module Kobako
|
|
|
127
104
|
end
|
|
128
105
|
end
|
|
129
106
|
|
|
130
|
-
# Give back reserved-but-unfilled capacity — a failed construction or
|
|
131
|
-
# a discarded Sandbox — and wake one waiting checkout to claim it.
|
|
132
107
|
def release_capacity!
|
|
133
108
|
@mutex.synchronize do
|
|
134
109
|
@constructed -= 1
|
|
@@ -142,17 +117,12 @@ module Kobako
|
|
|
142
117
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
143
118
|
end
|
|
144
119
|
|
|
145
|
-
# Pre-flight for +slots+ — no coercion, a positive Integer is
|
|
146
|
-
# the only accepted shape.
|
|
147
120
|
def validate_slots!(slots)
|
|
148
121
|
return if slots.is_a?(Integer) && slots.positive?
|
|
149
122
|
|
|
150
123
|
raise ArgumentError, "slots must be a positive Integer, got #{slots.inspect}"
|
|
151
124
|
end
|
|
152
125
|
|
|
153
|
-
# Coerce +checkout_timeout+ into the Float seconds the wait loop
|
|
154
|
-
# consumes, or +nil+ to wait indefinitely — the same normalisation
|
|
155
|
-
# idiom +SandboxOptions+ applies to +timeout+.
|
|
156
126
|
def normalize_checkout_timeout(checkout_timeout)
|
|
157
127
|
return nil if checkout_timeout.nil?
|
|
158
128
|
unless checkout_timeout.is_a?(Numeric)
|
data/lib/kobako/runtime.rb
CHANGED
|
@@ -1,27 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Kobako
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# +#eval+ / +#run+, capture and usage readers) is registered from Rust
|
|
12
|
-
# at ext load time.
|
|
13
|
-
class Runtime
|
|
14
|
-
# Absolute path to the gem-bundled +data/kobako.wasm+ artifact. Computed
|
|
15
|
-
# from this file's location so it works for both +bundle exec+ (running
|
|
16
|
-
# from the repo) and an installed gem (running from the gem dir).
|
|
17
|
-
#
|
|
18
|
-
# Returns a String regardless of whether the file currently exists —
|
|
19
|
-
# call sites that need the file to be present should pass this through
|
|
20
|
-
# +Kobako::Runtime.from_path+, which raises
|
|
21
|
-
# +Kobako::ModuleNotBuiltError+ with a clear remediation message.
|
|
22
|
-
# Raises +Kobako::SetupError+ if +__dir__+ is unavailable so that
|
|
23
|
-
# +rescue Kobako::SetupError+ around +Kobako::Sandbox.new+ catches every
|
|
24
|
-
# construction-layer failure uniformly, including this path-resolution one.
|
|
4
|
+
# The native wasmtime runtime, defined by the ext; this file adds only the
|
|
5
|
+
# default Guest Binary path.
|
|
6
|
+
class Runtime # :nodoc:
|
|
7
|
+
# The path is answered whether or not the file exists; from_path is
|
|
8
|
+
# where a missing artifact is refused. A missing +__dir__+ raises
|
|
9
|
+
# SetupError so one rescue around Sandbox.new covers every construction
|
|
10
|
+
# failure.
|
|
25
11
|
def self.default_path
|
|
26
12
|
dir = __dir__ or raise Kobako::SetupError, "Kobako::Runtime.default_path requires __dir__"
|
|
27
13
|
File.expand_path("../../data/kobako.wasm", dir)
|
data/lib/kobako/sandbox.rb
CHANGED
|
@@ -10,168 +10,129 @@ require_relative "catalog"
|
|
|
10
10
|
require_relative "context"
|
|
11
11
|
|
|
12
12
|
module Kobako
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# the +Kobako::Catalog::Services+ / +Catalog::Snippets+ / +Catalog::Extensions+
|
|
18
|
-
# registries. Each +#eval+ / +#run+ seals that config on the first call and
|
|
19
|
-
# drives one guest invocation through a fresh +Kobako::Context+ — its own
|
|
20
|
-
# Handle table, dispatch +Proc+, captures, and usage — so the reusable
|
|
21
|
-
# Sandbox holds no per-invocation state. The underlying wasmtime Engine and
|
|
22
|
-
# compiled Module are cached at process scope by the native ext and never
|
|
23
|
-
# surface to Ruby — constructing many Sandboxes amortises both costs
|
|
24
|
-
# automatically.
|
|
25
|
-
#
|
|
26
|
-
# A run's observables — the captured +#stdout+ / +#stderr+ (bounded by
|
|
27
|
-
# +stdout_limit+ / +stderr_limit+, enforced inside the WASI pipe) with their
|
|
28
|
-
# +#stdout_truncated?+ / +#stderr_truncated?+ predicates, and +#usage+ — live
|
|
29
|
-
# on the +Kobako::Execution+ each +#eval+ / +#run+ returns, or on the one its
|
|
30
|
-
# raised error carries. The Sandbox itself keeps none of them, so nothing a
|
|
31
|
-
# run observes carries into the next one.
|
|
13
|
+
# Runs untrusted mruby scripts in an isolated Wasm instance. Configure it
|
|
14
|
+
# once, binding Services and preloading snippets, then call #eval or #run as
|
|
15
|
+
# often as needed: each call returns an Execution and leaves nothing behind
|
|
16
|
+
# for the next.
|
|
32
17
|
class Sandbox
|
|
33
18
|
extend Forwardable
|
|
34
19
|
|
|
35
|
-
|
|
20
|
+
# The Guest Binary this Sandbox runs.
|
|
21
|
+
attr_reader :wasm_path
|
|
22
|
+
|
|
23
|
+
# The SandboxOptions this Sandbox was built with. Each option is also
|
|
24
|
+
# readable on the Sandbox itself, as in #timeout.
|
|
25
|
+
attr_reader :options
|
|
36
26
|
|
|
37
|
-
# Per-option accessors forward to the immutable +SandboxOptions+ Value
|
|
38
|
-
# Object so the Host App still reads them off Sandbox directly.
|
|
39
27
|
def_delegators :@options, :timeout, :memory_limit, :stdout_limit, :stderr_limit, :profile, :gvl
|
|
40
28
|
|
|
41
|
-
# Build a
|
|
29
|
+
# Build a Sandbox on the Guest Binary at +wasm_path+, the one bundled
|
|
30
|
+
# with the gem by default. The other keywords are the options
|
|
31
|
+
# SandboxOptions describes.
|
|
42
32
|
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# requested isolation profile (+profile+) — is forwarded verbatim to
|
|
47
|
-
# +Kobako::SandboxOptions+, which owns the DEFAULT fallbacks and
|
|
48
|
-
# normalisation. The constructed +SandboxOptions+ is exposed as
|
|
49
|
-
# +#options+ and every option remains readable directly on Sandbox via
|
|
50
|
-
# +Forwardable+ delegation. The runtime builds the requested profile —
|
|
51
|
-
# +:hermetic+ (the default) denies the guest ambient time and entropy,
|
|
52
|
-
# +:permissive+ leaves them live — and construction refuses a runtime
|
|
53
|
-
# whose declared profile falls below the request, raising
|
|
54
|
-
# +Kobako::SetupError+ before any invocation entry point runs.
|
|
33
|
+
# Raises ArgumentError for an invalid option, and SetupError when the
|
|
34
|
+
# runtime cannot be built or provides less isolation than +profile+ asks
|
|
35
|
+
# for.
|
|
55
36
|
def initialize(wasm_path: nil, **)
|
|
56
37
|
@wasm_path = wasm_path || Kobako::Runtime.default_path
|
|
57
38
|
@options = SandboxOptions.new(**)
|
|
58
39
|
@services = Kobako::Catalog::Services.new
|
|
59
40
|
@snippets = Catalog::Snippets.new
|
|
41
|
+
@flags = Catalog::Flags.new
|
|
60
42
|
@extensions = Catalog::Extensions.new
|
|
43
|
+
@sealed = false
|
|
61
44
|
@runtime = build_runtime!
|
|
62
45
|
end
|
|
63
46
|
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
# (+"MyService::KV"+ or a top-level +"File"+). Returns +self+ for
|
|
67
|
-
# chaining.
|
|
47
|
+
# Make +object+ reachable from the guest as the constant at +path+, a
|
|
48
|
+
# Symbol or String such as <tt>"MyService::KV"</tt>. Returns +self+.
|
|
68
49
|
#
|
|
69
|
-
# The guest reaches the public methods +object+'s own class and
|
|
70
|
-
# itself define in source,
|
|
71
|
-
#
|
|
72
|
-
#
|
|
50
|
+
# The guest reaches the public methods that +object+'s own class and
|
|
51
|
+
# +object+ itself define in source, nothing inherited or mixed in. An
|
|
52
|
+
# +object+ defining a private <tt>respond_to_guest?(name)</tt> decides
|
|
53
|
+
# instead, on every call.
|
|
73
54
|
#
|
|
74
|
-
# Called with only
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
# guest dispatch to an unfilled fillable surfaces as
|
|
78
|
-
# +Kobako::ServiceError+ when left unrescued.
|
|
55
|
+
# Called with only +path+, it declares a fillable Service: the guest sees
|
|
56
|
+
# the constant, and Context#bind supplies the object for each invocation.
|
|
57
|
+
# A call to one left unfilled fails as a ServiceError.
|
|
79
58
|
#
|
|
80
|
-
# Raises
|
|
81
|
-
#
|
|
82
|
-
# grouping prefix, never both), or when called after the first
|
|
83
|
-
# invocation has sealed Service registration.
|
|
59
|
+
# Raises ArgumentError for a malformed path, a path that collides with an
|
|
60
|
+
# existing binding, or a call after the first invocation.
|
|
84
61
|
def bind(path, object = Unresolved)
|
|
62
|
+
refuse_once_invoked!("bind")
|
|
85
63
|
@services.bind(path, object)
|
|
86
64
|
self
|
|
87
65
|
end
|
|
88
66
|
|
|
89
|
-
# Install
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
# +name+ / +source+ / +backend+ / +depends_on+; +Kobako::Extension+ is
|
|
93
|
-
# the bundled value type. Returns +self+.
|
|
67
|
+
# Install Extensions, each guest source paired with an optional host
|
|
68
|
+
# backend. Any object answering +name+, +source+, +backend+ and
|
|
69
|
+
# +depends_on+ works; Extension is the bundled one. Returns +self+.
|
|
94
70
|
#
|
|
95
|
-
# Raises
|
|
96
|
-
#
|
|
97
|
-
# an unmet +depends_on+.
|
|
71
|
+
# Raises ArgumentError for a malformed Extension, a call after the first
|
|
72
|
+
# invocation, or, at the first invocation, a missing dependency.
|
|
98
73
|
def install(*extensions)
|
|
99
|
-
|
|
74
|
+
refuse_once_invoked!("install")
|
|
100
75
|
|
|
101
76
|
extensions.each { |extension| @extensions.install(extension, snippets: @snippets, services: @services) }
|
|
102
77
|
self
|
|
103
78
|
end
|
|
104
79
|
|
|
105
|
-
# Register a snippet
|
|
106
|
-
#
|
|
107
|
-
# * +preload(code: source, name: Name)+ — +source+ is mruby source
|
|
108
|
-
# as a +String+ and +Name+ matches +/\A[A-Z]\w*\z/+. Compile
|
|
109
|
-
# failures surface as +Kobako::SandboxError+ on the first
|
|
110
|
-
# invocation's replay. The +name+
|
|
111
|
-
# becomes the snippet's +(snippet:Name)+ backtrace filename and
|
|
112
|
-
# is the dedupe key that rejects a duplicate +code:+ snippet.
|
|
113
|
-
# * +preload(binary: bytes)+ — +bytes+ is precompiled RITE
|
|
114
|
-
# bytecode as a +String+. The canonical name, when present,
|
|
115
|
-
# lives in the bytecode's embedded +debug_info+ and is resolved
|
|
116
|
-
# by the guest at load time; the host treats the bytes as
|
|
117
|
-
# opaque. Structural failures surface as +Kobako::BytecodeError+
|
|
118
|
-
# on the first invocation.
|
|
80
|
+
# Register a snippet that every invocation runs, in the order registered,
|
|
81
|
+
# before its own code. Returns +self+.
|
|
119
82
|
#
|
|
120
|
-
#
|
|
121
|
-
# snippet
|
|
122
|
-
#
|
|
83
|
+
# [<tt>preload(code: source, name: Name)</tt>]
|
|
84
|
+
# mruby source, named <tt>(snippet:Name)</tt> in backtraces.
|
|
85
|
+
# [<tt>preload(binary: bytes)</tt>]
|
|
86
|
+
# Precompiled RITE bytecode, trusted as host code: the unmodified
|
|
87
|
+
# compiler output of the mruby release the Guest Binary is built on.
|
|
123
88
|
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
# supplied, when both forms are mixed (e.g., +code:+ and +binary:+
|
|
128
|
-
# together, or +binary:+ paired with +name:+), when +code+ / +bytes+
|
|
129
|
-
# is not a +String+, when +name+ does not match the constant
|
|
130
|
-
# pattern, when +name+ duplicates an already-registered +code:+ form
|
|
131
|
-
# snippet, or when called after the first invocation has sealed the
|
|
132
|
-
# snippet table.
|
|
89
|
+
# Raises ArgumentError for a malformed or duplicate snippet, or a call
|
|
90
|
+
# after the first invocation. A snippet that fails to load fails every
|
|
91
|
+
# invocation with SandboxError, or BytecodeError for bytecode.
|
|
133
92
|
def preload(code: nil, name: nil, binary: nil)
|
|
134
|
-
|
|
93
|
+
refuse_once_invoked!("preload")
|
|
135
94
|
|
|
136
95
|
@snippets.register(code: code, name: name, binary: binary)
|
|
137
96
|
self
|
|
138
97
|
end
|
|
139
98
|
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
|
|
99
|
+
# Set the flag +name+, a Symbol or String, to +value+ for every
|
|
100
|
+
# invocation; an empty +value+ is a plain switch. A Guest Binary that
|
|
101
|
+
# does not recognise the flag fails each invocation with
|
|
102
|
+
# UnsupportedFlagError, unless +advisory+ says the guest may skip it.
|
|
103
|
+
# Mark a flag advisory only when a script cannot tell it was skipped.
|
|
104
|
+
# Returns +self+.
|
|
105
|
+
#
|
|
106
|
+
# Raises ArgumentError for a malformed or duplicate flag, or a call after
|
|
107
|
+
# the first invocation.
|
|
108
|
+
def flag(name, value = "", advisory: false)
|
|
109
|
+
refuse_once_invoked!("flag")
|
|
110
|
+
|
|
111
|
+
@flags.register(name, value, advisory: advisory)
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Call +call+ on the preloaded top-level constant +target+ with +args+
|
|
116
|
+
# and +kwargs+, and return the Execution. A given block receives the
|
|
117
|
+
# invocation's Context before the guest runs.
|
|
118
|
+
#
|
|
119
|
+
# Raises TypeError when +target+ is not a Symbol or String, and
|
|
120
|
+
# ArgumentError when it is not a constant name or the arguments carry a
|
|
121
|
+
# Handle or a non-Symbol keyword. A failed run raises as #eval does.
|
|
150
122
|
def run(target, *args, **kwargs, &block)
|
|
151
123
|
request = Transport::Run.new(entrypoint: target, args: args, kwargs: kwargs)
|
|
152
124
|
new_invocation.run(request, &block)
|
|
153
125
|
end
|
|
154
126
|
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
# Source delivery uses the WASI stdin three-frame protocol
|
|
160
|
-
# ({docs/wire-codec.md Invocation channels}[link:../../docs/wire-codec.md]):
|
|
161
|
-
# Frame 1 carries the preamble (Service registry snapshot), Frame 2 the
|
|
162
|
-
# user source UTF-8 bytes, and Frame 3 the snippet table registered via
|
|
163
|
-
# +#preload+. Each frame is prefixed by a 4-byte big-endian u32 length;
|
|
164
|
-
# Frame 1 and Frame 3 are mandatory-presence — an empty registry sends a
|
|
165
|
-
# present, empty frame rather than an absent one.
|
|
127
|
+
# Evaluate +code+, mruby source as a String, and return the Execution,
|
|
128
|
+
# whose +value+ is the last expression. A given block receives the
|
|
129
|
+
# invocation's Context before the guest runs.
|
|
166
130
|
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
# (including when +code+ is +nil+ or not a String, or when a preloaded
|
|
173
|
-
# snippet's replay raises); +Kobako::ServiceError+ on an unrescued
|
|
174
|
-
# Service capability failure.
|
|
131
|
+
# Raises TrapError when the engine stopped the guest, a deadline or
|
|
132
|
+
# memory budget included; SandboxError when the guest failed, +code+ not
|
|
133
|
+
# being a String or the Guest Binary not starting the run included; and
|
|
134
|
+
# ServiceError when a Service call failed and the guest left it
|
|
135
|
+
# unrescued.
|
|
175
136
|
def eval(code, &block)
|
|
176
137
|
raise SandboxError, "code must be a String, got #{code.class}" unless code.is_a?(String)
|
|
177
138
|
|
|
@@ -180,10 +141,8 @@ module Kobako
|
|
|
180
141
|
|
|
181
142
|
private
|
|
182
143
|
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
# +SandboxOptions#enforce_floor!+ owns the ladder comparison, so a
|
|
186
|
-
# runtime that cannot honor the request never runs guest code.
|
|
144
|
+
# A runtime whose declared posture falls below the request never runs
|
|
145
|
+
# guest code.
|
|
187
146
|
def build_runtime!
|
|
188
147
|
runtime = Kobako::Runtime.from_path(@wasm_path, @options.timeout, @options.memory_limit,
|
|
189
148
|
@options.stdout_limit, @options.stderr_limit, @options.profile,
|
|
@@ -192,23 +151,27 @@ module Kobako
|
|
|
192
151
|
runtime
|
|
193
152
|
end
|
|
194
153
|
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
# run's Handle table, resolved Extension backends, captures, and usage, so
|
|
198
|
-
# no per-invocation state is written back onto the shared config.
|
|
154
|
+
# Per-invocation state lives on the Context, so nothing an invocation
|
|
155
|
+
# does is written back onto the shared config.
|
|
199
156
|
def new_invocation
|
|
200
157
|
begin_invocation!
|
|
201
|
-
Context.new(runtime: @runtime, services: @services, snippets: @snippets,
|
|
158
|
+
Context.new(runtime: @runtime, services: @services, snippets: @snippets, flags: @flags,
|
|
159
|
+
extensions: @extensions)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Registration closes at the first invocation, so every invocation ships
|
|
163
|
+
# the same Services, snippets, flags and Extensions.
|
|
164
|
+
def refuse_once_invoked!(verb)
|
|
165
|
+
raise ArgumentError, "cannot #{verb} after first Sandbox invocation" if @sealed
|
|
202
166
|
end
|
|
203
167
|
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
# +#install+ all gate on. Per-invocation provider resolution and observable
|
|
208
|
-
# state live on the +Context+, not here.
|
|
168
|
+
# Sealing before the dependency check keeps a Sandbox whose check failed
|
|
169
|
+
# closed to further registration. The sealed snippets are compiled once,
|
|
170
|
+
# before the first invocation replays them.
|
|
209
171
|
def begin_invocation!
|
|
210
|
-
@
|
|
211
|
-
@extensions.
|
|
172
|
+
@sealed = true
|
|
173
|
+
@extensions.assert_dependencies!
|
|
174
|
+
@snippets.compile { |entries| @runtime.compile(@services.paths, entries, @flags.entries) }
|
|
212
175
|
end
|
|
213
176
|
end
|
|
214
177
|
end
|