A custom xdg-desktop-portal backend that claims the age verification interface and returns nothing. Applications get a valid portal response. No age data leaves the system.
This implementation is proposed. It targets xdg-desktop-portal PR #1922, which is still in draft. The interface name, method signatures, and portal behavior may all change before this ships. We will build against the final specification.
The Linux age verification ecosystem is converging on two layers: a data store (systemd userdb
birthDate, already merged)
and an application API (xdg-desktop-portal, PR #1922). The
D-Bus stub daemon
handles the original org.freedesktop.AgeVerification1 interface. But the original
proposal author now considers the portal path the realistic one:
"The original D-Bus API I proposed probably isn't how an age API would be implemented in the future if it ended up happening. [The xdg-desktop-portal PR] has a more realistic API; this is probably the one you'll want to plan on blocking." — Aaron Rainbolt, March 2026
The portal approach is different from a standalone D-Bus service. xdg-desktop-portal acts as middleware: applications talk to the portal daemon, which routes requests to backends provided by the desktop environment. GNOME ships one backend, KDE ships another. Each implements the same portal interfaces using DE-specific tooling.
The countermeasure is to ship our own backend.
An xdg-desktop-portal backend is a D-Bus-activated service that implements one or more
org.freedesktop.impl.portal.* interfaces. Three files make a backend
discoverable:
An executable that connects to the session bus, registers a well-known name
(e.g., org.freedesktop.impl.portal.desktop.ageless), and handles method calls
on one or more portal interfaces. Can be written in any language that speaks D-Bus.
A .portal file installed to
/usr/share/xdg-desktop-portal/portals/. Declares the backend's D-Bus name and
which interfaces it implements:
[portal] DBusName=org.freedesktop.impl.portal.desktop.ageless Interfaces=org.freedesktop.impl.portal.ParentalControls
The interface name above is tentative — PR #1922's naming is still under debate.
Alternatives include AgeRange and AgeSignals.
A .service file in /usr/share/dbus-1/services/ for D-Bus
activation. When the portal daemon needs the backend, D-Bus starts it automatically:
[D-BUS Service] Name=org.freedesktop.impl.portal.desktop.ageless Exec=/usr/libexec/xdg-desktop-portal-ageless
Additionally, a portal configuration file in
/usr/share/xdg-desktop-portal/ can specify which backend handles which
interface. This is how we override the desktop's default backend for age verification
without affecting file choosers, notifications, or anything else:
# /usr/share/xdg-desktop-portal/ageless-portals.conf [preferred] org.freedesktop.impl.portal.ParentalControls=ageless
The backend implements the age verification portal interface and returns an error for every query. The portal daemon sees a working backend. Applications see a valid portal response. The response is: "this system does not provide age data."
PR #1922's proposed interface lets applications submit age thresholds (e.g.,
[13, 16, 18]) and receive a bracket response. Our backend would return an
error code indicating the operation is not supported, or return a sentinel value (e.g.,
[-1, -1]) meaning "no age data available."
The exact error code depends on the final portal spec. xdg-desktop-portal backends typically return portal-level errors via the portal response signal, not D-Bus-level errors. We will match whatever convention the spec defines for "backend cannot fulfill this request."
The backend implements only the age verification interface. It does not replace or interfere with any other portal functionality. File access, screen sharing, notifications, print dialogs — all continue to be handled by the desktop's own backend (GNOME, KDE, wlroots, etc.).
This is surgical: one interface, one backend, one refusal.
Aaron Rainbolt (author of the original D-Bus age verification proposal and contributor to Kicksecure) recommended writing the backend in C and compiling it at package install time. This avoids shipping architecture-specific binaries while keeping a single Debian package.
This technique is used in Kicksecure's security-misc package for its FileManager1 D-Bus shim. Three components:
/usr/src/xdg-desktop-portal-ageless//usr/libexec/xdg-desktop-portal-ageless/build — calls
gcc with hardened flags, links against libdbus-1, outputs the
binary to /usr/libexec/dpkg --configure
Architecture independence. One .deb works on amd64, arm64,
riscv64, or any architecture with gcc and the required dev headers. No
multi-arch build matrix. No cross-compilation. The user's machine compiles for itself.
Auditability. The source is right there in /usr/src/. Anyone
can read what the backend does (nothing), verify it does nothing, and rebuild it.
Hardened by default. The build script applies full compiler hardening:
-fstack-protector-all, -fstack-clash-protection,
_FORTIFY_SOURCE=3, -pie, -Wl,-z,relro,
-Wl,-z,now, and architecture-specific mitigations (CFI on x86_64, BTI on
aarch64). A program that does nothing should still be built as if it does everything.
The package will require at install time:
gcc pkg-config libdbus-1-dev
These are standard development packages available on every Debian-based distribution.
become-ageless.sh will handle installing them as part of the conversion
process.
The following is a structural outline of the backend. This is not final code — the portal interface does not exist yet. It shows the shape of the implementation: connect to the session bus, claim the backend name, handle the age portal method, return nothing.
#include <dbus/dbus.h>
/*
* xdg-desktop-portal-ageless
*
* Implements org.freedesktop.impl.portal.ParentalControls
* (or whatever PR #1922 names it).
*
* Every method call returns an error or sentinel indicating
* "no age data available." No data is collected, stored, or
* transmitted.
*/
static const char *BUS_NAME =
"org.freedesktop.impl.portal.desktop.ageless";
static const char *IFACE =
"org.freedesktop.impl.portal.ParentalControls";
/* ... D-Bus connection setup, name acquisition ... */
void handle_get_age_range(DBusMessage *msg, DBusConnection *conn) {
/*
* The PR proposes: app sends age gates [int],
* backend responds with [lower, upper] bracket.
*
* We respond with an error indicating the operation
* is not supported, or [-1, -1] (no data), depending
* on the final spec.
*/
DBusMessage *reply = dbus_message_new_error(msg,
"org.freedesktop.portal.Error.NotAllowed",
"Age verification is not available on this system.");
dbus_connection_send(conn, reply, NULL);
dbus_connection_flush(conn);
dbus_message_unref(reply);
}
/* ... main loop: pop messages, dispatch to handler ... */
The error name, the interface name, the method signature, the response format — all depend on the final portal specification. This skeleton shows intent and architecture, not a shippable implementation. We will build the real thing when there is a real specification to build against.
The portal backend is one layer in a stack of countermeasures. Even without it, Ageless Linux systems are protected:
become-ageless.sh neutralizes the systemd userdb
birthDate field. The portal backend reads epoch dates or null.agelessd timer re-neutralizes every 24 hours, surviving manual
or automated attempts to set a birth date.xdg-desktop-portal-ageless intercepts age queries at the API
level before they reach any data store. (This page. Proposed.)org.freedesktop.AgeVerification1 interface for
non-sandboxed applications.An application querying age data on an Ageless Linux system hits at minimum two barriers: the portal returns an error (or the stub returns "unknown"), and even if it somehow bypasses the API layer and reads userdb directly, the data is neutralized.
The xdg-desktop-portal-ageless package will ship the following files:
xdg-desktop-portal-ageless/
usr/
src/xdg-desktop-portal-ageless/
portal-ageless.c # backend source
libexec/xdg-desktop-portal-ageless/
build # gcc invocation with hardened flags
share/
xdg-desktop-portal/
portals/ageless.portal # portal registration
ageless-portals.conf # interface-to-backend mapping
dbus-1/services/
org.freedesktop.impl.portal.desktop.ageless.service
debian/
control # Depends: gcc, pkg-config, libdbus-1-dev
postinst # calls build script
postrm # removes compiled binary
birthDate merged (March 18, 2026). Data layer exists.xdg-desktop-portal-ageless ships as a .deb and is integrated
into become-ageless.sh.
The portal backend approach and the install-time compilation pattern were recommended by
Aaron Rainbolt (arraybolt3), author of the original
org.freedesktop.AgeVerification1 D-Bus proposal and contributor to
Kicksecure/Whonix. The build pattern is based on Kicksecure's
security-misc
FileManager1 shim.