BastionGuard™ Secure Connection
BastionGuard™ Secure Connection is an interactive application firewall for GNU/Linux inspired by Little Snitch. It intercepts outgoing network connections and asks the user whether to allow or deny them, while remembering previous choices.
BastionGuard™ is a trademark of Calogero Scarnà. The BastionGuard™ name and branding are not licensed under the GPL.
Architecture
The entire project is written in C++23. The daemon and the UI are two separate processes communicating through a Unix domain socket using a length-prefixed JSON protocol. No gRPC, no protobuf, and no code generation.
┌─────────────────────────────────────────────────────┐
│ UI (C++/gtkmm4) bastionguard-sc-ui │
│ ├── MainWindow (sidebar + notebook, unobtrusive) │
│ ├── AlertDialog (allow/deny popup, NON-modal) │
│ │ ├── app name + icon from .desktop │
│ │ └── "🛡 proxy configured" hint on match │
│ ├── ConnectionsPage (real-time log) │
│ ├── AppsPage (aggregated per-application view) │
│ ├── RulesPage, StatsPage, AboutPage │
│ ├── TrayIcon (D-Bus/StatusNotifierItem) │
│ └── UIServer (Unix socket server) │
│ ▲ │
│ │ Unix socket + framed JSON │
│ │ (unix:///tmp/bsd-daemon.sock) │
│ ▼ │
│ Daemon (C++) bsc-daemon │
│ ├── NFQueue + nft chain bsc_intercept │
│ ├── In-memory "always" verdict cache │
│ ├── isAsking flag (one popup at a time) │
│ ├── SocketSnapshot (/proc/net fallback) │
│ ├── ProcMon + ProcEventMonitor (netlink) │
│ ├── Rule engine (simple/regexp/range/network/list) │
│ ├── DnsResolver (reverse DNS) │
│ ├── TaskRunner (scan/heartbeat/rules_reload) │
│ └── UIClient (Unix socket + automatic reconnect) │
└─────────────────────────────────────────────────────┘
The UI is the server — the daemon connects to the UI.
nft chain installed by the daemon
At startup, the daemon automatically installs an inet bsc_intercept output chain through nft. The chain:
- Whitelists infrastructure traffic that must not require user approval, because doing so would make the system unusable: loopback, DNS (UDP/TCP 53), DHCP (67/68), mDNS (5353), NTP (123), ICMP/ICMPv6, and IGMP.
- Intercepts all other new traffic (ct state new) and sends it to NFQUEUE number 0 with bypass. If the daemon dies, packets are allowed through instead of becoming stuck.
The chain is removed during shutdown. If nft or CAP_NET_ADMIN is missing, the daemon automatically falls back to polling /proc/net/{tcp,udp,tcp6,udp6}.
Verdict cache and isAsking
To avoid overwhelming the user with popups:
- An in-memory cache in the daemon stores "always" verdicts for the current session using the key app_path|host|port. If the user selects "allow always" for Firefox → google.com:443, the next 100 connections are allowed without another popup.
- An isAsking flag prevents more than one popup from being active at the same time. While the user is deciding on an alert, any additional incoming connections receive the default_action—typically allow—instead of opening a second dialog.
Wire protocol
Framing: [uint32_le length][UTF-8 JSON payload].
Daemon → UI messages: hello, ping, ask_rule, alert.
UI → daemon messages: pong, ask_rule_reply, notification.
Defined in common/include/bsc/proto/Wire.hpp.
Dependencies
Build dependencies
| Package | Minimum version |
| meson | ≥ 1.0 |
| ninja | any |
| c++ | GCC ≥ 13 or Clang ≥ 16 (C++23) |
| pkg-config | any |
UI runtime dependencies
| Package | Notes |
| libgtkmm-4.0-dev | GUI toolkit |
| libglibmm-2.68-dev | GLib C++ bindings |
| libgiomm-2.68-dev | Gio C++ bindings |
| libsystemd-dev | sd-bus for the system tray |
| libgdk-pixbuf-2.0-dev | Icons |
| nlohmann-json3-dev | Protocol serialization |
Daemon runtime dependencies
| Package | Notes |
| libnetfilter-queue-dev | Packet interception (optional) |
| nftables | nft used to install the bsc_intercept chain |
| nlohmann-json3-dev | Configuration, rule, and protocol parsing |
| libssl-dev | MD5/SHA1/SHA256 process hashing |
Installing dependencies on Debian/Ubuntu
sudo apt install \
libgtkmm-4.0-dev libglibmm-2.68-dev libgiomm-2.68-dev \
libsystemd-dev libgdk-pixbuf-2.0-dev \
libnetfilter-queue-dev nftables \
nlohmann-json3-dev libssl-dev \
meson ninja-build pkg-config
Building
meson setup build --prefix=/usr --sysconfdir=/etc --localedir=share/locale
meson compile -C build -j$(nproc)
sudo meson install -C build
Running
sudo systemctl enable --now bsc-daemon
bastionguard-sc-ui
The main window can remain closed. Essential interaction takes place through the non-modal, independent AlertDialog, so it does not block the rest of the desktop or the main UI.
Development
# UI only, without the daemon — displays "Daemon disconnected"
BSC_DATA=ui/data build/ui/bastionguard-sc-ui
# Daemon in development mode, as root, with a real NFQUEUE
sudo build/daemon/bsc-daemon \
--config-file daemon/data/default-config.json \
--rules-path /etc/bastionguard-secure-connectiond/rules \
--ui-socket unix:///tmp/bsd-daemon.sock \
--debug
Configuration
Communication socket
Default: unix:///tmp/bsd-daemon.sock with 0666 permissions. The daemon runs as root, while the UI runs as a regular user.
InterceptUnknown
"InterceptUnknown": true is enabled by default. Every new connection without a matching rule triggers a popup. Change it to false to make the daemon always apply DefaultAction without asking the user.
Proxy
BSC does not manage proxy traffic; that is handled by bastionguard-cef. However, it automatically detects proxies configured through http_proxy, HTTPS_PROXY, ALL_PROXY, and SOCKS_PROXY, including values loaded from /etc/environment, ~/.profile, and ~/.bashrc.
When the destination matches a configured proxy, the AlertDialog displays a green 🛡 indicator so the user can immediately recognize expected proxy traffic.
No special nft rule is required. Traffic to the proxy follows the regular interception pipeline, and the user can select "Allow always" on the first popup to avoid seeing it again.
JSON rules
Rules are stored in /etc/bastionguard-secure-connectiond/rules/.
Example:
{
"name": "allow-proxy",
"enabled": true,
"action": "allow",
"duration": "always",
"operator": {
"type": "simple",
"operand": "dest.host",
"data": "proxy.local"
}
}
Supported operands:
- process.path
- process.command
- process.hash.{md5,sha1,sha256}
- user.id
- user.name
- dest.ip
- dest.host
- dest.port
- protocol
- list
- lists
Debugging
The protocol uses JSON over a Unix socket, so it can be inspected directly:
# While the daemon is running, inspect the traffic
# Format: 4-byte little-endian length + JSON payload
sudo socat - UNIX-CONNECT:/tmp/bsd-daemon.sock | xxd
utils/scripts/fake_ui.py is a minimal fake server written in Python. It simulates the UI and is useful for testing the daemon without compiling the GUI.
Uninstallation
sudo systemctl stop bsc-daemon
sudo systemctl disable bsc-daemon
sudo ninja -C build uninstall
sudo nft delete table inet bsc_intercept 2>/dev/null
License
BastionGuard™ Secure Connection is free software licensed under GPL-3.0. The code is inspired by OpenSnitch.
Copyright © 2025–2026 Calogero Scarnà
BastionGuard™ is a trademark of Calogero Scarnà. The BastionGuard™ name and branding are not licensed under the GPL.