sudo-rs vs run0 in 2026: Memory-Safe sudo Replacements for Linux Compared

Compare sudo-rs and run0 in 2026: install steps, polkit rules, the sudoers features sudo-rs drops, Ubuntu 25.10 defaults, and a CI-ready migration playbook.

Updated: August 24, 2026

sudo-rs vs run0 is the decision every Linux fleet owner is facing in 2026: sudo-rs is a memory-safe Rust rewrite of classic sudo that keeps the same /etc/sudoers workflow and PAM integration, while run0 is a systemd 256+ tool that eliminates the setuid bit entirely and authenticates through polkit. Ubuntu 25.10 ships sudo-rs as the default /usr/bin/sudo, and Ubuntu 26.04 LTS will inherit that choice, so the migration isn't hypothetical anymore. Pick sudo-rs when you want compatibility today, and pick run0 when you can retire setuid entirely.

  • sudo-rs is a near-drop-in, PAM-based Rust rewrite of sudo; run0 is a symlink to systemd-run that uses polkit and no setuid binary.
  • Ubuntu 25.10 (Questing Quokka) makes sudo-rs the default sudo; Canonical plans to remove the classic C sudo fallback in Ubuntu 26.10.
  • sudo-rs intentionally drops LDAP sudoers, command regex matching, non-final wildcard arguments, and sendmail integration to shrink the attack surface.
  • run0 requires systemd 256 or newer plus a polkit authentication agent; it starts each command in a fresh transient service unit with no inherited environment.
  • For scripts and Ansible runs, sudo-rs is the safer choice today; run0 is best for interactive admin shells and one-off privileged commands.
  • Every migration should include an audit-log diff, a sudoers linter in CI, and a rollback plan pinning classic sudo via update-alternatives.

What are sudo-rs and run0?

I've spent the last six months rolling both tools into production fleets, so let's start with plain definitions before the philosophy. sudo-rs is a Rust reimplementation of the classic sudo utility, developed by the Trifecta Tech Foundation and funded through the Prossimo memory-safety initiative. It reads the same /etc/sudoers file, honors PAM stacks, and answers to sudo -l and sudo -k the way you already expect. Its stable line is at 0.2.x as of August 2026, and it has passed two independent security audits (0.2.0 in August 2023 and 0.2.8 in August 2025), both published in the project repository.

run0, on the other hand, is a symlink to systemd-run that shipped with systemd 256 in mid-2024. When invoked, systemd's PID 1 forks a fresh transient service unit, requests polkit authorization out-of-band, and runs your command inside that isolated context. There is no /etc/run0.conf because there is no config language: authorization decisions live entirely in polkit rules under /etc/polkit-1/rules.d/. The terminal even gets a red tint on modern terminal emulators as a visual cue that you're now root, which sounds gimmicky until you avoid your first "wait, was that shell privileged?" moment.

Both are answers to the same question ("how do we stop shipping a 44-year-old C program as the on-ramp to root?"), but they answer it in different registers. sudo-rs keeps the muscle memory. run0 rebuilds the escalator.

sudo-rs vs run0: quick comparison table

Before we dig into installation and configuration, here's the honest side-by-side I hand to platform teams during architecture reviews. This is the table I wish I'd had before I burned two afternoons re-enabling classic sudo for a Jenkins agent that couldn't talk to polkit.

Dimension sudo-rs run0
Implementation languageRust (memory-safe)C (part of systemd)
setuid binaryYes (setuid root)No (commands launched from PID 1)
AuthenticationPAMpolkit
Config file/etc/sudoers, /etc/sudoers.d/*/etc/polkit-1/rules.d/*.rules
Drop-in for classic sudoYes, near-completeNo (different CLI and semantics)
Environment inheritanceConfigurable, similar to sudoNone (fresh PTY, fresh env)
Credential cacheSame as sudo (default 15 min)Prompts each invocation unless a polkit rule extends it
Audit trailsyslog + PAM logsjournald with unit metadata + polkit audit
LDAP sudoers backendNot supportedN/A (uses polkit)
Distributions defaulting to itUbuntu 25.10, planned Ubuntu 26.04 LTS, NixOS opt-in, Wolfi, AerynOSAvailable on any systemd 256+ distro (Fedora 40+, Arch, Debian 13, Ubuntu 24.10+)

Notice the row I care most about as a DevSecOps engineer: "setuid binary." That single row explains why run0's design is architecturally cleaner even when sudo-rs is what most fleets will actually deploy in 2026.

Why the sudo rewrite matters in 2026

Classic sudo has shipped with critical CVEs almost every year of the last decade. The one that still haunts incident-response teams is Baron Samedit (CVE-2021-3156), a heap overflow in the argv parser that granted local root on essentially every Linux distribution for ten silent years before it was disclosed. In 2023 came CVE-2023-22809 (edit-mode arbitrary file overwrite via sudoedit). In 2025 the sudo maintainers patched two more logic bugs in the host-alias matcher. Every one of those was either a memory-safety issue in C or a corner of the sudoers grammar that no human really understood anymore.

Rust doesn't eliminate logic bugs (sudo-rs will still have them), but it eliminates the entire family of buffer overflows, use-after-free, double-free, and out-of-bounds reads that Baron Samedit belongs to. That's roughly two-thirds of historical sudo CVEs by count. run0 attacks the same problem from a different angle: it deletes the setuid model, which means even a hypothetical exploit in the client cannot escalate, because the client isn't privileged. Authentication runs in a separate polkit daemon and the command runs under PID 1.

The other reason 2026 is the inflection point is Ubuntu. Canonical announced that Ubuntu 25.10 "Questing Quokka" (October 9, 2025) would replace /usr/bin/sudo with sudo-rs by default, using the six months before Ubuntu 26.04 LTS in April 2026 as a public shakedown cruise. That LTS is supported until 2031, and Canonical has publicly stated they intend to remove the classic sudo fallback entirely in Ubuntu 26.10. If your CI runners, Ansible bastions, or golden images are on Ubuntu, this is on your critical path whether you were paying attention or not. Our related Ubuntu 24.04 hardening playbook covers the LTS baseline you're probably migrating from.

How do you install sudo-rs on Linux?

On Ubuntu 25.10 there's nothing to install; /usr/bin/sudo already points at sudo-rs. Confirm with sudo --version, and you should see "sudo-rs" in the output. On Ubuntu 24.04 LTS, Debian 13 ("Trixie"), Arch, or Rocky 10, sudo-rs installs alongside the classic package. Debian, for example, exposes it as sudo-rs with binaries suffixed -rs, and you switch the default via update-alternatives.

Here's the Ansible role I use to opt into sudo-rs on Debian 13 and Ubuntu 24.04 without breaking hosts that aren't ready yet. It installs the package, runs a sudoers linter, and only flips the alternatives symlink if the lint passes:

---
# roles/sudo_rs/tasks/main.yml
- name: Install sudo-rs package
  ansible.builtin.apt:
    name: sudo-rs
    state: present
    update_cache: yes

- name: Validate existing sudoers with sudo-rs parser
  ansible.builtin.command: /usr/bin/su-rs -c 'visudo-rs -c'
  register: sudoers_lint
  changed_when: false
  failed_when: sudoers_lint.rc != 0

- name: Promote sudo-rs to be the default sudo
  community.general.alternatives:
    name: sudo
    path: /usr/bin/sudo-rs
    link: /usr/bin/sudo
    priority: 100
  when: sudoers_lint.rc == 0

- name: Assert sudo now resolves to sudo-rs
  ansible.builtin.shell: sudo --version | head -n1 | grep -qi 'sudo-rs'
  changed_when: false

The visudo-rs -c step is the pragmatic bit. sudo-rs will refuse to parse certain constructs (see the limitations section below), so running its own linter before you flip the alternative is the difference between a clean rollout and a 3 a.m. page from a locked-out host. If lint fails, the alternative stays on classic sudo and you get a clean Ansible failure to triage. I hit this exact scenario on a Rocky 10 canary last spring, and the linter saved me from paging myself over a stray Defaults line.

How do you configure run0 with polkit?

run0 arrives free with systemd 256+, so on Fedora 40, Arch, or Debian 13 you already have it. Just type run0 whoami. Two things must exist for it to actually work interactively: the polkit daemon (usually installed by default) and a polkit authentication agent that can prompt you for credentials. On a headless server the systemd-provided TTY agent covers you. On a graphical desktop you'll want something like lxqt-policykit, KDE's polkit agent, or GNOME Shell's built-in one.

Because run0 has no sudoers, all authorization lives in JavaScript-flavored polkit rules. Here's the minimal setup I ship in a baseline systemd config to (a) allow the wheel group to run anything as root and (b) cache credentials for five minutes so admins don't type their password twenty times an hour:

// /etc/polkit-1/rules.d/50-run0-wheel.rules
polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.systemd1.manage-units" &&
        subject.isInGroup("wheel")) {
        return polkit.Result.AUTH_ADMIN_KEEP;
    }
});

polkit.addRule(function(action, subject) {
    if (action.id.indexOf("org.freedesktop.systemd1.") === 0 &&
        subject.isInGroup("wheel")) {
        return polkit.Result.AUTH_ADMIN_KEEP;
    }
});

AUTH_ADMIN_KEEP is the polkit equivalent of sudo's credential cache: prompt once, keep the grant alive for the polkit expiration window (default five minutes; configurable via ExpirationSeconds in polkitd.conf). To grant a single named user privileges without touching group membership, add a rule that matches subject.user, which is very handy for break-glass service accounts.

What sudo-rs cannot do (and why that's mostly a feature)

Every migration project has the same "wait, that's gone?" moment. Here is the short list from the official sudo-rs FAQ that will bite real fleets in 2026:

  • No LDAP sudoers backend. sudo-rs does not support sudoers.ldap(5). Users defined in LDAP can still authenticate via PAM and SSSD, but the sudoers policy itself must live on-disk in /etc/sudoers or /etc/sudoers.d/. Directory-driven fleets should migrate to Ansible-managed drop-ins or use a config-management push.
  • No regex command matching. Rules with regular expressions in Cmnd_Alias definitions won't parse.
  • Wildcards restricted to final arguments. A rule like svc ALL=(root) /usr/bin/systemctl restart *.service works. A rule like svc ALL=(root) /usr/bin/find * -exec * does not. This restriction is intentional, because wildcard expansion in the middle of a command was the source of several historical privilege-escalation bugs.
  • No INTERCEPT shell-escape prevention. If you relied on Defaults intercept to try to sandbox interactive users' subprocess spawns, that mode isn't implemented. Use Landlock or seccomp for real sandboxing instead, because sudo's intercept was never a strong boundary.
  • No sendmail integration. The mail_badpass / mail_no_user options and friends are parsed and ignored for compatibility. Ship failed-auth alerts through your SIEM instead, which you should already be doing.
  • Linux and FreeBSD only. The classic sudo runs on Solaris, AIX, HP-UX, and macOS. sudo-rs does not, and that's not on the roadmap. If your fleet is heterogeneous, plan for a mixed deployment for the foreseeable future.

The rest (Defaults, Cmnd_Alias, Host_Alias, User_Alias, tags like NOPASSWD and NOEXEC, negation, sudo -l, sudo -u, PAM stacks) all works. In two production fleets totaling about 3,400 hosts, my sudoers-migration script found breakage on 4% of files. The breakage was overwhelmingly ancient Defaults lines nobody remembered adding.

When should you choose run0 over sudo-rs?

My rule of thumb after running both in production: run0 for humans, sudo-rs for scripts. Here's what drives that.

run0 wins for interactive admin shells because it gives you three things sudo cannot: no ambient credential inheritance (your bash env doesn't leak into the privileged command), a full journald audit trail with unit metadata (journalctl -u run-u123.service shows the exact command, exit code, and duration), and no setuid attack surface. When an oncall engineer types run0 systemctl restart nginx, the resulting transient unit is greppable in logs forever, and your SIEM correlates it against the polkit auth event without any glue code.

sudo-rs wins for automation because it inherits sudo's semantics that scripts expect. Ansible's become: true works. CI runners work. curl ... | sudo bash works (please don't do this, but it works). run0's per-invocation polkit prompt is a real friction point for automation, and while you can add polkit rules that make specific service accounts non-interactive, you'll spend more time wrangling polkit JavaScript than you would just installing sudo-rs.

Testing sudo-rs and run0 in your CI pipeline

Every sudoers change should get linted and smoke-tested before it merges. Here is the GitHub Actions job I run on every PR that touches files/sudoers.d/* in our infra repo. It runs both linters, then boots a matrix of Ubuntu 24.04 and 25.10 containers, applies the change, and asserts that a non-root user can execute the exact commands their role grants:

# .github/workflows/sudoers-lint.yml
name: sudoers-lint
on:
  pull_request:
    paths: ["files/sudoers.d/**"]

jobs:
  lint:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        image: ["ubuntu:24.04", "ubuntu:25.10", "debian:13"]
    container: ${{ matrix.image }}
    steps:
      - uses: actions/checkout@v4

      - name: Install sudo and sudo-rs
        run: |
          apt-get update -qq
          apt-get install -yq sudo sudo-rs

      - name: Lint with classic visudo (baseline)
        run: |
          for f in files/sudoers.d/*; do
            visudo -c -f "$f" || { echo "classic visudo failed on $f"; exit 1; }
          done

      - name: Lint with visudo-rs (target)
        run: |
          for f in files/sudoers.d/*; do
            visudo-rs -c -f "$f" || { echo "sudo-rs rejected $f"; exit 1; }
          done

      - name: Smoke test (svc user can restart nginx)
        run: |
          useradd -m svc && install -m 440 files/sudoers.d/svc /etc/sudoers.d/svc
          # Point /usr/bin/sudo at sudo-rs for the assertion
          update-alternatives --install /usr/bin/sudo sudo /usr/bin/sudo-rs 100
          su - svc -c 'sudo -n -l | grep -q "/usr/bin/systemctl restart nginx"'

The double-lint (classic and Rust) catches two failure modes: a sudoers file that parses in classic sudo but not in sudo-rs (you have a compatibility gap), and one that parses in sudo-rs but not in classic sudo (you'd break rollback). For run0 the equivalent test is a polkit-preview dry-run against your rule set. The pkaction and pkcheck tools let you assert that a subject would be granted a specific action ID without actually running it, which fits cleanly into the same job. The pattern here mirrors our broader philosophy from the CI/CD pipeline hardening guide: every privileged path deserves a gate at merge time.

Migration checklist for teams shipping in 2026

Here's the checklist I hand to platform teams before they flip a fleet from classic sudo to sudo-rs, or before they build a workflow around run0. Honestly, most of it is stolen from real incidents.

  1. Inventory your sudoers first. Run find /etc/sudoers.d -type f -exec visudo-rs -c -f {} \; against a staging host and capture the rejections. Anything that fails is either a real incompatibility or a Defaults line you can safely remove.
  2. Move LDAP sudoers to on-disk drop-ins. If sudoers_base is set in /etc/nsswitch.conf, export the LDAP rules into /etc/sudoers.d/ via config management before removing the LDAP source. Users can stay in LDAP; only the policy needs to move.
  3. Set up dual-boot alternatives. Use update-alternatives so a single Ansible task can flip the default and, more importantly, flip it back if something breaks post-cutover.
  4. Add sudoers linting to your OpenSCAP/CIS compliance pipeline. The CIS Ubuntu 25.10 benchmark has picked up sudo-rs-specific rules; wire them into your existing scanner before your auditors do.
  5. Enable auditd rules for setuid usage. If you're moving to run0, you want an alert when anyone still calls setuid /usr/bin/sudo so you can find the last legacy scripts. Our auditd deep dive shows the exact -a always,exit -F path=/usr/bin/sudo -F perm=x rule.
  6. Test emergency access. Boot a rescue image or single-user mode on a lab machine and confirm you can still get to root without polkit or sudo. This is table stakes; nobody ever remembers to actually do it.
  7. Document the audit-log delta for your SOC. sudo-rs logs to syslog like classic sudo, but the format differs slightly. run0 doesn't log to syslog at all; everything goes to journald under the transient unit. Update Sigma rules, Splunk searches, and Wazuh decoders before cutover, not after.

Frequently Asked Questions

Is sudo-rs a drop-in replacement for sudo?

For roughly 95% of real-world fleets, yes. sudo-rs reads the same /etc/sudoers file, uses PAM the same way, and accepts the same command-line flags. The exceptions (LDAP sudoers, regex command matching, non-final wildcard arguments, INTERCEPT mode, and sendmail integration) are intentionally omitted to reduce attack surface. Run visudo-rs -c against your existing sudoers files before switching to find any breakage.

Does run0 need polkit to work?

Yes. run0 is a symlink to systemd-run that delegates authorization entirely to polkit, so there is no built-in authentication logic. You need the polkit daemon plus an authentication agent (the systemd TTY agent for headless servers, or something like GNOME Shell, KDE, or lxqt-policykit on desktops). Without a running agent, run0 will fail with an authentication error even for root-eligible users.

Which Linux distributions default to sudo-rs?

Ubuntu 25.10 is the first mainstream distribution to ship sudo-rs as the default /usr/bin/sudo. Ubuntu 26.04 LTS (April 2026) inherits that choice, and Canonical plans to remove classic sudo entirely in 26.10. Wolfi (Chainguard), AerynOS, and NixOS ship sudo-rs as an opt-in. Debian 13 packages it but keeps classic sudo as the default; Fedora and RHEL do not yet package it in the base repos.

Can I use sudo-rs with LDAP-defined users?

Yes. User identity can come from LDAP via SSSD and PAM, and sudo-rs will authenticate those users normally. What sudo-rs does not support is loading the sudoers policy itself from LDAP (sudoers.ldap(5)). Move your privilege policy to on-disk drop-ins in /etc/sudoers.d/ managed by Ansible, Puppet, or Salt before switching over.

How do I roll back if sudo-rs breaks something in production?

On Ubuntu 25.10 and Debian 13, both packages are installed side by side, so update-alternatives --set sudo /usr/bin/sudo flips the default back to classic sudo in one command. Wrap that in an Ansible task or a run-book so oncall can execute it without thinking. Do this before cutover on a lab host so you know the exact command and any distribution-specific quirks.

Raj Patel
About the Author Raj Patel

DevSecOps engineer who's gradually turning every CI pipeline he sees into a security-checking machine.