The RootAsRole project
by Eddie Billoir
This is the documentation for the RootAsRole project, including dosr, chsr, capable, and gensr, assuming that you want to use the latest version of the project. If you are looking for older versions, please refer to the github history of the project.
Features
- A structured access control model based on Roles
- Linux Capabilities support
- Highly configurable
- Command matching with glob for binary path and PCRE2 for command arguments
- 🛠️ Configuration Helpers:
Mascott
In this book, you’ll find CaRoot, our mascott! It’ll give advices all along you read the documentation.
Installation
Compatibility
The Linux capabilities feature is available in kernel >= 4.3. So dosr tool is compatible with at least this version.
The eBPF compilation minimal features are available in kernel >= 5.0, so capable, gensr tools are compatible with at least this version.
Install from Linux distributions
We really need your help to bring the project to Linux distributions repositories! Please contribute 🙏!
Arch Linux (AUR)
git clone https://aur.archlinux.org/dosr.git
cd dosr
makepkg -si
you can also use yay AUR manager or any other one you like. Please vote for the AUR if you want it into pacman extra repo! All you need is an Arch AUR account and you could vote for the AUR 🙂
Compile and install from source
This is a complete process for compiling and installing dosr and chsr binaries.
Prerequisites
- Linux system with PAM
- Rust toolchain
- Administrative rights (
sudoor equivalent)
Retrieve the source code
git clone https://github.com/LeChatP/RootAsRole
cd RootAsRole
Dependencies installation, compiling and installing
Before installing the tool, you might want to define some default behaviors. You’ll find them in this page Build-time default configuration.
It might be trivial, but in order to install RootAsRole, you needs full administrative privileges. But only for specific cases when needed. Again, even here, we wants to apply PoLP. However, compiling a program will never need privileges. So managing unprivileged compilation and privileged installation is a bit tricky.
To manage that, we developed an installation helper tool
cargo xtask install -bip sudo
This command performs:
- dependency installation when required (privileged)
- project build
- deployment of
dosrandchsrinRAR_BIN_PATH(privileged) - capability/ownership setup for
dosr(privileged) - installation of PAM config for
dosrwithRAR_PAM_SERVICEname (privileged) - installation of the configuration and policy with
RAR_CFG_PATHandRAR_CFG_DATA_PATHpaths (privileged) - immutable flag setup on policy file when supported by the filesystem controlled with
RAR_CFG_IMMUTABLE(privileged)
If you don't have sudo installed, you can use su, doas or please binaries. They should work as expected.
Build-time default configuration
All this configuration was thought to apply the Fail-safe default principle by Saltzer & Scroeder. This implies enforcing the most strict security options at the top-level of the configuration and overriding only at the lowest according to needs! So if you want to modify Build-time defaults, be very careful and understand the implications of your changes.
Before installing the tool, you might want to set default behavior. You’ll find in .cargo/config.toml :
[env]
RAR_CFG_TYPE = "json"
RAR_CFG_PATH = "/etc/security/rootasrole.json"
RAR_CFG_DATA_PATH = "/etc/security/rootasrole.d/"
RAR_PAM_SERVICE = "dosr"
RAR_BIN_PATH = "/usr/bin"
RAR_CFG_IMMUTABLE = "true"
RAR_CHSR_EDITOR_PATH = "/usr/bin/vim"
RAR_TIMEOUT_TYPE = "ppid"
RAR_TIMEOUT_DURATION = "00:05:00"
RAR_TIMEOUT_MAX_USAGE = ""
RAR_PATH_DEFAULT = "delete"
RAR_PATH_ADD_LIST = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
RAR_PATH_REMOVE_LIST = ""
RAR_ENV_DEFAULT = "delete"
RAR_ENV_KEEP_LIST = "HOME,USER,LOGNAME,COLORS,DISPLAY,HOSTNAME,KRB5CCNAME,LS_COLORS,PS1,PS2,XAUTHORY,XAUTHORIZATION,XDG_CURRENT_DESKTOP"
RAR_ENV_CHECK_LIST = "COLORTERM,LANG,LANGUAGE,LC_.*,LINGUAS,TERM,TZ"
RAR_ENV_DELETE_LIST = "PS4,SHELLOPTS,PERLLIB,PERL5LIB,PERL5OPT,PYTHONINSPECT"
RAR_ENV_SET_LIST = ""
RAR_ENV_OVERRIDE_BEHAVIOR = "false"
RAR_AUTHENTICATION = "perform"
RAR_EXEC_INFO_DISPLAY = "hide"
RAR_USER_CONSIDERED = "user"
RAR_BOUNDING = "strict"
RAR_UMASK = "0022"
RAR_TIMEOUT_STORAGE = "/var/run/rar/ts"
RAR_WORKDIR_BEHAVIOR = "all"
RAR_WORKDIR_ADD_LIST = ""
RAR_WORKDIR_REMOVE_LIST = ""
#RAR_WORKDIR_FALLBACK = ""
These variables allows a customized compilation of the binary. With these variables you’ll be able to edit every behaviors, either hardcoded in the binary such as the root configuration file path, or the execution options when they aren’t defined at all in the policy.
Core Path & Configuration Storage Variables Reference
| Variable Name | Type / Format | Description |
|---|---|---|
RAR_BIN_PATH | Folder Path | Used by the xtask command: defines the directory where the compiled binaries will be copied. |
RAR_CFG_TYPE | String ("json" | "cbor") |
RAR_CFG_PATH | File Path | Absolute filesystem path to the primary policy configuration file. |
RAR_CFG_DATA_PATH | Directory/File Path | Path to configuration data directory. The directory must exist before compiling, or its name must end with .d. |
RAR_CFG_IMMUTABLE | Boolean String ("true" | "false") |
RAR_CHSR_EDITOR_PATH | Binary Path | Defines the binary path for the chsr policy editor utility (e.g., /usr/bin/vim or /usr/bin/nano). |
RAR_PAM_SERVICE | String | The exact system PAM service configuration identifier called during user authentication. |
For security reasons, you'll have to set absolute paths.
Environment Variable Management
| Variable Name | Type / Format | Description |
|---|---|---|
RAR_ENV_DEFAULT | String ("none" | "check" |
RAR_ENV_KEEP_LIST | Comma-separated (,) | A list of explicit environment variables allowed to safely inherit and pass through unchanged. |
RAR_ENV_CHECK_LIST | Comma-separated (,) | Environment variables routed to strict validation/compliance check logic. |
RAR_ENV_DELETE_LIST | Comma-separated (,) | Environment variables aggressively wiped and stripped out of the execution context entirely. |
RAR_ENV_SET_LIST | Newline-separated (\n), K=V | Static key-value pairs to inject directly into the target execution environment. |
RAR_ENV_OVERRIDE_BEHAVIOR | Boolean String ("true" | "false") |
Malformed values strings create compile-time errors.
Execution Context & Workspace Controls
| Variable Name | Type / Format | Description |
|---|---|---|
RAR_PATH_DEFAULT | String ("none" | "keep_safe" | "keep_unsafe") | Default processing stance for target system PATH variable management. Keep safe ignores relative paths, where keep_unsafe is allowing relative paths. |
RAR_PATH_ADD_LIST | Colon-separated (:) | Explicit directory paths appended/inserted directly into the PATH. |
RAR_PATH_REMOVE_LIST | Colon-separated (:) | Explicit directory paths to actively scrub out of the PATH. |
RAR_WORKDIR_BEHAVIOR | String ("allowlist" | "blacklist") | Defines the basic behavioral model of the target directory ruleset. |
RAR_WORKDIR_ADD_LIST | Comma-separated (,) | Allowed system directories where users are explicitly authorized to execute commands. |
RAR_WORKDIR_REMOVE_LIST | Comma-separated (,) | Denylist of forbidden working directories where execution requests are rejected. |
RAR_WORKDIR_FALLBACK | Optional File Path | Forces a static fallback working directory for the executed command if not defined. Warning! This will always set the working directory for every commands. |
Be very careful with PATH management, "keep-unsafe" is insecure. And I strongly not reccommend it.
Working directory management is somewhat useful for some cases, such as allowing restricted commands only in a specific restricted directory. Of course that means that allowed commands shouldn't change internally the working directory. For example, if you set "/bin/ls" command, and setting fallback workdir to the "/tmp", then all "dosr ls" command will only show "/tmp" content, no matter where the user is located. Otherwise, if you set "/bin/ls .*" for the command, then the user still be able to retrieve folder contents everywhere on the system.
Security, Timeouts & Authentication
| Variable Name | Type / Format | Description |
|---|---|---|
RAR_AUTHENTICATION | String ("perform" | "skip") | Controls whether explicit user authentication sequences must be requested. |
RAR_BOUNDING | String ("strict" | "ignore") | Dictates whether to actively lock down or ignore the Linux capabilities “Bounding” set. |
RAR_USER_CONSIDERED | String ("root" | "user") | Determines if UID 0 requests are treated like a regular user or preserve classic legacy root behavior. |
RAR_UMASK | Octal / Decimal Integer | Default file creation mask applied directly to processes. |
RAR_TIMEOUT_TYPE | String ("ppid" | "tty" | "uid") | The operating system context key used to track and anchor elevation timeouts. |
RAR_TIMEOUT_DURATION | Duration "hh:mm:ss" | The temporal lifespan window allowed for a token before authorization validation expires. |
RAR_TIMEOUT_MAX_USAGE | Integer (u64) | Absolute limits on how many times a given token context can be reused. |
RAR_TIMEOUT_STORAGE | Folder Path | Filesystem path used by the timeout engine to persist execution timestamp tokens. |
RAR_EXEC_INFO_DISPLAY | String ("hide" | "show") | Give the ability to users to know their allowed commands and configuration applied. |
Showing to a user their policy can help attackers in their enumeration phase. A good policy knows what command a user is allowed to do in a such way that the user don't know that there is a strict policy applied.
Your First Policy
In this chapter, we will build a minimal policy:
allowing users in the ops group to reboot the host via dosr.
1. Create a Role
In RootAsRole, everything starts with a Role. A Role can be used to represent job operations, or a full procedure that some users are allowed to perform. The main idea here is to organize system administration into wellknown sets of actions, mastering your system’s operations.
Here, we create a role named ops and grant it to the system group of the same name:
chsr role ops add
chsr role ops grant -g ops
2. Define a Task
A single Role can encompass many different actions.To keep your system clean and granular, we break down Roles into Tasks. Let’s create a task specifically dedicated to rebooting the system:
chsr role ops task reboot add
Keep your tasks narrow! One operational action per task is the golden rule. If you find yourself naming a task 'manage_everything', you are accidentally recreating the 'root' user!
3. Define Execution Constraints
This is where we define what can be run and how it interacts with the system. The good practice is a whitelist approach to prevent arbitrary command execution.
3.1. Specify the Command
We must define which binary it is allowed to execute. We add the reboot command to our whitelist:
chsr role ops task reboot cmd whitelist add /usr/bin/reboot
You must write the absolute path to the command. This is a security measure to avoid command hijacking through PATH manipulation.
3.2. Configure the Execution Context
Since systemd requires the root UID, we must configure dosr to handle this securely:
# Set effective UID to root for this task
chsr r ops t reboot cred set --setuid root
# Disable all capabilities explicitly for a clean slate
chsr r ops t reboot cred caps setpolicy deny-all
# Lock the execution environment
chsr r ops t reboot o root user
chsr r ops t reboot o bounding strict
The commands are doing the following:
cred set --setuid root: This sets the UID to root for the task.cred caps setpolicy deny-all: This denies all capabilities for the task.o root user: This enables flag on the kernel to consider the root user as a not privileged user, thus disabling the special treatment of the root user made by the kernel.o bounding strict: This enforces a strict bounding set of capabilities, further limiting the elevation of privileges.
In fact, The last 3 commands are not altering the configuration at all. Because RaR enforces already strict defaults, so, they were already set. But it is a good practice to explicitly set them, to avoid any misconfiguration by inheritance.
You might wonder why we need setuid if we have Linux Capabilities. Modern systems rely heavily on systemd. Unlike older tools that check for CAP_SYS_BOOT capability, systemd do a simple check: "Are you UID 0?". If you are not root, it denies the request, even if you technically have the capability.
5. Execution and Verification
Your policy is now ready! As a member of the ops group, you can now invoke the dosr engine:
dosr reboot
If a user has multiple roles or tasks that could match the command,
dosr will attempt to resolve the conflict by choosing the least privileged option.
However, when using dosr in automation (like Ansible), it is best to be explicit to ensure deterministic behavior:
dosr -r ops -t reboot reboot
Configuration
This section is written by human and AI-enhanced for covering edges and simplifying the content.
This chapter explains how to configure RootAsRole (RaR) efficiently and safely. It covers the structure of the policy, every field in it, and the reasons behind each of them. The policy format is the result of a long engineering effort grounded in research and security rationale, so most fields exist to answer a concrete risk.
To make these risks tangible, the whole chapter follows a single example: a small administration team, the WebOps team, migrating from sudo to RaR. Each section introduces a concept, then applies it to the WebOps team. At the end of the chapter, you will have built their complete policy step by step.
Keep one idea in mind along the way: dangerous permissions are sometimes necessary, and they are rarely obvious. The goal is not to remove every risk, but to identify each risk and make a deliberate decision about it.
Introduction with the WebOps scenario
The team and the system
The WebOps team is made of 3 administrators who manage a web infrastructure: servers, applications and related services. Their daily tasks include restarting services, checking logs and applying updates.
The machine runs a Linux distribution with systemd as init system, and Caddy as webserver:
- websites are served from
/srv/www/; - Caddy runs as
www-data, and its configuration lives in/etc/caddy/; - Caddy logs are written to
/var/log/caddy/, readable by theadmgroup.
The system has the following users: alice, bob, charly, sshd, www-data and of course root. The administrators have distinct responsibilities: Alice manages website content, Bob operates the webserver and its configuration and logs, and Charly handles system and webserver updates.
The starting point: sudo
Initially, the team used sudo with the following sudoers configuration:
Cmnd_Alias WEB_CONTENT = /usr/bin/install -d -m * -o * -g * /srv/www/*
Cmnd_Alias CADDY_MGMT = /bin/systemctl restart caddy, \
/bin/systemctl reload caddy, \
/bin/systemctl start caddy, \
/bin/systemctl stop caddy, \
/bin/systemctl status caddy, \
/usr/bin/journalctl -u caddy*, \
/bin/journalctl -u caddy*, \
/usr/local/bin/caddy-probe
Cmnd_Alias CADDY_LOGS = /usr/bin/tail -n * /var/log/caddy/*, \
/usr/bin/tail -f /var/log/caddy/*, \
/usr/bin/less /var/log/caddy/*
Cmnd_Alias SYS_UPDATES = /usr/bin/apt update, \
/usr/bin/apt upgrade, \
/usr/bin/apt --only-upgrade install caddy, \
/usr/bin/apt dist-upgrade, \
/usr/bin/reboot
alice ALL = (ALL) WEB_CONTENT
bob ALL = (ALL) CADDY_MGMT, CADDY_LOGS
charly ALL = (ALL) SYS_UPDATES
caddy-probe is a custom tool used by bob to diagnose the Caddy configuration and to capture network traffic. This configuration looks restrictive, but it is not. Let’s review it.
Alice. In sudoers, * also matches spaces, so it can swallow additional arguments. Alice can run install -d -m 777 -o alice -g alice /srv/www/x /etc: since install -d changes the owner and mode of directories that already exist, she now owns /etc. On top of that, (ALL) allows her to run the command as any user she wants.
Bob. less lets the user open other files (:e /etc/shadow) and spawn a shell (!sh). The same applies to systemctl status and journalctl, which open a pager by default. The wildcard is also exposed to path traversal: /var/log/caddy/../../../etc/shadow matches /var/log/caddy/*. Every one of these escapes runs as root.
Charly. apt is root-equivalent by nature: package maintainer scripts run arbitrary code as root. Restricting the arguments does not change that. Moreover, apt reads the APT_CONFIG environment variable: if it reaches the command, an APT::Update::Pre-Invoke hook spawns a root shell. Finally, charly can reboot the system at any time. This is not a privilege escalation, but it is an availability risk, and it is a different kind of action than updating packages.
caddy-probe. The tool only needs to read the Caddy configuration and to capture packets, but sudo runs it with full root privileges.
The incident
One day, the team got attacked: the attacker stole bob’s credentials. Then, they found a vulnerability in caddy-probe that allowed them to execute arbitrary commands. Since the tool ran as root with all privileges, the attacker immediately became root.
The vulnerability itself was not avoidable by configuration. However, its impact was. If the principle of least privilege had been applied, the tool would have had only the privileges required to listen to the network and to read a few files, and the attacker would have gained almost nothing.
What the team needs
After this incident, the team decided to replace sudo with RootAsRole. The rest of this chapter follows them as they build their policy, one need at a time.
We first see where the policy lives on the system and how it is protected. Then, since sudo tied every permission to an individual, the team turns its responsibilities into roles and assigns them to groups instead of users. Each role is made of tasks, and each task answers two questions. The first one is what can be run: the commands section shows how to allow exactly the intended commands and arguments, without the loopholes of sudoers wildcards. The second one is with which privileges: the credentials section gives each command the minimal user, groups and capabilities it needs, so that an escape or a vulnerability like the one in caddy-probe yields almost nothing.
A command also runs in a context. The options section explains how to stop the caller’s environment from crossing the privilege boundary, as APT_CONFIG did, and how to set safe defaults once for the whole policy. Then, hierarchy and separation of duty organise the roles together, and keep the most exposed account away from the most critical role. After a tour of the other plugins, we assemble the complete WebOps policy and check it against every issue listed above. Finally, we look at what remains open, root-equivalent tasks, and how Zero Trust addresses it.
Where the policy lives
The data is split into two parts: the root file, which tells RaR where the policy is stored, and the policy itself. The root file is /etc/security/rootasrole.json:
{
"version": "4.1.0",
"storage": {
"method": "json",
"settings": {
"path": "/etc/security/rootasrole.d",
"immutable": true
}
}
}
version: policy schema version written by tooling. It is mainly used to facilitate migrations during upgrades.storage: describes where the policy data is stored.method: can bejsonorcbor. To migrate from one format to another, use thechsr convertcommand. See File Format Conversion for more details.settings: parameters of the storage method. JSON and CBOR are both file-based, so they have apathparameter. Ifpathpoints to the root file itself, the roles declared directly in the root file are used. Otherwise, the policy files found atpathare loaded.immutable: enforces the IMMUTABLE flag on the policy directory and files, to prevent accidental overwriting. Only privileged users withCAP_LINUX_IMMUTABLEcan modify the policy. Therefore, this capability becomes a highly sensitive one: we will make sure no WebOps task grants it.
For the WebOps team, the policy will be stored in /etc/security/rootasrole.d/default.json. A policy file has the following skeleton:
{
"version": "4.1.0",
"options": { },
"roles": [ ]
}
When your policy is ready, you can convert it to CBOR, which is much faster to load.
Roles and actors
The policy is based on Role-Based Access Control (RBAC). Permissions are not given to users or groups, but to roles. Users and groups are then assigned to roles as actors. This subtle change gives better control over permissions and a better separation of duties.
A role is composed of actors and tasks. Each task defines which commands can be run, and with which credentials.
Role engineering for WebOps
The first step is a role-engineering process: listing responsibilities. From the scenario, the WebOps team needs four roles:
| Role | Purpose | Initially assigned to |
|---|---|---|
webadmin | Publish website content | alice |
caddy-audit | Read Caddy logs | bob |
caddy-admin | Operate and diagnose the webserver | bob |
sys-admin | Apply system updates and reboot the machine | charly |
Splitting caddy-audit from caddy-admin may look excessive for now. It will pay off when we introduce role hierarchy.
We can draft the webadmin role as follows:
{
"name": "webadmin",
"actors": [
{ "user": "alice" }
],
"tasks": []
}
But alice may leave her job, and someone else will take over her responsibilities. Instead of editing the policy every time the team changes, we assign a group to the role, and add alice to that group:
{
"name": "webadmin",
"actors": [
{ "group": ["webadmin"] }
],
"tasks": []
}
Note that group accepts a list: when several groups are listed, the user must belong to all of them to be an actor of the role.
One question arises then: “Why use roles if there is a group with the same name?”. The answer is organisational. Roles define what can be done, groups gather who can do it. A role can apply to several groups, and rather than creating a new group that unites the members of several groups, it is better to have a role that focuses on the contextualisation of tasks.
Tasks
A task is a set of commands that can be run with specific credentials. Let’s define the web-push task of the webadmin role, which allows alice to publish website content:
{
"name": "web-push",
"purpose": "Publish website content from the staging area",
"commands": [
"/usr/bin/install ^-d -m 0?[0-7]{3} /srv/www/[^ ]+$",
"/usr/bin/rsync ^-rlt --delete /srv/staging/[^ ]+/ /srv/www/[^ ]+/$"
],
"cred": {
"setuid": "www-data",
"setgid": "www-data",
"capabilities": []
}
}
nameidentifies the task. When a user has several tasks matching the same command, the name is the only element that allows selecting one explicitly. If omitted, the task is identified by its index in the role.purposeis a human-readable description. It is not used by the engine, but it documents why the permission exists.commandsandcredare detailed in the next two sections.
Commands
commands supports 3 forms:
-
String policy:
"commands": "all" -
Explicit allow-list:
"commands": ["/usr/bin/systemctl restart caddy"] -
Full object (selector):
"commands": { "default": "none", "add": ["/usr/bin/systemctl restart caddy"] }
Supported default values are all and none. sub (alias del) removes commands from the set.
Command matching
Commands in the policy must be absolute paths. However, you can use globs to match multiple binary paths. See globset syntax for details.
To match a command typed without its path (like ls), the path option must include the directory containing the command (see The path option). Otherwise, the user can still run the command, but only by writing its absolute path, for example /usr/bin/ls instead of ls.
Arguments can be matched in two ways:
-
exact match: the arguments are written as a plain string, and must be typed exactly as written, for example
/usr/bin/systemctl status --no-pager caddy; -
regular expression: the arguments are written in the
^...$format, for example:/usr/bin/install ^-d -m 0?[0-7]{3} /srv/www/[^ ]+$
Regular expressions follow the syntax of the Rust regex crate. Note that this syntax does not support look-around nor backreferences.
We are still working on the way to express a regular expression safely on the arguments.
Applying it to WebOps
Remember alice’s sudoers rule: the wildcard swallowed extra arguments. A naive regex translation, ^-d -m .+ -o .+ -g .+ /srv/www/.+$, has the exact same flaw, because .+ also matches spaces. This is why the web-push task uses:
0?[0-7]{3}for the mode, which only accepts an octal mode;[^ ]+for paths, which cannot contain a space and thus cannot introduce another argument;- no
-oand-goptions at all: as we’ll see in the next section, the command already runs aswww-data.
The same reasoning applies to bob’s commands. We force --no-pager to remove the pager shell escape, and we forbid / in log file names to prevent path traversal:
"commands": [
"/usr/bin/systemctl ^(start|stop|restart|reload) caddy$",
"/usr/bin/systemctl status --no-pager caddy"
]
"commands": [
"/usr/bin/tail ^(-n [0-9]+|-f) /var/log/caddy/[^/ ]+$",
"/usr/bin/less ^/var/log/caddy/[^/ ]+$",
"/usr/bin/journalctl --no-pager -u caddy"
]
Whenever the arguments are fixed, prefer an exact match: it leaves no room for interpretation.
less is still allowed and still has a shell escape. Command matching alone cannot fix that: the next section will make this escape worthless, and the environment section will disable it.
Credentials (cred)
One big strength of RaR is its ability to enforce fine-grained execution credentials. The cred section of a task defines the user, groups and capabilities the command runs with. This completely shifts away from the “all-or-nothing” model of sudo: users can now run commands with a subset of privileges, while keeping their identity. Even the root user can run commands with no privileges at all (see the root option).
The cred section is optional. If omitted, the command runs with the credentials of the user invoking it. This can be useful to require authentication for a command without granting any additional privilege.
setuid
The setuid field sets the user the command runs as. In security terms, this is impersonation. Impersonation is a powerful tool for administrators, but also for attackers. The general advice is to avoid it, and when it is needed, to impersonate a user who owns exactly the resources required by the task.
Most administrators want to be a single user: root, because the system’s discretionary access control (DAC) is built around it. RaR suggests another approach: when impersonating root is needed, grant it without capabilities. Root then only has the rights given by file permissions, like a regular user. Going further, system configuration that administrators need should have a segregated DAC, owned by a dedicated user, so that bypassing it requires an explicit Linux capability.
setgid
The setgid field makes DAC configuration easier. You can define a single group, or a list of groups. If a list is defined, the first group is the primary group, and the others are supplementary groups.
capabilities
Linux divides the privileges of the superuser into distinct units called capabilities. Unlike DAC permissions tied to files, capabilities are assigned to processes. They allow granting a specific privilege to a task without running it as root.
Some capabilities are very powerful and can be used to gain more privileges than intended. For example, CAP_SYS_ADMIN gathers privileged features that were not worth a dedicated capability; combining all of them is close to a second definition of root. Many other capabilities can be abused to exploit the system. You can read more on the HackTricks reference.
Applying it to WebOps
Let’s give each task the minimal credentials it needs:
| Task (role) | setuid | setgid | capabilities | Why |
|---|---|---|---|---|
web-push (webadmin) | www-data | www-data | none | /srv/www is owned by www-data: no privilege is needed, only the right owner. |
read-logs (caddy-audit) | — | adm, systemd-journal | none | Bob stays bob; the groups are enough to read the logs. |
caddy-service (caddy-admin) | root | — | none | systemd authorizes service management for uid 0. No capability is required. |
caddy-probe (caddy-admin) | — | www-data | CAP_NET_RAW | Reading /etc/caddy needs the group; capturing packets needs CAP_NET_RAW only. |
apt (sys-admin) | root | root | all | Package installation is root-equivalent. We accept it, and constrain it elsewhere. |
reboot (sys-admin) | root | — | none | systemctl reboot is authorized for uid 0, without capabilities. |
Now let’s replay the issues found earlier:
- If alice abuses
installorrsync(for example with--rsh), she only obtains a shell aswww-data: the rights the task already grants her. - If bob escapes from
less, he obtains a shell as bob with theadmandsystemd-journalgroups./etc/shadowstays unreadable. - If the attacker exploits
caddy-probeagain, they get bob’s identity, thewww-datagroup andCAP_NET_RAW. They can sniff traffic, but they are not root anymore. If the tool needs promiscuous mode, addCAP_NET_ADMIN, knowing it also allows changing firewall and routing rules. aptremains root-equivalent. This is an honest limit: some tasks cannot be made safe by configuration. What we can do is identify them, isolate them in their own role, and protect the role (environment, authentication, separation of duties).
This is the main lesson of this section: when you cannot prevent an escape, make it worthless.
Selector forms
So far, credentials were forced: the user has no choice. RaR also allows users to choose a user or a group among a set allowed by the policy, with the -u and -g options of dosr. The following forms describe these sets.
capabilities
"capabilities": "all"
Gives all Linux capabilities to the command. One subtlety: all is limited to the capabilities available to the invoking process. Once a capability is dropped from the bounding set, it cannot be regained, so all will not give it back.
"capabilities": ["CAP_NET_BIND_SERVICE", "CAP_SYS_BOOT"]
Gives a list of capabilities. Again, they must be available to the invoking process, otherwise the command results in a permission denied.
"capabilities": {
"default": "all",
"del": ["CAP_SYS_ADMIN", "CAP_LINUX_IMMUTABLE"]
}
Gives every capability, except CAP_SYS_ADMIN and CAP_LINUX_IMMUTABLE. Remember that denylists are fragile: many other capabilities lead to root.
"capabilities": {
"default": "none",
"add": ["CAP_NET_BIND_SERVICE"]
}
Gives no capability, except CAP_NET_BIND_SERVICE. This is a common use case for network services, because CAP_NET_BIND_SERVICE allows binding to ports below 1024.
setuid
"setuid": {
"default": "none",
"fallback": "root",
"add": ["alice"]
}
"setuid": {
"default": "all",
"fallback": "root",
"del": ["bob"]
}
fallback is the user used when the caller does not specify -u. The first example is the good practice, because it respects the fail-safe default principle. The second one has to exist for very specific use cases. Use it with care.
setgid
setgid follows the same philosophy. Each entry is a group set, the first group of a set being the primary group:
"setgid": {
"default": "none",
"fallback": ["root"],
"add": [["wheel"]]
}
setuid and setgid can also be written in their compact mandatory form (a single user, or a group/group list), as in the WebOps tasks.
Options and inheritance
Commands and credentials define what runs. Options define in which context it runs: environment variables, PATH, authentication, working directory, etc.
Options can be set at three levels: the policy file (global), a role, or a task. The most specific level wins: Task → Role → Policy file → Built-in defaults. This lets the WebOps team set safe defaults once, globally, and only harden the tasks that need it.
Environment variables (env)
The env option decides which variables the command receives. Passing unvetted environment variables across privilege boundaries is one of the most common vectors of local privilege escalation. Charly’s APT_CONFIG issue is a perfect example. For this reason, dosr processes the environment through several filters, fail-safe checks and layers of inheritance.
Allowlisting and denylisting
The main field is default, which can be set to one of three modes:
deletemode (allowlist): The environment starts empty. Variables are only passed if they are named in thekeeplist, or named in thechecklist and validated as safe. As it is impossible to maintain an exhaustive list of every dangerous variable, this is the built-in default, following the fail-safe default principle.keepmode (denylist): The environment starts with the user’s full environment. Variables are passed unless they match thedeletelist. Variables matchingcheckare verified before they can pass. Use this mode only in trusted environments.inheritmode: Preserves the mode chosen by the level above. If every level omitsdefaultor sets it toinherit, the built-in defaultdeletemode applies.
With these modes, the policy manages environment variables like mathematical sets.
Pattern matching and the evaluation pipeline
Entries in keep, check and delete support simple wildcard matching with * (such as LC_* or *_PROXY). Since version 4.1, regular expressions have been replaced entirely by these wildcard patterns.
Regardless of the mode, every execution goes through this filtering pipeline:
- Shellshock hardening: any variable whose value begins with
()is dropped. checklist: a feature inherited from sudo. A value is deemed safe only if it contains neither slashes (/) nor percent signs (%). In the early 2000s, these characters were exploited through poorly written printf-based programs; this check is a legacy safeguard.- Timezone validation:
TZis an exception, and accepts values likeEurope/Paris, provided they do not start with a slash, contain path traversal sequences like.., or include spaces and control characters. - Set combination: in
deletemode, a variable matched by bothkeepandcheckmust pass the check. Inkeepmode, a variable matched by bothdeleteandcheckis deleted. - Target and execution context:
dosrpopulatesPATHstrictly from thepathoption (overriding any existingPATH), sets the identity variables of the target user (USER,LOGNAME,HOMEandSHELL), and adds audit variables describing the caller and the command (RAR_UID,RAR_GID,RAR_USER,RAR_COMMAND, and theirSUDO_*counterparts).TERMdefaults tounknownif no valid terminal was passed. setdirective: finally, the key-value assignments underset(for example{"NAME": "value"}) are applied. They take absolute precedence, overriding anything from earlier stages, includingPATHandHOME.
Level inheritance: preserving protections by default
dosr resolves the configuration hierarchically, starting from built-in defaults (set at compilation time via RAR_ENV_*), then global, role and task options.
To prevent security regressions, inheritance follows one rule: a protection must never disappear simply because a lower level failed to mention it.
The most specific level that defines default (delete or keep) sets the mode. For the lists (keep, check, delete and set):
- If a level defines a list without declaring
default, its entries are merged with the inherited list. - If a level defines a list and sets
defaulttodeleteorkeep, its list replaces the inherited one. - Unmentioned lists are always inherited. If a lower level switches to
keepmode but omits thedeletelist, it still inherits the dangerous variables blocked upstream. The only way to strip an inherited restriction is a deliberate empty list ([]).
Concrete behavior example
Consider global options configured as:
{"env": {"default": "delete", "keep": ["EDITOR"]}}
A user invokes dosr with the following environment:
EDITOR=vim PAGER=less FOO=bar LD_PRELOAD=/tmp/x.so LANG=fr_FR.UTF-8 LC_ALL=../x TZ=Europe/Paris, along with an exported shell function.
Depending on the task definition, the command receives:
- Task defines no
envoptions:EDITOR,LANGandTZ.EDITORcomes from the global allowlist, whileLANGandTZpass the built-inchecklist.PAGERandFOOare discarded by the allowlist,LC_ALLis rejected by the check because of the/. - Task specifies
{"keep": ["PAGER"]}: no mode is declared, soPAGERis added to the inherited allowlist. The command receivesEDITOR,PAGER,LANGandTZ. - Task specifies
{"default": "delete", "keep": ["PAGER"]}: the mode is re-declared, so the upstreamkeeplist is replaced. The command receivesPAGER,LANGandTZ, losingEDITOR. - Task specifies
{"default": "delete", "keep": [], "check": []}: the empty lists suppress both the allowlist and the check list. The command receives none of the caller’s variables. - Task specifies
{"default": "keep"}: the task switches to denylist mode, passingEDITOR,PAGER,FOO,LANGandTZ. Since the inheriteddeletelist was not cleared, built-in protections still blockLD_PRELOAD.
Preserving the environment with dosr -E
-E asks for keep mode. It is only accepted when override_behavior is true on the most specific level that sets it (built-in default: RAR_ENV_OVERRIDE_BEHAVIOR, false). A task that does not allow it is not selected: dosr chooses another task, or answers “Permission denied”.
-E is keep mode, not “everything”: the delete list, the check list and the shell function rule still apply. The -E option does not aim to silently allow everything, but to let the user keep some of their variables. RaR enforces trust boundaries, and allowing everything by default would break them. If you really want to pass everything, set delete and check to empty lists in the task, so that it is explicit in the configuration.
Applying it to WebOps
The global level keeps the built-in delete mode, which already drops APT_CONFIG: charly’s hook escalation is gone without writing a single line. The team simply makes it explicit:
"options": {
"env": { "default": "delete" }
}
For bob’s read-logs task, the set directive disables the shell escape of less and any pager:
"options": {
"env": {
"set": {
"LESSSECURE": "1",
"SYSTEMD_PAGER": ""
}
}
}
Since no default is declared at the task level, the global mode and lists are inherited, and these values are added on top.
The path option
The PATH variable is a colon-separated list of directories searched for executables. The path option controls it and must be carefully configured to avoid privilege escalation.
One question could be: “Why isn’t path part of env?”. Because this option is also used to find the command typed by the user: it is not only the resulting PATH variable, it is part of the task selection process.
This option works, again, like mathematical sets, with add and sub (alias del) lists. Its default mode can be:
delete: start from an empty list and add directories. This is the recommended mode.keep-safe: start from the caller’sPATH, and remove relative paths (such as.orbin). Absolute directories are kept as is, even if they are writable by the caller.keep-unsafe: start from the caller’sPATHas is. Avoid it.inherit: use the mode of the level above.
For WebOps, the global level only allows system directories, including /usr/local/bin where caddy-probe lives:
"path": {
"default": "delete",
"add": ["/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"]
}
These directories must only be writable by root; otherwise, anyone able to write to them could plant a binary that a task would run.
Other options
| Option | Values | Description |
|---|---|---|
authentication | perform | skip | Whether the user must authenticate before running the command. |
timeout | type, duration, max_usage | How long, and how many times, an authentication remains valid, and its scope (ppid, tty or uid). |
root | privileged | user | user sets the SECBIT_* securebits that disable the special privileges of uid 0: root becomes a regular user, and gets only the capabilities granted by the task. privileged keeps the legacy root behavior. |
bounding | strict | ignore | strict enforces the granted capabilities on the bounding set: the command and its children can never regain any other capability. |
execinfo | show | hide | Whether dosr --info may disclose the existence of the matching policy element to the user. |
workdir | path | selector | Working directory of the command, or the set of directories the user may choose. |
umask | string | File creation mask of the command. |
The root option deserves attention. On Linux, a process running as uid 0 that executes a binary automatically obtains all capabilities. Without "root": "user", a task with "setuid": "root" and "capabilities": [] would get them back as soon as the command runs another program. This is what makes the WebOps caddy-service and reboot tasks really unprivileged, so the team sets root to user and bounding to strict globally.
execinfo also has a security impact: listing what the policy allows is valuable information for an attacker who stole an account. The team sets it to hide globally.
For sys-admin, the most critical role, the team requires authentication on every use, with a short and non-reusable timeout:
"options": {
"authentication": "perform",
"timeout": { "type": "tty", "duration": "00:01:00", "max_usage": 1 }
}
Migrating configurations from version 4.0
Upgrading from version 4.0 requires running chsr upgrade as root. Package maintenance scripts run it automatically during updates.
This utility addresses differences in pattern syntax and list inheritance:
- It converts legacy regex syntax into wildcards, translating patterns like
X.*intoX*and stripping anchors (^,$). More complex regular expressions that cannot be adapted automatically are reported in the logs for manual review. - It updates legacy global
deletedefinitions containing the old 6-entry default to the current, expanded default set. - To ensure existing security profiles do not change under the new inheritance model, it injects explicit
"keep": []and"check": []into legacydelete-mode levels that left them undefined.
Tasks still using unmigrated regular expressions are rejected by dosr at runtime until the upgrade has been completed.
Hierarchy and separation of duty
Hierarchy and separation of duty (SoD) are designed as plugins on top of the core RBAC-0 model. They are not part of the core model, but they are implemented in the RaR project.
- The hierarchy plugin lets a role declare its parent roles, through the
parentsfield. The role inherits the tasks of its parents. - The static separation of duty plugin declares conflicting roles, through the
ssdfield: a user should never be an actor of two conflicting roles.
Applying it to WebOps
Bob operates Caddy, so he also needs to read its logs. Instead of duplicating the read-logs task, caddy-admin declares caddy-audit as its parent, and inherits its tasks:
{
"name": "caddy-admin",
"parents": ["caddy-audit"],
...
}
Later, when a security auditor joins the team, they can be assigned caddy-audit alone, without getting the ability to restart the webserver. This is why we split these roles earlier.
The incident also taught the team that bob’s account is the most exposed one: it uses caddy-probe, a custom tool that parses untrusted network traffic. This account must never accumulate the root-equivalent sys-admin role, even if bob replaces charly during his holidays:
{
"name": "sys-admin",
"ssd": ["caddy-admin"],
...
}
Conflict resolution
There are currently some issues on the combination of role hierarchy and static separation of duties.
Both plugins have fail-safe behaviors:
- If a user is an actor of two conflicting roles, they cannot execute any task from either role.
- If a role inherits from roles that are in conflict with each other, the user cannot execute any task from the inherited roles.
In other words, a misconfiguration denies access rather than granting it.
Other plugins and extensions
RaR supports extension fields, such as parents and ssd above. These fields are preserved by the policy model and consumed by the relevant tooling and plugins.
files with Landlock
The files field of cred defines a Landlock sandbox for the command. Landlock is a Linux security module that allows unprivileged processes to confine themselves to a limited set of resources, such as specific directories or files. In RaR, files maps paths to their access rights (read, write, execute), enforced by Landlock when the command is executed.
This is a natural next step for the WebOps team: the web-push task could, for instance, be denied write access anywhere outside /srv/www, even as www-data. Keep in mind that the sandboxed command still needs to read and execute its own libraries.
dbus for D-Bus/Polkit enforcement
dbus entries are primarily used by gensr-driven workflows to generate D-Bus/Polkit enforcement artifacts.
Deprecation of the hashchecker plugin
The hashchecker plugin was deprecated in version 4.1.0. It verified the integrity of executed binaries by comparing their cryptographic hashes against an allowlist defined in the policy. RaR now recommends the IPE LSM (Integrity Policy Enforcement) for binary integrity verification. As RaR handles privileged operations, only the IPE LSM can completely avoid TOCTOU (Time-of-Check to Time-of-Use) vulnerabilities.
A TOCTOU vulnerability is a race condition where an attacker modifies data between the time it is checked and the time it is used. In RaR’s case, an attacker with enough privileges could modify the binary between the hash check and its execution, giving a false sense of security. This is because RaR runs in userspace. A kernel-level security module is required, which is why the IPE LSM is the recommended way to enforce binary integrity.
The complete WebOps policy
Here is the resulting /etc/security/rootasrole.d/default.json:
{
"version": "4.1.0",
"options": {
"path": {
"default": "delete",
"add": ["/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"]
},
"env": { "default": "delete" },
"root": "user",
"bounding": "strict",
"execinfo": "hide",
"authentication": "perform"
},
"roles": [
{
"name": "webadmin",
"actors": [{ "group": ["webadmin"] }],
"tasks": [
{
"name": "web-push",
"purpose": "Publish website content from the staging area",
"commands": [
"/usr/bin/install ^-d -m 0?[0-7]{3} /srv/www/[^ ]+$",
"/usr/bin/rsync ^-rlt --delete /srv/staging/[^ ]+/ /srv/www/[^ ]+/$"
],
"cred": {
"setuid": "www-data",
"setgid": "www-data",
"capabilities": []
}
}
]
},
{
"name": "caddy-audit",
"actors": [{ "group": ["caddy-audit"] }],
"tasks": [
{
"name": "read-logs",
"purpose": "Read Caddy access logs and journal",
"commands": [
"/usr/bin/tail ^(-n [0-9]+|-f) /var/log/caddy/[^/ ]+$",
"/usr/bin/less ^/var/log/caddy/[^/ ]+$",
"/usr/bin/journalctl --no-pager -u caddy"
],
"cred": {
"setgid": ["adm", "systemd-journal"],
"capabilities": []
},
"options": {
"env": {
"set": { "LESSSECURE": "1", "SYSTEMD_PAGER": "" }
}
}
}
]
},
{
"name": "caddy-admin",
"parents": ["caddy-audit"],
"actors": [{ "group": ["caddy-admin"] }],
"tasks": [
{
"name": "caddy-service",
"purpose": "Manage the Caddy systemd unit",
"commands": [
"/usr/bin/systemctl ^(start|stop|restart|reload) caddy$",
"/usr/bin/systemctl status --no-pager caddy"
],
"cred": {
"setuid": "root",
"capabilities": []
}
},
{
"name": "caddy-probe",
"purpose": "Diagnose Caddy configuration and capture traffic",
"commands": ["/usr/local/bin/caddy-probe"],
"cred": {
"setgid": "www-data",
"capabilities": ["CAP_NET_RAW"]
}
}
]
},
{
"name": "sys-admin",
"ssd": ["caddy-admin"],
"actors": [{ "group": ["sys-admin"] }],
"options": {
"timeout": { "type": "tty", "duration": "00:01:00", "max_usage": 1 }
},
"tasks": [
{
"name": "apt",
"purpose": "Apply system and Caddy updates (root-equivalent)",
"commands": [
"/usr/bin/apt update",
"/usr/bin/apt upgrade",
"/usr/bin/apt dist-upgrade",
"/usr/bin/apt --only-upgrade install caddy"
],
"cred": {
"setuid": "root",
"setgid": "root",
"capabilities": "all"
}
},
{
"name": "reboot",
"purpose": "Reboot the machine after updates",
"commands": ["/usr/bin/systemctl reboot"],
"cred": {
"setuid": "root",
"capabilities": []
}
}
]
}
]
}
Let’s wrap up this policy from compared to before. The policy replaces permissions tied to individuals with roles assigned to groups. Compared to sudo-rs, it also removes the unrestricted (ALL) target-user selection by forcing setuid and setgid for each task. Strict regular expressions prevent wildcards from swallowing extra arguments in install, while log-file patterns reject / so that paths cannot traverse outside the Caddy log directory.
Pager escapes are addressed by requiring --no-pager for systemctl and journalctl, setting LESSSECURE=1 for less, and ensuring that any escape only retains Bob’s own identity and group privileges. The vulnerable caddy-probe no longer runs as root: it runs with the www-data group and CAP_NET_RAW only. The global "root": "user" and "bounding": "strict" options also prevent a root process from regaining every privilege after executing another program.
Finally, the built-in delete environment mode blocks the APT_CONFIG hook. The root-equivalent apt task is isolated in its own role, protected by strict authentication and separation of duty from caddy-admin. Rebooting is a separate task with its own purpose and runs as root without capabilities.
Going further: towards Zero Trust
One issue remains open: charly’s apt task is root-equivalent, and nobody verifies what he does with it. Zero Trust follows the principle “never trust, always verify”. When an action cannot be prevented, it must at least be attributed and verified by someone else.
The system ships with a configured auditd service. The idea is to let charly keep his root-equivalent task, while making sure he cannot tamper with the audit trail of his own actions.
First, we remove the audit-related capabilities from the apt task:
"capabilities": {
"default": "all",
"del": ["CAP_AUDIT_CONTROL", "CAP_AUDIT_WRITE", "CAP_AUDIT_READ", "CAP_LINUX_IMMUTABLE"]
}
CAP_AUDIT_CONTROLallows changing the audit rules, and changing the login uid of a process. Removing it keeps every action attributed to charly, even after the switch to root.CAP_AUDIT_WRITEallows forging audit records.CAP_AUDIT_READallows reading the audit stream.CAP_LINUX_IMMUTABLE, as seen in Where the policy lives, protects the policy itself.
Then, the audit rules should be locked with auditctl -e 2, so that they cannot be changed until the next reboot.
However, this is a denylist on a root-equivalent task, and we warned earlier that denylists are fragile. A malicious package can still stop auditd, edit /var/log/audit/ through root’s DAC ownership, or load a kernel module (CAP_SYS_MODULE). And the reboot task unlocks the audit rules. The local audit trail is therefore not enough on its own. This is why:
- audit logs must be exported in real time to a remote collector (including RaR logs too), ideally a SOC, so that they leave the machine before they can be altered;
- they must be retrieved and verified by someone outside the WebOps team. If this person is given a role on the machine, this role should be declared in
ssdwithsys-admin.
The result is not a prevention mechanism: charly can still do anything as root. But nothing he does can go unnoticed, and this is the best guarantee available for a root-equivalent task.
The final configuration structure in UML
This section is AI-generated based on the schema.
This diagram brings all the previous structures together to show how a complete policy is assembled. Rather than re-explaining how each feature works, this view focuses on how the components connect, how elements reference each other, and where each setting lives.
Files and loading (Root file)
Everything starts with RootFile (rootasrole.json), which holds a Storage block to locate policy data. Both RootFile and standalone PolicyFile structures share the same foundation via the abstract PolicyContent structure: a format version, an optional set of options, and a list of roles.
As indicated by the note on StorageSettings, the path attribute drives how the policy is loaded. If it points back to the root file itself, the roles declared directly inside RootFile are used. If it points elsewhere (a file or a directory), the engine loads external PolicyFile resources instead.
Authorization model (Policy)
The Policy package contains the core entities for access control:
Roleacts as the central link. It ties who can run commands (Actor) to what can actually be run (Task). Theparentsandssdfields represent hooks for role hierarchy and separation of duties.Actorbranches into either a singleUserActoror aGroupActor(which requires all specified groups).Taskpairs an execution context (Credentials) with allowed commands (CommandRepresentation).Credentialsdefines the target execution identity (setuid,setgid), the Linuxcapabilities, and a file sandbox map managed by the Landlock plugin.CommandRepresentationhandles how commands are matched: either as blanket rules (all,none), a simple list ofCommandobjects, or a dynamicCommandSelector.
Granular filtering (Selectors)
At the bottom of the policy package, the Selectors group (UserSelector, GroupSelector, CapSelector, and CommandSelector) provides a standard way to build sets of allowed values, as explained earlier. Each selector uses a default baseline, adds or subtracts specific items via add and sub (aliased as del), and provides an optional fallback where applicable.
Configuration scope (Inheritable Settings)
The Inheritable Settings package defines runtime configurations through the Options structure. It groups several specialized sub-options:
PathOptionsandEnvOptionsmanage paths and environment variables.WorkdirOptionscontrols directory handling.Timeoutmanages credential caching and expiration limits.- Direct fields handle system toggles like
root,bounding,authentication,execinfo, andumask.
As shown by the three composition links originating from PolicyContent, Role, and Task, an Options block can be attached at any of these levels. The resolution follows the cascade indicated by the note: options set on a specific Task override those defined on its parent Role, which in turn override global policy options and built-in defaults.
See also
Security Model
This section needs rewriting.
A software security model is a conceptual framework that defines the rules, boundaries, and access controls a system must follow to protect its data and resources from unauthorized use or tampering.
In the RaR context, it describes the security mechanisms that secures the program from its own usage.
Security controls
- PAM-based authentication for
dosr - Capability-oriented privilege model
- Optional timestamp/timeout behavior
- Immutable policy file workflow
- Hardened policy editor path in
chsr editor(landlock/seccomp)
Operational safeguards
- Grant only required capabilities.
- Prefer explicit command allow-lists.
- Use dedicated roles for automation.
- Force explicit role/task selection in scripts.
- Review and test policy changes before deployment.
Typical risks
- Over-broad command patterns
- Unnecessary capability grants
- Ambiguous task overlap
- Reusing interactive roles for unattended jobs
Minimal hardening checklist
- Keep
/etc/security/rootasrole.jsonprotected and immutable in production. - Restrict
chsrusage to a small admin set :
- Implement review processes for policy changes.
- Implement segregation of duties for policy management and execution.
- Use
dosr -iduring policy validation (requires to allowdosr -iin policy for testing). - Add CI checks for policy syntax and expected command paths.
- Track policy changes in version control.
Comparison with other tools
| Feature | setcap?? | doas | sudo | sudo-rs | dosr (RootAsRole) |
|---|---|---|---|---|---|
| Change user/groups | N/A | ✅ | ✅ | ✅ | ✅✅ mandatory or optional |
| Environment variables | N/A | partial | ✅ | partial | ✅ |
| Specific command matching | N/A | strict | strict & regex | strict & wildcard | strict & regex |
| Centralized policy | ❌ | ❌ | ✅ | ❌ | Planned |
| Secure signal forwarding | N/A | ❌ | ✅ | ✅ | ✅ |
| Set capabilities | ⚠️ files | ❌ | ❌ | ❌ | ✅ |
| Prevent direct privilege escalation | ❌ | ❌ | ❌ | ❌ | ✅ |
| Untrust authorized users | ❌ | ❌ | ❌ | ❌ | ✅ |
| Standardized policy format | ❌ | ❌ | ❌ | ❌ | ✅ |
| Scalable access control model | N/A | ❌ ACL | ❌ ACL | ❌ ACL | ✅ RBAC |
The sudo centralized policy feature relies on creating some LDAP entries that will be retrieved by sudo. This is somehwat not a conventional way to use LDAP. LDAP provides identity and directory information, but it does not evaluate authorization policies. So, scalability is a concern. Today, the preferred way is to use sudoers files with a configuration management tool like Ansible.
setcap is a tool to set capabilities on files, therefore comparing it with RaR or others is not relevant. We included it as many requested the comparison.
FAQ
Why not cargo install rootasrole?
cargo install targets user-local binaries. dosr requires system-level deployment (PAM files, capabilities, policy file location/permissions). For that reason, use distro packages or the project installer flow.
capable does not work on my host
capable depends on eBPF and kernel features that are not always available or enabled.
Check:
- kernel support for required eBPF features
- security profile restrictions in your environment
- memory/resource limits
If it still fails, open an issue with kernel version, distro, and reproducible steps.
Why are capable results different from expected behavior?
Possible causes:
- Access is granted by normal ACL/ownership, so no capability is needed.
- Program exits before capability checks happen.
- Program behavior changes based on UID/GID.
Treat output as a starting point for iterative testing, not final ground truth.
Why does chsr editor refuse to save my changes?
chsr editor validates policy consistency before writing.
Typical causes are:
- invalid field type (for example string vs object)
- unsupported values in command/capability sets
- broken inheritance/option structure
Review the reported error, fix the corresponding field, and retry. For schema examples, see Configuration File Format.
Why does chsr convert fail?
Common causes:
- source path is wrong or unreadable
- target path is not writable
--fromformat does not match the actual source content
Retry with explicit --from and verify file permissions.
See File Format Conversion for command forms.
dosr
dosr executes commands through RaR policy checks.
Usage
Execute privileged commands with a role-based access control system Usage: dosr [OPTIONS] [COMMAND]... Arguments: [COMMAND]... Command to execute Options: -r, --role <ROLE> Role to select -t, --task <TASK> Task to select (--role required) -u, --user <USER> User to execute the command as -g, --group <GROUP<,GROUP...>> Group(s) to execute the command as -E, --preserve-env Keep environment variables from the current process -D, --chdir <DIR> Change working directory before executing the command -p, --prompt <PROMPT> Prompt to display -K Remove timestamp file -i, --info Print the execution context of a command if allowed by a matching task -h, --help Print help (see more with '--help') -V, --version Print version
If you’re accustomed to utilizing the sudo tool and find it difficult to break that habit, consider creating an alias :
alias sudo="dosr"
alias sr="dosr"
Future Design Considerations
Today, we copied completely the CLI of sudo, thus ensuring old scripts compatibility.
We might following a new design in the future but with retro-compatibility enabled. For example, we could detect when the command name is sudo then using the sudo CLI design, otherwise fallback to a new design.
Examples
dosr reboot
dosr -r ops -t reboot reboot
chsr
This section needs rewriting.
chsr is the policy administration tool for RaR.
Use it to manage roles, tasks, command rules, credentials, and options.
Usage
chsr [COMMAND] [ARGS...]
Main command families
role: create/delete roles, grant/revoke actorsrole ... task: create/delete tasksrole ... task ... cmd: manage command allow/deny rulesrole ... task ... cred: managesetuid,setgid, and capabilitiesoptions: manage global/role/task execution optionsconvert: convert policy storage between JSON and CBOReditor: open interactive editor mode (when enabled)
chsr editor
chsr editor opens an interactive policy editing mode.
When the edit session is applied, RaR validates the policy before saving. Invalid content is rejected with explicit errors.
Use chsr editor for multi-field edits when you want immediate validation.
For policy field reference, see Configuration File Format. For storage migration, see File Format Conversion.
File Format Conversion
This section needs rewriting.
chsr convert converts RootAsRole policy storage between JSON and CBOR.
Supported formats
jsoncbor
Command forms
chsr convert [--from <from_type> <from_file>] <to_type> <to_file>
chsr convert -r [--from <from_type> <from_file>] <to_type> <to_file>
--from: explicitly select source format and source file.-r/--reconfigure: update/etc/security/rootasrole.jsonso storage points to the new file.
Common convert examples
Convert current policy storage to CBOR and reconfigure runtime:
chsr convert -r cbor /etc/security/rootasrole.bin
Convert back to JSON and reconfigure runtime:
chsr convert -r json /etc/security/rootasrole.json
Explicit source/target conversion:
chsr convert --from json /etc/security/rootasrole.json cbor /etc/security/rootasrole.bin
Operational notes
- Keep target files in protected system paths.
- If policy content changes during migration prep, prefer
chsr editorto catch invalid policies before write. - Validate policy execution after conversion with
dosr -i <command>. - Use version control and backups before large migrations.
For policy field details, see Configuration File Format.
Common general examples
chsr role ops add
chsr role ops grant -g ops
chsr role ops task reboot add
chsr role ops task reboot cmd whitelist add reboot
chsr role ops task reboot cred caps whitelist add CAP_SYS_BOOT
For complete policy fields, see Configuration File Format.
Why RootAsRole
Cybersecurity threats are not just from outside. The major landscape of cybersecurity is undergoing a shift, with threats originating from within organizations’ trusted boundaries now posing a risk as significant as external attacks.
Yearly Data Breach Investigations Reports by Verizon state that “between 20 and 35% of breaches were caused by insiders” (e.g., employees, contractors, and other trusted individuals). Financial motives are frequently the root cause of these incidents, revealing that individuals granted excessive trust and access within an organization can abuse their privileges for personal reasons. This clearly emphasizes the significant role of internal threats in modern cyberattacks and the need to monitor and control privileged access to a system properly. Managing user privileges is particularly challenging due to modern networks and software components’ growing complexity and technological heterogeneity.
Some incidents illustrate the scale of these threats. For example, a former Ubiquiti cybersecurity engineer exploited administrative access to steal confidential data, extort money from the company, and significantly damage its reputation. Similarly, a former Chief Information Security Officer (CISO) of the Hospi Grand Ouest healthcare group in France orchestrated a cyberattack that compromised sensitive healthcare data. In March 2025, a former employee in Ohio was found guilty of installing a kill switch—a mechanism designed to destroy the infrastructure—in the event his privileged account was revoked. These cases, frequent in the news, underscore the urgent need for a targeted approach to securing administrative privileges, particularly in environments where the risk of abuse is high.
Company employees can have malicious intentions, but they can also, unbeknownst to them, become involuntary attackers. Current IT ecosystems are software supply chains, where an application depends on a long chain of other software modules developed and maintained by third parties. For instance, in the context of development environments, vulnerabilities in third-party code, such as VSCode extensions, can become attack vectors, undermining entire systems.
This internal threat is further amplified by the interconnected nature of modern software, which functions as a vast supply chain. A vulnerability concealed within a third-party library or a development tool can be exploited to compromise an entire infrastructure, turning even the most diligent administrator into an unwitting accomplice. The recent XZ incident or an innocent Visual Studio Code module is a potent reminder of this reality. Consequently, the focal point of risk converges on the concept of privileged access. The superuser accounts used by system administrators, by their very nature, offer almost unlimited control and thus represent the most fertile ground for exploitation, whether through malicious intent or unintentional error.
Faced with this challenge, cybersecurity has provided since the 1970s a clear theoretical answer: the Principle of Least Privilege (PoLP). Formulated by Saltzer and Schroeder, this principle is an engineering process that involves understanding users’ responsibilities to grant them only the minimum permissions required to accomplish their tasks using computer systems. This project focuses on a critical application of this doctrine, the Principle of Least Administrative Privilege (PoLAP), which concerns those administrators who manage and modify the system itself. In principle, the systematic application of PoLAP is already encompassed by PoLP. However, the position of Administrator is generally already an exception in the infrastructure design. Whereas it should be an exception, the reality shows in big companies that hundreds of administrators become Administrator of every new system installed, duplicating the risk considerably.
A significant gap persists between this principle and its practical implementation. On modern operating systems, particularly Linux, enforcing PoLAP is a task of considerable complexity. The administrator is confronted with a mosaic of disparate and overlapping security mechanisms: POSIX permissions, ACLs, Linux Capabilities, SELinux, AppArmor, and others. The attempt to configure these mechanisms coherently creates a daunting administrative burden. The typical response of adding a new security module often serves only to augment this complexity, failing to address the fundamental challenge of orchestrating all of these security mechanisms in a coherent policy.
The central premise is that the root cause of this failure is not technical, but lies in an incomplete methodology and a lack of consistency in the security mechanisms implemented. We postulate that a solution cannot be found by adding another layer of technology, but rather by adopting a new perspective—one that uses security models that acknowledge user needs for security. A security mechanism that is too complex, or doesn’t include user needs is, in effect, insecure. It is by examining the origins of our concepts of hierarchy, order, and control, and by placing usability at the center of the design, that we can hope to develop truly effective solutions.
It is from this perspective that we developed the RootAsRole (RaR) project. RaR is not presented as yet another security module to be added to the existing stack. Rather, it is conceived as a security orchestrator, in addition to being a better alternative to solutions found in the open-source community. As a performant, memory-safe, user-centric alternative to traditional tools like sudo, its purpose is to bring order and clarity to the existing complexity. It simplifies the management of fine-grained permissions and enables a pragmatic application of PoLAP through the Just-in-Time (JIT) elevation of privileges. RaR is thus the materialization of our approach: a pragmatic solution designed to make secure co-administration an achievable reality.[Billoir 2025]
The RBAC model and access control model history
PoLP has its first implementation in access control theory, by managing the interaction (e.g., read, write) between subjects (e.g., users, processes) and objects (e.g., files, databases) within a system referred to as the SOA (Subject, Object, Action) matrix [Lampson 1974],[Sandhu & Samarati 1994]. This matrix was both resource-intensive for humans and computationally demanding to fully implement, as it required exhaustive enumeration and continuous maintenance of all possible subject-object-action combinations within the system. The resulting complexity is \(O(n_s \cdot n_o \cdot n_a)\), where \(n_s\), \(n_o\), and \(n_a\) represent the number of subjects, objects, and actions, respectively, just to define the matrix.
The sudo policy model is describing one line per subject, object and action. While we can use wildcards and aliases to reduce the number of lines, it is still a model that lacks of organisational means to manage it. In other words, sudo is not implementing any access control model.
Then, We are skipping the history of access control models. But to make it short, many engineering-focused access control models have been proposed to solve purely technical problems, such as the Object Capability Model for solving the Confused Deputy problem (or a Runtime Safety problem).
Now, I’ll dive a bit into the Role-Based Access Control (RBAC) model. Role-based access control (RBAC) is an access control model that assigns permissions to roles rather than directly to individual users [Sandhu et al. 1995]. Permissions are the foundation of the model, defining the allowed interactions within the system. These permissions are not assigned directly to individuals. Instead, a Permission Assignment links them to a new entity called Role. A role is a logical grouping of permissions that corresponds to a specific job function or authority level within an organization (e.g., System Administrator or Guest User). By being assigned to a role, they inherit all the permissions associated with it, enabling them to perform their designated tasks. Finally, the model introduces Sessions. A User Session is an active connection between a user and the system. During a session, a user can activate one or more of their assigned roles, and the system uses a Role Session to grant them the corresponding permissions. This allows users to operate under different roles for different tasks within the same login.
This model is particularly useful and highly representing how our society is organized. If you want to learn more about it, you can read the PhD thesis [Billoir 2025] at section 1.2.5 where it makes a link with bees, ants as an example.
RootAsRole uses a RBAC model because admin delegation is first an assignment organisational problem. [Billoir 2025], [Sandhu et al. 1996], [Ferraiolo et al. 2001], [Wazan et al. 2021],[Wazan et al. 2022], [Billoir et al. 2023]
Linux capabilities
Linux implements a fine-grained privilege model through capabilities [Miller et al. 2003] (known as capability module), which partition the comprehensive power of the superuser (root) into distinct, manageable units. This mechanism allows specific privileges to be delegated to processes on an as-needed basis, obviating the requirement for them to operate with full root permissions. This design philosophy enhances system security by adhering to PoLP.
The concept was originally derived from the IEEE POSIX 1003.1e draft standard [IEEE and The Open Group 2024], [Security Working Group 1997]. Although this draft was ultimately withdrawn, its concepts were adopted and have since been independently maintained and significantly enhanced by the Linux kernel development community. The result is a LSM for defining discrete and mandatory access control policy for privileges on a per-thread basis. [Wazan et al. 2022] [Billoir et al. 2023] [Billoir et al. 2024]
In other terms, Linux capabilities switch from an Identity based access control with the root user to a set of privileges that can be granted to processes. It is an interesting features as long it allows to rely on a new mechanism to create organisational policies, such as Roles. This is where all started with this project.
For kernel-level capability documentation, see capabilities(7).
What is eBPF?
An eBPF program is a kernel-based virtual machine that allows the execution of user-defined bytecode in a secure and high-performance manner. Kprobe is a kernel debugging mechanism that enables the attachment of our eBPF program to the cap_capable kernel function entry point, which provides a dynamic way to trace and monitor Linux capabilities requests. [Sharaf et al. 2022] [Billoir 2025]
In RaR, eBPF is used by capable to observe capability checks during command execution. Therefore, it can determine the effective requested capabilities of a command. However, that does not mean that the command needs these capabilities to run. Or even mean that the command is requesting all possible capabilities of its use-case.
Even if we used to analyse the source code of a command to try finding the capabilities it needs, it is not a reliable method. This is for two main reasons:
- The privileges are requested given the context of the execution. For example,
lscommand does not need any capabilities to run but if you want to show the content of a folder that is not accessible by the user, then it needsCAP_DAC_READ_SEARCHto bypass the access control. - The capabilities requests are only made inside the kernel. So reading the source code does not give any direct information about the capabilities that might request a program. Again, if you read the source code of
ls, you will not find any explicit request forCAP_DAC_READ_SEARCHcapaibility.
There are other purely practical reasons such as the fact that some progams are closed-source, but that is not a real issue, while annoying.
Is a Linux system without a root user possible?
Short answer: not really.
Practical answer: you can design operations so daily work does not require logging in as root. That is exactly the RootAsRole objective with Linux capabilities.
Example: preparing Apache management without root processes.
First, create the service account:
dosr adduser apache2
Then create a task to install Apache with that account:
dosr chsr r r_root t install_apache2 add
dosr chsr r r_root t install_apache2 cmd whitelist add /usr/sbin/apt install apache2
dosr chsr r r_root t install_apache2 cmd whitelist add "/usr/sbin/apt ^upgrade( -y)? apache2$"
dosr chsr r r_root t install_apache2 cred set --caps CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_NET_BIND_SERVICE,CAP_SETUID --setuid apache2 --setgid apache2
Add another task to start/stop Apache:
dosr chsr r r_root t start_apache2 add
dosr chsr r r_root t start_apache2 cmd whitelist add "/usr/sbin/systemctl ^((re)?start|stop) apache2$"
dosr chsr r r_root t start_apache2 cmd whitelist add "/usr/bin/service ^apache2 ((re)?start|stop)$"
dosr chsr r r_root t install_apache2 cred set --caps CAP_NET_BIND_SERVICE,CAP_SETUID --setuid apache2 --setgid apache2
Now installation can be delegated through policy:
dosr apt install apache2
Then service control can also run through delegated tasks:
dosr systemctl start apache2
Performance Evaluation and Optimisation
Infrastructure-as-code environments can generate large and complex privilege policies. Tools such as Ansible rely heavily on privilege escalation through become: true.
With RaR policies, every role and task can introduce additional access control rules. In large infrastructures, this can result in thousands of policy entries being evaluated during production operations. Therefore, the privilege evaluation engine must scale efficiently.
The initial implementation of dosr revealed a scalability challenge. The first JSON-based policy engine was faster than sudo for small policies, but its execution time increased significantly as the number of tasks grew.
As shown above, the initial implementation suffered from poor scalability. While it performed well with small policies, the execution time quickly surpassed sudo as policy size increased, making it unsuitable for large-scale deployments.
Moving from JSON to CBOR
The first optimization focused on the policy storage format. JSON is human-readable and convenient, but parsing large JSON documents introduces unnecessary overhead. To reduce this cost, dosr introduced support for Compact Binary Object Representation (CBOR), a binary format designed for efficient serialization and parsing, and completely compatible with the JSON structure.
Although CBOR reduced part of the parsing overhead, the improvement remained limited. The performance curve continued to increase significantly with policy size, demonstrating that file parsing was only one part of the problem. The main bottleneck was located deeper in the policy evaluation architecture.
Architectural Optimisation
The next optimization phase focused on the complete execution path of dosr. Instead of only improving the policy format, the internal architecture was redesigned to remove unnecessary overhead.
The main improvements included:
- reducing redundant memory allocations;
- improving internal data structures;
- streamlining policy loading and evaluation;
- optimizing rule matching and execution paths.
These changes, combined with the CBOR policy format, resulted in a significant improvement in both execution time and scalability.
The optimized dosr engine now outperforms sudo by up to 77% for a single-rule policy. More importantly, its execution time scales linearly and grows approximately 40% slower than sudo, maintaining a performance advantage even with policies containing tens of thousands of tasks.
Do you see these very straight lines compared to the previous ones? This is because we changed the way to way to plot the data. Instead of using a some arbitrary increase (e.g., 100, 200, 300), we used a logarithmic increase.
Large-Scale Automation
This scalability improvement is particularly important in automation environments.
Tools such as Ansible frequently invoke privilege escalation through become: true configuration option. With RaR, every role and task can introduce additional access control rules, potentially resulting in thousands of policy entries on a single system.
The benchmark results show that this additional security granularity does not introduce a proportional performance cost. The optimized dosr engine can reach the execution time of a single sudo rule while evaluating approximately 4,000 RaR rules.
This enables administrators to define significantly more precise privilege policies while maintaining operational efficiency.
Comparison with sudo-rs
For comparison, sudo-rs provides performance comparable to sudo in standard scenarios. However, its implementation shows a hardcoded limit of 100 rules.
Future Improvements
One potential direction is the integration of a SQLite-based policy backend. The RBAC model of RaR map to relational data structures. A relational database could provide additional advantages as we could do efficient indexing as an example.
The benchmarks presented above were performed in July 2025. Since then, additional performance optimisations have been implemented across the codebase, improving internal execution paths and reducing overhead further. [Billoir 2025]
Research & Publications
- PhD Thesis (2025): Orchestrating and enforcing the principle of least administrative privileges in Linux systems — E. Billoir
- Conference (2025): No Root, No Problem: Automating Linux Least Privilege and Securing Ansible Deployments — ESORICS 2025
- Conference (2024): Enhancing Secure Deployment with Ansible: A Focus on Least Privilege and Automation for Linux — ARES 2024
- Journal Article (2024): Implementing the Principle of Least Administrative Privilege on Operating Systems: Challenges and Perspectives — Annals of Telecommunications
- Conference (2023): Implementing the Principle of Least Privilege Using Linux Capabilities: Challenges and Perspectives — CSNet 2023
- Journal Article (2022): RootAsRole: a security module to manage the administrative privileges for Linux — Computers & Security
- Conference (2021): RootAsRole: Towards a Secure Alternative to sudo/su Commands for Home Users and SME Administrators — SACMAT 2021
Architecture Overview
Binaries
dosr: policy lookup, authentication flow, and command execution uner a restricted context.chsr: policy editing and conversion interface.capable: policy discovery, available in its own repository.gensr: fully automated policy generation, available in its own repository
Internal crates
rootasrole-core(incorefolder): shared policy model, storage handling, migrations, utility logic.rootasrole-exec(inexecfolder): execution pipeline primitives (runner, terminal/pty, pipe, signals).
Did you know that there are two approaches for secure execution?The version 4.0 of RaR is switching from the first to second design! We believe that the risk taken by implementation error is worth the security feature to protect against innapropriate user inputs.
- The openBSD one is about calling execve() directly for minimal code implementation. Less features, more robust is the software.
- The sudo one is about adding a intermediary process in order to oversee the communication between user and privileged process.
Contributing to RootAsRole
First off, thanks for taking the time to contribute! ❤️
All types of contributions are encouraged and valued. See the Table of Contents for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
And if you like the project, but just don’t have time to contribute, that’s fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
- Star the project
- Use
dosrin your daily work- Document your scripts with
dosrusage- Mention the project at local meetups and tell your friends/colleagues
Table of Contents
- Code of Conduct
- I Have a Question
- I Want To Contribute
- Reporting Bugs
- Suggesting Enhancements
- Your First Code Contribution
- Improving The Documentation
- Styleguides
- Commit Messages
- Join The Project Team
Code of Conduct
This project and everyone participating in it is governed by the RootAsRole Code of Conduct. By participating, you are expected to uphold this code. For now, we are too few to be able to enforce it.
I Have a Question
If you want to ask a question, we assume that you have read the available Documentation.
Before you ask a question, it is best to search for existing Issues that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
If you then still feel the need to ask a question and need clarification, we recommend the following:
- Open an Issue.
- Provide as much context as you can about what you’re running into.
- Provide project and platform versions, depending on what seems relevant.
We will then take care of the issue as soon as possible.
I Want To Contribute
Legal Notice
When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
Reporting Bugs
Before Submitting a Bug Report
A good bug report shouldn’t leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
- Make sure that you are specified the version correctly.
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the documentation. If you are looking for support, you might want to check this section).
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the bug tracker.
- Collect information about the bug:
- Stack trace (Traceback)
- OS, Platform and Version (Ubuntu 20.04, Debian 10, RedHat 8, etc.)
- Version of Rust (e.g.
rustc --version) - Possibly your input and the output
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
How Do I Submit a Good Bug Report?
For security issues, please read the Security Policy.
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
- Open an Issue. (Since we can’t be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
- Explain the behavior you would expect and the actual behavior.
- Please provide as much context as possible and describe the reproduction steps that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
- Provide the information you collected in the previous section.
Once it’s filed:
- The project team will label the issue accordingly.
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as
needs-repro. Bugs with theneeds-reprotag will not be addressed until they are reproduced. - If the team is able to reproduce the issue, it will be marked
needs-fix, as well as possibly other tags (such ascritical), and the issue will be left to be implemented by someone.
To help you to fill out the issue, please follow the templates provided by GitHub when you open a new issue.
Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for RootAsRole, including completely new features and minor improvements to existing functionality. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
Before Submitting an Enhancement
- Make sure that you are using the latest version.
- Read the documentation carefully and find out if the functionality is already covered, maybe by an individual configuration.
- Perform a search to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
- Find out whether your idea fits with the scope and aims of the project. It’s up to you to make a strong case to convince the project’s developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you’re just targeting a minority of users, consider writing an add-on/plugin library.
How Do I Submit a Good Enhancement Suggestion?
Enhancement suggestions are tracked as GitHub issues.
- Use a clear and descriptive title for the issue to identify the suggestion.
- Provide a step-by-step description of the suggested enhancement in as many details as possible.
- Describe the current behavior and explain which behavior you expected to see instead and why. At this point you can also tell which alternatives do not work for you.
- Explain why this enhancement would be useful to most RootAsRole users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
Again, to help you to fill out the enhancement, please follow the templates provided by GitHub when you open a new issue.
Improving The Documentation
The documentation needs to be improved. If you find a typo, error, or something that is not clear, please help us by correcting it. If you have a suggestion for improving the documentation, please follow the steps below:
- Open an Issue
- If you want to make the change yourself, fork the repository and make the changes in your fork. Then open a pull request with the changes. We will review the changes and merge them if they are good.
Attribution
This guide is based on the contributing-gen. Make your own!
RootAsRole Code of Conduct
Our Pledge
We pledge to make our community welcoming, safe, and equitable for all.
We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant.
Encouraged Behaviors
While acknowledging differences in social norms, we all strive to meet our community’s expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language.
With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including:
- Respecting the purpose of our community, our activities, and our ways of gathering.
- Engaging kindly and honestly with others.
- Respecting different viewpoints and experiences.
- Taking responsibility for our actions and contributions.
- Gracefully giving and accepting constructive feedback.
- Committing to repairing harm when it occurs.
- Behaving in other ways that promote and sustain the well-being of our community.
Restricted Behaviors
We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct.
- Harassment. Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop.
- Character attacks. Making insulting, demeaning, or pejorative comments directed at a community member or group of people.
- Stereotyping or discrimination. Characterizing anyone’s personality or behavior on the basis of immutable identities or traits.
- Sexualization. Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community.
- Violating confidentiality. Sharing or acting on someone’s personal or private information without their permission.
- Endangerment. Causing, encouraging, or threatening violence or other harm toward any person or group.
- Behaving in other ways that threaten the well-being of our community.
Other Restrictions
- Misleading identity. Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions.
- Failing to credit sources. Not properly crediting the sources of content you contribute.
- Promotional materials. Sharing marketing or other commercial content in a way that is outside the norms of the community.
- Irresponsible communication. Failing to responsibly present content which includes, links or describes any other restricted behaviors.
Enforcement
For now, our Code of Conduct is a set of values that we voluntarily agree to follow. As the project is growing, we may need to enforce this Code of Conduct by following the Contributor Covenant CoC.
Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at https://www.contributor-covenant.org/version/3/0/.
Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit https://creativecommons.org/licenses/by-sa/4.0/
For answers to common questions about Contributor Covenant, see the FAQ at https://www.contributor-covenant.org/faq. Translations are provided at https://www.contributor-covenant.org/translations. Additional enforcement and community guideline resources can be found at https://www.contributor-covenant.org/resources. The enforcement ladder was inspired by the work of Mozilla’s code of conduct team.
The RootAsRole history
1.0 (2018)
The project is initiated by SIERA IRIT CNRS research team with Ahmad Samer WAZAN as owner of the proof of concept. It is presented for the first time at “Le capitole du libre” in Toulouse. A paper is published[Wazan et al. 2021].
2.0 (2019)
RootAsRole is still a proof of concept but now proposes an eBPF subprogram to obtain the capabilities of a generic command. A paper is published at Computer and Security[Wazan et al. 2022]
3.0 (2024)
RootAsRole direction is delegated to Eddie BILLOIR, a SIERA PhD student that contributed to the project all along its courses. It is now entirely rewritten in Rust, the eBPF subprogram is now in a separate project called RootAsRole-capable. The project takes a new direction and aims to be production ready with a massive reconception of every tools, a new configuration file format and complete documentation.
4.0 (2026)
RootAsRole reaches a major stabilization milestone with the modernized execution stack crate rootasrole-exec for managing a secure execution for commands, thus handling signals and pty completely, introducing a way to monitor and curtail the execution in the future, exactly like sudo tool. It also introduces more configuration capabilities, such as folder-based configuration and CBOR-only format for even more performance. It also enhances the execution context, by adding the working directory management in the policy.
Documentation is refocused on operational usage, policy clarity, and contributor architecture. A comprehensive PhD thesis [Billoir 2025] consolidates the research foundation and design rationale for the project.
A new mascot, CaRoot, is introduced to personify the project and make it fancy. CaRoot was designed by EvaLaFougère, a talented artist! Her work is available on her website.
An unexpected CaRoot story
Chapter 1: A detective in the garden
"It happened," said detective CaRoot. "See, there are some grated carrots lying on the ground."
"Do you think it is our suspect?" asked the officer.
"Definitely," replied the detective. "I have seen this kind of trace before. All we know is that our suspect has struck again."
"I must warn you, detective. We need to find the crime scene and save our victim before it is too late," said the officer.
"Yes, we must hurry," replied the detective.
* * *
Unfortunately, no crime scene was found.
"We are out of clues, and it is getting late," said the detective.
Back at her apartment, CaRoot had been looking for this suspect for a long time. Every piece of information she had was there, in her notebook and displayed on the walls of her apartment. The methods, the traces, the decoys... the crime could have happened anywhere.
"Oh yes, tomorrow I need to report to the mayor about the current situation, so I need to prepare my report," said CaRoot.
After reading her notes, she realized that it wasn't grass around the traces this afternoon, but some lettuce...
To be continued...