v0.4.56
Signedpublished October 11, 2026What changed
-
Agents copy files between workspaces without spending tokens. The new
copy_filesMCP tool copies a file or a whole directory from one workspace to another, or within one, inside DevGrail: the content never passes through the agent, so a large or binary file costs nothing to move. Until now an agent read the file and wrote it back out withwrite_file, paying for every byte twice. It needs a read grant on the source and a write grant on the destination, and both ends run as the workspace user, so a copy reads only whatread_filecould and writes only wherewrite_filecould; setuid bits, ownership and anything that would land outside the copy are refused or dropped. The descriptions ofwrite_file,edit_fileandread_filenow say when to use which. Tools are granted one by one, so an existing API key getscopy_filesonly once you add it to the key. -
Desktop: move the controls tab out of the way. Drag the small tab that opens the desktop controls anywhere along the edges of the view, if it covers something in the desktop; this browser remembers where you left it. The controls now open as a panel floating beside the tab, instead of a drawer pinned to the left edge.
-
Desktop: see how much data the desktop uses. The new Connection section in the desktop controls shows what the browser tab receives and sends each second, and the round trip to the desktop. Click it for the picture and the sound apart, a graph of the last minute, the data used since the page opened, roughly how much an hour takes at that rate, how many pictures a second arrive, and how the sound's buffer is holding up. Turn on Show on the picture to keep the readout in the bottom left corner while you work. It measures only while it is on screen.
-
Desktop: the speakers cost nothing while the desktop is quiet. With Speakers on, the desktop used to send about 100 kbit/s of sound even in silence, some 40 MB an hour. Now it stops half a second after the sound does and starts again the moment anything plays, so an idle desktop with the speakers on uses next to no data.
-
Desktop: the microphone badge is a switch. The round microphone button in the bottom left corner is always there now and turns the microphone on as well as off: grey and struck through while it is off, red while the desktop hears you, amber while it is on but the sound is not through yet.
-
Nested domains: expose a DevGrail running inside a workspace yourself. The owner of a Docker-enabled workspace can claim every name under
*.<label>.<base domain>for it from the workspace's page, the API or the newset_workspace_nested_domainMCP tool. HTTPS is passed through untouched, so the inner DevGrail (or any other proxy) gets its own certificates, and plain HTTP is forwarded too; the ports default to the onesdev.shpublishes, 10443 and 10880. After the claim, the page checks every 10 seconds what is still missing (the DNS record to add, the operator's--acme-bypass=yes, something listening in the workspace), shows the settings for the inner DevGrail ready to copy, and links its dashboard once everything passes;check_workspace_nested_domaindoes the same for agents. A link with?nested_label=<label>opens the form filled in. An admin allows it per account on the Users page, and turning it off withdraws the account's claims. A label cannot clash with an app subdomain, a base domain, the dashboard or an admin route, in either direction, and the refusal says what holds it. Admins see and withdraw every claim on the Traefik routes page, which also says whether this server passes certificate challenges through. -
Workspace setup in the Control Center. A workspace domain profile sets up a nested DevGrail with TLS, public site hostnames and manual outer route instructions. Setup checks the public dashboard and can install an optional restart hook for the dev loop and Control Center. Public Control Center access is an explicit opt-in and always requires its access token; existing env values and startup hooks are preserved.
-
Correct dev-loop status URL.
./dev.sh stopkeeps the HTTP URL in.dev/status.jsonwhenDEVGRAIL_TLS=off, matching the running dev loop. -
Consistent test commands and VPS integration.
test.sh,make testand fast CI now run the same Go, dashboard, desktop, installer and repository checks, with a separate Linux Go race job.test/vps/vps.sh integrationcompiles tests locally and runs them against the existing disposable VPS installation. Live tests fail on missing prerequisites or cleanup errors, restore original MCP settings, and support staging certificate trust and configurable admin names. Binaries sent from Windows use VPS-local file ownership so the internal MCP probe works with Docker user namespace remapping. The installer shell tests run only on Linux, behind a Docker command that refuses any call a test has not faked. The test guides describe the current suites and their remaining coverage gaps. -
Workspaces come with Node.js 26. New and recreated workspaces have Node.js 26.11.1 instead of 22. Projects that need an older Node can install it inside the workspace, for example with nvm.
-
Security: the dashboard's router is up to date. The dashboard now uses React Router 7.18.4, which fixes an open redirect through links written with a backslash.
-
Notification cleanup. Notification timers are cancelled when dismissed or when leaving a page. Requests finishing after navigation no longer start notification timers on the page that was closed.
-
Copy error messages. Error notifications now include a copy button so you can copy the full message to the clipboard for troubleshooting or sharing.
-
Clearer subdomain visibility guidance. The workspace page now explains External (public internet access) and Internal (access from the workspace and its project's other workspaces) beside the subdomain controls, including how to view an internal app and why a browser outside those workspaces gets a 404.
-
Internal subdomains and custom domains. An app subdomain or custom domain can now be internal: the same
https://URL, with the same valid certificate, but it answers only requests from the workspace that publishes it and the other workspaces in the same project. Everyone else gets the same 404 as a hostname that does not exist, including your own browser outside those workspaces, so an agent's API can be shared with its project's other agents without putting it on the internet. To look at one in a browser, use a browser inside a workspace of the project, such as the desktop add-on's Firefox. Choose External or Internal when adding a subdomain or custom domain, on the create form, or switch an existing one from its row; the workspace keeps running. Over the API it isvisibilityon the subdomain and custom-domain requests and responses, plusPATCH /workspaces/{id}/subdomains/{label}andPATCH /workspaces/{id}/domains/{host}. For agents,add_workspace_subdomainandcreate_workspacetakevisibility, the newupdate_workspace_subdomaintool switches it, andget_workspacereports it. An internal name still has to point at this server in public DNS so its certificate can be issued, and like every certificate's name it appears in public Certificate Transparency logs. Snapshots keep each route's visibility. docs/RUNBOOK.md has a section on diagnosing an internal route that does not answer. -
Switch subdomains and custom domains off and on. Each subdomain and custom domain on the workspace page now has an on/off switch. Switched off, it is not served to anyone and its URL answers 404, but it stays on the workspace: the label or domain remains reserved, so no other workspace can take it, and switching it back on serves it again with the same port and visibility. New subdomains and custom domains are always on. The workspace keeps running either way. Over the API it is
enabledon the subdomain and custom-domain responses and on the samePATCHrequests that change visibility, each of which now changes only the fields it is given; agents passenabledtoupdate_workspace_subdomain, andget_workspacereports it. Snapshots keep a switched-off route switched off, and the Traefik routes page marks a custom domain its owner has switched off. -
Change certificate environments from Administration. Switch between Let's Encrypt production and staging while preserving both certificate stores. The page explains the temporary interruption, warns that staging can block browser access, and provides
sudo devgrail certificates productionfor SSH recovery. Traefik routes shows the active environment and links to the setting. Switching also works in the localdev.shloop, with a local terminal recovery command and no installer or systemd requirement. -
Docker-enabled workspaces. A workspace can now run Docker: its own daemon, containers,
docker buildanddocker compose, without access to the server's Docker and without being a privileged container. An admin allows it per account on the User accounts page, where a dialog explains what it changes first. Those accounts then see a "Docker-enabled workspace" option when creating a workspace (runtime_mode: "docker"in the API and the MCPcreate_workspacetool). It is chosen at creation and cannot be switched later. Inside, install Docker the usual way (sudo apt install docker.io, Docker's own instructions, or the official "Install Docker" script, run from Scripts or with plainsudoin the terminal); it starts by itself, and again after every restart.What to know before allowing it: these workspaces expose more of the Linux kernel to the code in them (they may mount filesystems, use FUSE and manage their own networking), so a kernel bug is likelier to let that code break out. A break-out still lands as an unprivileged user, and such a workspace runs under its own security profile that keeps it away from other workspaces' files. The inner daemon and its containers share the workspace's CPU, memory and process limits. Admins can now raise a single workspace's process limit from the System page, which warns when a Docker-enabled workspace is below 8192. Inner images live on a volume of their own: snapshots leave it out, deleting the workspace removes it, and it counts toward the account's disk usage. File-heavy work inside inner containers is several times slower than on the host, and a few image builds fail (replacing a dangling symlink over a file from a lower layer, as Ubuntu's
localespackage does). The host needs Docker's user namespace remapping and/dev/fuse; the installer enables the feature when both are present. DevGrail's own workspace image now builds inside such a workspace too, so DevGrail can be developed withdev.shin one. -
A DevGrail inside a workspace can have its own HTTPS certificates. A second DevGrail running in a Docker-enabled workspace can now serve its dashboard and app subdomains, for example under
*.inner.example.com, with Let's Encrypt certificates it orders itself. Re-run the installer with--acme-bypass=yesand add a TLS-passthrough Traefik route for those names to the workspace; the installer remembers the answer. Without the option, this DevGrail answered every certificate challenge itself, so the inner one could never get a certificate. The option is off by default and does not change how this DevGrail's own certificates are issued. The setup, with the route for plain-HTTP redirects, is indocs/RUNBOOK.md.dev.shnow also honoursDEVGRAIL_ACME_CASERVER, so a dev loop can use the Let's Encrypt staging CA. HTTP companion and TCP passthrough routes can share the same host, so the wildcard HTTPS route can coexist with its HTTP redirect route. -
Routes to your own workspaces, and a preset for a nested DevGrail. The admin route dialog can now target a port of one of your own workspaces by picking it, rather than typing its container name, and Expose a nested DevGrail creates both routes a DevGrail in one of your Docker-enabled workspaces needs: HTTPS passed through to it and plain HTTP for its redirects. A wildcard host on a route without passthrough is now served over plain HTTP with TLS switched off for you, since it could never get a certificate; the dialog says so for HTTP routes as well as passthrough ones.
-
Security: the database and certificates are closed to workspaces. Docker keeps DevGrail's database and its TLS certificates in the same place on disk as every workspace's files. If code in a workspace ever broke out of its container without gaining full control of the server, it could have read the database or deleted the certificates. Both are now readable by the server and Traefik only. Existing installs are fixed automatically when the upgrade restarts the stack, and nothing changes in how DevGrail works.
-
Security: workspaces are closed to each other at the host. Every workspace's files live in one place on the host, under one shared set of user IDs. Code that broke out of one workspace without taking over the server could have read or changed every other workspace. On hosts with AppArmor (Ubuntu and Debian have it), workspaces now run under a DevGrail security profile that blocks that place on disk, and the profile still applies after such a break-out. The installer loads it and keeps it across reboots. New workspaces get it right away. An existing workspace gets it when its container is next rebuilt, for example by restoring a snapshot. Inside a workspace the only difference is that
/var/lib/dockerand/run/dockercannot be used. Docker could not run in a workspace anyway; a Docker-enabled workspace (above) keeps its inner Docker elsewhere. -
Error messages are hard to miss. Pop-up messages at the bottom right of the dashboard now sit on a solid surface instead of a see-through tint, so the page under them no longer shows through. Each kind has its own icon and coloured edge: red for errors, amber for warnings, green for success. They slide in when they appear, show above an open dialog instead of behind it, and fit the screen on a phone. Screen readers announce errors right away.
-
Two people can give workspaces the same name. Creating a workspace no longer fails because another user already has one with that name. A workspace's address (its terminal URL, Docker container and network) now ends in a short tag tied to your account, such as
web-3f9a1c, so it never collides with someone else's. Names are unique among your own workspaces instead: creating or renaming one to a name you already use is refused with a clear message. Upgrading renames any duplicates you already have to "Web (2)" and so on. Existing workspaces keep their current addresses. -
A slow workspace create no longer breaks the next ones. If the browser or API client stopped waiting while a workspace was still starting, the container could keep running with no workspace attached to it, holding an SSH port. Every later create was then handed that same port and failed with "port is already allocated". A create now runs to the end once it has begun: it either finishes or cleans up after itself, whether or not anyone is still waiting for the answer.
Container images
Installer files
sha256:2f2bf722f47a45ee336dd9c9d76b984cb53840a8584b4a4e915543b247fe42bcsha256:f6dc82abc6b0624660adb45d6e234f1bc5b93f2e030e1cf39a976234f939efebsha256:363d208181fa777a44af1579b57b79852f832ad6ce6c969e4ff67d3626589d09sha256:5f515619ca7f5ca246377bf24b1d2242887c81b0087aad0c012cf3d6e477caa1Signature
One signature covers every artifact above. It is made off this host, and neither this site nor the registry behind it can check it — that is the point, and it is why verifying it catches things a checksum from the same server cannot. How to verify it.
curl -fsSL https://web-dev.trixibot.com/api/registry/releases/v0.4.56 -o release.json
# -j, not -r: -r appends a newline the signer never saw, and the check fails.
jq -j '.signature.payload' release.json > payload
jq -r '.signature.signature' release.json | base64 -d > payload.sig
openssl dgst -sha256 -verify devgrail-release.pem -signature payload.sig payload
# -> Verified OK
# The payload must name this release, so an older signed one cannot be replayed.
head -2 payload
# ...and it must name this exact file. -qxF: whole line, literal.
grep -qxF 'image devgrail-container 41 amd64 2f4e28dc59dd04f37f4414569cac7d5166e6e7e78d4636422fa6246b33281ee9' payload && echo "digest is covered by the signature"The signed payload — shown for reading, not for verifying
Copy this from the manifest with jq -j, never from this page: the signature covers exact bytes, and rendering here drops the trailing newline the signer included.
devgrail-release-signature-v1
release v0.4.56
asset devgrail-desktop-files.tar.gz 5 2f2bf722f47a45ee336dd9c9d76b984cb53840a8584b4a4e915543b247fe42bc
asset devgrail-desktop-install.sh 5 f6dc82abc6b0624660adb45d6e234f1bc5b93f2e030e1cf39a976234f939efeb
asset devgrail-desktop-linux-amd64 5 363d208181fa777a44af1579b57b79852f832ad6ce6c969e4ff67d3626589d09
asset devgrail-desktop-linux-arm64 5 5f515619ca7f5ca246377bf24b1d2242887c81b0087aad0c012cf3d6e477caa1
asset docker-compose.yml 66 5ba6b6d89a988ec87ac18f252a067a8e5902c291d945317ee2b0b2cef9c3e841
asset install.sh 66 7569c552a36cad6cac0fcacaa5db8769eb9e9e00fe1c7e8006ec3d3062ab8ca5
asset traefik-dynamic-http.yml 65 d857ec0f454d5e0e1593c8ff9105c8f7b2954c2ccf04caa791510bb56017c93b
asset traefik-dynamic.yml 66 ac2f53650064223ab682a8f3e3a8836928566e7c864fa155f4e9588656d4de33
asset uninstall.sh 66 bb505e7b33f61de131f45dec9d76e80fb3e60bbdc227a2f5163760f9ac789778
image devgrail-container 41 amd64 2f4e28dc59dd04f37f4414569cac7d5166e6e7e78d4636422fa6246b33281ee9
image devgrail-container 41 arm64 463e6def84e50544425b898edf607a8dc696a5794aef90e1ce571d8ee2e14853
image devgrail-server 82 amd64 29148cacac764a76876d0e69ccf1d86dda9035b86bc487ccb0a176b918d5a874
image devgrail-server 82 arm64 b625afeeb25bed62ee2a61fd7b1661b6a169e1005f7f3b53e35486a8de217be7