When I first began working at Meta, I was introduced to textexpander. I found it very pragmatic and so I looked into a FOSS alternative and found Espanso. Espanso is FOSS and runs on Linux. Hooray 🍻! Life is good.

However, if you daily drive Linux, you’re more than likely using Wayland. Espanso describes it’s wayland compatibility as experimental and you have to compile it from source on non Debian based distros.

The last gotcha is a potential security risk as described here:

Adding the required Capabilities

Espanso requires access to the /dev/input/eventX and /dev/uinput interfaces to detect triggers and inject expansions respectively. Although you could run it as root to grant the necessary permissions, Espanso supports a safer alternative that consists in adding the CAP_DAC_OVERRIDE capability to the binary’s set of Permitted ones. To do so, run the following command:

sudo setcap "cap_dac_override+p" $(which espanso)

The documention says this in a callout:

In a nutshell, this capability grants Espanso the permissions to read and write to any file in the system, but only when explicitly activated by the binary itself.

Ok, fair enough. It’ll enable this permission when initializing but turn it off afterwards.

I decided to verify that this is the case myself and while I’m not a Rust expert myself, this seems to hold true.

Here are the functions that actually grant the permission: [1]

(1)
pub fn can_use_capabilities() -> bool {
  match caps::has_cap(None, CapSet::Permitted, Capability::CAP_DAC_OVERRIDE) {
    Ok(has_cap) => has_cap,
    Err(err) => {
      error!("error while checking if capabilities are enabled: {}", err);
      false
    }
  }
}
(2)
pub fn grant_capabilities() -> Result<()> {
  caps::raise(None, CapSet::Effective, Capability::CAP_DAC_OVERRIDE)?;
  Ok(())
}
(3)
pub fn clear_capabilities() -> Result<()> {
  caps::clear(None, CapSet::Effective)?;
  caps::clear(None, CapSet::Permitted)?;
  Ok(())
}
1 Verifies it can do the override
2 Actually grants the override
3 Resets the permission

The functions themselves are self explanatory and it’s using the caps library to work with the permissions in question.

The next step is look at the references to these functions. All of the functions are only referenced in the adjacent file, mod.rs.

fn grant_linux_capabilities(use_evdev_backend: bool) -> bool {
  if use_evdev_backend {
    if crate::capabilities::can_use_capabilities() { (1)
      debug!("using linux capabilities to grant permissions needed by EVDEV backend");
      if let Err(err) = crate::capabilities::grant_capabilities() { (2)
        error!("unable to grant CAP_DAC_OVERRIDE capability: {}", err);
        false
      } else {
        debug!("successfully granted permissions using capabilities");
        true
      }
    } else {
      ... (3)
}
1 Verifies it has the override
2 Actually grants the permission
3 Code is omitted here but basically fails if there isn’t a evdev backend.

The first two functions we investigated has been nicely put together here with exception handling. It’s invoked in the main setup and then the permission is cleared.[2]

        (1)
      let has_granted_capabilities = grant_linux_capabilities(use_evdev_backend);
        (2)
      if has_granted_capabilities {
        if let Err(err) = crate::capabilities::clear_capabilities() {
          error!("unable to revoke linux capabilities: {}", err);
        }
      }
1 Grants the override
2 Clears it with error handling

Final Thoughts

The beauty of Open Source software is that we can always look into the source code of what we’re using. Especially for something this critical.

I very much appreciate the transparency and concise explanation written in the documentation. Kudos to everyone that’s worked on Espanso!