Security Layers
mcdbus does not implement its own access control. By design, it delegates all security decisions to the operating system’s existing D-Bus permission stack. The same mechanisms that protect D-Bus from any other client also protect it from mcdbus.
This page explains the rationale and the four independent layers. For concrete deployment steps, see Lock Down Permissions.
Design philosophy
Section titled “Design philosophy”When an LLM agent talks to system services through D-Bus, the temptation is to build access control into the bridge itself — an allowlist of services, a set of blocked methods, a permission database. mcdbus deliberately does not do this, for three reasons.
First, the operating system already has a mature, well-tested permission stack for D-Bus. Reimplementing it inside mcdbus would be redundant and likely less thorough.
Second, the OS permission stack is transparent to system administrators. They already know how to write D-Bus bus policy files, polkit rules, and systemd unit hardening. An mcdbus-specific config format would be one more thing to learn and audit.
Third, the OS permission stack applies regardless of what client is connecting. If you lock down D-Bus policy for the mcdbus user, those restrictions apply whether the user is running mcdbus, busctl, dbus-send, or any other D-Bus client. There is no way to bypass the restrictions by using a different tool.
The one thing mcdbus does add is the confirmation flow — an interactive prompt before system bus method calls and property mutations. This is not access control in the traditional sense. It is an intent verification step between the LLM and the user, not between the process and the kernel.
Layer 1: systemd service sandboxing
Section titled “Layer 1: systemd service sandboxing”Scope: process isolation. Granularity: filesystem paths, network families, syscalls, capabilities.
Running mcdbus as a systemd service with hardening directives gives you a sandbox around the entire process. The sandbox uses Linux kernel features — namespaces, seccomp, capability bounding sets — that are enforced by the kernel itself. There is no userspace component to bypass.
What systemd sandboxing controls:
- Which parts of the filesystem the process can see and write to
- Which socket address families (AF_UNIX, AF_INET, AF_INET6, etc.) the process can create
- Which Linux capabilities the process has (none, in the recommended configuration)
- Which syscalls the process can invoke
- Whether the process can map memory as writable and executable simultaneously
- Resource limits on memory and tasks
What it does not control: which D-Bus services the process can talk to. As long as the process can reach the D-Bus socket (which requires AF_UNIX), the bus daemon handles message routing. To restrict D-Bus communication specifically, you need Layer 2.
Layer 2: D-Bus bus policy
Section titled “Layer 2: D-Bus bus policy”Scope: message filtering. Granularity: service name, interface, method name, message type.
D-Bus bus policy operates inside the bus daemon (or bus broker). Every message that passes through the bus is checked against the policy rules. If a rule denies a message, the bus daemon drops it silently (or with an error reply). The sending process never knows the message was filtered — it just gets an access denied error.
Policy files are XML documents installed in /etc/dbus-1/system.d/ or /etc/dbus-1/session.d/. They match on the sender’s Unix user or group and filter by destination service name, interface, method name, and message type.
This is the most reliable layer for controlling D-Bus access because it operates at the message transport level. Even if mcdbus or dbus-fast had a vulnerability that allowed arbitrary D-Bus messages, the bus daemon would still enforce the policy.
Layer 3: polkit
Section titled “Layer 3: polkit”Scope: action authorization. Granularity: per-action, per-user, per-group, per-session.
polkit sits above D-Bus in the stack. When a system service receives a D-Bus method call that requires authorization, the service asks polkit whether the calling user is allowed to perform that action. This check happens inside the service — it is the service’s choice to consult polkit, not something the bus daemon enforces.
Most system services on a modern Linux desktop use polkit for their authorization decisions: systemd checks polkit before starting or stopping units, NetworkManager checks polkit before changing network configuration, udisks2 checks polkit before mounting filesystems.
polkit rules are JavaScript files that can make decisions based on the action ID, the subject’s user name, group membership, whether the session is local, and whether the session is active. This allows fine-grained policies like “allow users in the mcdbus group to read systemd unit status but not start or stop units.”
The limitation is that polkit action IDs are often coarser than the actual operations. For example, systemd uses org.freedesktop.systemd1.manage-units for both reading unit status and stopping units. To distinguish between those two operations, you need Layer 2 (D-Bus bus policy) to filter at the method level.
Layer 4: xdg-dbus-proxy
Section titled “Layer 4: xdg-dbus-proxy”Scope: socket-level filtering. Granularity: service name, interface, object path.
xdg-dbus-proxy is a Flatpak utility that creates a filtered D-Bus socket. It sits between the client and the real bus socket, inspecting every message and only passing through those that match the configured rules.
This is the most granular layer. You can allow access to a specific method on a specific interface at a specific object path on a specific service. You can also set different access levels per service: --see for read-only visibility (introspectable but no method calls), --talk for full communication, and --own for name registration.
xdg-dbus-proxy is most useful for the session bus, where D-Bus bus policy is less commonly configured. For the system bus, Layers 2 and 3 are usually sufficient. The trade-off is that xdg-dbus-proxy adds a proxy process and a temporary socket, which adds a small amount of latency and operational complexity.
How layers compose
Section titled “How layers compose”Each layer is independent. They do not know about each other and do not interfere with each other. When a D-Bus message travels from mcdbus to a service, it passes through whatever layers are active:
mcdbus process | | (Layer 1: systemd sandbox -- can the process create a Unix socket?) | vxdg-dbus-proxy (if Layer 4 is deployed) | | (Layer 4: proxy filtering -- does the message match the allow rules?) | vD-Bus bus daemon | | (Layer 2: bus policy -- is this user allowed to send to this destination?) | vTarget service | | (Layer 3: polkit -- is this user authorized for this action?) | vOperation executes (or is denied)A message must pass every active layer to succeed. Blocking at any layer stops the operation. This means the layers stack: adding a layer can only make the system more restrictive, never less.
Choosing layers
Section titled “Choosing layers”Layer 1 alone covers the vast majority of deployments. It is free to set up (a single systemd unit file), has zero runtime overhead, and provides broad isolation. Even if mcdbus or any of its dependencies had a vulnerability, the sandbox limits what an attacker could do.
Layers 1 + 2 add service-level filtering when you want to restrict which D-Bus services mcdbus can communicate with. This is the right combination when the threat model includes mcdbus being used to talk to services it should not (e.g., udisks2 for disk operations when only battery and network status are needed).
Layers 1 + 2 + 3 add per-action authorization for system services that check polkit. This is appropriate for shared systems or environments where multiple users have access and you want different privilege levels.
Layer 4 fills a niche: fine-grained session bus filtering. Use it when you need to restrict which session bus services mcdbus can see, or when you need per-path filtering that the bus policy cannot express.