Diluvium 5.5.1_build4
Pre-release[5.5.1_build4] - 2026-08-11 (prerelease)
v5.5.1_build4 · Lua 5.5.1 · bytecode format 0x46
The capability layer becomes a boundary.
5.5.1_build3 shipped as a pre-release for two stated reasons:
hibernation was switched off, and the capability layer was a
structuring device rather than a security boundary, because a program
could reach past it through the debug library. This release closes
the second one. A program loaded into an instance can no longer forge
an endpoint reference, read the runtime's registry, walk past a
protected metatable, or switch off the instruction budget it was given.
That last one was not on anyone's list. A lua_State has one hook slot
and the instruction budget is a count hook in it, so debug.sethook()
-- the documented way to clear a hook, one line, no setup -- disarmed
it: an instance limited to 200,000 instructions ran three million and
reported nought used. It was found while closing the forgery route and
is closed by the same change.
Still a pre-release, and now for one reason rather than two.
Hibernation is off by default and should stay off; the defect behind
that switch is unchanged and is described under Known issues. Every
other confirmed finding from the M0-M7 audit that is not part of
hibernation is now fixed -- twenty-nine of thirty-five, and the six
that remain are hibernation entire.
An instance is now sealed by default, and that is the change most
likely to affect you. Section 18.2 said the debug library was the one
item between a deployment and running programs it did not write. That was
wrong: dv_new opened every standard library, so an instance also had
os.execute, io.popen, io.open, package.loadlib, dofile and
loadfile, and a program that can start a process has no need to forge an
endpoint reference.
It is a default rather than a flag because it is what the design already
said. An instance reaches outside itself by yielding a request its host
answers -- queue.wait is that, and doc/Determinism.md calls the general
form a hostcall. io/os/package were a second boundary that arrived by
inheriting luaL_openlibs and was never decided anywhere. Having them
costs more than the obvious: the instruction budget stops meaning anything
(a subprocess costs no VM instructions) and the swarm stops being
replayable (inputs stop arriving through the message log), and neither
failure announces itself.
Beyond that: four checks that reported success without checking
anything, and a defect where destroying an endpoint queue made its
token permanently unusable.
Added
-
DV_FLAG_UNSAFE_STDLIBindv_config.flags: putio,osand
packageback, withdofileandloadfile. Scaffolding for
programs that predate the sealed default, not a supported
configuration -- see Changed and Upgrading.A snapshot does not cross this flag: the permanents fingerprint covers
the module tables, so a sealed instance and an unsealed one disagree
anddv_restorerefuses with the permanents-set message. That is the
right answer -- a program captured holdingio.opencannot wake
somewhere there is none. It does crossDV_FLAG_UNSAFE_DEBUG,
because there the names are all still present. -
dvs_allow_unsafe_stdlibin the swarm layer, and flag attenuation
with it.Flags were the one authority in that layer that did not narrow:
buildzeroed itsdv_configand never consulted the parent, so a
sealed supervisor spawned children that hados.execute. Now the
swarm has a ceiling (off by default), the root takes it, and a child
inherits its parent's set. A spawn request carryingsealed = true
narrows further, which is the supervisor that needsos.timeitself
but hands its workers an instance without it. There is no way to
widen -- section 9.3 applied to flags. -
DV_FLAG_UNSAFE_DEBUGindv_config.flags: open the wholedebug
library in this instance rather than the narrowed one. For profile A
hosts, whose programs are their own. See Upgrading.
Changed
-
An instance no longer has
io,osorpackage, ordofileand
loadfile. Breaking for a program that used them; see Upgrading
for the one-flag escape and what it costs.Taken now rather than deferred because the arithmetic only gets worse:
dv_newhas shipped exactly once, in 5.5.1_build3, which is flagged
prerelease, is notlatest, and is not on the release mirror -- so the
set of hosts affected is as small as it will ever be, and grows from
the moment a release carryingdv_newbecomeslatest. Deciding it
here also means theDV_FLAG_SEALEDof earlier drafts never ships and
is not deprecated one release after being introduced.
Fixed
-
Destroying an endpoint queue no longer makes its token permanently
unusable.Nothing removed an entry from the token-to-handle map, and
bind
short-circuits on it, so binding the same reference again returned
the destroyed handle and reported success. Pushing through it raised
"handle 4 has been destroyed", and the host'sdv_endpoint_queue
went on naming the dead handle as the buffer to drain. Reachable by
accident rather than by trying:endpoint.bindandqueue.destroy
are both in the guest table. Audit finding 11. -
Four checks that reported success without checking anything, which is
worse than an absent check because an absent check is visibly absent.dsnap_check's "every header refusal code has its own sentence"
walked to the code that was last when it was written, so the two
added since -- the two most likely to be reached by a hostile
snapshot -- were never inspected, and it compared each sentence only
against the fallback, so two codes could share one.make verify_wasmnamed four files no target produces and swallowed its
own exit status through| head, so its first step could not fail
and the target only ever failed for the wrong reason.
patch_series.sh checkexited 0 having examined nothing when the
fork point was unreachable, which a shallow clone does.
test.yml'sinclude_skippednamed six tests when three are
skipped, three of them recovered a release ago.Audit findings 18, 29, 30 and 31. Each is now confirmed to fail
against the mutation it was supposed to catch. -
Two assertions that could not fail, and a row of section 6.4 that
nothing checked from the host side.test_msgpack.lua's malformed-input loops counted
pcall(...) == nil, which cannot happen, so both were tautologies:
the decoder could have returned a partially built value for every
truncated input and the file would still have printed "0 failed".
They now assert the property -- each single byte decodes exactly when
it is a whole encoding, and every proper prefix of a real encoding is
refused. Anddisabledwas the one row of 6.4 with no host-side
assertion: deleting the enabled check indiluvium_queue_push_bytes
made a host push into a queue the program had disabled succeed
silently, and nothing turned red. Audit findings 9 and 10. -
The parent-visible half of build3's sticky-error fix now has a test.
The fix was in and asserted at the ABI level; what no test reproduced
was what a supervisor is told. A supervisor that spawns a worker
and asks for it to be hibernated in the same batch -- "start it
asleep" -- had the hibernate correctly denied, and the refusal was
left on the child, so when the child finished its work it was
reported asfaultedand a restart policy restarted it. Audit
findings 15 and 19.
Security
-
An instance had every standard library, which section 18.2 did not
say and this release did not intend.os.execute,os.remove,io.popen,io.open,package.loadlib,
require,dofileandloadfilewere all reachable by a program
loaded into an instance;io.open('/etc/passwd')returned a file
handle.dv_newcalledluaL_openlibs, and no section of
doc/Messaging.mdever decided the standard library surface -- it
was inherited and never looked at.This is not a smaller version of the forgery problem below. It is a
larger one, and it means 18.2's profile B was not reachable in
5.5.1_build3 for a reason nobody had written down. The fix is the
sealed default in Changed, not a flag you have to know about.Recorded as S1 in
doc/audit/M0-M7.mdunder "Found since the sweep". -
An endpoint reference can no longer be forged through the
registry.A reference is a table wearing a private metatable, and identity was
metatable equality.__metatable = falsehid it fromgetmetatable
but not fromdebug.getmetatable, and the metatable itself sat in
the registry under its own__name, whichdebug.getregistry
returns. So a program that had been given nothing could prime the
metatable into existence with any table at all, read it back out of
the registry, wrap guessed peer bytes in a table wearing it, and get
a live endpoint handle to any peer the host had pre-authorised. Every
message it pushed arrived.Section 7.3's "a reference cannot be forged" was false, and 9.3's
attenuation -- which treats a reference as a capability rather than a
guessable name -- rested on it. Audit finding 6, reproduced end to
end before it was fixed.No registry-side scheme fixes this while
debug.getregistryis open,
including keeping a weak-keyed set of the references the runtime
actually made: a program that can read the registry finds that table
too and adds itself to it. So the library narrows. See Upgrading. -
A program can no longer switch off its own instruction budget.
debug.sethook()takes the single hook slot alua_Statehas, and
the budget of section 9.4 is a count hook in it. One line, no setup,
no capability required: an instance given 200,000 instructions ran
three million to completion,dv_exceededreported false and
dv_usagereported nought used.Not found by the M0-M7 audit. It is the same shape as two defects
that audit did find on the host side -- a budget stored, reported by
an accessor, and enforced by nothing -- reached from the guest side.
Known issues
-
Hibernation is switched off by default, and this release still
recommends leaving it off. Unchanged from 5.5.1_build3.dvs_hibernaterefuses unless a host callsdvs_allow_hibernation
first. The reason is a defect the layer above cannot work around: the
thread record does not carryu2.funcidx, so an error raised in a
restored program unwinds from the stack base instead of the frame
that raised it, closing every to-be-closed slot in the thread and
writing the error object over the driver's own function slot. That is
memory corruption, not a wrong answer, and the ordinary
wake-then-error path reaches it.Note the switch covers the swarm layer and not the ABI underneath
it.dv_restoreis a public call with no equivalent gate, so a
host driving instances directly can reach the same path. Treat
dv_snapshotanddv_restoreas unsupported in this release unless
you have read section 18.2 ofdoc/Messaging.mdand decided
otherwise.A deployment that keeps agent state at the application level and
spawns a fresh instance per unit of work never enters that path, and
pays little for it:dv_newplusdv_loadof a small chunk is
comparable todv_newplusdv_restoreof a value graph. Sizing for
that: 42 KB per resident instance (make footprint).Four smaller snapshot defects sit behind the same switch: a woken
instance's instruction budget is not re-armed, the swarm layer passes
no host-identity stamp so its own snapshots are not authenticated,
endpoints do not survive a snapshot, and the snapshot fuzzer's
field-validation coverage is currently hollow -- a digest added later
refuses every mutant before the field checks it was built to
exercise, so "0 crashes" there is true but proves less than it
appears to.These six are now the whole of what is open against the M0-M7 audit,
and section 18.3 states the decision they amount to: whether this
project supports hibernation at scale, or says it does not and
strikes them. -
A sealed program has no way to ask for the time.
Sealing closes the second boundary; it does not populate the first. An
instance is supposed to reach outside itself by yielding a request its
host answers, andqueue.waitis the only thing that implements that
-- there is no general hostcall yet. So a program that genuinely needs
a clock or a file hasDV_FLAG_UNSAFE_STDLIBand nothing better, which
is exactly why that flag is scaffolding rather than a configuration.doc/Determinism.mdcarries the design, and it needs no ABI: a
hostcall is a message on a queue the host drains and an answer on a
queue it pushes to, which is measured working against this tree today
with no new machinery. The one thing with a deadline is reserving a
correlation token in the request encoding before the first hostcall
ships, since a guest may have several outstanding at once. -
DV_FLAG_UNSAFE_DEBUGis a real hole, deliberately.A host that sets it gets the whole
debuglibrary back and, with it,
the endpoint forgery route and the budget escape this release closed.
That is profile A's configuration and it is supported -- but the flag
is not a convenience toggle, and an instance carrying it should be
treated as running trusted code.dv_checkasserts that a program in
such an instance can still switch its own budget off, so the cost is
recorded in a test rather than only here. -
Section 10.7's precondition that a snapshot capture involve a single
thread is documented but not enforced: nested coroutines are captured
rather than refused. Audit finding 14, and part of the hibernation
block above.
Upgrading
A program running inside an instance now gets a narrowed debug
library. This is the one change here that can break working code.
Twelve of its sixteen functions raise instead of running: debug,
getregistry, getmetatable, setmetatable, getupvalue,
setupvalue, upvalueid, upvaluejoin, getuservalue,
setuservalue, sethook and setlocal. Each names what it would have
defeated, so a program that hits one reports something a human can act
on rather than "attempt to call a nil value". getinfo, getlocal,
gethook and traceback are unchanged: a program may read its own
frames.
This applies to instances only -- states created by dv_new, which
includes every child the swarm layer spawns. The diluvium and
diluvium_compiler binaries, the browser build and anything embedding
the runtime through luaL_openlibs are unaffected, and the upstream
test suite still drives the whole library.
A host that wants the old behaviour sets DV_FLAG_UNSAFE_DEBUG in
dv_config.flags. Do that only where the program is one you wrote or
generated yourself: it restores the forgery route, the registry and the
budget escape together, which is why it is spelled that way.
Snapshots are unaffected. The twelve names are still present -- they
are refusals, not deletions -- so the permanents fingerprint is
unchanged and a snapshot taken by 5.5.1_build3 still restores. A
snapshot also crosses DV_FLAG_UNSAFE_DEBUG in both directions, and
the permanent resolves in the restoring instance, so a program captured
holding debug.sethook wakes with whichever one the instance it wakes
in is entitled to.
An instance no longer has io, os or package, nor dofile and
loadfile. This is the breaking change in this release.
Nothing fails at load time -- sealing cannot be checked statically -- so
a program that uses os reports attempt to index a nil value (global 'os') when it reaches that line, and a program whose only use is on an
error path will not fail until that path runs. Grep your programs for
os., io., package., require, dofile and loadfile rather than
relying on a test pass.
Two ways forward. Prefer handing the program what it needs through a
queue: section 8.3 already says the host owns the clock, and a value that
arrives as a message is one that can be replayed and faked in a test.
Failing that, set DV_FLAG_UNSAFE_STDLIB (unsafe_stdlib in Rust and
Python, unsafeStdlib in JavaScript, dvs_allow_unsafe_stdlib for a
swarm) and understand that it makes dv_usage approximate and the swarm
non-replayable.
A program that feature-detects -- if os and os.time then -- keeps
working and takes its own fallback, which is why the libraries are
removed rather than replaced with stubs that raise.
print is unaffected; it is in the base library and does not go through
io.
LUAC_FORMAT is unchanged at 0x46; chunks compiled by 5.5.1_build3
load without recompiling.
Built from commit 6aa17c7facb5.
Download guide
| Platform | File | Notes |
|---|---|---|
| Linux (standard) | diluvium_linux_static_x86_64 |
Static; works on Ubuntu, Fedora, CentOS, Arch |
| Raspberry Pi 3/4/5 | diluvium_linux_static_aarch64 |
64-bit OS only |
| Raspberry Pi Zero | diluvium_linux_static_armv7l |
32-bit ARM |
| macOS (Apple silicon) | diluvium_darwin_arm64 |
M1/M2/M3/M4 |
| macOS (Intel) | diluvium_darwin_x86_64 |
|
| Windows | diluvium_windows_x86_64.exe |
64-bit |
| WASI | diluvium_wasi.wasm |
Needs a runtime with wasm exception-handling (wasmtime 28+ with -W exceptions=y) |
| Browser | libdiluvium_wasm_unknown.a |
wasm32-unknown-unknown static library |
Each platform also ships a diluvium_compiler_* binary (luac
with the -r analysis report flag) and a libdiluvium_*.a static
library for embedding.
Verifying a download
sha256sum -c SHA256SUMS.txt --ignore-missing
BUILDINFO.txt records the exact commit, build time and workflow run.