The Secret Service API is a D-Bus interface
(org.freedesktop.secrets) implemented by GNOME Keyring. Applications use it to store and retrieve
credentials. Once your desktop session is unlocked (at login), any process running as your user can
talk to this interface without further authentication.
The Threat: a one-liner can enumerate every credential stored in the keyring — service names, usernames, and secrets — with no knowledge of what is stored there.
import secretstorage
bus = secretstorage.dbus_init()
for c in secretstorage.get_all_collections(bus):
c.unlock()
for item in c.get_all_items():
print(item.get_label(), item.get_attributes())In scope: malicious or compromised code running in your login session — a rogue script, a supply-chain-compromised CLI tool, a malicious Python package, etc.
Out of scope: an attacker with physical access or root; an attacker who can read
~/.local/share/keyrings/ directly (the files are encrypted with your login password, but if
the session is already unlocked the daemon will serve secrets anyway).
The Secret Service API exposes several enumeration vectors:
| Method | Interface | What it returns | Blocked? |
|---|---|---|---|
SearchItems({}) |
Secret.Service |
All item paths (broad search) | No |
SearchItems({}) |
Secret.Collection |
All item paths in a collection | No |
Properties.Get('Items') |
DBus.Properties |
All item paths in a collection | Partial* |
Properties.GetAll |
DBus.Properties |
All properties incl. item list | Yes |
D-Bus policy rules is very weak to protect against this threat model.
One can write a file ~/.config/dbus-1/session.d/block-keyring-enum.conf to block Properties.GetAll
on org.freedesktop.secrets. This prevents the single-call "give me every property of
every collection" attack while leaving targeted lookups intact.
What this stops: an attacker calling GetAll on a collection to retrieve its full
item list and all metadata in one call.
What this does not stop: an attacker who knows the D-Bus API well enough to call
SearchItems({}) or Properties.Get('Items') directly.
D-Bus policy rules operate at the method level — they cannot inspect message body arguments. It is therefore impossible to distinguish:
SearchItems({})— enumerate everything (attack)SearchItems({'service': 'github.com', 'username': 'martin'})— targeted lookup (legitimate)
Blocking SearchItems entirely breaks applications that use libsecret for credential lookup
(browsers, GNOME apps, custom scripts using keyring or secretstorage).
The same argument applies: Properties.Get('Items') (lists all items — attack) and
Properties.Get('Label') (reads a collection name — used by secretstorage during init)
are the same D-Bus method call. Blocking it breaks the keyring Python library and
any app that reads collection metadata.
| Vector | Mitigation |
|---|---|
Brute-force object paths via GetSecret |
Paths are sequential integers — low effort for an attacker with session access |
| Read keyring files directly | Protected by filesystem permissions; moot if session is unlocked |
D-Bus filtering proxy: A userspace proxy (e.g. using dbus-broker with Lua policy,
or a custom proxy daemon) can inspect message body arguments and allow SearchItems only
when called with non-empty attributes. This is the only way to fully close the remaining
vectors while keeping apps functional. Significantly more complex to set up.
As root, create /etc/dbus-1/session-local.conf (world-readable, mode 644):
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<includedir>/home/YOUR_USER/.config/dbus-1/session.d</includedir>
</busconfig>This hooks your user-writable ~/.config/dbus-1/session.d/ into the session bus policy
load path. Replace YOUR_USER with your username (tilde and ${HOME} are not expanded
by dbus-daemon in <includedir>).
Content of ~/.config/dbus-1/session.d/block-keyring-enum.conf
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy context="default">
<deny send_destination="org.freedesktop.secrets"
send_interface="org.freedesktop.DBus.Properties"
send_member="GetAll"/>
</policy>
</busconfig>