Summary
Recent Electron releases register their tray item on Chromium’s D-Bus object path
/org/chromium/StatusNotifierItem/<N> instead of /StatusNotifierItem. The
desktop-legacy and unity7 interfaces only permit /StatusNotifierItem and
/org/ayatana/NotificationItem/*, so the host’s tray implementation is denied
when it reads the item’s properties.
The item still registers with org.kde.StatusNotifierWatcher, so the tray
reserves a slot for it. The result is an empty, unclickable gap where the icon
should be — no icon, no context menu, no activation.
This affects every strictly confined Electron snap, not just the two below.
Environment
- snapd 2.76.1 (rev 27591), series 16
- KDE Plasma 5.27.12, X11 session,
org.kde.StatusNotifierWatcherserved by kded5 - signal-desktop 8.21.0 (rev 927, snapcrafters)
- slack 4.51.180 (rev 254)
Both apps regressed simultaneously after an auto-refresh; both are Electron.
Reproducer
Launch a recent Electron snap with a tray icon, then query its item:
$ qdbus org.kde.StatusNotifierWatcher /StatusNotifierWatcher \
org.kde.StatusNotifierWatcher.RegisteredStatusNotifierItems
...
:1.147/org/chromium/StatusNotifierItem/1
$ dbus-send --session --print-reply --dest=:1.147 \
/org/chromium/StatusNotifierItem/1 \
org.freedesktop.DBus.Properties.GetAll string:"org.kde.StatusNotifierItem"
Error org.freedesktop.DBus.Error.AccessDenied: An AppArmor policy prevents this
sender from sending this message to this recipient; type="method_call",
sender=":1.152" (uid=1000 pid=7810 comm="dbus-send ...") label="unconfined"
interface="org.freedesktop.DBus.Properties" member="GetAll"
destination=":1.147" (uid=1000 pid=6954
comm="/snap/signal-desktop/927/opt/Signal/signal-desktop"
label="snap.signal-desktop.signal-desktop (enforce)")
Cause
The generated profile permits only these object paths:
path=/{StatusNotifierItem,org/ayatana/NotificationItem/*}
path=/{StatusNotifierItem/menu,org/ayatana/NotificationItem/*/Menu}
path=/{StatusNotifierItem,StatusNotifierItem/menu,org/ayatana/NotificationItem/**}
/org/chromium/StatusNotifierItem/* is absent, and Chromium additionally serves
its DBusMenu under that same prefix.
snapd is not the component that changed. The rule strings are identical in both the previous and current snapd revisions:
$ strings /snap/snapd/25935/usr/lib/snapd/snapd | grep -oE "org/ayatana/NotificationItem[^\"']*|/\{?StatusNotifierItem[^\"']*" | sort -u
$ strings /snap/snapd/27591/usr/lib/snapd/snapd | grep -oE "org/ayatana/NotificationItem[^\"']*|/\{?StatusNotifierItem[^\"']*" | sort -u
# identical output
The change is on the Electron side — see https://github.com/electron/electron/issues/40936
for Electron delegating tray handling to Chromium’s implementation, which owns
the /org/chromium/... path.
Note that two distinct object trees need covering. Chromium exports the menu
on its own tree rather than below the item’s path — the item’s Menu property
reads:
$ dbus-send --session --print-reply=literal --dest=:1.198 \
/org/chromium/StatusNotifierItem/1 \
org.freedesktop.DBus.Properties.Get \
string:"org.kde.StatusNotifierItem" string:"Menu"
variant /org/chromium/DbusMenu/1
so com.canonical.dbusmenu.GetLayout on /org/chromium/DbusMenu/1 is denied
separately. Permitting only the item path yields a visible icon with a
non-functional context menu.
Suggested fix
Extend the object paths in the desktop-legacy and unity7 interfaces to cover
both Chromium trees, e.g.:
path=/{StatusNotifierItem,org/ayatana/NotificationItem/*,org/chromium/StatusNotifierItem/*}
path=/{StatusNotifierItem/menu,org/ayatana/NotificationItem/*/Menu,org/chromium/DbusMenu/*}
Verified workaround
Tested on the environment above: icon renders and the context menu works, with
confinement otherwise intact. Drop the following into
/etc/apparmor.d/abstractions/dbus-session-strict.d/local-chromium-sni and
reload the affected profiles with apparmor_parser -r. That abstraction is
included inside every snap profile’s profile {} block, and the .d directory
is a supported extension point, so the addition survives both snapd profile
regeneration and apparmor package upgrades.
dbus (send)
bus=session
path=/org/chromium/StatusNotifierItem/*
interface=org.kde.StatusNotifierItem
member="New{AttentionIcon,Icon,IconThemePath,OverlayIcon,Status,Title,ToolTip}"
peer=(label="{plasmashell,unconfined}"),
dbus (receive)
bus=session
path=/org/chromium/StatusNotifierItem/*
interface=org.kde.StatusNotifierItem
member={Activate,ContextMenu,Scroll,SecondaryActivate,ProvideXdgActivationToken,XAyatanaSecondaryActivate}
peer=(label="{plasmashell,unconfined}"),
dbus (send)
bus=session
path=/org/chromium/DbusMenu/*
interface=com.canonical.dbusmenu
member="{LayoutUpdated,ItemsPropertiesUpdated}"
peer=(label="{plasmashell,unconfined}"),
dbus (send)
bus=session
path=/org/chromium/StatusNotifierItem/**
interface=org.freedesktop.DBus.Properties
member=PropertiesChanged
peer=(label="{plasmashell,unconfined}"),
dbus (receive)
bus=session
path=/org/chromium/DbusMenu/**
interface={org.freedesktop.DBus.Properties,com.canonical.dbusmenu,org.freedesktop.DBus.Introspectable}
member={Get*,AboutTo*,Event*,Introspect}
peer=(label="{plasmashell,unconfined}"),
dbus (receive)
bus=session
path=/org/chromium/StatusNotifierItem/**
interface={org.freedesktop.DBus.Properties,com.canonical.dbusmenu,org.freedesktop.DBus.Introspectable}
member={Get*,AboutTo*,Event*,Introspect}
peer=(label="{plasmashell,unconfined}"),
The snap-tuning hook is not usable for this: #include if exists "/var/lib/snapd/apparmor/snap-tuning" sits outside the profile {} block, so it
accepts tunables but not permission rules. Without the abstraction trick the only
remaining option is devmode, which disables confinement entirely.