kobako 0.10.0-aarch64-linux → 0.11.1-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 +14 -0
- data/README.md +15 -8
- data/SECURITY.md +35 -0
- 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 +5 -7
- data/lib/kobako/catalog/handles.rb +24 -31
- data/lib/kobako/catalog/namespaces.rb +19 -27
- data/lib/kobako/catalog/snippets.rb +10 -16
- data/lib/kobako/codec/utils.rb +6 -9
- data/lib/kobako/errors.rb +33 -39
- data/lib/kobako/handle.rb +2 -3
- data/lib/kobako/namespace.rb +8 -11
- data/lib/kobako/outcome.rb +12 -14
- data/lib/kobako/pool.rb +18 -24
- data/lib/kobako/sandbox.rb +61 -83
- data/lib/kobako/sandbox_options.rb +5 -9
- data/lib/kobako/snapshot.rb +2 -4
- data/lib/kobako/snippet/binary.rb +1 -3
- data/lib/kobako/snippet/source.rb +1 -2
- data/lib/kobako/snippet.rb +1 -2
- data/lib/kobako/transport/dispatcher.rb +39 -38
- data/lib/kobako/transport/request.rb +1 -1
- data/lib/kobako/transport/run.rb +23 -28
- data/lib/kobako/transport/yielder.rb +11 -17
- data/lib/kobako/transport.rb +2 -3
- data/lib/kobako/usage.rb +10 -13
- data/lib/kobako/version.rb +1 -1
- data/release-please-config.json +1 -0
- data/sig/kobako/transport/dispatcher.rbs +2 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea2898e0ad1df3bcda9f6b71f7c811890f248c8bf8203c8114a271aa97dd234b
|
|
4
|
+
data.tar.gz: 59faa2b92a0f751b7fe13c339c1bf47502d1560577a0d74ea77cfb72d68eb0aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f125e8a66ce7b03dc9608f30cc10b6ea3dd6d41115104c39852725c09e2fd0f0f3df2c07c8b4801f4f57f27e80485548d591281f972f30151a9ac8d7001b5856
|
|
7
|
+
data.tar.gz: 35ae1f7b98347be2a7b7f9e28173d8ffad8ea0129784c507530c11227a29585805a774b541be6e5e59d501304ee6325a3cf9206b48318e643eb3e0edc800f483
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.11.1","wasm/kobako-core":"0.5.1","wasm/kobako":"0.5.1","wasm/kobako-io":"0.5.1","wasm/kobako-regexp":"0.5.1","wasm/kobako-baker":"0.5.1"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.11.1](https://github.com/elct9620/kobako/compare/v0.11.0...v0.11.1) (2026-06-14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **guest:** adopt beni 0.7.0 protected dispatch (B-51) ([c61655b](https://github.com/elct9620/kobako/commit/c61655bcead336d32a4b6ff7ff1b34c21cdfccd9))
|
|
9
|
+
|
|
10
|
+
## [0.11.0](https://github.com/elct9620/kobako/compare/v0.10.0...v0.11.0) (2026-06-13)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **transport:** narrow guest-reachable methods via respond_to_guest? ([7a25fe3](https://github.com/elct9620/kobako/commit/7a25fe3b440b523b8a692b7601d08877b1305b0d))
|
|
16
|
+
|
|
3
17
|
## [0.10.0](https://github.com/elct9620/kobako/compare/v0.9.2...v0.10.0) (2026-06-12)
|
|
4
18
|
|
|
5
19
|
|
data/README.md
CHANGED
|
@@ -69,7 +69,7 @@ The script executes inside the Wasm guest. It cannot read your filesystem, open
|
|
|
69
69
|
|
|
70
70
|
### Services
|
|
71
71
|
|
|
72
|
-
Declare a Namespace, then `bind` any Ruby object as a Member; the guest reaches it as a `<Namespace>::<Member>` proxy and invokes its public methods through the Transport wire. See [`docs/behavior.md`](docs/behavior.md) B-07..B-12.
|
|
72
|
+
Declare a Namespace, then `bind` any Ruby object as a Member; the guest reaches it as a `<Namespace>::<Member>` proxy and invokes its public methods through the Transport wire. See [`docs/behavior/registration.md`](docs/behavior/registration.md) B-07..B-12.
|
|
73
73
|
|
|
74
74
|
```ruby
|
|
75
75
|
class User
|
|
@@ -93,7 +93,7 @@ Names must match `/\A[A-Z]\w*\z/`. Symbol kwargs travel transparently to the hos
|
|
|
93
93
|
|
|
94
94
|
### Output Capture
|
|
95
95
|
|
|
96
|
-
Guest writes through `puts` / `print` / `p` / `$stdout` / `$stderr` are buffered per-channel and exposed independently of the return value ([`docs/behavior.md`](docs/behavior.md) B-04). Buffers clear at the start of each invocation; overflow is clipped at the cap and flagged by `#stdout_truncated?` / `#stderr_truncated?`.
|
|
96
|
+
Guest writes through `puts` / `print` / `p` / `$stdout` / `$stderr` are buffered per-channel and exposed independently of the return value ([`docs/behavior/lifecycle.md`](docs/behavior/lifecycle.md) B-04). Buffers clear at the start of each invocation; overflow is clipped at the cap and flagged by `#stdout_truncated?` / `#stderr_truncated?`.
|
|
97
97
|
|
|
98
98
|
```ruby
|
|
99
99
|
result = sandbox.eval(<<~RUBY)
|
|
@@ -134,7 +134,7 @@ end
|
|
|
134
134
|
|
|
135
135
|
### Resource Limits
|
|
136
136
|
|
|
137
|
-
Each invocation enforces a wall-clock `timeout` and a per-invocation linear-memory `memory_limit`; exhaustion raises a `TrapError` subclass. Pass `nil` to `timeout` / `memory_limit` to disable that cap. Read [`Sandbox#usage`](lib/kobako/sandbox.rb) after the call — populated on every outcome including traps — for actual consumption ([`docs/behavior.md`](docs/behavior.md) B-35).
|
|
137
|
+
Each invocation enforces a wall-clock `timeout` and a per-invocation linear-memory `memory_limit`; exhaustion raises a `TrapError` subclass. Pass `nil` to `timeout` / `memory_limit` to disable that cap. Read [`Sandbox#usage`](lib/kobako/sandbox.rb) after the call — populated on every outcome including traps — for actual consumption ([`docs/behavior/lifecycle.md`](docs/behavior/lifecycle.md) B-35).
|
|
138
138
|
|
|
139
139
|
```ruby
|
|
140
140
|
sandbox = Kobako::Sandbox.new(
|
|
@@ -202,7 +202,9 @@ For workloads that must be isolated from each other (one Sandbox per tenant, per
|
|
|
202
202
|
|
|
203
203
|
### Pooling
|
|
204
204
|
|
|
205
|
-
For hosts that serve many short invocations, `Kobako::Pool` keeps a bounded set of warm, identically set-up Sandboxes and hands each one to a single exclusive holder at a time ([`docs/behavior.md`](docs/behavior.md) B-46..B-48). Construction forwards every `Sandbox.new` keyword verbatim; the optional block is the per-Sandbox setup window and runs exactly once per constructed Sandbox.
|
|
205
|
+
For hosts that serve many short invocations, `Kobako::Pool` keeps a bounded set of warm, identically set-up Sandboxes and hands each one to a single exclusive holder at a time ([`docs/behavior/runtime.md`](docs/behavior/runtime.md) B-46..B-48). Construction forwards every `Sandbox.new` keyword verbatim; the optional block is the per-Sandbox setup window and runs exactly once per constructed Sandbox.
|
|
206
|
+
|
|
207
|
+
`Kobako::Pool` is experimental today and is best treated as a convenience for warm, pre-configured reuse rather than a throughput optimisation. B-49 bakes the shared boot state into the artifact and every dynamic script still compiles and runs per invocation, so all a pool actually saves is the ~30 µs host-side `Sandbox.new`. For the workload kobako is built for — many small, short-lived Sandboxes running dynamic scripts — that is not a significant gain (~4-5% in the [serverless example](examples/serverless/README.md), and proportionally less once the script itself does real work).
|
|
206
208
|
|
|
207
209
|
```ruby
|
|
208
210
|
pool = Kobako::Pool.new(slots: 4) do |sandbox|
|
|
@@ -221,7 +223,7 @@ Sandboxes construct lazily on first demand. `#with` yields a Sandbox with empty
|
|
|
221
223
|
|
|
222
224
|
### Service Blocks
|
|
223
225
|
|
|
224
|
-
A Service method can accept a guest-supplied block via `&blk` and `yield` into it. The block body runs inside the Wasm guest; `break` / `next` / exceptions follow normal Ruby semantics, scoped to the single dispatch. See [`docs/behavior.md`](docs/behavior.md) B-23..B-30.
|
|
226
|
+
A Service method can accept a guest-supplied block via `&blk` and `yield` into it. The block body runs inside the Wasm guest; `break` / `next` / exceptions follow normal Ruby semantics, scoped to the single dispatch. See [`docs/behavior/yield.md`](docs/behavior/yield.md) B-23..B-30.
|
|
225
227
|
|
|
226
228
|
```ruby
|
|
227
229
|
sandbox.define(:Seq).bind(:Map, ->(items, &blk) { items.map(&blk) })
|
|
@@ -232,7 +234,7 @@ sandbox.eval('Seq::Map.call([1, 2, 3]) { |x| x * 2 }')
|
|
|
232
234
|
|
|
233
235
|
### Handle Management
|
|
234
236
|
|
|
235
|
-
A non-wire-representable host object — returned from a Service (B-14), passed to `#run` (B-34), or handed back from the guest (B-37) — crosses the boundary as an opaque `Kobako::Handle` proxy and is restored to the original object before host code sees it; any other unrepresentable value raises `Kobako::SandboxError`. Handles are scoped to a single invocation ([`docs/behavior.md`](docs/behavior.md) B-13..B-21, B-34, B-37).
|
|
237
|
+
A non-wire-representable host object — returned from a Service (B-14), passed to `#run` (B-34), or handed back from the guest (B-37) — crosses the boundary as an opaque `Kobako::Handle` proxy and is restored to the original object before host code sees it; any other unrepresentable value raises `Kobako::SandboxError`. Handles are scoped to a single invocation ([`docs/behavior/dispatch.md`](docs/behavior/dispatch.md) B-13..B-21, B-34, B-37).
|
|
236
238
|
|
|
237
239
|
```ruby
|
|
238
240
|
class Greeter
|
|
@@ -268,7 +270,7 @@ This is deliberate, not a leak. Handle IDs run to 2³¹ − 1 per invocation and
|
|
|
268
270
|
|
|
269
271
|
### Snippets & Entrypoints
|
|
270
272
|
|
|
271
|
-
`Sandbox#preload` registers named mruby snippets that replay into every invocation's canonical boot state; `Sandbox#run(:Target, *args, **kwargs)` dispatches into a top-level `Object` constant defined by those snippets ([`docs/behavior.md`](docs/behavior.md) B-31..B-33).
|
|
273
|
+
`Sandbox#preload` registers named mruby snippets that replay into every invocation's canonical boot state; `Sandbox#run(:Target, *args, **kwargs)` dispatches into a top-level `Object` constant defined by those snippets ([`docs/behavior/invocation.md`](docs/behavior/invocation.md) B-31..B-33).
|
|
272
274
|
|
|
273
275
|
```ruby
|
|
274
276
|
sandbox = Kobako::Sandbox.new
|
|
@@ -320,9 +322,14 @@ sandbox.define(:Cfg).bind(:Settings, ThemeReader.new) # not: bind(:Settings, Ap
|
|
|
320
322
|
sandbox.eval('Cfg::Settings.color') # => "#3366ff" — every other method raises NoMethodError
|
|
321
323
|
```
|
|
322
324
|
|
|
325
|
+
When a purpose-built wrapper is more than you need, an object can gate its own surface in
|
|
326
|
+
place: a private `respond_to_guest?(name)` answers, per method, whether the guest may call
|
|
327
|
+
it. Returning `false` for every name makes the object opaque — a credential the guest
|
|
328
|
+
forwards to another Service but never reads — while a named subset becomes an allow-list.
|
|
329
|
+
|
|
323
330
|
Guest code can name any `<Namespace>::<Member>` path, but a forged name only resolves to
|
|
324
331
|
something you bound — the real authorization gate is this host-side allowlist. Give each
|
|
325
|
-
trust context its own Sandbox, and see [`docs/security.md`](docs/security.md) for the rest
|
|
332
|
+
trust context its own Sandbox, and see [`docs/security-model.md`](docs/security-model.md) for the rest
|
|
326
333
|
as security-design concerns: validating untrusted input, default-deny external effects,
|
|
327
334
|
and controlling the return surface.
|
|
328
335
|
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
kobako runs untrusted guest code inside an in-process Wasm sandbox, so a break in
|
|
4
|
+
its isolation boundary is treated as a security issue. This file is about **reporting
|
|
5
|
+
such an issue**; for how the boundary is meant to work and where your
|
|
6
|
+
responsibilities as a host begin, see [`docs/security-model.md`](docs/security-model.md).
|
|
7
|
+
|
|
8
|
+
## Supported versions
|
|
9
|
+
|
|
10
|
+
kobako is pre-1.0. Security fixes land on the latest released `0.x` version only;
|
|
11
|
+
upgrade to it before reporting.
|
|
12
|
+
|
|
13
|
+
## Reporting a vulnerability
|
|
14
|
+
|
|
15
|
+
Report privately through GitHub's **[Report a vulnerability](https://github.com/elct9620/kobako/security/advisories/new)**
|
|
16
|
+
flow — please do not open a public issue or pull request for a suspected vulnerability.
|
|
17
|
+
|
|
18
|
+
Include the affected version, a minimal guest script or host setup that reproduces the
|
|
19
|
+
issue, and what boundary you expected to hold. You can expect an initial acknowledgement
|
|
20
|
+
within a few days; once a fix or mitigation is agreed, disclosure is coordinated through
|
|
21
|
+
a GitHub Security Advisory. Reporters are credited in the published advisory unless you
|
|
22
|
+
ask to stay anonymous.
|
|
23
|
+
|
|
24
|
+
## Scope
|
|
25
|
+
|
|
26
|
+
In scope is anything that lets guest code cross the isolation boundary it should not:
|
|
27
|
+
reaching host memory, the filesystem, the network, or `ENV`; obtaining ambient time or
|
|
28
|
+
entropy the host froze; reaching a `Namespace::Member` you never bound; or a
|
|
29
|
+
memory-safety fault in the host codec or wasmtime driver.
|
|
30
|
+
|
|
31
|
+
Out of scope is what a bound Service is *designed* to expose: if guest code reaches a
|
|
32
|
+
method because you bound an object carrying it, that is a host-side authorization
|
|
33
|
+
choice, not a sandbox escape — narrow the bound surface as described in the security
|
|
34
|
+
model. Resource exhaustion that stays within the limits you configured is likewise
|
|
35
|
+
expected behaviour, not a vulnerability.
|
data/data/kobako.wasm
CHANGED
|
Binary file
|
data/lib/kobako/3.3/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/3.4/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/4.0/kobako.so
CHANGED
|
Binary file
|
data/lib/kobako/capture.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Kobako
|
|
|
4
4
|
# Host-side captured prefix of guest stdout / stderr produced during a
|
|
5
5
|
# single +Kobako::Sandbox+ invocation, paired with the truncation flag
|
|
6
6
|
# the WASI pipe sets when the guest wrote past the configured per-channel
|
|
7
|
-
# cap
|
|
7
|
+
# cap.
|
|
8
8
|
#
|
|
9
9
|
# Immutable value object: the captured bytes and the truncation flag
|
|
10
10
|
# always travel together and the instance is frozen on construction.
|
|
@@ -30,14 +30,12 @@ module Kobako
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# Returns +true+ iff the underlying capture channel exceeded its
|
|
33
|
-
# configured cap during the originating +Sandbox+ invocation
|
|
34
|
-
# ({docs/behavior.md B-04}[link:../../docs/behavior.md]).
|
|
33
|
+
# configured cap during the originating +Sandbox+ invocation.
|
|
35
34
|
def truncated? = @truncated
|
|
36
35
|
|
|
37
|
-
# Pre-invocation sentinel
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
# yet".
|
|
36
|
+
# Pre-invocation sentinel. Empty UTF-8 bytes and +truncated? == false+;
|
|
37
|
+
# reused by every fresh +Sandbox+ and by +Sandbox+ between invocations
|
|
38
|
+
# to denote "no capture yet".
|
|
41
39
|
EMPTY = new(bytes: "", truncated: false)
|
|
42
40
|
end
|
|
43
41
|
end
|
|
@@ -5,34 +5,29 @@ require_relative "../handle"
|
|
|
5
5
|
module Kobako
|
|
6
6
|
module Catalog
|
|
7
7
|
# Host-side mapping from opaque integer Handle IDs to Ruby objects.
|
|
8
|
-
# The table is owned by +Kobako::Sandbox+
|
|
9
|
-
# ({docs/behavior.md B-19}[link:../../../docs/behavior.md]) and injected
|
|
8
|
+
# The table is owned by +Kobako::Sandbox+ and injected
|
|
10
9
|
# into the per-Sandbox +Kobako::Catalog::Namespaces+ so guest→host dispatch
|
|
11
10
|
# resolves Handle targets and arguments against the same table that
|
|
12
|
-
# host→guest wire encoding allocates into
|
|
13
|
-
# ({docs/behavior.md B-14, B-34}[link:../../../docs/behavior.md]).
|
|
11
|
+
# host→guest wire encoding allocates into.
|
|
14
12
|
#
|
|
15
|
-
# Lifecycle invariants
|
|
13
|
+
# Lifecycle invariants:
|
|
16
14
|
#
|
|
17
|
-
# -
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# +#alloc+.
|
|
15
|
+
# - Handle IDs are allocated by a monotonically increasing counter
|
|
16
|
+
# scoped to a single invocation. The first ID issued in an
|
|
17
|
+
# invocation is 1; ID 0 is reserved as the invalid sentinel and is
|
|
18
|
+
# never returned by +#alloc+.
|
|
22
19
|
#
|
|
23
|
-
# -
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# allocation source (B-14 Service return or B-34 host-injected
|
|
20
|
+
# - At every invocation boundary (via +#reset!+), every Handle issued
|
|
21
|
+
# under the old state becomes invalid. Reset applies uniformly
|
|
22
|
+
# regardless of allocation source (Service return or host-injected
|
|
27
23
|
# argument).
|
|
28
24
|
#
|
|
29
|
-
# -
|
|
30
|
-
#
|
|
31
|
-
# immediately — no silent truncation, no wrap, no ID reuse.
|
|
25
|
+
# - The cap is +0x7fff_ffff+ (2³¹ − 1). Allocation beyond the cap
|
|
26
|
+
# raises immediately — no silent truncation, no wrap, no ID reuse.
|
|
32
27
|
class Handles
|
|
33
28
|
# Build a fresh, empty table. +next_id+ is an internal seam that
|
|
34
|
-
# sets the starting value of the monotonic counter (defaults to 1
|
|
35
|
-
#
|
|
29
|
+
# sets the starting value of the monotonic counter (defaults to 1);
|
|
30
|
+
# tests pass a value near +Kobako::Handle::MAX_ID+ to exercise
|
|
36
31
|
# the cap-exhaustion path without 2³¹ allocations.
|
|
37
32
|
def initialize(next_id: 1)
|
|
38
33
|
@entries = {} # : Hash[Integer, untyped]
|
|
@@ -45,8 +40,7 @@ module Kobako
|
|
|
45
40
|
# +[Kobako::Handle::MIN_ID, Kobako::Handle::MAX_ID]+. Raises
|
|
46
41
|
# +Kobako::HandlerExhaustedError+ if the next ID would exceed the
|
|
47
42
|
# cap. The cap is anchored on +Kobako::Handle+ — the wire codec
|
|
48
|
-
# and the allocator share the same invariant
|
|
49
|
-
# ({docs/behavior.md B-21}[link:../../../docs/behavior.md]).
|
|
43
|
+
# and the allocator share the same invariant.
|
|
50
44
|
#
|
|
51
45
|
# Returning a Handle (rather than a bare Integer id) keeps the
|
|
52
46
|
# allocator's output a domain entity; +Kobako::Handle.restore+
|
|
@@ -69,9 +63,8 @@ module Kobako
|
|
|
69
63
|
@entries[id]
|
|
70
64
|
end
|
|
71
65
|
|
|
72
|
-
# Clear all entries AND reset the counter to 1. Called at the
|
|
73
|
-
# boundary by +Kobako::Sandbox
|
|
74
|
-
# {docs/behavior.md B-19}[link:../../../docs/behavior.md]. Returns +self+.
|
|
66
|
+
# Clear all entries AND reset the counter to 1. Called at the
|
|
67
|
+
# per-invocation boundary by +Kobako::Sandbox+. Returns +self+.
|
|
75
68
|
def reset!
|
|
76
69
|
@entries.clear
|
|
77
70
|
@next_id = 1
|
|
@@ -89,12 +82,12 @@ module Kobako
|
|
|
89
82
|
|
|
90
83
|
private
|
|
91
84
|
|
|
92
|
-
# Refuse to mint a Capability Handle for a reflective gadget
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
85
|
+
# Refuse to mint a Capability Handle for a reflective gadget:
|
|
86
|
+
# a +Binding+ / +Method+ / +UnboundMethod+ would hand the guest a
|
|
87
|
+
# callable proxy onto host reflection (a returned +Binding+ reaches
|
|
88
|
+
# +Binding#eval+). Raising here keeps the rule at the single mint
|
|
89
|
+
# point, so it holds on both the Service-return and the +#run+
|
|
90
|
+
# host→guest auto-wrap paths.
|
|
98
91
|
def reject_unwrappable!(object)
|
|
99
92
|
case object
|
|
100
93
|
when Binding, Method, UnboundMethod
|
|
@@ -102,7 +95,7 @@ module Kobako
|
|
|
102
95
|
end
|
|
103
96
|
end
|
|
104
97
|
|
|
105
|
-
# Guard {#alloc} against issuing an ID past the
|
|
98
|
+
# Guard {#alloc} against issuing an ID past the cap. Returns +nil+
|
|
106
99
|
# on success; raises +Kobako::HandlerExhaustedError+ at exhaustion.
|
|
107
100
|
def ensure_capacity!
|
|
108
101
|
cap = Kobako::Handle::MAX_ID
|
|
@@ -9,8 +9,7 @@ module Kobako
|
|
|
9
9
|
module Catalog
|
|
10
10
|
# Kobako::Catalog::Namespaces — per-Sandbox registry of
|
|
11
11
|
# +Kobako::Namespace+ entities. Holds the Namespace / Member bindings
|
|
12
|
-
# and the preamble emitted on Frame 1
|
|
13
|
-
# ({docs/behavior.md B-07..B-11}[link:../../../docs/behavior.md]).
|
|
12
|
+
# and the preamble emitted on Frame 1.
|
|
14
13
|
#
|
|
15
14
|
# Public API:
|
|
16
15
|
#
|
|
@@ -24,14 +23,13 @@ module Kobako
|
|
|
24
23
|
# +Kobako::Transport::Dispatcher+'s responsibility — the Dispatcher
|
|
25
24
|
# receives this registry and the +Catalog::Handles+ as arguments from
|
|
26
25
|
# the +Runtime#on_dispatch+ Proc that +Kobako::Sandbox#initialize+
|
|
27
|
-
# installs
|
|
28
|
-
# The registry holds an injected +Catalog::Handles+ reference so
|
|
26
|
+
# installs. The registry holds an injected +Catalog::Handles+ reference so
|
|
29
27
|
# dispatch target resolution and host→guest auto-wrap share the same
|
|
30
|
-
# Sandbox-owned allocator
|
|
28
|
+
# Sandbox-owned allocator.
|
|
31
29
|
class Namespaces
|
|
32
30
|
# Build a fresh registry. +handler+ is an internal seam that injects
|
|
33
31
|
# a pre-configured +Catalog::Handles+; tests pass one whose +next_id+
|
|
34
|
-
# is pinned near +MAX_ID+ to exercise the
|
|
32
|
+
# is pinned near +MAX_ID+ to exercise the cap-exhaustion path
|
|
35
33
|
# without 2³¹ allocations. Production callers leave it at the default.
|
|
36
34
|
def initialize(handler: Catalog::Handles.new)
|
|
37
35
|
@namespaces = {} # : Hash[String, Kobako::Namespace]
|
|
@@ -40,14 +38,12 @@ module Kobako
|
|
|
40
38
|
@encoded = nil # : String?
|
|
41
39
|
end
|
|
42
40
|
|
|
43
|
-
# Declare or retrieve the Namespace named +name+ (idempotent
|
|
44
|
-
# {docs/behavior.md B-10}[link:../../../docs/behavior.md]).
|
|
41
|
+
# Declare or retrieve the Namespace named +name+ (idempotent).
|
|
45
42
|
# +name+ is a constant-form name as a +Symbol+ or +String+ (must satisfy
|
|
46
43
|
# +Namespace::NAME_PATTERN+). Returns the +Kobako::Namespace+ for that
|
|
47
44
|
# name, creating it if it does not exist. Raises +ArgumentError+ when
|
|
48
45
|
# +name+ is malformed, or when called after the owning Sandbox has been
|
|
49
|
-
# sealed by its first invocation
|
|
50
|
-
# ({docs/behavior.md B-07}[link:../../../docs/behavior.md]).
|
|
46
|
+
# sealed by its first invocation.
|
|
51
47
|
def define(name)
|
|
52
48
|
raise ArgumentError, "cannot define after first Sandbox invocation" if @sealed
|
|
53
49
|
|
|
@@ -74,20 +70,18 @@ module Kobako
|
|
|
74
70
|
namespace.fetch(member_name)
|
|
75
71
|
end
|
|
76
72
|
|
|
77
|
-
# Encode the preamble as msgpack bytes for stdin Frame 1 delivery
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# +String+ of msgpack bytes.
|
|
73
|
+
# Encode the preamble as msgpack bytes for stdin Frame 1 delivery.
|
|
74
|
+
# Routes through {Kobako::Codec::Encoder} like every other host-side
|
|
75
|
+
# wire encode so there is a single codec path; the preamble carries
|
|
76
|
+
# only Strings and Arrays, so none of the kobako ext types actually
|
|
77
|
+
# fire. Structure: +[["Namespace", ["MemberA", "MemberB"]], ...]+.
|
|
78
|
+
# Returns a binary +String+ of msgpack bytes.
|
|
84
79
|
#
|
|
85
80
|
# Once sealed, the bytes are computed once and reused for every
|
|
86
|
-
# subsequent invocation:
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# and never alters Frame 1.
|
|
81
|
+
# subsequent invocation: sealing freezes Service registration at the
|
|
82
|
+
# first invocation, so the preamble is exactly the bindings that
|
|
83
|
+
# existed at that moment — a bind reaching a +Kobako::Namespace+
|
|
84
|
+
# after the seal raises +ArgumentError+ and never alters Frame 1.
|
|
91
85
|
def encode
|
|
92
86
|
return @encoded if @encoded
|
|
93
87
|
|
|
@@ -97,11 +91,9 @@ module Kobako
|
|
|
97
91
|
end
|
|
98
92
|
|
|
99
93
|
# Mark the registry as sealed and propagate the seal to every
|
|
100
|
-
# declared +Kobako::Namespace+
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
# raises ArgumentError (E-18) and +Namespace#bind+ raises
|
|
104
|
-
# ArgumentError (E-45). Idempotent.
|
|
94
|
+
# declared +Kobako::Namespace+. Called by +Sandbox+ on the first
|
|
95
|
+
# invocation. After sealing, both #define and +Namespace#bind+
|
|
96
|
+
# raise ArgumentError. Idempotent.
|
|
105
97
|
def seal!
|
|
106
98
|
return self if @sealed
|
|
107
99
|
|
|
@@ -6,8 +6,7 @@ require_relative "../snippet"
|
|
|
6
6
|
module Kobako
|
|
7
7
|
module Catalog
|
|
8
8
|
# Kobako::Catalog::Snippets — per-Sandbox insertion-ordered registry
|
|
9
|
-
# of preloaded snippets
|
|
10
|
-
# ({docs/behavior.md B-32 / B-33}[link:../../../docs/behavior.md]).
|
|
9
|
+
# of preloaded snippets.
|
|
11
10
|
#
|
|
12
11
|
# Entries replay against the fresh +mrb_state+ before per-invocation
|
|
13
12
|
# source / entrypoint resolution. Each +Snippet::Source+ entry's +name+
|
|
@@ -15,18 +14,16 @@ module Kobako
|
|
|
15
14
|
# +debug_info+ that surfaces in every backtrace frame originating from
|
|
16
15
|
# the snippet as +(snippet:Name):line+. Duplicate names within the
|
|
17
16
|
# +code:+ form would produce ambiguous attribution and are rejected at
|
|
18
|
-
# registration time
|
|
19
|
-
# ({docs/behavior.md E-33}[link:../../../docs/behavior.md]).
|
|
17
|
+
# registration time.
|
|
20
18
|
# +Snippet::Binary+ entries carry no host-side name — their canonical
|
|
21
19
|
# name lives in the bytecode's +debug_info+ and is read by the guest at
|
|
22
20
|
# load time; the host does not extract it.
|
|
23
21
|
#
|
|
24
|
-
# Sealing
|
|
22
|
+
# Sealing is governed by the owning Sandbox — the registry itself
|
|
25
23
|
# is append-only and exposes no mutation API beyond +#register+; the
|
|
26
24
|
# Sandbox guards +#register+ behind the seal check before delegating.
|
|
27
25
|
class Snippets
|
|
28
|
-
# Ruby constant-name pattern enforced on snippet names
|
|
29
|
-
# ({docs/behavior.md E-34}[link:../../../docs/behavior.md]).
|
|
26
|
+
# Ruby constant-name pattern enforced on snippet names.
|
|
30
27
|
NAME_PATTERN = /\A[A-Z]\w*\z/
|
|
31
28
|
|
|
32
29
|
def initialize
|
|
@@ -45,7 +42,7 @@ module Kobako
|
|
|
45
42
|
# self-encode.
|
|
46
43
|
#
|
|
47
44
|
# The bytes are memoized — the table is replayed verbatim on every
|
|
48
|
-
# invocation after
|
|
45
|
+
# invocation after sealing, so Frame 3 never changes between
|
|
49
46
|
# encodes; {#register} drops the memo while the table is still open.
|
|
50
47
|
def encode
|
|
51
48
|
return @encoded if @encoded
|
|
@@ -53,8 +50,7 @@ module Kobako
|
|
|
53
50
|
@encoded = Codec::Encoder.encode(@entries.map { |entry| entry_payload(entry) }).freeze
|
|
54
51
|
end
|
|
55
52
|
|
|
56
|
-
# Register one preloaded snippet in either of two forms
|
|
57
|
-
# ({docs/behavior.md B-32}[link:../../../docs/behavior.md]).
|
|
53
|
+
# Register one preloaded snippet in either of two forms.
|
|
58
54
|
#
|
|
59
55
|
# * Source form +register(code: src, name: Name)+ — +src+ is the
|
|
60
56
|
# mruby source as a String; the bytes are re-encoded as UTF-8
|
|
@@ -65,15 +61,13 @@ module Kobako
|
|
|
65
61
|
# precompiled RITE bytecode as a String, duplicated and forced
|
|
66
62
|
# to ASCII-8BIT so msgpack-ruby ships it as +bin+. Returns
|
|
67
63
|
# +nil+ — bytecode entries are anonymous on the host side; any
|
|
68
|
-
# structural validation
|
|
69
|
-
# ({docs/behavior.md E-37 / E-38}[link:../../../docs/behavior.md])
|
|
70
|
-
# is deferred to the guest at first replay.
|
|
64
|
+
# structural validation is deferred to the guest at first replay.
|
|
71
65
|
#
|
|
72
66
|
# The two forms are mutually exclusive: shape validation lives
|
|
73
67
|
# here so callers (chiefly +Kobako::Sandbox#preload+) collapse to
|
|
74
68
|
# a single delegation. Raises +ArgumentError+ on mixed forms,
|
|
75
|
-
# missing keywords, wrong types, malformed +name
|
|
76
|
-
# duplicate +code:+ +name
|
|
69
|
+
# missing keywords, wrong types, malformed +name+, or
|
|
70
|
+
# duplicate +code:+ +name+.
|
|
77
71
|
def register(code: nil, name: nil, binary: nil)
|
|
78
72
|
@encoded = nil
|
|
79
73
|
if binary
|
|
@@ -89,7 +83,7 @@ module Kobako
|
|
|
89
83
|
|
|
90
84
|
# Source-form register path. Delegates argument-shape checks to
|
|
91
85
|
# +ensure_source_args!+ (which returns the narrowed +[code, name]+
|
|
92
|
-
# pair), normalises +name+ to a Symbol, rejects duplicates
|
|
86
|
+
# pair), normalises +name+ to a Symbol, rejects duplicates,
|
|
93
87
|
# and appends the Source entry.
|
|
94
88
|
def register_source!(code, name)
|
|
95
89
|
code, name = ensure_source_args!(code, name)
|
data/lib/kobako/codec/utils.rb
CHANGED
|
@@ -19,8 +19,7 @@ module Kobako
|
|
|
19
19
|
# - Representability predicate ({representable?}) and the symmetric
|
|
20
20
|
# host→guest +#run+ argument walk ({deep_wrap}) used by
|
|
21
21
|
# +Kobako::Transport::Run#encode+ to route non-representable leaves
|
|
22
|
-
# through the Sandbox's +Kobako::Catalog::Handles
|
|
23
|
-
# ({docs/behavior.md B-34}[link:../../../docs/behavior.md]).
|
|
22
|
+
# through the Sandbox's +Kobako::Catalog::Handles+.
|
|
24
23
|
#
|
|
25
24
|
# All helpers are pure — they only inspect inputs, never mutate
|
|
26
25
|
# them — except {deep_wrap}, whose only side effect is allocating
|
|
@@ -84,8 +83,7 @@ module Kobako
|
|
|
84
83
|
|
|
85
84
|
# Deep-walk Array / Hash containers in +value+ and replace every
|
|
86
85
|
# leaf that fails {representable?} with a +Kobako::Handle+
|
|
87
|
-
# allocated from +handler
|
|
88
|
-
# ({docs/behavior.md B-34}[link:../../../docs/behavior.md]). The
|
|
86
|
+
# allocated from +handler+. The
|
|
89
87
|
# walk only descends through representable container shapes
|
|
90
88
|
# (Array, Hash) one structural level at a time; a non-representable
|
|
91
89
|
# leaf is wrapped as-is without inspecting its internal structure.
|
|
@@ -116,8 +114,7 @@ module Kobako
|
|
|
116
114
|
|
|
117
115
|
# Deep-walk Array / Hash containers in +value+ and replace every
|
|
118
116
|
# +Kobako::Handle+ leaf with the host-side object +handler+ resolves
|
|
119
|
-
# it to
|
|
120
|
-
# The symmetric inverse of {deep_wrap}: that walk allocates objects
|
|
117
|
+
# it to. The symmetric inverse of {deep_wrap}: that walk allocates objects
|
|
121
118
|
# into Handles on the host→guest argument path; this walk resolves
|
|
122
119
|
# Handles back to their objects on every guest→host value path — the
|
|
123
120
|
# +#eval+ / +#run+ result and the yield-block result alike. The walk
|
|
@@ -126,11 +123,11 @@ module Kobako
|
|
|
126
123
|
# unchanged.
|
|
127
124
|
#
|
|
128
125
|
# +value+ is a decoded Ruby value (a Handle here is a wire-decoded
|
|
129
|
-
# +Kobako::Handle+, never a guest-forged one
|
|
126
|
+
# +Kobako::Handle+, never a guest-forged one); +handler+ must
|
|
130
127
|
# respond to +#fetch(id) -> object+ (a host-side
|
|
131
128
|
# +Kobako::Catalog::Handles+). +handler.fetch+ raises
|
|
132
|
-
# +Kobako::SandboxError+ for an id with no live binding,
|
|
133
|
-
# corrupted-runtime fallback
|
|
129
|
+
# +Kobako::SandboxError+ for an id with no live binding, the
|
|
130
|
+
# corrupted-runtime fallback.
|
|
134
131
|
def deep_restore(value, handler)
|
|
135
132
|
case value
|
|
136
133
|
when ::Array then value.map { |element| Utils.deep_restore(element, handler) }
|