An outbound firewall for Linux desktops and servers that asks before a program reaches the network, and remembers the answer.
Linux ships no outbound firewall that works per program: ufw, firewalld and plain nftables decide by address and port, so a browser and a payload phoning home on port 443 look the same to them, and opensnitch, the closest equivalent of Windows Firewall Control, needs a Go daemon plus a 200 MB PyQt5 GUI. Colony Firewall Control is a Rust port of the opensnitch model: the kernel queues every new connection, a root daemon decides it from rules that name the program, and an iced app, a tray icon or the terminal asks you about the rest.
Status: the daemon, the app, the tray and the
cfcCLI cover outbound filtering end to end, and opt-in inbound filtering. CI tests real packet verdicts on a live NFQUEUE in a network namespace, loads the eBPF layer on several kernels and installs the RPM end to end on Fedora; the app and the tray are covered by unit tests only. Nothing is published on the AUR yet: use the release tarball or build the PKGBUILD each release carries. The copy Colony installs is a read-only monitor (see Via Colony). Explicit application confinement is experimental. Releases after 0.8.0 are signed with the organisation key.
- Rules name programs, not just ports. A rule names the executable that connects, checked against the image actually running, so allowing a browser on 443 does not allow everything else on 443. An Allow rule that names no program takes an administrator password.
- One Rust workspace instead of Go plus PyQt. The daemon, the app, the
tray and
cfcshare one gRPC schema over a Unix socket, and the root daemon that parses untrusted packets is memory-safe. - Headless machines are first class.
sudo cfc promptsanswers prompts over SSH, and every command takes--json. - It fails closed, and says so. With the daemon down, new non-loopback
connections drop instead of passing, and
cfc statusand the app say when nothing is being enforced.
+----------------------------+ +----------------+
| cfc-ui (iced, user) | | cfc (tty) |
| pop-ups, rules editor | | status, rules|
| live feed, stats | | prompts, log |
+------------+---------------+ +-------+--------+
| |
| tonic gRPC over UDS, 0660 root:colony-firewall
| (peer credentials checked per RPC)
v v
+--------------------------------------------------+
| cfc-daemon (systemd, root) |
| - NFQUEUE intercept, out-of-order verdicts |
| - process resolution (sock_diag + /proc, cached)|
| - decision engine (deterministic precedence) |
| - reject injection (TCP RST / ICMP unreachable) |
| - sqlite: rules + event log |
| - prompt router (sync NFQ <-> async clients) |
+--------------------------------------------------+
- Per-application outbound filtering with NFQUEUE intercept
- Live pop-ups for unknown connections, persistent rules for known ones
- iced GUI with parchment / burgundy theme, four tabs (Prompts / Rules / Live / Stats), a countdown on every prompt, and desktop notifications when the window is hidden
- Answer prompts from a terminal (
sudo cfc prompts) - headless servers and SSH sessions are not second-class citizens - Persistent verdict log (
cfc log): what did this app contact, and what did we do about it --jsonon every command, NDJSON for the streaming ones, and a documented exit-code contract, socfcscripts cleanly- Real
Reject: a TCP RST or ICMP port-unreachable, so a blocked app fails immediately instead of hanging on its own timeout - Group-gated control socket (
root:colony-firewall, 0660) with per-RPC peer-credential checks and an audit trail in the journal - Hot policy reload on
SIGHUP, and a systemdType=notifyunit that reports ready only once it is actually filtering - Headless CLI (
cfc) for status, rule CRUD, live feed, prompts and log - System-tray companion (
colony-firewall-tray): status at a glance, pending-prompt badge and notifications, quick pause/resume - JSON export / import for backup and machine-to-machine sync
- opensnitch JSON import for one-shot migration
- Named profiles: relaxed / balanced / strict (in
daemon.toml) - Shell completions and man pages, generated from the binary
- Optional eBPF backend: exec tracking and DNS response diagnostics
from inside the kernel supplement
/procattribution; uncorrelated DNS observations never supply policy identity - Memory-safe Rust top to bottom for a root daemon parsing untrusted packets
Search for Colony Firewall Control in Colony (category Security) and install it. Colony installs the app for your user and keeps it updated.
A firewall also needs parts that Colony, which runs nothing as root, cannot
install: the daemon, its systemd units, the nftables ruleset, the
colony-firewall group and the polkit policy. Install those from the
release tarball, the
Arch recipe or a source build, then
follow First run. The copy Colony
installs is a read-only monitor: it shows the status, prompts, rules and
live traffic, but the daemon accepts changes only from the root-owned
/usr/bin/colony-firewall and /usr/bin/colony-firewall-tray that the
packages install, and the app shows the daemon's reason when it refuses one.
Not published on the AUR yet. Each release carries the recipe CI built
against its tag, with the real source checksum (PKGBUILD and SRCINFO);
packaging/README.md shows how to build
it. From a checkout, the -git recipe builds the current source:
build=$(mktemp -d)
cp packaging/arch/PKGBUILD-git "$build"/PKGBUILD
cp packaging/arch/colony-firewall-control.install "$build"/
cd "$build" && makepkg -siEither package installs everything except the eBPF object - the three
units, the sysusers fragment, both nftables rulesets and the inbound
lockout guard, the polkit policy, the desktop entries and icon,
completions and man pages - and prints the first-run steps on install.
Without the object the daemon runs on sock_diag + /proc; the release
tarball ships it, see packaging/README.md.
Grab the assets from the latest release:
| Asset | What it is |
|---|---|
colony-firewall-control-<V>-linux-x86_64.tar.zst |
everything: the daemon, the app, the tray, cfc, the units, the eBPF object and an install.sh |
colony-firewall-control-linux |
the app alone, x86_64; the file Colony installs |
PKGBUILD, SRCINFO |
the Arch recipe for this release |
SHA256SUMS |
sha256 of the tarball and of the app |
Check a download as described under Code signing policy, then install the tarball as root:
tar --zstd -xf colony-firewall-control-<V>-linux-x86_64.tar.zst
cd colony-firewall-control-<V>-linux-x86_64
sudo ./install.sh # then First runsudo ./uninstall.sh from the same directory removes it again. What the
tarball contains and how its scripts are generated:
packaging/README.md.
git clone https://github.com/Project-Colony/Colony-Firewall-Control
cd Colony-Firewall-Control
cargo build --workspace --release --lockedRequires Rust 1.89 or newer and protobuf-compiler. Installing the build by
hand (binaries, units, nftables ruleset, group, polkit policy) is in
docs/guide/first-run.md; building
and testing for development is in docs/DEVELOPMENT.md.
Full documentation is in docs/. Start at docs/README.md: first run, the CLI, profiles, hardening, troubleshooting and the architecture.
Every release asset is signed by the Project Colony organisation in CI, never on a developer machine. Next to each asset on the release page:
| File | What it is |
|---|---|
<asset>.sig |
an ed25519 signature over the asset, made with the organisation's release key |
<asset>.meta |
three lines binding the asset to its file name, its sha256 and the release version |
<asset>.meta.sig |
an ed25519 signature over the .meta |
The private key is an organisation secret, used only by the shared sign-and-publish workflow in a job that builds nothing; the jobs that compile Colony Firewall Control never see it. Colony checks all three files before it installs or updates Colony Firewall Control, and refuses a release older than the one installed. To check a download yourself with OpenSSL 3 (the same commands work for every asset):
cat > colony-release.pub <<'EOF'
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEARNjg3Nn8H6/aBg1unwGjkUTcrdTxERNefVaqU8cFu0s=
-----END PUBLIC KEY-----
EOF
a=colony-firewall-control-linux
openssl pkeyutl -verify -pubin -inkey colony-release.pub -rawin -in "$a" -sigfile "$a.sig"
openssl pkeyutl -verify -pubin -inkey colony-release.pub -rawin -in "$a.meta" -sigfile "$a.meta.sig"
cat "$a.meta" # version=<tag>, asset=<file name>, sha256=<digest>
sha256sum "$a" # the digest must equal the sha256 lineReleases up to 0.8.0 predate this policy and carry no signatures. The app and the tarball also carry a build provenance attestation, see SECURITY.md.
How releases are built, signed and published: design/releases.md.
Colony Firewall Control sends no telemetry, no analytics and no crash reports, checks for no updates and has no server of its own. What it keeps stays on this machine:
| Data | Stored or sent | Where, and why |
|---|---|---|
| Rules | stored | /var/lib/colony-firewall/rules.db, readable by root only: what you allowed or blocked, with program paths, and a hash for rules pinned to a binary |
| Connection log | stored | the same database. For every decided flow: the time, protocol, source and destination address and port, destination host name, program path, pid, uid, verdict and deciding rule. cfc log reads it. Capped at [events] max_rows rows (100000 by default), oldest first; set it to 0 in /etc/colony-firewall/daemon.toml and restart the daemon to keep none |
| System journal | stored | blocked connections (program, pid, uid, destination) and every accepted or refused change (who asked, which program, the polkit answer), through systemd-journald like any service log |
| Host names | sent | the daemon names a destination by a reverse (PTR) lookup, confirmed by a forward lookup, through the system resolver, so your DNS server sees those lookups. With the eBPF layer on, DNS answers this machine receives are also read in the kernel; they stay in the daemon's memory |
| App and tray | stored | no files. The tray writes its icon to $XDG_RUNTIME_DIR/cfc-tray/icons, which lives in memory until you log out, only when the icon theme lacks it |
| Rule exports | stored | only where cfc rules export --out is told to write |
The app, the tray and cfc talk to the daemon only through the local socket
/run/colony-firewall/cfc.sock (root:colony-firewall, 0660). Colony
Firewall Control connects to no server: the DNS lookups above are the only
network traffic it causes itself.
GPL-3.0-or-later, see LICENSE. Inherited from opensnitch since this is a derivative port.
- opensnitch by Simone Margaritelli (evilsocket) and Gustavo Iniguez Goia - the project we are porting.
- The Rust tonic, aya, iced, and nfq crates.