Lock Down Permissions
mcdbus delegates all security to the operating system. It does not implement its own access control. This guide covers four independent layers you can deploy to restrict what mcdbus can do. They are all optional, all independent, and all stack on top of each other.
For the design rationale behind this approach, see Security Layers.
Quick reference
Section titled “Quick reference”| Layer | Scope | Granularity | Bus |
|---|---|---|---|
| systemd | Process isolation | Filesystem, network, syscalls | Both |
| D-Bus policy | Message filtering | Service, interface, method | Both |
| polkit | Action authorization | Per-action, per-user/group | System (mostly) |
| xdg-dbus-proxy | Socket filtering | Service, interface, path | Session (mostly) |
Layer 1: systemd service sandboxing
Section titled “Layer 1: systemd service sandboxing”The single most effective thing you can do. Running mcdbus as a systemd service gives you process-level isolation using Linux namespaces, seccomp, and capability dropping — all with declarative directives.
The unit file
Section titled “The unit file”[Unit]Description=mcdbus D-Bus MCP serverDocumentation=https://github.com/supported-systems/mcdbusAfter=dbus.service
[Service]Type=simpleExecStart=/usr/bin/env mcdbusDynamicUser=yesProtectSystem=strictProtectHome=yesPrivateTmp=yesPrivateDevices=yesProtectKernelTunables=yesProtectKernelModules=yesProtectKernelLogs=yesProtectControlGroups=yesProtectHostname=yesProtectClock=yesProtectProc=invisibleProcSubset=pidReadWritePaths=RestrictAddressFamilies=AF_UNIXPrivateNetwork=noCapabilityBoundingSet=AmbientCapabilities=NoNewPrivileges=yesSystemCallFilter=@system-serviceSystemCallArchitectures=nativeSystemCallErrorNumber=EPERMMemoryDenyWriteExecute=yesLockPersonality=yesRestrictRealtime=yesRestrictSUIDSGID=yesRemoveIPC=yesRestrictNamespaces=yesEnvironment=MCDBUS_TIMEOUT=30StandardOutput=journalStandardError=journalSyslogIdentifier=mcdbusMemoryMax=256MTasksMax=64Restart=on-failureRestartSec=5
[Install]WantedBy=default.targetWhat it does
Section titled “What it does”DynamicUser=yes creates an ephemeral Unix user for each invocation. No home directory, no persistent UID, no leftover state.
Filesystem restrictions: ProtectSystem=strict makes /usr, /boot, and /efi read-only. ProtectHome=yes hides /home, /root, and /run/user entirely. PrivateTmp=yes gives the process its own /tmp.
Network restrictions: RestrictAddressFamilies=AF_UNIX limits socket creation to Unix domain sockets. D-Bus only needs AF_UNIX, so this blocks any TCP, UDP, or raw socket activity.
Capabilities: CapabilityBoundingSet= (empty) drops every Linux capability. The process cannot change file ownership, load kernel modules, bind privileged ports, or perform any other capability-gated operation.
Syscall filtering: SystemCallFilter=@system-service restricts the process to the syscall set that a typical well-behaved service needs. Things like mount, reboot, and kexec_load are blocked. Blocked syscalls return EPERM.
Memory protection: MemoryDenyWriteExecute=yes prevents mapping memory as both writable and executable, which stops most classes of code injection.
Resource limits: MemoryMax=256M and TasksMax=64 prevent runaway resource consumption.
Deploy it
Section titled “Deploy it”sudo cp mcdbus.service /etc/systemd/system/sudo systemctl daemon-reloadsudo systemctl enable --now mcdbus.serviceTest it
Section titled “Test it”systemd ships a built-in security auditor:
systemd-analyze security mcdbus.serviceThis produces a score from 0.0 (fully exposed) to 10.0 (fully locked down). The unit file above should score above 7.0. The output lists each directive and its impact.
Check the journal for sandbox-related denials:
journalctl -u mcdbus.service -fEnvironment variables
Section titled “Environment variables”The unit file sets MCDBUS_TIMEOUT=30 by default. To override or add other variables, create a drop-in:
sudo systemctl edit mcdbus.service[Service]Environment=MCDBUS_TIMEOUT=60Environment=MCDBUS_REQUIRE_ELICITATION=1Layer 2: D-Bus bus policy
Section titled “Layer 2: D-Bus bus policy”D-Bus itself has a message filtering layer at the bus daemon (or bus broker) level. Policy files are XML documents that control which messages any client can send or receive based on the sender’s Unix identity.
The policy file
Section titled “The policy file”<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<!-- Default deny for mcdbus user --> <policy user="mcdbus"> <deny send_destination="*"/>
<!-- Bus daemon (required for discovery) --> <allow send_destination="org.freedesktop.DBus"/>
<!-- Desktop notifications --> <allow send_destination="org.freedesktop.Notifications" send_interface="org.freedesktop.Notifications"/> <allow send_destination="org.freedesktop.Notifications" send_interface="org.freedesktop.DBus.Introspectable"/>
<!-- MPRIS media players (one block per player) --> <allow send_destination="org.mpris.MediaPlayer2.firefox"/> <allow send_destination="org.mpris.MediaPlayer2.chromium"/> <allow send_destination="org.mpris.MediaPlayer2.spotify"/> <allow send_destination="org.mpris.MediaPlayer2.vlc"/> <allow send_destination="org.mpris.MediaPlayer2.mpv"/>
<!-- UPower battery status (read-only) --> <allow send_destination="org.freedesktop.UPower" send_interface="org.freedesktop.DBus.Properties" send_member="Get"/> <allow send_destination="org.freedesktop.UPower" send_interface="org.freedesktop.DBus.Properties" send_member="GetAll"/> <allow send_destination="org.freedesktop.UPower" send_interface="org.freedesktop.UPower" send_member="EnumerateDevices"/> <allow send_destination="org.freedesktop.UPower" send_interface="org.freedesktop.DBus.Introspectable"/>
<!-- bluez Bluetooth (read-only) --> <allow send_destination="org.bluez" send_interface="org.freedesktop.DBus.ObjectManager" send_member="GetManagedObjects"/> <allow send_destination="org.bluez" send_interface="org.freedesktop.DBus.Properties" send_member="Get"/> <allow send_destination="org.bluez" send_interface="org.freedesktop.DBus.Properties" send_member="GetAll"/> <allow send_destination="org.bluez" send_interface="org.freedesktop.DBus.Introspectable"/>
<!-- NetworkManager (read-only) --> <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.DBus.Properties" send_member="Get"/> <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.DBus.Properties" send_member="GetAll"/> <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.DBus.Introspectable"/>
<!-- systemd (list and read only) --> <allow send_destination="org.freedesktop.systemd1" send_interface="org.freedesktop.systemd1.Manager" send_member="ListUnits"/> <allow send_destination="org.freedesktop.systemd1" send_interface="org.freedesktop.DBus.Properties" send_member="Get"/> <allow send_destination="org.freedesktop.systemd1" send_interface="org.freedesktop.DBus.Properties" send_member="GetAll"/> <allow send_destination="org.freedesktop.systemd1" send_interface="org.freedesktop.DBus.Introspectable"/> </policy>
</busconfig>How it works
Section titled “How it works”The policy engine evaluates rules top to bottom. The last matching rule wins. The pattern is: default deny everything, then selectively allow specific destinations, interfaces, and methods.
Each <allow> or <deny> element can filter on send_destination (service name), send_interface, send_member (method name), and send_type (message type). Combining attributes narrows the filter — an allow with both send_destination and send_member only permits that specific method on that specific service.
Deploy it
Section titled “Deploy it”For the system bus:
sudo cp mcdbus.conf /etc/dbus-1/system.d/sudo systemctl reload dbus.serviceFor dbus-broker instead of dbus-daemon:
sudo systemctl reload dbus-broker.serviceTest it
Section titled “Test it”Denied messages appear in the bus daemon journal:
journalctl -u dbus-broker.service --since "5 min ago" | grep -i denyLayer 3: polkit rules
Section titled “Layer 3: polkit rules”polkit provides per-action authorization. Where D-Bus bus policy controls which messages can be sent, polkit controls whether a specific action is authorized for a specific user. Many system services (systemd, NetworkManager, UPower, udisks2) check polkit before performing privileged operations.
The rules file
Section titled “The rules file”polkit.addRule(function(action, subject) {
// Only apply to users in the mcdbus group if (!subject.isInGroup("mcdbus")) { return polkit.Result.NOT_HANDLED; }
// Read-only systemd operations var systemdReadActions = [ "org.freedesktop.systemd1.manage-units", "org.freedesktop.systemd1.reload-daemon" ];
// NetworkManager: allow reading connection and device info var nmReadActions = [ "org.freedesktop.NetworkManager.network-control", "org.freedesktop.NetworkManager.settings.modify.system" ];
// UPower: allow reading battery status var upowerActions = [ "org.freedesktop.upower.get-history", "org.freedesktop.upower.get-statistics" ];
// UDisks2: allow reading disk info var udisksReadActions = [ "org.freedesktop.udisks2.ata-smart-selftest" ];
if (systemdReadActions.indexOf(action.id) >= 0) { if (subject.local && subject.active) { return polkit.Result.YES; } }
if (nmReadActions.indexOf(action.id) >= 0) { if (subject.local && subject.active) { return polkit.Result.YES; } }
if (upowerActions.indexOf(action.id) >= 0) { return polkit.Result.YES; }
if (udisksReadActions.indexOf(action.id) >= 0) { if (subject.local && subject.active) { return polkit.Result.YES; } }
return polkit.Result.NOT_HANDLED;});Create the group and deploy
Section titled “Create the group and deploy”sudo groupadd mcdbussudo usermod -aG mcdbus $(whoami)# Log out and back in for the group membership to take effect
sudo cp 50-mcdbus.rules /etc/polkit-1/rules.d/sudo systemctl restart polkit.serviceTest it
Section titled “Test it”pkcheck --action-id org.freedesktop.systemd1.manage-units \ --process $$ --allow-user-interactionList all registered polkit actions to find the ones relevant to your deployment:
pkaction | grep -E 'systemd|NetworkManager|UPower|udisks'Layer 4: xdg-dbus-proxy
Section titled “Layer 4: xdg-dbus-proxy”xdg-dbus-proxy (part of Flatpak) creates a filtered D-Bus socket. You point it at the real bus socket, declare which services to expose, and it creates a new socket that only passes through matching messages. mcdbus connects to the proxy socket and never sees the rest of the bus.
The proxy script
Section titled “The proxy script”#!/usr/bin/env bashset -euo pipefail
MCDBUS_CMD="${MCDBUS_CMD:-mcdbus}"
if ! command -v xdg-dbus-proxy &>/dev/null; then echo "error: xdg-dbus-proxy not found. Install it first." >&2 exit 1fi
if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then echo "error: DBUS_SESSION_BUS_ADDRESS is not set." >&2 exit 1fi
PROXY_DIR="$(mktemp -d /tmp/mcdbus-proxy.XXXXXX)"PROXY_SOCKET="${PROXY_DIR}/bus"
cleanup() { if [ -n "${PROXY_PID:-}" ]; then kill "$PROXY_PID" 2>/dev/null || true wait "$PROXY_PID" 2>/dev/null || true fi rm -rf "$PROXY_DIR"}trap cleanup EXIT INT TERM
xdg-dbus-proxy "$DBUS_SESSION_BUS_ADDRESS" "$PROXY_SOCKET" \ --filter \ --talk=org.freedesktop.Notifications \ --talk=org.mpris.MediaPlayer2 \ --see=org.freedesktop.UPower \ --see=org.freedesktop.NetworkManager \ --see=org.bluez \ --see=org.freedesktop.systemd1 \ --talk=org.kde.KWin \ &
PROXY_PID=$!
for i in $(seq 1 50); do if [ -e "$PROXY_SOCKET" ]; then break fi sleep 0.1done
if [ ! -e "$PROXY_SOCKET" ]; then echo "error: proxy socket did not appear at $PROXY_SOCKET" >&2 exit 1fi
export DBUS_SESSION_BUS_ADDRESS="unix:path=${PROXY_SOCKET}"exec $MCDBUS_CMDPolicy levels
Section titled “Policy levels”xdg-dbus-proxy supports three levels of access per service:
| Level | Effect |
|---|---|
--see=NAME | Service appears in ListNames and can be introspected, but method calls are blocked. Read-only visibility. |
--talk=NAME | Full bidirectional communication. Method calls, property reads and writes, and signals all pass through. |
--own=NAME | The client can register (own) the given bus name. mcdbus does not need this. |
You can also filter at the interface and path level:
--call=org.freedesktop.UPower=org.freedesktop.DBus.Properties.GetAll@/org/freedesktop/UPower/*Install and deploy
Section titled “Install and deploy”sudo pacman -S xdg-dbus-proxysudo apt install xdg-dbus-proxysudo dnf install xdg-dbus-proxychmod +x mcdbus-proxy.sh./mcdbus-proxy.shOr integrate it into the systemd unit:
[Service]ExecStart=/usr/local/bin/mcdbus-proxy.shChoosing layers
Section titled “Choosing layers”Layer 1 alone is sufficient for most deployments. It costs nothing to deploy and provides broad process-level isolation.
Layers 1 + 2 + 3 together provide defense in depth for high-security environments or shared systems. The bus policy restricts which services mcdbus can talk to, and polkit controls which privileged operations are authorized.
Layer 4 fills the gap when you need fine-grained session bus filtering that the other layers cannot express. The trade-off is an additional proxy process.
All four layers are independent. Deploy whichever combination matches your threat model.