Skip to content

Security: Project-Colony/Eidos

SECURITY.md

Security Policy

Eidos mounts a filesystem, and its launch binary can optionally carry a file capability. That is unusual for a mod manager, so this document is specific about what it does, what could go wrong, and what we want to hear about.

Two things to know before you install

1. Eidos runs a FUSE daemon as your user. Every read and write a launched game makes against its Data directory goes through Eidos's code (crates/eidos-fuse) while the game runs. The mount lives in a private mount namespace and is invisible to the rest of the system, and the daemon exits when the game does. Nothing is mounted when Eidos is not running a game.

2. Eidos runs rootless by default; cap_sys_admin+ep is an opt-in. Without any capability Eidos mounts inside a user+mount namespace, and that is the supported mode: mods deploy identically either way. The installer grants the capability only when asked (./install.sh --cap, or just setcap for a source build). With it, Eidos takes a plain mount namespace instead, and that is the only way to make FUSE kernel passthrough available. Passthrough itself stays off unless EIDOS_FUSE_PASSTHROUGH=1 is also set, because measured on Skyrim SE it stops the game opening its own archives and plugins. If you do grant the capability, be clear-eyed about what it means:

  • CAP_SYS_ADMIN is the broadest capability in Linux. Anyone who can execute that binary gets it, because +ep raises the capability on exec for every caller, not just for you.
  • A program that can mount inside a namespace and then exec a command of the caller's choosing is the textbook local privilege escalation primitive. That is exactly why unprivileged unshare --mount is not allowed in the first place. Eidos is not written as a hardened setuid-style tool, and it does not attempt to be one.
  • On a single-user desktop this grants you nothing you did not already have, and that is the case Eidos is built for.
  • On a shared or multi-user machine, do not install the capability-carrying binary somewhere every user can execute. Keep it in your own prefix (~/.local/bin, the installer's default) and restrict it, for example chmod 0750 ~/.local/bin/eidos with a home directory other users cannot traverse.
  • Declining it is the default. Eidos then uses the rootless user+mount namespace, and everything works except FUSE passthrough, which is off by default anyway. The launch and the GUI's Diagnostics tab only speak up when EIDOS_FUSE_PASSTHROUGH is set and the capability is missing.

The capability lives on the file, so replacing the file drops it. A rebuild or a reinstall silently returns you to the rootless path. getcap "$(command -v eidos)" is the check.

Never set the capability on eidos-linux, the single-file release asset Colony installs. That file is the window as well as the CLI, so a capability on it would cover a GUI toolkit, image decoders and archive previews that have no use for one. The window clears every capability it holds before it opens, but the opt-in belongs on the separate eidos binary of the release bundle or a source build, and nowhere else.

Supported versions

Eidos follows semantic versioning and is past 1.0. Only the most recent release and the current main get security fixes. There is no backport branch, and older releases are not patched: the fix ships in the next release.

Reporting a vulnerability

Do not open a public issue. Use GitHub's private reporting:

https://github.com/Project-Colony/Eidos/security/advisories/new

Include whatever you have:

  • What an attacker controls and what they get.
  • Version (release tag or commit), distro, kernel version.
  • Whether the eidos binary carried cap_sys_admin=ep at the time (getcap "$(command -v eidos)"), since it changes the impact of most findings.
  • Reproduction steps, or a proof of concept. A mod archive or a config file that triggers it is ideal.

What to expect: this is a small volunteer project, so no response-time guarantee is made, but the aim is an acknowledgement within 7 days and an assessment within 30. Fixes land on main and then in a tagged release. You get credit in the advisory unless you ask otherwise. Please give us a reasonable window before publishing, 90 days as a default. There is no bug bounty.

In scope

The interesting attack surface, roughly in order of severity:

  • Local privilege escalation through the capability-carrying binary. Any input, argument, environment variable, instance file or config that steers the privileged launch path into mounting or binding somewhere it should not, or into executing something the caller did not choose. This is the highest severity class in Eidos.
  • The FUSE daemon (crates/eidos-fuse). Path resolution that escapes the configured layers, copy-up or whiteout handling that writes outside the Overwrite layer, case-folding or inode confusion that exposes or corrupts a file the caller should not reach, and memory safety in the unsafe blocks that talk to libc.
  • Namespace and mount setup (crates/eidos-launch). Anything that leaks a mount into the host namespace, leaves a mount behind after exit, or lets a process inside the namespace affect the host mount table.
  • Untrusted mod content. Mod archives are attacker-controlled data from the internet. Extraction that writes outside the destination directory ("zip slip"), absolute or .. paths in an archive or in a FOMOD, symlinks that escape the mod folder, and parser crashes or memory-safety bugs on malformed archives, FOMOD XML, INI or plugin (.esp/.esm/.esl) files.
  • The nxm:// handler. Eidos can register itself as the system handler for nxm:// links, which means a web page can hand it a URL. Anything in that path that turns a crafted URL into a file written outside the download directory, a request to an attacker-chosen host, or command execution.
  • Credential handling. The Nexus OAuth session (access and refresh tokens in ~/.config/Colony/Eidos/nexus.ini, written with mode 0600), the sign-in flow's loopback listener, and anywhere a token could be logged or sent to a host other than Nexus. Eidos does not accept personal API keys.
  • Runtime provisioning. Eidos can download a .NET runtime (the dotnet10 prerequisite) and unpack it into ~/.local/share/Colony/Eidos/runtimes/. The expected SHA-256 is compiled into the binary rather than fetched alongside the file, and a mismatch refuses the install rather than warning about it; the archive is unpacked into a staging directory and renamed into place only once complete. Anything that defeats that - a path escaping the runtime directory during extraction, a checksum that can be bypassed, a download that can be pointed at another host - is in scope.

Out of scope

  • Mods themselves. Eidos mounts what you tell it to mount and launches what you tell it to launch. It does not sandbox mod content, and a mod behaving badly inside the game is not a vulnerability in Eidos. A mod archive that escapes its own directory during install is in scope; a mod that does something unpleasant once you deliberately enable it is not.
  • A missing capability. Running without CAP_SYS_ADMIN is a supported degraded mode, not a vulnerability.
  • The capability itself, reported as a finding. That a cap_sys_admin+ep binary is powerful is documented above, by design, and optional. A concrete way to abuse it is very much in scope; the general observation is not.
  • Bugs in Proton, Wine, Steam, the kernel's FUSE implementation, or the games. Report those upstream. Tell us anyway if Eidos makes one meaningfully worse.
  • Vulnerabilities in third-party crates. Report them to the crate, then open a normal issue here so the dependency can be bumped, unless Eidos's specific use makes the impact worse than upstream's advisory says.
  • Anything requiring an attacker who already has your user account, or physical access to an unlocked machine.

Build and release integrity

  • Releases are produced by .github/workflows/release.yml and nowhere else: merging the release pull request tags the commit, and the same run builds the tag, tests it and hands the files to the organisation's shared signing workflow. Every asset is published with an ed25519 .sig, a .meta naming its version, file name and sha256, and a .meta.sig. How to check them is in the README's Code signing policy.
  • The signing key is an organisation secret that only the shared workflow's publishing job receives. That job checks out nothing and builds nothing, so no code from this repository or its dependencies ever runs next to the key.
  • Every action in the workflows is pinned to a full commit SHA, and so is the shared signing workflow. Each job's token is read-only unless it publishes: the release-please job, the one place a third-party action (googleapis/release-please-action) runs, and the shared publishing job.
  • The Rust toolchain version is pinned in the workflows, so a release is built by a known compiler rather than by whatever was current that day.
  • There is no Flatpak or AppImage build. Both formats strip or ignore the file capability Eidos needs, so shipping one would mean shipping a build that silently degrades. The mechanism is documented in the release workflow.

There aren't any published security advisories