Proposed

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.

Background

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.

How Portal Backends Work

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:

1. The Backend Binary

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.

2. The Portal File

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.

3. The D-Bus Service File

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

What the Backend Does

Nothing.

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."

Scope

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.

Implementation: C with Install-Time Compilation

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.

The Pattern

This technique is used in Kicksecure's security-misc package for its FileManager1 D-Bus shim. Three components:

1. C source shipped in /usr/src/xdg-desktop-portal-ageless/
2. Build script in /usr/libexec/xdg-desktop-portal-ageless/build — calls gcc with hardened flags, links against libdbus-1, outputs the binary to /usr/libexec/
3. postinst hook in the Debian package — calls the build script during dpkg --configure

Why Build at Install Time?

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.

Build Dependencies

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.

Skeleton

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 ... */

This will change

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.

Defense in Depth

The portal backend is one layer in a stack of countermeasures. Even without it, Ageless Linux systems are protected:

Data layerbecome-ageless.sh neutralizes the systemd userdb birthDate field. The portal backend reads epoch dates or null.
Persistenceagelessd timer re-neutralizes every 24 hours, surviving manual or automated attempts to set a birth date.
Portal layerxdg-desktop-portal-ageless intercepts age queries at the API level before they reach any data store. (This page. Proposed.)
D-Bus layer — The stub daemon handles the original 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.

Package Structure

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

Timeline

done systemd birthDate merged (March 18, 2026). Data layer exists.
now PR #1922 in draft, locked. Waiting for resolution.
next PR #1922 is accepted or a desktop ships a backend. We build against it.
then xdg-desktop-portal-ageless ships as a .deb and is integrated into become-ageless.sh.

Credit

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.