dejima


 

Devbox Hardener

Bounds what a compromised development VM can reach on your network, and tells you what that bound actually is.

A devbox is a long-lived VM that runs dejima sessions. Unlike a CI runner it holds uncommitted work, so it cannot be rolled back on a schedule, and rollback is the whole containment story of the sister project, dejima-ci-hardener. What replaces it is the hypervisor firewall, plus knowing precisely what sits inside it.

It does not prevent root on the devbox. Nothing here tries to. It makes root reach less, and makes that reach something you can check rather than assume. What notices if it happens is dejima-guard, which is optional and lives on another machine.

proxmox/README.md Start here. The node side: create the VM, apply the firewall. Everything in the web console
docs/building-a-devbox.md The guest side: install, bootstrap, verify. The same on any hypervisor
docs/threat-model.md What each layer grants, what was rejected and why, and what is not bounded
Updating Already have a devbox? Check its version and walk the upgrade path

Do the node side first, through the end of its §3.

Developed in dejima


Before you use it

Early development. Not ready to be depended on. Nothing here has been through outside review, an external audit, or adversarial testing by anyone but the person who wrote it. Every security property this describes is a claim awaiting scrutiny, not one that has survived it. Do not put this anywhere that being wrong about it would matter.

One maintainer, no support. Nothing about this is promised: not a reply, not a fix, not a release. SECURITY.md has the detail, and is where to report a vulnerability rather than in a public issue.

One person's work per devbox. A condition of the tool rather than an observation about how devboxes get used: the blast radius here is stated in terms of what is on the box, so a machine holding several people's work is out of spec and the reasoning does not hold for it. If you need separation between people, use a devbox each.

Container escape is deliberately deprioritised. An escape gains root, which is more than the container has, and root on this VM is already a row in the blast radius table, bounded by the same hypervisor firewall however it was reached. So patching is what is left worth doing, and separating escape from root inside the guest is not.

Use

sudo ./bin/dejima-devbox-bootstrap    # once, in the guest
cd ~/dejima && ./install.sh           # dejima's own step, as your own account

sudo dejima-devbox-audit              # all three
sudo dejima-devbox-audit reach        # containment only, what the timer runs
sudo dejima-devbox-audit radius       # what root here would get
sudo dejima-devbox-audit drift        # what changed since you last said it was clean

./bin/dejima-devbox-upgrade status    # what this box is missing, from a checkout

Bootstrap installs what dejima needs from the host - Docker with the compose v2 plugin, python3, jq - and clones dejima to ~/dejima. It stops there. Installing dejima is dejima's step: install.sh writes into your ~/.claude, ~/.local/bin and ~/.config/dejima, and what it does there is that project's to define and to change. A copy of that contract kept here would go stale without anything noticing.

The harness is a step before that one, and neither this tool nor install.sh arranges it: whichever harness dejima will run for you, it is installed on this box as the account that will run dejima, and signed in, before the first session. The credential proxy runs on this side and reads that account's copy, and the container's mounts are that account's directories. docs/building-a-devbox.md §5 has what each half breaks if it is missing.

The audit needs root. The config is 0640 root, and the radius report stats files in the home of the account that runs dejima, which it reads from the config rather than from $HOME - under sudo and under the timer that is root's, whose home on a devbox holds nothing.

Updating an existing devbox

Check what it is at, from a checkout rather than the installed copy: an installed copy knows only the versions that existed when it was installed, and says so rather than reporting "up to date" about one it has never heard of.

git -C ~/dejima-devbox-hardener pull
cd ~/dejima-devbox-hardener
./bin/dejima-devbox-upgrade status

That names every version this box is missing. Do them in order: each note assumes the one before it, and some install what a later one uses.

upgrades/ One note per version: the console work, the guest work, and what it costs
docs/upgrading.md The process, and why the two halves are counted separately

Nothing here converges on its own, which is why any of this exists. A devbox is pinned to the day it was built, half its controls were typed into a console no git pull reaches, and a snapshot restore can move it backwards without anyone deciding to. dejima needs none of it: short-lived containers, tip of main only, and a pull closes any gap.

How it fails

The firewall is on the hypervisor, so the guest cannot verify it by reading it. That is what makes it worth having and what makes checking it awkward. The audit tests behaviour instead, by probing the default gateway: already in the routing table so it reveals nothing, guaranteed alive where an arbitrary address is not, and inside the ranges the firewall drops.

A timeout has two causes, and telling them apart is the whole design:

PASS Nothing answered and the network demonstrably works
FAIL Something answered. Rules missing, or an exception above them
INDETERMINATE Nothing answered, including things that should. Not a pass.

A check that reports success when it does not know is worse than no check, because it gets believed.

The IPv6 half has the same three states for different reasons. It asks the segment who is there over IPv6 and counts the answers, and there a silence is unambiguous: the node dropped the family, or the guest never had it, and both are the outcome wanted. So silence is a plain PASS with no positive control. INDETERMINATE is reserved for the probe not running at all, which is still not a pass. It is a separate check because the node's other rules name IPv4 ranges and match none of that traffic - see the LAN boundary is two boundaries.

A rule that REJECTs rather than DROPs reports FAIL even though it is working. bash's /dev/tcp cannot tell a rejection from a live host refusing. That is the safe direction to be wrong in, and it is why the template specifies DROP.

Nothing is stored about what this VM must not reach. Such a list would be a map of valuable targets, kept on the machine this project assumes will be owned. The audit derives its targets from the routing table and its controls from your forge, so there is nothing to type and nothing to leak.

Nothing here is watching

Every check on this page runs on the box it is checking, when you run it. Root on that box can lie to all of them, and the baseline drift compares against lives on the machine whose drift it is describing. The audit bounds an intruder who did not think to look. It has nothing to say about one who did, and nothing at all to say at three in the morning.

dejima-guard is the other half, and it is a separate machine on purpose. It reaches in over SSH on a cadence, keeps the baseline on its own disk where root here cannot reach it, and alerts from there. The inversion is the point: a devbox that has been taken cannot silence a watcher it cannot talk to, and a collection that stops arriving is itself the alarm.

It needs nothing new from this box beyond v5: a restricted key that can run two read-only commands and nothing else, and one inbound firewall rule if the guard does not already sit on your admin subnet.

Tests

bats test/

test/devbox.bats asserts against a real devbox and skips everywhere else. It is gated on DEJIMA_DEVBOX=1 rather than on detecting a devbox, because a detector that guesses wrong skips silently, and a suite that passes by asserting nothing is worse than one that fails.

Further reading

docs/ Index of everything below
docs/threat-model.md What each layer grants, why escape is deprioritised, what was rejected
docs/reference.md The three verdicts, the three addressing modes, the one config key
SECURITY.md What this claims, and where to report a vulnerability
dejima-ci-hardener The sister project, for hosts that can be rolled back
dejima-guard Watches a devbox from a machine that is not the devbox, and alerts on what changed

License

GPL-3.0-or-later. See LICENSE.

The dejima wordmark and logo are not code and are not covered by that licence. They are trademarks; who holds them and what may be done with them is stated once for the organisation, at dejima's ATTRIBUTION.md.

S
Description
Hardens a development VM, bounds what it can reach on your network if compromised, and tells you what that bound actually is
Readme GPL-3.0
897 KiB
Languages
Shell 100%