microsandbox-rb 0.16.0 → 0.17.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 011c5bcfdd4eff6b2f86ec6ded70720520b5c77d93ceb575dc4ac9b0ce0a5078
4
- data.tar.gz: 2e84a073bd4852e2cdbc124f4ce6b400beb5b20012c6b9ff81833169f13d58e1
3
+ metadata.gz: 8fbc8d09d8029bef1ec00d128bf554b037c937e51ebe8b9b8a6014043fce178e
4
+ data.tar.gz: 626313c0471102e108793f3880bcd1f1e30ba9f48470274bf53495a86cfadff7
5
5
  SHA512:
6
- metadata.gz: 6a12d8b308f479630dc8b266c3129bb7e337939da1963faf975fda7ebb890fe95d50ccbf77b37e9f33d3ccb0506dd94633e0374ac6c52cac599c6c80348ee931
7
- data.tar.gz: 730a26aef15b87d348e95a23ede127520aee10d410856526f956d3366118959dc51f60d418e7c876781191be0c72009daafaa442a353afc6b6010643adf1a46f
6
+ metadata.gz: 49461a4a9ca44f5af82d62fe30086bd4b153d3885267274fce7de8328b071cb418055f202b3b2feea3f149d76572a2a78874596c707ac094d62f7cc5171269aa
7
+ data.tar.gz: 929b0a19b88eb839d371899191e6a0229eded4eb616100dbd8d316352eac3bf68dcfefa91fb04c4ce41ad540deaa41233c1533d2471222bbf4772d8708451be6
data/CHANGELOG.md CHANGED
@@ -6,6 +6,235 @@ All notable changes to this gem are documented here. The format is based on
6
6
  microsandbox runtime it embeds; each release notes the upstream runtime tag it
7
7
  wraps, and the README's Versioning section keeps the full gem→runtime map.
8
8
 
9
+ ## [Unreleased]
10
+
11
+ ## [0.17.1] - 2026-10-12
12
+
13
+ Gem-only security and hardening release; the embedded runtime stays at
14
+ **`v0.6.18`**. Upgrading is recommended for everyone on `0.17.0` or earlier.
15
+ Two fixes tighten input handling and can reject input that used to be
16
+ accepted: `Volume.fs` paths containing a `..` segment, and
17
+ `Microsandbox.runtime_path = nil`.
18
+
19
+ ### Security
20
+
21
+ - **Relative host paths are pinned when a sandbox is created**
22
+ ([GHSA-mcw9-9rf6-p4rm](https://github.com/superradcompany/microsandbox/security/advisories/GHSA-mcw9-9rf6-p4rm),
23
+ high; fixed upstream in runtime `v0.7.6`, which this gem does not embed yet).
24
+ The embedded runtime stored a relative host path exactly as written and
25
+ resolved it against the working directory of whichever process later
26
+ started the sandbox, so restarting a sandbox from another directory mounted
27
+ a different host directory than the one originally intended. The native
28
+ layer now anchors every relative host path to the creating process's
29
+ directory before the configuration is persisted: bind and disk mount
30
+ sources in `volumes:`, a directory or disk-image `image:`, a `root_disk:`
31
+ image path, `Patch.copy_file` / `Patch.copy_dir` sources, the `tls:`
32
+ certificate paths, and a path-form `from_snapshot:`. This covers sandboxes
33
+ created through the gem; the `msb` CLI the gem installs is unchanged, so use
34
+ absolute paths with it. Paths are anchored
35
+ without following symlinks and without requiring the target to exist, so a
36
+ symlinked bind-mount root is still refused unless
37
+ `follow_root_symlinks: true`. Absolute paths, named volumes, and OCI
38
+ references are unchanged, and on a cloud backend paths are forwarded as
39
+ written (they belong to the remote host).
40
+
41
+ **Existing sandboxes are not rewritten.** A sandbox created by an earlier
42
+ gem version with a relative path keeps that saved path and its old
43
+ behaviour: recreate it, or only ever start it from its original directory.
44
+ Affects every earlier `microsandbox-rb` release; the upstream advisory does
45
+ not list this gem.
46
+ - **Errors no longer repeat values that can be credentials.** A URL-style
47
+ proxy address (`socks5://user:pass@host:port`) is refused by shape without
48
+ being echoed; an `env:` / `labels:` / `scripts:` value that is not a Hash
49
+ (or convertible to one, like `ENV`), a malformed patch, and a non-Hash
50
+ secret spec are reported by class name only.
51
+ - **Dependencies with published advisories updated in `Cargo.lock`**:
52
+ `russh` 0.63.1 → 0.63.3 (GHSA-35g8-35p8-c8fw, SSH server side),
53
+ `rustls` 0.23.41 → 0.23.45 (RUSTSEC-2026-0285), `h2` 0.4.15 → 0.4.16
54
+ (RUSTSEC-2026-0258), plus `anyhow` 1.0.104 and `event-listener` 5.4.2
55
+ (unsoundness fixes). Those pulled in `aws-lc-rs` 1.18.1, `aws-lc-sys`
56
+ 0.45.0, `rustls-webpki` 0.103.15, and `num-bigint` 0.5.1 in place of
57
+ `internal-russh-num-bigint`. `rkyv` and `rsa` have no compatible fixed
58
+ release at the pinned runtime and are unchanged.
59
+
60
+ ### Added
61
+
62
+ - `SECURITY.md`: how to report a vulnerability privately, and a table mapping
63
+ upstream microsandbox advisories to the `microsandbox-rb` versions they
64
+ affect (upstream advisories do not list this gem). Shipped in the gem.
65
+ - `require "microsandbox-rb"` as an entry point named after the gem. Outside
66
+ Bundler, `require "microsandbox"` can resolve to the unrelated `microsandbox`
67
+ gem on RubyGems.
68
+
69
+ ### Fixed
70
+
71
+ - **Documentation examples that did not run.** The README quick start failed
72
+ on its second run (`SandboxAlreadyExistsError`; it now uses
73
+ `ephemeral: true`), and the README / configuration-guide `secrets:` example
74
+ used the `#modify` shape and raised `TypeError`. The observability and
75
+ errors guides named the wrong error class for the metrics startup window
76
+ (it is the base `Microsandbox::Error`, matched by message), and
77
+ `log_stream(follow: true)` was documented as ending when the sandbox stops
78
+ (it does not). The guides now also state that an inline secret `value:` is
79
+ stored in cleartext in the local catalog until the sandbox is removed, that
80
+ the block form stops but does not remove, that a runtime home belongs to one
81
+ runtime line (`MSB_HOME`), and list known issues at the pinned runtime
82
+ (including guest `fallocate` truncating bind-mounted files on macOS hosts).
83
+ - **A rejected option no longer triggers a runtime download.** The first-use
84
+ runtime check ran before most argument validation, so `Sandbox.create` with
85
+ a bad option downloaded and installed `msb` before raising. It now runs
86
+ after the options are validated, in `create`, `connect_or_create` and
87
+ `create_with_progress`.
88
+ - **First-use provisioning is serialized within a process.** Several threads
89
+ making their first `Sandbox.create` at once each ran the installer, which
90
+ replaces files in place. `Microsandbox.install`, `Microsandbox.setup` and
91
+ the first-use check now share one lock, and the check runs once.
92
+ - **`Microsandbox.runtime_path = nil` / `libkrunfw_path = nil`** (or `""`)
93
+ raise `ArgumentError` instead of writing an empty path into the set-once
94
+ slot for the rest of the process.
95
+ - **Companion gem detection no longer depends on the host project's name.**
96
+ When the host project is itself a gem with its bundle installed inside the
97
+ project directory, `microsandbox-rb-binaries` could be attributed to the
98
+ host project and ignored, depending on gem load order. The owner is now
99
+ the gem with the innermost matching root.
100
+ - **`patches:` with `from_snapshot:`** raises `ArgumentError` up front. The
101
+ runtime rejects the pair only after claiming the sandbox name on disk,
102
+ which left an empty directory holding the name.
103
+ - **Block forms keep the caller's exception.** `fs.write_stream { }`,
104
+ `ssh.open_client { }` and `client.sftp { }` closed in a bare `ensure`, so a
105
+ failing close replaced whatever the block had raised. The block's exception
106
+ now wins; a close failure is still raised when the block itself succeeded.
107
+ - **`PullSession#sandbox`** re-raises the original create failure when called
108
+ again, instead of an unrelated "already taken" error.
109
+ - **Timestamps are exact to the millisecond.** `Time.at(ms / 1000.0)` went
110
+ through a Float and reported one millisecond low for about half of all
111
+ values (`FsEntry#modified`, `FsMetadata#modified` / `#created`,
112
+ `LogEntry#timestamp`, `Metrics#timestamp`, and every `created_at` /
113
+ `updated_at` / `last_used_at` / `observed_at` reader).
114
+ - **`secrets:` given as a `{ "NAME" => { ... } }` Hash** (the `#modify`
115
+ shape) raises a descriptive `ArgumentError` instead of `TypeError`; a single
116
+ bare spec Hash is accepted as a list of one, as is a single patch Hash for
117
+ `patches:`.
118
+
119
+ ### Changed
120
+
121
+ - **`Volume.fs` paths may not contain a `..` segment.** Every `VolumeFs` operation
122
+ (`read`, `read_text`, `write`, `list`, `mkdir`, `remove_file`, `remove_dir`,
123
+ `exists?`, `copy`, `rename`, `stat`) now refuses a path that contains a `..`
124
+ segment with `FilesystemError`, in the native layer as well as the Ruby
125
+ wrapper. Volume paths are relative to the volume root and never need one.
126
+ A path such as `"a/../b"` that used to be accepted must now be written as
127
+ `"b"`. Names that merely contain dots (`"..hidden"`, `"a..b"`) are fine.
128
+ - **README restructured** along the lines of a conventional Ruby SDK README:
129
+ badges, a Highlights list, a short install + quick start, a Documentation
130
+ table, and a sourced comparison with the official `microsandbox` gem. The
131
+ detailed guides moved into `docs/` (`lifecycle`, `configuration`,
132
+ `execution`, `filesystem`, `observability`, `images`, `runtime`, `errors`,
133
+ `surface`, `releasing`), which the gem now ships (`spec.files`), so the
134
+ links resolve on rubygems.org and rubydoc.info. `docs/surface.md` replaces
135
+ the duplicated implemented-surface prose in README and `DESIGN.md` as the
136
+ single scope statement. `docs/errors.md` lists every error class and marks
137
+ the four the native layer does not currently raise (`PathNotFoundError`,
138
+ `ImagePullFailedError`, `SecretViolationError`, `TlsError`) as reserved.
139
+
140
+ ## [0.17.0] - 2026-09-13
141
+
142
+ Adopts upstream runtime **`v0.6.16` → `v0.6.18`**, stepping through the
143
+ intermediate tag (`v0.6.17` verified and committed on its own).
144
+
145
+ ### Added
146
+
147
+ - **Strict hostname policy** — `strict:` (Boolean, default `false`) on
148
+ `Sandbox.create` / `connect_or_create` / `create_with_progress` (upstream
149
+ `v0.6.18`, from the security fork-merge below). In strict mode a flow that the
150
+ egress policy allows *only* through a hostname rule must be backed by an
151
+ inspectable request authority — the plain-HTTP `Host` header, or SNI /
152
+ `:authority` under TLS interception. Without that visibility (bypassed or
153
+ non-intercepted HTTPS) the runtime fails closed *before* dialing upstream
154
+ instead of trusting opaque hostname evidence. Mirrors the Python SDK's
155
+ `Network(strict=...)`. An explicit `false` is sent as-is (not dropped), like
156
+ `trust_host_cas:`. Create-only in Ruby, matching the Python SDK's `modify`
157
+ surface (which does not expose `strict`); the core itself does carry a
158
+ generated `strict` field on its network config patch (`ConfigPatch` derive),
159
+ so this is an SDK-parity choice, not a core limitation. Accepted by the cloud
160
+ create contract (`CloudNetworkSpec.strict`), so there is no `UnsupportedError`
161
+ path.
162
+
163
+ - **Outbound SOCKS proxies** — `proxy:` on `Sandbox.create` /
164
+ `connect_or_create` / `create_with_progress` (upstream #1234 SOCKS4/SOCKS5
165
+ and #1507 SOCKS5 UDP + credentials, `v0.6.17`). The runtime's host-side
166
+ network stack dials the proxy for the sandbox's egress — SOCKS4 for TCP,
167
+ SOCKS5 for TCP and non-DNS UDP — uniformly for TLS-intercepted and
168
+ bypassed/plain TCP, while the egress policy (`network:`) still decides which
169
+ destinations may be reached. The address is resolved from the **host**
170
+ (`127.0.0.1` is the host's loopback). Spelled like the Python SDK's
171
+ `proxy=`, as a top-level create option rather than inside `network:`:
172
+ - `Microsandbox::OutboundProxy.socks4(address, user_id: nil)` and
173
+ `Microsandbox::OutboundProxy.socks5(address)`, the latter chaining
174
+ `#credentials(username, password)` which returns a **new** proxy (the
175
+ objects are immutable, like Python's frozen dataclass).
176
+ - `Microsandbox::SecretSource.env("VAR")` — the only password source: the
177
+ host environment variable's *name* is what reaches the runtime, which reads
178
+ the value host-side. No password value ever travels through the binding or
179
+ appears in `inspect`/error output.
180
+ - The equivalent plain Hash is accepted too: `{ protocol: :socks5,
181
+ address: "IP:port", credentials: { username:, password: { env: "VAR" } } }`
182
+ or `{ protocol: :socks4, address:, user_id: }`.
183
+ - Validation mirrors the Python SDK's `OutboundProxy.__post_init__`
184
+ (`ArgumentError`): `user_id` only for SOCKS4, credentials only for SOCKS5,
185
+ username and password together or neither, a non-empty String address.
186
+ `IP:port` parsing is left to the core, which reports an unparseable address
187
+ (or an invalid SOCKS4 user ID) at create time, before any boot; the binding
188
+ surfaces that as `InvalidConfigError` — a malformed proxy is a configuration
189
+ mistake, not a policy one — while every other network-builder error keeps
190
+ its `NetworkPolicyError` class. The cloud backend rejects the option with
191
+ `UnsupportedError` (`sandbox.create`, hint naming `network.outbound_proxy`).
192
+
193
+ ### Changed
194
+
195
+ - **Plain-HTTP requests are now checked against the egress policy by their
196
+ `Host` / `:authority`, whenever the policy has any domain rule — in
197
+ non-strict mode too** (upstream `v0.6.18` hardening, `crates/network/lib/
198
+ tcp/proxy.rs`: `enforce_http_authority = network_policy.has_domain_rules()`).
199
+ Previously a plain-HTTP connection admitted by an IP/CIDR/group rule could
200
+ name any host in its `Host` header; now, if the policy carries `Domain` /
201
+ `DomainSuffix` rules (including `deny_domains:` / `deny_domain_suffixes:`,
202
+ which are domain rules), the request's authority is evaluated against the
203
+ **ordered** egress policy (rules first, then `default_egress`) together with
204
+ the real destination IP/port, and — for a request carrying valid authority
205
+ metadata — the request is denied only when that evaluation denies it.
206
+ Separately, a plain-HTTP request with a *missing*, *duplicate* or
207
+ *unparsable* `Host` / `:authority` is now rejected outright whenever domain
208
+ rules are present (the upstream validator requires exactly one well-formed
209
+ authority before any policy evaluation), so an HTTP/1.0 request without a
210
+ `Host` header no longer passes on such a policy. Existing IP/CIDR/group allows and a permissive
211
+ `default_egress` still count: a plain-HTTP request to an allowed IP that
212
+ sends an unrelated `Host` is still allowed unless a rule (or the default)
213
+ denies that hostname. Policies with no domain rules are unaffected, and so is
214
+ HTTPS without interception. What changes in practice: an IP-based request
215
+ whose `Host` names a host the ordered policy *denies* (a `deny_domains:`
216
+ entry, or a domain not matched by any allow under `default_egress: :deny`)
217
+ now fails even in non-strict mode — allow the hostname it actually sends.
218
+ `strict:` is not a substitute for a restrictive policy; it only tightens
219
+ hostname-rule allows that lack an inspectable authority.
220
+
221
+ ### Security
222
+
223
+ - Runtime `v0.6.18` carries the upstream security fork-merge (`439a88a5`, no
224
+ public advisory identifier at the time of writing): aligns HTTP `Host` policy
225
+ checks with the rest of the egress enforcement (the Changed note above) and
226
+ adds the fail-closed pre-connect strict hostname mode (`strict:` above). No
227
+ gem-side change was needed beyond exposing `strict:`.
228
+
229
+ ### Fixed
230
+
231
+ - Runtime `v0.6.17` restores the released database-migration order (upstream
232
+ #1517, fixing #1502): a local `~/.microsandbox` database last opened by an
233
+ upstream **`v0.6.15`** binary — typically the `msb` CLI, since this gem never
234
+ shipped `v0.6.15` on its own — failed to open under `v0.6.16` with "database
235
+ schema is newer than this msb binary". Any such database now migrates
236
+ forward cleanly on the next `require "microsandbox"` + first operation.
237
+
9
238
  ## [0.16.0] - 2026-09-01
10
239
 
11
240
  Adopts upstream runtime **`v0.6.14` → `v0.6.16`**, stepping through the
@@ -1162,7 +1391,11 @@ microsandbox runtime, aligned with the official Python/Node/Go SDKs.
1162
1391
  core crate has Apple-native deps). Until precompiled gems are published,
1163
1392
  installing from source requires a Rust toolchain (stable >= 1.91).
1164
1393
 
1165
- [Unreleased]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.14.0...HEAD
1394
+ [Unreleased]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.17.1...HEAD
1395
+ [0.17.1]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.17.0...v0.17.1
1396
+ [0.17.0]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.16.0...v0.17.0
1397
+ [0.16.0]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.15.0...v0.16.0
1398
+ [0.15.0]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.14.0...v0.15.0
1166
1399
  [0.14.0]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.13.0...v0.14.0
1167
1400
  [0.9.0]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.8.2...v0.9.0
1168
1401
  [0.8.2]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.8.1...v0.8.2