A POSIX shell shim that translates sudo(8) invocations to doas(1), with full option coverage, POSIX-safe argument parsing, and security hardening. Drop it in as sudo on systems where doas is the privilege escalation tool; scripts that call sudo (mostly) work without modification.
Inspired by jirutka/doas-sudo-shim (if you don't need edit-mode support and want a minimal translation layer, consider this as an option).
Compatibility: Linux, FreeBSD, OpenBSD, NetBSD, DragonFly BSD, macOS
| sudo option | Notes |
|---|---|
-u USER |
passed through to doas |
-n |
passed through to doas |
-H |
sets HOME to the target user's passwd entry |
-i |
login shell via target user's passwd entry |
-s |
shell via $SHELL or invoking user's passwd entry |
-e / sudoedit / editas |
edit mode for unprivileged invokers; see below |
-k |
clears doas auth (doas -L); with a command, runs it afterward |
-K |
clears doas auth (doas -L); no command or other options permitted |
-l |
prints a "not supported" notice |
-v |
validates via doas true (honors -n/-u); best-effort, fails closed |
-E, -A, -S, -D, -R |
warned and ignored |
-b, -g |
fatal; see sudo --help for rationale |
SUDO_UID, SUDO_GID, SUDO_USER, SUDO_HOME, and SUDO_TTY are set for the target process. The shim provides no support for SUDO_COMMAND; programs in the shim call stack will see it unset.
The shim requires a broad, non-cmd-scoped doas rule. For example:
permit :wheel
Restrictive cmd-scoped rules are not supported. For instance, granting edit mode under one requires adding unrestricted shell access; use doas directly with a narrowly scoped editor rule instead.
When invoked as sudo -e, sudoedit, or editas, the shim copies target files to a temporary directory owned by the invoking user, runs the editor unprivileged, then writes back any changed files as the privileged user.
Each file is processed in a separate editor session and written back independently, unlike real sudoedit(8), which opens all files at once. For the common single-file case the behavior is identical.
The editor is taken from $SUDO_EDITOR, $VISUAL, $EDITOR, or vi. It must be a single absolute path (no spaces, tabs, or flags). To pass options to the editor (e.g. vim -u NONE), use a wrapper script:
# /usr/local/bin/vim-sudoedit
#!/bin/sh
exec /usr/bin/vim -u NONE "$@"Then set SUDO_EDITOR=/usr/local/bin/vim-sudoedit. See sudo --help.
Edit mode can be opted out at build time (see Installation).
The shim installs editas alongside sudoedit. The name doasedit already exists in the wild, but editas mirrors doas naming better: edit as [user].
- Symbolic links may not be edited.
- Files in a user-writable directory may not be edited.
- Device files may not be edited.
- Edit mode may not be invoked by root.
Two attack families in edit mode are in scope. Symlink substitution: an attacker replaces a path component or the target with a symlink, so the privileged write-back lands on the wrong file. Temp-file substitution: the unprivileged working copy is replaced or modified during the edit session so unexpected content reaches the real target during privileged write-back. A set of mitigations address these; the full security model is documented in the SECURITY NOTE at the top of doasudo.in.
The default edit mode does not prevent same-UID exposure for the lifetime of the editor session. An optional broker keeps the working copy and editor policy outside the invoking user's tree, and returns edited bytes through a framed protocol; privileged write-back is unchanged. Enable with DOASUDO_EDIT_BROKER=1; for installation and security details see: broker/README.md.
Setting DOASUDO_CONFIRM_DIFF=1 in the environment will show a unified diff and require confirmation before each write-back. Without an interactive TTY (or with -n), edit mode exits.
make # full test suite, then build shim (run as a normal user)
doas make install # live prefix, default /usr/local
make install PREFIX=/usr # custom PREFIX (still elevated if under system paths)
make install DESTDIR=/tmp/pkg # staged install (no host post-install folded)make install installs files only; it does not run the test suite (run make first). On a live install as root with empty DESTDIR, the Makefile tail-invokes post-install (broker user + staging chown). Otherwise run make post-install (or the shipped post-install.sh) after unpack / from %post, then merge doas-snippet.conf into /etc/doas.conf. See packaging/README.md.
To build a shim without edit-mode support, use make EDIT_MODE=0 and make EDIT_MODE=0 install. This omits sudoedit/editas, edit-mode code, and edit-broker artifacts; sudo -e still parses but exits with a feature-not-built error.
make uninstallSee packaging/README.md for what is removed and how removal is validated.
Main test entry points:
make check-src # test shim and broker from source; skips the final rebuild step a full `make` does
make # full test suite and shim build (run before privileged install)For per-test details and docker images, see tests/README.md.
If the test suite cannot run in your environment, build with make doasudo and install files to match a normal make install layout (the shim expects shim-utils.sh under $(PREFIX)/libexec/doasudo/; with edit mode enabled, it also expects edit-broker-client.sh, the broker, and contracts beside it). When using DESTDIR for a staged install, run make (or make check-src) on a host similar to the deployment target first. See packaging/README.md.
Design and architecture by p-zubieta. Parts of the codebase were written with the help of AI coding assistants. All changes were reviewed and tested by the maintainers.
MIT