{"slug": "sandboxing-with-minimal-effort", "title": "Sandboxing with minimal effort", "summary": "Yorick Peterse merged a new Inko feature that lets applications sandbox themselves with minimal effort, using Linux Landlock, macOS Seatbelt via sandbox_init, FreeBSD Capsicum, and OpenBSD pledge and unveil. The cross-platform API handles platform-specific details such as setting Landlock rules for the ELF program interpreter /lib64/ld-linux-x86-64.so.2, but on FreeBSD the sandbox is a no-op because Capsicum requires fundamentally restructuring a program. Peterse noted that memory safety alone is insufficient since code is still written by developers, and argued that using an LLM to write code makes things worse.", "body_md": "[The other\nday](https://github.com/inko-lang/inko/commit/088b45b6d9b85b239c132d70fc660f46eb698d0b)\nI merged a new feature for [Inko](https://inko-lang.org/) that I think is quite\ninteresting: the ability to sandbox an application with minimal effort.\n\nWhile memory safety is a goal of Inko (ignoring the usual escape hatches such as the FFI), memory safety only gets you so far. Most notably, the code is still written by developers and developers are, by and large stupid, myself included. And no, using an LLM to do the writing instead doesn't improve things; if anything it makes it even worse given the average LLM has the intellect of a talking parrot with a bad drinking habit.\n\nOne approach popularized by [Docker](https://www.docker.com/) is to run the\nprogram in a container. Not just because it makes distribution easier, but also\nbecause additional restrictions may be applied to the container, such as\nlimiting the files it has access to. For example, this website is served by\n[shost](https://github.com/yorickpeterse/shost), a static file server written in\nInko. To run the server I use the following [Podman\nquadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html):\n\n```\n[Container]\nImage=ghcr.io/yorickpeterse/shost:main\nPull=missing\nContainerName=shost\nExec=shost --sites /var/lib/shost/sites --tls /var/lib/shost/tls --log json --ip ::\nPodmanArgs=--memory 1024m\nReloadSignal=HUP\nNetwork=host\n\nReadOnly=true\nUserNS=auto:uidmapping=0:500,gidmapping=0:500,size=1024\nDropCapability=all\nAddCapability=CAP_NET_BIND_SERVICE\n\nVolume=/var/lib/shost:/var/lib/shost:z,ro\n\n[Service]\nRestart=on-failure\nRestartSec=60\nTimeoutStopSec=15\n\n[Install]\nWantedBy=default.target\n```\n\nIf you're not familiar with quadlets, they're essentially systemd unit files for running containers using Podman. It's a bit similar to Docker Compose, but a lot nicer to work with.\n\nEither way, the point here is that in the above quadlet I apply some\nrestrictions to the container: all capabilities except for the \"bind\" capability\nare dropped, and the files that need to be served are mounted into the container\nas a read-only volume. Oh and if you're wondering what that `UserNS` line is\nfor, that's to work around [this\nissue](https://github.com/podman-container-tools/podman/discussions/29423).\n\nNow this is great and all, but it would be even better if the application itself included some mechanism to restrict its own capabilities, regardless of how it's run.\n\nFortunately, most mainstream operating systems offer some way for an application\nto sandbox itself. For example, on Linux one can use\n[Landlock](https://docs.kernel.org/userspace-api/landlock.html) while on macOS\none can use Seatbelt through\n[`sandbox_init`](https://www.manpagez.com/man/3/sandbox_init/). FreeBSD in turn\nhas [Capsicum](https://wiki.freebsd.org/Capsicum), and OpenBSD has\n[pledge](https://man.openbsd.org/pledge.2) and\n[unveil](https://man.openbsd.org/unveil.2).\n\nThe sandboxing API provided by Inko uses these primitives to provide a\ncross-platform way of sandboxing your application, and tries to handle platform\nspecific behavior/differences as much as possible. For example, on macOS\nallowing a file to be executed is easy but when using Landlock you also have to\nset up the appropriate rules for the ELF program interpreter\n(`/lib64/ld-linux-x86-64.so.2` in most cases). If shared libraries are in a\nnon-standard location you also need to make sure those can be read.\n\nOf course this new API is not without its trade-offs. Most notably, on FreeBSD\nthe sandbox is a no-op. Not because I was too lazy to make use of Capsicum, but\nrather because Capsicum requires you to fundamentally change the structure of\nyour program. On Linux and macOS you can apply sandbox restrictions without\nhaving to change your program, other than whatever lines of code are necessary\nto list the sandbox rules (i.e. the above `enable_sandbox` method). Capsicum on\nthe other hand works a little differently: once you call\n[`cap_enter`](https://man.freebsd.org/cgi/man.cgi?query=cap_enter&sektion=2&manpath=FreeBSD+15.1-RELEASE+and+Ports.quarterly)\nyou can no longer open resources using the usual system calls such as `open`.\nInstead, Capsicum requires that you either open all the appropriate resources\n*before* calling `cap_enter`, or that you open directories ahead of time and\nthen use [`openat`](https://man.freebsd.org/cgi/man.cgi?query=openat&sektion=2&manpath=FreeBSD+15.1-RELEASE+and+Ports.quarterly)\nto open resources relative to that directory. In some cases you may also have to\nuse [libcasper](https://man.freebsd.org/cgi/man.cgi?query=libcasper&manpath=FreeBSD+15.1-RELEASE+and+Ports.quarterly).\nOf course for simple programs this may not be much of an issue, but for larger\nprograms it may require you to extensively change how they are written.\n[`openat` itself also has its issues](https://marc.info/?l=openbsd-tech&m=174844109910709&w=2).\n\nThat's not to say you can't make Capsicum work or that it's somehow \"bad\", rather it means you (unfortunately) can't use Capsicum in many instances unless you're willing to adjust your program to specifically cater towards FreeBSD and Capsicum.\n\nSo how difficult is it to sandbox an Inko application using [this new\nAPI](https://docs.inko-lang.org/std/main/module/std/sandbox/Sandbox/)? Well,\nhere's all that was necessary to [sandbox\nshost](https://github.com/yorickpeterse/shost/commit/b6f5bcb7ecaa20b4ffb9d11b1b4992d0e6744c70):\n\n``` python\nimport std.sandbox (Sandbox, bind, read)\n\n# ...\n\nfn enable_sandbox(config: ref Config) {\n  let s = Sandbox.new\n\n  match config.tls {\n    case Some(v) -> s.path(v, read)\n    case _ -> {}\n  }\n\n  s.path(config.sites.path, read)\n  s.tcp(config.port, bind)\n  s.enable\n}\n```\n\nThat is: we allow access to the directory containing TLS certificates (if TLS is enabled), we allow access to the directory containing the files to serve, and we allow binding to the TCP port the server listens on (e.g. 443 when TLS is enabled). Everything else is denied.\n\nWhile one could consider applying a sandbox to something like shost redundant,\ngiven it already runs in a restricted container, it's also so easy to use this\nnew API there's no reason *not* to use it.\n\nAnd that brings me to the following that's worth repeating before we wrap things up for the day: the value of a security feature lies not in what it can do, but rather in it's ease of use. I think the API provided by Inko does a pretty good job at achieving just that, though I may be biased on account of, well, having written it.", "url": "https://wpnews.pro/news/sandboxing-with-minimal-effort", "canonical_source": "https://yorickpeterse.com/articles/sandboxing-with-minimal-effort/", "published_at": "2026-09-23 05:08:03+00:00", "updated_at": "2026-09-23 05:52:56.627655+00:00", "lang": "en", "topics": ["ai-tools"], "entities": ["Inko", "Yorick Peterse", "Landlock", "Seatbelt", "sandbox_init", "Capsicum", "OpenBSD pledge", "shost"], "alternates": {"html": "https://wpnews.pro/news/sandboxing-with-minimal-effort", "markdown": "https://wpnews.pro/news/sandboxing-with-minimal-effort.md", "text": "https://wpnews.pro/news/sandboxing-with-minimal-effort.txt", "jsonld": "https://wpnews.pro/news/sandboxing-with-minimal-effort.jsonld"}}