D-Bus Proxy¶
By default, integration.dbus = true enables a proxied D-Bus session bus
with only the XDG portal interfaces the enabled capabilities actually need
(org.freedesktop.portal.Notification for notifications,
org.freedesktop.portal.OpenURI for xdg_open) — the container never gets
unfiltered host bus access unless you explicitly opt in.
The org.freedesktop.portal.Desktop service is never granted wholesale
via --talk= (which would expose host-privileged portal interfaces such as
DynamicLauncher, Screenshot, ScreenCast, and Settings). Instead,
access is scoped per interface with xdg-dbus-proxy --call=/--broadcast=
rules.
This is handled by a companion systemd unit that runs xdg-dbus-proxy
to filter which D-Bus services the container can interact with.
How it works¶
When [dbus] talk or own rules are configured:
-
podbox enablewrites an additional file: -
The generated
.containerquadlet gains: -
Instead of
Volume=%t/bus:%t/bus, the container gets the proxy socket: -
The proxy service runs
xdg-dbus-proxy, which forwards only the explicitly allowed D-Bus services to the container.
Configuration¶
[dbus]
talk = [
"org.freedesktop.Notifications",
"org.mpris.MediaPlayer2.*",
]
own = [
"org.mpris.MediaPlayer2.podbox_app",
]
| Key | Type | Description |
|---|---|---|
talk |
string[] | D-Bus services the container can call (two-way communication) |
own |
string[] | D-Bus services the container can register on the host bus |
Wildcards (*) are supported per the xdg-dbus-proxy filtering rules.
Warning: adding
org.freedesktop.portal.*(or anyorg.freedesktop.portal.*/org.freedesktop.impl.portal.*name) totalkre-grants the full portal bus surface, including host-privileged interfaces likeDynamicLauncher,Screenshot,ScreenCastandSettings. Prefer the built-in interface-scoped rules described below;podboxprints a warning when it sees a portal-familytalkentry.
Portal access model¶
The portal preset (applied by default when [dbus] has no explicit rules)
does not add org.freedesktop.portal.* to the talk list. Instead, the
generated proxy exposes org.freedesktop.portal.Desktop through
interface-scoped rules, one per enabled capability:
| Capability | Rule granted |
|---|---|
integration.notify |
--call=org.freedesktop.portal.Desktop=org.freedesktop.portal.Notification.*@/org/freedesktop/portal/desktop |
integration.xdg_open |
--call=org.freedesktop.portal.Desktop=org.freedesktop.portal.OpenURI.*@/org/freedesktop/portal/desktop |
| either | --call=org.freedesktop.portal.Desktop=org.freedesktop.portal.Request.*@/org/freedesktop/portal/desktop/request/* (async Request pattern, incl. Request.Close) |
| either | --broadcast=org.freedesktop.portal.Desktop=org.freedesktop.portal.Request.*@/org/freedesktop/portal/desktop/request/* (Request.Response result signals) |
| either | --call=org.freedesktop.portal.Desktop=org.freedesktop.DBus.Introspectable.*@/org/freedesktop/portal/* (read-only introspection, needed by GIO clients to parse call arguments) |
Because xdg-dbus-proxy treats any granted method or signal on a name as
TALK for that name, these rules let the container reach exactly those portal
interfaces — and nothing else on the portal service. A disabled capability
contributes no rules, so it cannot be exercised through the proxy at all.
Behavior matrix¶
integration.dbus |
[dbus] config |
What the container gets |
|---|---|---|
false |
any | No D-Bus access |
true |
default (empty) | Proxied — preset = "portal" applied automatically with interface-scoped portal rules for notify/xdg_open |
true |
preset / talk / own set | Proxied via xdg-dbus-proxy with those rules plus interface-scoped portal rules for enabled capabilities |
true |
preset = "", empty talk + own |
Unfiltered Volume=%t/bus:%t/bus |
Generated proxy unit¶
When rules are present, a companion systemd service is generated at
~/.config/containers/systemd/<name>-proxy.service:
[Unit]
Description=D-Bus Proxy for podbox container <name>
PartOf=<name>.service
[Service]
Type=simple
RuntimeDirectory=podbox
ExecStart=/usr/bin/xdg-dbus-proxy \
unix:path=%t/bus \
%t/podbox/<name>-dbus.sock \
--talk=org.freedesktop.Notifications \
--talk=org.mpris.MediaPlayer2.* \
--call=org.freedesktop.portal.Desktop=org.freedesktop.portal.Notification.*@/org/freedesktop/portal/desktop \
--call=org.freedesktop.portal.Desktop=org.freedesktop.portal.OpenURI.*@/org/freedesktop/portal/desktop \
--call=org.freedesktop.portal.Desktop=org.freedesktop.portal.Request.*@/org/freedesktop/portal/desktop/request/* \
--call=org.freedesktop.portal.Desktop=org.freedesktop.DBus.Introspectable.*@/org/freedesktop/portal/* \
--broadcast=org.freedesktop.portal.Desktop=org.freedesktop.portal.Request.*@/org/freedesktop/portal/desktop/request/* \
--own=org.mpris.MediaPlayer2.podbox_app
Restart=on-failure
[Install]
WantedBy=<name>.service
The proxy's lifecycle is tied to the container via PartOf=<name>.service.
Stopping the container stops the proxy; restarting the container restarts
the proxy.
Requirements¶
xdg-dbus-proxymust be installed on the host system (packagexdg-dbus-proxy, commonly shipped with Flatpak)integration.dbus = true(the master switch)- A D-Bus session bus socket must be present on the host (auto-detected)
Verification¶
Test an allowed service¶
gdbus call --session \
--dest org.freedesktop.Notifications \
--object-path /org/freedesktop/Notifications \
--method org.freedesktop.Notifications.Notify \
"podbox" 0 "" "Hello" "Proxied message." [] {} 5000
This should succeed and show a desktop notification on the host.
Test isolation¶
gdbus call --session \
--dest org.freedesktop.systemd1 \
--object-path /org/freedesktop/systemd1 \
--method org.freedesktop.DBus.Peer.Ping
This should fail with an access denied error — the proxy blocks the
unapproved org.freedesktop.systemd1 service.
Portal surface audit¶
The reachable org.freedesktop.portal.Desktop surface was audited from inside
a container (gdbus call against the proxied socket). Only the capability-gated
interfaces are allowed; every other portal interface is denied by the proxy.
Allowed (must succeed):
| Interface | Result |
|---|---|
org.freedesktop.portal.Notification.AddNotification |
() (host notification shown) |
org.freedesktop.portal.OpenURI.OpenURI |
request handle returned |
org.freedesktop.DBus.Introspectable.Introspect |
introspection XML (required by GIO clients) |
Audited and denied (all return org.freedesktop.DBus.Error.AccessDenied):
| Interface | Result |
|---|---|
Screenshot.Screenshot |
AccessDenied |
ScreenCast.CreateSession |
AccessDenied |
RemoteDesktop.CreateSession |
AccessDenied |
InputCapture.CreateSession |
AccessDenied |
Settings.Read |
AccessDenied |
Documents.Add |
AccessDenied |
Account.GetUserInformation |
AccessDenied |
GameMode.QueryStatus |
AccessDenied |
Lockdown.GetDisabled |
AccessDenied |
Print.PreparePrint |
AccessDenied |
Wallpaper.SetWallpaperURI |
AccessDenied |
DynamicLauncher.RequestInstallToken |
AccessDenied |
FileChooser.OpenFile |
AccessDenied |
The denial happens at the proxy before any request reaches the host portal, so screen capture, input capture, screenshots, file access and dynamic launcher install are unreachable from inside a container unless the corresponding capability is enabled.