kobako 0.12.1 → 0.13.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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +22 -0
  4. data/Cargo.lock +138 -136
  5. data/Cargo.toml +6 -2
  6. data/README.md +3 -1
  7. data/crates/kobako-runtime/CHANGELOG.md +16 -0
  8. data/crates/kobako-runtime/Cargo.toml +23 -0
  9. data/crates/kobako-runtime/README.md +34 -0
  10. data/crates/kobako-runtime/src/dispatch.rs +22 -0
  11. data/crates/kobako-runtime/src/error.rs +64 -0
  12. data/crates/kobako-runtime/src/lib.rs +18 -0
  13. data/crates/kobako-runtime/src/profile.rs +35 -0
  14. data/crates/kobako-runtime/src/runtime.rs +57 -0
  15. data/crates/kobako-runtime/src/snapshot.rs +46 -0
  16. data/crates/kobako-runtime/src/yielder.rs +22 -0
  17. data/crates/kobako-wasmtime/CHANGELOG.md +16 -0
  18. data/crates/kobako-wasmtime/Cargo.toml +62 -0
  19. data/crates/kobako-wasmtime/README.md +32 -0
  20. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/ambient.rs +8 -5
  21. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/cache.rs +30 -41
  22. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/capture.rs +2 -2
  23. data/crates/kobako-wasmtime/src/config.rs +33 -0
  24. data/crates/kobako-wasmtime/src/dispatch.rs +110 -0
  25. data/crates/kobako-wasmtime/src/driver.rs +297 -0
  26. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/exports.rs +5 -6
  27. data/crates/kobako-wasmtime/src/frames.rs +276 -0
  28. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/guest_mem.rs +38 -15
  29. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/instance_pre.rs +13 -21
  30. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/invocation.rs +54 -49
  31. data/crates/kobako-wasmtime/src/lib.rs +47 -0
  32. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/trap.rs +29 -35
  33. data/data/kobako.wasm +0 -0
  34. data/ext/kobako/Cargo.toml +9 -32
  35. data/ext/kobako/src/lib.rs +0 -2
  36. data/ext/kobako/src/runtime/bridge.rs +150 -0
  37. data/ext/kobako/src/runtime/errors.rs +45 -13
  38. data/ext/kobako/src/runtime.rs +246 -420
  39. data/lib/kobako/catalog/handles.rb +3 -3
  40. data/lib/kobako/catalog/namespaces.rb +4 -0
  41. data/lib/kobako/catalog/snippets.rb +4 -0
  42. data/lib/kobako/codec/encoder.rb +5 -1
  43. data/lib/kobako/codec/factory.rb +41 -13
  44. data/lib/kobako/codec/handle_walk.rb +4 -0
  45. data/lib/kobako/codec.rb +1 -1
  46. data/lib/kobako/errors.rb +18 -16
  47. data/lib/kobako/sandbox.rb +71 -39
  48. data/lib/kobako/sandbox_options.rb +71 -13
  49. data/lib/kobako/transport/dispatcher.rb +2 -2
  50. data/lib/kobako/transport/response.rb +14 -14
  51. data/lib/kobako/transport/run.rb +2 -6
  52. data/lib/kobako/transport/yield.rb +1 -1
  53. data/lib/kobako/transport/yielder.rb +2 -2
  54. data/lib/kobako/version.rb +1 -1
  55. data/lib/kobako.rb +0 -1
  56. data/release-please-config.json +78 -3
  57. data/sig/kobako/codec/factory.rbs +3 -0
  58. data/sig/kobako/errors.rbs +7 -14
  59. data/sig/kobako/runtime.rbs +16 -6
  60. data/sig/kobako/sandbox.rbs +11 -7
  61. data/sig/kobako/sandbox_options.rbs +15 -4
  62. data/sig/kobako/transport/dispatcher.rbs +1 -1
  63. data/sig/kobako/transport/run.rbs +2 -2
  64. data/sig/kobako/transport/yielder.rbs +2 -2
  65. data/sig/kobako/transport.rbs +8 -0
  66. metadata +28 -15
  67. data/ext/kobako/src/runtime/config.rs +0 -25
  68. data/ext/kobako/src/runtime/dispatch.rs +0 -211
  69. data/ext/kobako/src/runtime/frames.rs +0 -188
  70. data/ext/kobako/src/snapshot.rs +0 -110
  71. data/lib/kobako/snapshot.rb +0 -38
  72. data/sig/kobako/snapshot.rbs +0 -15
data/Cargo.toml CHANGED
@@ -6,8 +6,12 @@
6
6
  members = ["./ext/kobako"]
7
7
  # `wasm/` is a sibling workspace (kobako-wasm crate) compiled for
8
8
  # wasm32-wasip1; excluding it keeps the host (wasmtime) and guest
9
- # dependency graphs separate.
10
- exclude = ["wasm", "vendor"]
9
+ # dependency graphs separate. `crates/` is the native host-side crate
10
+ # workspace; excluding it lets the ext consume its members as plain
11
+ # path dependencies resolved as standalone packages — the same shape
12
+ # the packaged gem sees, which ships the crates without their
13
+ # workspace manifest.
14
+ exclude = ["wasm", "vendor", "crates"]
11
15
  resolver = "2"
12
16
 
13
17
  # Strip the local-symbol table from the shipped ext (~22% of the binary).
data/README.md CHANGED
@@ -127,7 +127,7 @@ end
127
127
  |---------------------------------|----------------|------------------------------------------------------|
128
128
  | `Kobako::TimeoutError` | `TrapError` | Per-invocation `timeout` exhausted |
129
129
  | `Kobako::MemoryLimitError` | `TrapError` | Per-invocation `memory_limit` exhausted |
130
- | `Kobako::HandlerExhaustedError` | `SandboxError` | Handle counter reached its 2³¹ − 1 cap |
130
+ | `Kobako::HandleExhaustedError` | `SandboxError` | Handle counter reached its 2³¹ − 1 cap |
131
131
  | `Kobako::BytecodeError` | `SandboxError` | `#preload(binary:)` failed RITE validation at replay |
132
132
 
133
133
  `SandboxError` and `ServiceError` carry structured `origin` / `klass` / `backtrace_lines` / `details` fields when the guest produced a panic envelope.
@@ -154,6 +154,8 @@ sandbox = Kobako::Sandbox.new(
154
154
 
155
155
  `memory_limit` covers the per-invocation `memory.grow` delta from the entry baseline, so a Sandbox reused across invocations does not silently accumulate against a global budget.
156
156
 
157
+ A fifth option, `profile:`, requests the Sandbox's isolation posture on the `:permissive` < `:hermetic` ladder (default `:hermetic`). `:hermetic` denies the guest ambient time and entropy; `:permissive` lets the guest's `wasi:clocks` / `wasi:random` read live host sources — an explicit trade of reproducibility, with filesystem, environment, and network still unreachable. The request is also a floor: construction fails with `Kobako::SetupError` on a runtime that declares a weaker posture than requested. See [`docs/security-model.md`](docs/security-model.md) § Isolation profiles.
158
+
157
159
  ### Invocation Lifecycle
158
160
 
159
161
  One Sandbox serves many invocations. Service bindings and preloaded snippets persist across calls; capability state (Handles, stdout, stderr, memory delta) resets between them.
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## [0.7.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.6.1...kobako-runtime-v0.7.0) (2026-07-03)
4
+
5
+
6
+ ### Features
7
+
8
+ * **crates:** build the requested isolation profile into the WASI context ([63c25d8](https://github.com/elct9620/kobako/commit/63c25d835d4d03010c1658217cee412318e6b5d8))
9
+ * **runtime:** runtimes declare their isolation profile ([f89717a](https://github.com/elct9620/kobako/commit/f89717a6bc9809f0de0df78f97da33a49a1474ac))
10
+
11
+ ## [0.6.1](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.6.0...kobako-runtime-v0.6.1) (2026-07-02)
12
+
13
+
14
+ ### Miscellaneous Chores
15
+
16
+ * **kobako-runtime:** Synchronize kobako crates versions
@@ -0,0 +1,23 @@
1
+ # kobako-runtime — engine-neutral host runtime contract.
2
+ #
3
+ # The contract a wasm engine must satisfy to drive a kobako guest,
4
+ # free of any engine or frontend type: the `Runtime` trait plus the
5
+ # neutral value types (`Snapshot`, `Trap`, `SetupError`, ...) and the
6
+ # dispatch / yield re-entry traits a frontend supplies. Engine
7
+ # implementations (`kobako-wasmtime`) and host frontends (the Ruby
8
+ # ext) meet on this surface, so the engine stays swappable.
9
+ #
10
+ # Self-contained by design: the Ruby gem ships this crate without the
11
+ # `crates/` workspace manifest, so no `workspace = true` inheritance
12
+ # fields may appear here.
13
+
14
+ [package]
15
+ name = "kobako-runtime"
16
+ version = "0.7.0"
17
+ edition = "2021"
18
+ description = "Engine-neutral host runtime contract for embedding kobako Wasm guests."
19
+ license = "Apache-2.0"
20
+ repository = "https://github.com/elct9620/kobako"
21
+ readme = "README.md"
22
+ keywords = ["wasm", "mruby", "sandbox", "wasi"]
23
+ categories = ["wasm", "virtualization"]
@@ -0,0 +1,34 @@
1
+ # kobako-runtime
2
+
3
+ Engine-neutral host runtime contract for
4
+ [kobako](https://github.com/elct9620/kobako), an in-process Wasm
5
+ sandbox for running untrusted mruby scripts.
6
+
7
+ A kobako host drives a Guest Binary through a wasm engine; this crate
8
+ is the surface where the two meet, free of any engine or frontend
9
+ type, so the engine stays swappable:
10
+
11
+ - `runtime` — the `Runtime` trait: one guest invocation on a fresh
12
+ instance in, its observable `Snapshot` out
13
+ - `snapshot` — the per-invocation observables: `Completion` (outcome
14
+ or trap), the two output `Capture`s, and resource `Usage`, uniform
15
+ across success and trap
16
+ - `error` — the neutral failure channels: `Trap` (engine fault) and
17
+ `SetupError` (the invocation never started)
18
+ - `dispatch` / `yielder` — the `DispatchHandler` and `Yielder` traits
19
+ a frontend supplies for guest→host dispatch and block-yield re-entry
20
+
21
+ Engine implementations (such as `kobako-wasmtime`) implement
22
+ `Runtime`; host frontends (such as the kobako Ruby gem's native ext)
23
+ map the neutral types onto their own language surface.
24
+
25
+ ## Usage
26
+
27
+ ```toml
28
+ [dependencies]
29
+ kobako-runtime = "0.7.0" # x-release-please-version
30
+ ```
31
+
32
+ ## License
33
+
34
+ Apache-2.0
@@ -0,0 +1,22 @@
1
+ //! Engine-neutral guest→host dispatch contract, free of any `magnus`
2
+ //! dependency.
3
+ //!
4
+ //! The wasm runtime hands a handler the raw Request bytes a guest produced
5
+ //! and expects raw Response bytes back. What the handler *is* — a Ruby Proc,
6
+ //! a Rust closure — is the frontend's concern; the runtime only sees this
7
+ //! trait. The Ruby ext conforms by bridging its dispatch Proc behind it.
8
+
9
+ use crate::yielder::Yielder;
10
+
11
+ /// Host-side handler for a guest→host dispatch.
12
+ ///
13
+ /// `dispatch` receives the request bytes plus a `Yielder` for re-entering
14
+ /// the in-flight guest when a Service method yields to a block, and returns
15
+ /// the raw Response bytes — or `None` when the handler itself failed, in
16
+ /// which case the runtime walks its 0-return wire-fault path. The bound
17
+ /// handler is contracted to fold application failures into a `Response.err`
18
+ /// envelope, so `None` signals a contract violation (the handler raised)
19
+ /// rather than a normal dispatch outcome.
20
+ pub trait DispatchHandler: Send + Sync {
21
+ fn dispatch(&self, request: &[u8], yielder: &mut dyn Yielder) -> Option<Vec<u8>>;
22
+ }
@@ -0,0 +1,64 @@
1
+ //! Engine-neutral host error channels, free of any frontend dependency.
2
+ //!
3
+ //! The run path produces these instead of constructing host-language
4
+ //! exceptions directly; each frontend's boundary is the single place that
5
+ //! maps them onto its own error classes (the Ruby ext does so in its error
6
+ //! mapper). Keeping the channels frontend-free lets any engine
7
+ //! implementation produce them unchanged.
8
+
9
+ use std::fmt;
10
+
11
+ /// A guest invocation that faulted in the wasm engine, or a host-detected
12
+ /// runtime corruption during invocation, classified into the host-facing
13
+ /// kinds a frontend surfaces distinctly: the wall-clock cap (`Timeout`),
14
+ /// the linear-memory cap (`MemoryLimit`), and every other engine fault
15
+ /// (`Other`).
16
+ #[derive(Debug)]
17
+ pub enum Trap {
18
+ Timeout(String),
19
+ MemoryLimit(String),
20
+ Other(String),
21
+ }
22
+
23
+ /// A failure that yields no invocation outcome. The discriminant records
24
+ /// the runtime's state so a frontend can attribute the failure per SPEC:
25
+ /// `ModuleNotBuilt` (the guest artifact is absent), `Dead` (the runtime
26
+ /// could not be constructed), and `Intact` (the runtime is live but a
27
+ /// host-side pre-call step failed, so no discard-and-recreate recovery is
28
+ /// owed).
29
+ #[derive(Debug)]
30
+ pub enum SetupError {
31
+ ModuleNotBuilt(String),
32
+ Dead(String),
33
+ Intact(String),
34
+ }
35
+
36
+ /// A failure that prevented the invocation from starting — a pre-call
37
+ /// engine fault (`Trap`) or a host-side setup fault (`Setup`) — unified so
38
+ /// the run mechanics can propagate both with `?`; a frontend destructures
39
+ /// it back into the two channels. Faults after the guest export starts
40
+ /// ride in `Completion::Trap` instead, so captures and usage survive them.
41
+ #[derive(Debug)]
42
+ pub enum Error {
43
+ Trap(Trap),
44
+ Setup(SetupError),
45
+ }
46
+
47
+ impl From<Trap> for Error {
48
+ fn from(trap: Trap) -> Self {
49
+ Error::Trap(trap)
50
+ }
51
+ }
52
+
53
+ impl From<SetupError> for Error {
54
+ fn from(err: SetupError) -> Self {
55
+ Error::Setup(err)
56
+ }
57
+ }
58
+
59
+ impl fmt::Display for Trap {
60
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61
+ let (Trap::Timeout(msg) | Trap::MemoryLimit(msg) | Trap::Other(msg)) = self;
62
+ f.write_str(msg)
63
+ }
64
+ }
@@ -0,0 +1,18 @@
1
+ //! kobako-runtime — engine-neutral host runtime contract.
2
+ //!
3
+ //! The surface where a wasm engine implementation and a host frontend
4
+ //! meet: the `Runtime` trait, the isolation `Profile` a runtime
5
+ //! declares, the neutral per-invocation value types, and the
6
+ //! dispatch / yield re-entry traits a frontend supplies.
7
+ //! Nothing here depends on an engine or a frontend type — each engine
8
+ //! hides its own machinery behind `Runtime`, and each frontend maps
9
+ //! these shapes onto its own host-language surface at its boundary
10
+ //! (for the Ruby ext that is the error mapper in its runtime module),
11
+ //! so the engine stays swappable.
12
+
13
+ pub mod dispatch;
14
+ pub mod error;
15
+ pub mod profile;
16
+ pub mod runtime;
17
+ pub mod snapshot;
18
+ pub mod yielder;
@@ -0,0 +1,35 @@
1
+ //! Isolation profile — the posture a frontend requests and a runtime
2
+ //! builds and declares.
3
+ //!
4
+ //! A rung on an ordered ladder: the host application requests the
5
+ //! posture it wants, the runtime builds it and declares the posture it
6
+ //! actually built, and the frontend refuses a declaration below the
7
+ //! request — so the request is also the floor. The governing contract
8
+ //! lives in the spec corpus (docs/behavior/security.md).
9
+
10
+ /// The ordered isolation ladder a runtime builds one rung of.
11
+ ///
12
+ /// `Hermetic` is the full ambient-denial posture: ambient time and
13
+ /// entropy denied at the WASI boundary, no filesystem / environment /
14
+ /// network reachability, and no host import beyond the wire ABI's
15
+ /// `__kobako_dispatch`. `Permissive` differs in exactly one grant —
16
+ /// live ambient time and entropy at the WASI boundary. Ordering
17
+ /// follows strength (`Permissive < Hermetic`), so a floor check is a
18
+ /// plain comparison.
19
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
20
+ pub enum Profile {
21
+ Permissive,
22
+ Hermetic,
23
+ }
24
+
25
+ #[cfg(test)]
26
+ mod tests {
27
+ use super::*;
28
+
29
+ #[test]
30
+ fn a_declaration_satisfies_any_floor_at_or_below_it() {
31
+ assert!(Profile::Hermetic >= Profile::Hermetic);
32
+ assert!(Profile::Hermetic >= Profile::Permissive);
33
+ assert!(Profile::Permissive < Profile::Hermetic);
34
+ }
35
+ }
@@ -0,0 +1,57 @@
1
+ //! Engine-neutral host runtime contract.
2
+ //!
3
+ //! The contract a wasm engine must satisfy to drive a kobako guest: take a
4
+ //! per-invocation entry plus its stdin frames, run one invocation on a
5
+ //! fresh instance, and return the observable `Snapshot` — `Ok` iff the
6
+ //! guest export ran, `Err` when the invocation never started. Nothing here
7
+ //! mentions `magnus` or a Ruby type — a frontend supplies the dispatch
8
+ //! handler, the contract only borrows it.
9
+
10
+ use std::sync::Arc;
11
+
12
+ use crate::dispatch::DispatchHandler;
13
+ use crate::error::Error;
14
+ use crate::profile::Profile;
15
+ use crate::snapshot::Snapshot;
16
+
17
+ /// The per-invocation entry: a one-shot mruby source (`Eval`) or an
18
+ /// entrypoint-dispatch envelope (`Run`). Both ride alongside the stdin
19
+ /// `Frames`; `Run` additionally copies its envelope into guest memory.
20
+ pub enum Entry<'a> {
21
+ Eval { source: &'a [u8] },
22
+ Run { envelope: &'a [u8] },
23
+ }
24
+
25
+ /// The stdin frames shared by both entries: the Frame 1 preamble (the
26
+ /// Sandbox's registrations) and the Frame 3 snippet-replay payload.
27
+ pub struct Frames<'a> {
28
+ pub preamble: &'a [u8],
29
+ pub snippets: &'a [u8],
30
+ }
31
+
32
+ /// Engine-neutral runtime: drives one guest invocation on a fresh instance
33
+ /// and returns its observable `Snapshot`. `Ok` means the guest export ran
34
+ /// — the `Snapshot` carries the completion (outcome or trap), captures,
35
+ /// and usage uniformly; `Err` means the invocation never started.
36
+ ///
37
+ /// Safety contract for `handler`: the runtime only *borrows* the handler
38
+ /// for the duration of `invoke` and never roots it. A frontend whose
39
+ /// handler references a GC-managed object (e.g. a Ruby `Proc`) must keep
40
+ /// that object alive — and, under a moving GC, pinned — for the whole call.
41
+ /// The Ruby ext does this by holding the `Proc` on its long-lived Runtime
42
+ /// wrapper and GC-marking it; the runtime itself touches no frontend
43
+ /// value.
44
+ pub trait Runtime {
45
+ fn invoke(
46
+ &self,
47
+ entry: Entry<'_>,
48
+ frames: Frames<'_>,
49
+ handler: Option<Arc<dyn DispatchHandler>>,
50
+ ) -> Result<Snapshot, Error>;
51
+
52
+ /// The isolation profile this runtime provides. Deliberately
53
+ /// without a default: every engine states its posture explicitly,
54
+ /// and a frontend refuses construction when the declaration falls
55
+ /// below the floor its host application requested.
56
+ fn profile(&self) -> Profile;
57
+ }
@@ -0,0 +1,46 @@
1
+ //! Engine-neutral, frontend-free per-invocation observable bundle.
2
+ //!
3
+ //! The observables of a single guest invocation, expressed without any
4
+ //! frontend type and uniform across success and trap: how the invocation
5
+ //! completed, the two captured output channels, and the resource usage.
6
+ //! A `Snapshot` exists iff the guest export ran — a failure to even start
7
+ //! the invocation travels on the `invoke` `Err` channel instead.
8
+
9
+ use crate::error::Trap;
10
+
11
+ /// One captured output channel: the bytes the guest wrote (already clipped
12
+ /// to the channel's cap) and whether the cap was reached.
13
+ pub struct Capture {
14
+ pub bytes: Vec<u8>,
15
+ pub truncated: bool,
16
+ }
17
+
18
+ /// How the guest invocation completed: `Outcome` carries the
19
+ /// OUTCOME_BUFFER bytes the guest returned; `Trap` is an engine fault
20
+ /// after the export call started, kept as a value so the rest of the
21
+ /// `Snapshot` survives it.
22
+ pub enum Completion {
23
+ Outcome(Vec<u8>),
24
+ Trap(Trap),
25
+ }
26
+
27
+ /// Resource usage of one guest invocation, measured across the same
28
+ /// bracket as the wall-clock / memory caps: `wall_time` is the seconds
29
+ /// spent inside the guest export call; `memory_peak` is the high-water
30
+ /// `memory.grow` delta in bytes past the entry-time baseline.
31
+ #[derive(Clone, Copy)]
32
+ pub struct Usage {
33
+ pub wall_time: f64,
34
+ pub memory_peak: usize,
35
+ }
36
+
37
+ /// The observables of one guest invocation, uniform across completion
38
+ /// kinds: captures and usage are collected on trap and outcome alike.
39
+ /// What a frontend exposes from the trap path is its own contract
40
+ /// decision.
41
+ pub struct Snapshot {
42
+ pub completion: Completion,
43
+ pub stdout: Capture,
44
+ pub stderr: Capture,
45
+ pub usage: Usage,
46
+ }
@@ -0,0 +1,22 @@
1
+ //! Engine-neutral block-yield re-entry contract, free of `magnus` and of
2
+ //! any wasmtime type.
3
+ //!
4
+ //! During a guest→host dispatch, a Service method may yield to a guest
5
+ //! block. The host drives that re-entry through a `Yielder`: it ships the
6
+ //! yield-arguments payload into the in-flight guest and returns the
7
+ //! YieldResponse bytes. What backs the re-entry — a wasmtime `Caller`, some
8
+ //! other engine handle — is the implementer's concern; the dispatch
9
+ //! contract sees only this trait.
10
+
11
+ use crate::error::Trap;
12
+
13
+ /// Host-initiated re-entry into the in-flight guest instance to run a
14
+ /// yielded block.
15
+ ///
16
+ /// `yield_block` ships `args` to `__kobako_yield_to_block` and returns the
17
+ /// raw YieldResponse bytes, or a `Trap` — surfaced through the frontend's
18
+ /// trap-error mapping — when the re-entry traps, the guest returns an empty
19
+ /// result, or a payload exceeds the 16 MiB cap.
20
+ pub trait Yielder {
21
+ fn yield_block(&mut self, args: &[u8]) -> Result<Vec<u8>, Trap>;
22
+ }
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## [0.7.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.6.1...kobako-wasmtime-v0.7.0) (2026-07-03)
4
+
5
+
6
+ ### Features
7
+
8
+ * **crates:** build the requested isolation profile into the WASI context ([63c25d8](https://github.com/elct9620/kobako/commit/63c25d835d4d03010c1658217cee412318e6b5d8))
9
+ * **runtime:** runtimes declare their isolation profile ([f89717a](https://github.com/elct9620/kobako/commit/f89717a6bc9809f0de0df78f97da33a49a1474ac))
10
+
11
+ ## [0.6.1](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.6.0...kobako-wasmtime-v0.6.1) (2026-07-02)
12
+
13
+
14
+ ### Miscellaneous Chores
15
+
16
+ * **kobako-wasmtime:** Synchronize kobako crates versions
@@ -0,0 +1,62 @@
1
+ # kobako-wasmtime — the wasmtime implementation of the kobako runtime
2
+ # contract.
3
+ #
4
+ # The engine half of a kobako host: `Driver` implements
5
+ # `kobako_runtime::runtime::Runtime` over wasmtime, owning every
6
+ # engine-bound mechanic — the process-wide Engine + compiled-Module
7
+ # caches, the pre-linked `InstancePre`, the epoch wall-clock timeout,
8
+ # the linear-memory cap, and the frozen-clock / constant-RNG ambient
9
+ # denial. Frontends (the kobako Ruby gem's native ext) construct a
10
+ # `Driver` and drive invocations through the contract trait only.
11
+ #
12
+ # Self-contained by design: the Ruby gem ships this crate without the
13
+ # `crates/` workspace manifest, so no `workspace = true` inheritance
14
+ # fields may appear here.
15
+
16
+ [package]
17
+ name = "kobako-wasmtime"
18
+ version = "0.7.0"
19
+ edition = "2021"
20
+ description = "wasmtime implementation of the kobako host runtime contract."
21
+ license = "Apache-2.0"
22
+ repository = "https://github.com/elct9620/kobako"
23
+ readme = "README.md"
24
+ keywords = ["wasm", "mruby", "sandbox", "wasi", "wasmtime"]
25
+ categories = ["wasm", "virtualization"]
26
+
27
+ [dependencies]
28
+ # The engine-neutral contract this crate implements. The version pin
29
+ # rides the linked release group; the path keeps in-tree builds (and
30
+ # the Ruby gem, which ships both crates) resolving locally.
31
+ kobako-runtime = { version = "0.7.0", path = "../kobako-runtime" }
32
+ # wasmtime — host-side embedder for kobako.wasm. We disable default-features
33
+ # and opt back in only what kobako needs: a Cranelift-backed runtime that can
34
+ # compile a pre-built wasm32-wasip1 module on the host triple, plus the `wat`
35
+ # feature so test fixtures can be expressed as text.
36
+ # `cache` / `parallel-compilation` / `pooling` / `component-model` / `async`
37
+ # are intentionally off — kobako runs short-lived synchronous sandboxes.
38
+ wasmtime = { version = "46.0.1", default-features = false, features = [
39
+ "cranelift",
40
+ "runtime",
41
+ "gc",
42
+ "gc-drc",
43
+ "addr2line",
44
+ "demangle",
45
+ "wat",
46
+ ] }
47
+ # wasmtime-wasi provides WASI preview1 support for routing guest stdout/stderr
48
+ # into in-memory buffers. The `p1` feature enables the
49
+ # WasiCtxBuilder + preview1 adapter which wires fd 1/2 to pipes. We omit
50
+ # `p2` (component-model) and `p0`/`p3` (async) because kobako runs
51
+ # synchronous sandboxes only.
52
+ wasmtime-wasi = { version = "46.0.1", default-features = false, features = ["p1"] }
53
+ # sha2 keys the on-disk compiled-module cache by Guest Binary content
54
+ # (see cache.rs); a collision would load the wrong artifact, so the
55
+ # hash must be cryptographic.
56
+ sha2 = "0.11"
57
+
58
+ # libc supplies geteuid for the cache-directory ownership check gating
59
+ # the unsafe artifact deserialize (see cache.rs); std exposes a file's
60
+ # owner but not the process's effective uid.
61
+ [target.'cfg(unix)'.dependencies]
62
+ libc = "0.2"
@@ -0,0 +1,32 @@
1
+ # kobako-wasmtime
2
+
3
+ The [wasmtime](https://wasmtime.dev) implementation of the
4
+ [kobako](https://github.com/elct9620/kobako) host runtime contract
5
+ ([`kobako-runtime`](https://crates.io/crates/kobako-runtime)).
6
+
7
+ `Driver` implements the contract's `Runtime` trait over wasmtime and
8
+ owns every engine-bound mechanic, so frontends see only the neutral
9
+ contract surface:
10
+
11
+ - process-wide Engine and compiled-Module caches with an on-disk AOT
12
+ (`.cwasm`) artifact cache keyed by Guest Binary content
13
+ - a pre-linked `InstancePre` per guest path; every invocation runs on
14
+ a fresh instance and discards its Store afterwards
15
+ - the epoch-based wall-clock timeout and the per-invocation
16
+ linear-memory cap
17
+ - ambient denial: frozen WASI clocks and a constant RNG, so a guest
18
+ observes no real time and no real entropy
19
+
20
+ The kobako Ruby gem's native ext is the first frontend; a Rust host
21
+ SDK consumes the same surface.
22
+
23
+ ## Usage
24
+
25
+ ```toml
26
+ [dependencies]
27
+ kobako-wasmtime = "0.7.0" # x-release-please-version
28
+ ```
29
+
30
+ ## License
31
+
32
+ Apache-2.0
@@ -1,11 +1,14 @@
1
- //! Defense-in-depth denial of guest ambient authority at the WASI layer.
1
+ //! Denial of guest ambient authority at the WASI layer — the one grant
2
+ //! that separates the hermetic rung from permissive;
3
+ //! `frames::install_wasi_frames` wires these sources on `Hermetic` only.
2
4
  //!
3
5
  //! `wasmtime-wasi`'s `WasiCtxBuilder` defaults the guest's `wasi:clocks` to
4
6
  //! the host wall / monotonic clock and `wasi:random` to a fresh per-context
5
7
  //! seed. No allowlisted mrbgem reaches these preview1 imports today
6
8
  //! (`build_config/wasi.rb`), but a future libc-backed gem would silently
7
9
  //! obtain real time and host entropy — a covert timing channel and a
8
- //! nondeterminism source the sandbox deliberately excludes (docs/security-model.md).
10
+ //! nondeterminism source the hermetic posture deliberately excludes
11
+ //! (docs/security-model.md).
9
12
  //! Pinning the clocks to the Unix epoch and the RNG to a constant stream
10
13
  //! makes that denial a property of the host, not merely of the gem allowlist.
11
14
  //!
@@ -19,7 +22,7 @@ use wasmtime_wasi::random::Deterministic;
19
22
  use wasmtime_wasi::{HostMonotonicClock, HostWallClock};
20
23
 
21
24
  /// Wall clock frozen at the Unix epoch — the guest observes no real time.
22
- pub(super) struct FrozenWallClock;
25
+ pub(crate) struct FrozenWallClock;
23
26
 
24
27
  impl HostWallClock for FrozenWallClock {
25
28
  fn resolution(&self) -> Duration {
@@ -32,7 +35,7 @@ impl HostWallClock for FrozenWallClock {
32
35
  }
33
36
 
34
37
  /// Monotonic clock frozen at zero — the guest observes no elapsed time.
35
- pub(super) struct FrozenMonotonicClock;
38
+ pub(crate) struct FrozenMonotonicClock;
36
39
 
37
40
  impl HostMonotonicClock for FrozenMonotonicClock {
38
41
  fn resolution(&self) -> u64 {
@@ -46,7 +49,7 @@ impl HostMonotonicClock for FrozenMonotonicClock {
46
49
 
47
50
  /// Constant-stream RNG handed to the guest's `wasi:random`, so a guest that
48
51
  /// reaches `random_get` receives no host entropy.
49
- pub(super) fn deterministic_rng() -> Deterministic {
52
+ pub(crate) fn deterministic_rng() -> Deterministic {
50
53
  Deterministic::new(vec![0])
51
54
  }
52
55