Skip to content

File accessUser activity

recently-used.xbel: GNOME and GTK Recent Files on Linux

The freedesktop recently-used.xbel file on Linux desktops: which files and URIs a user opened through GTK (and newer KDE) applications, with UTC times.

Location
~/.local/share/recently-used.xbel
Proves
Which local or remote files a desktop user opened or saved through GUI applications, with which app and when
Timestamps
ISO 8601 UTC with Z suffix (microseconds when non-zero); legacy app 'timestamp' in Unix seconds
Access
Any user for own file (GTK sets mode 0600); root for other users
Retention
GTK default 30 days and 1000 items; GNOME sets recent-files-max-age -1 (keep) by default
Collection
UAC, Velociraptor, cp -a, tar

What it is

recently-used.xbel is the shared "recent files" list defined by the freedesktop.org Desktop Bookmark specification and implemented by GLib's GBookmarkFile and GTK's GtkRecentManager. When a GTK application opens or saves a document, it registers the URI here. The file dialog, Nautilus/Files "Recent" view and application recent menus read it back. Recent KDE Frameworks releases also write to it through KIO's KRecentDocument, so KDE applications can appear too.

It is an XML file (XBEL format) with one <bookmark> element per URI. Each bookmark carries timestamps, the MIME type and the list of applications that registered it, which makes it one of the few Linux artifacts that ties a user, a file path and a GUI program together.

Where it lives

EnvironmentPathNotes
GTK 3 / GTK 4 (GNOME, Cinnamon, MATE, Xfce GTK apps)~/.local/share/recently-used.xbel$XDG_DATA_HOME/recently-used.xbel if set
Old GTK 2~/.recently-used.xbelLater GTK 2 releases rename it to the XDG location
Flatpak apps~/.var/app/<app-id>/data/recently-used.xbelFlatpak points XDG_DATA_HOME at a per-app directory, so a sandboxed app without access to the host file may keep its own copy
KDE (additional stores)~/.local/share/RecentDocuments/*.desktop; ~/.local/share/kactivitymanagerd/KDE keeps its own recent-document files and an activity database besides the XBEL file

The location is the same across Debian/Ubuntu, RHEL/Fedora, Arch and other families. It only exists for accounts that used a graphical session. Headless servers normally have none.

What it proves

  • That a given URI (local path, removable media path or GVfs network location such as SMB or SFTP) was opened or saved through a registering application by this account.
  • Which applications handled it, how many times each registered it, and when each last did so.
  • When the entry was first added and last modified.
  • Paths to files that no longer exist, including on USB drives and network shares, since entries are not removed when the target disappears.

It does not prove:

  • That the file content was viewed in full, or that the user (rather than a process in their session) triggered the action.
  • Anything for applications that do not use GtkRecentManager or KRecentDocument (terminal tools, many Electron and Java apps).
  • Continued existence of the file, or its content at the time.

Key fields

Element / attributeMeaning
bookmark@hrefURI of the item, percent-encoded (file:///home/ana/Documents/Q3%20plan.odt)
bookmark@addedWhen the item was first added to the list
bookmark@modifiedWhen the entry was last changed (touched on each registration)
bookmark@visitedLast visit time as recorded by the writer
bookmark:privateItem should be shown only to the registering applications
mime:mime-type@typeMIME type at registration
bookmark:groups/bookmark:groupOptional groups set by the application
bookmark:application@nameApplication name (for example the program's display name)
bookmark:application@execShell-quoted command line with %u, identifying the executable
bookmark:application@modifiedLast time this application registered the item
bookmark:application@countNumber of registrations by this application
bookmark:application@timestampDeprecated form of modified, Unix epoch seconds, in older files

The metadata block sits in <info><metadata owner="http://freedesktop.org">, with namespaces http://www.freedesktop.org/standards/desktop-bookmarks and http://www.freedesktop.org/standards/shared-mime-info.

Timestamps

GLib writes UTC ISO 8601 strings with a Z suffix, adding microseconds when they are non-zero, for example 2026-09-28T07:41:09.512034Z. Older files may carry the legacy timestamp attribute in Unix seconds:

date -u -d @1727512345 +%FT%TZ

Retention

  • GTK prunes entries whose modified date is older than the gtk-recent-files-max-age setting (GTK default 30 days) and caps the list at 1000 items each time it saves the file. A value of 0 empties the list; -1 keeps everything.
  • On GNOME, gnome-settings-daemon maps org.gnome.desktop.privacy recent-files-max-age (default -1, keep indefinitely) and remember-recent-files (default true) onto the GTK settings. Check the user's dconf database (~/.config/dconf/user) for changed values.
  • The "Clear History" action in GNOME Settings (file history privacy page) and deleting the file remove everything; individual entries can also be removed from the Files "Recent" view.

Collection

UAC collects the file through files/system/linux_mru.yaml (included in ir_triage via files/system/*), searching each user home, and files/applications/kde_mru.yaml collects KDE's RecentDocuments.

sudo ./uac -p ir_triage /mnt/usb/case42
find /mnt/evidence/home /mnt/evidence/root -name 'recently-used.xbel*' -print0 | tar --null -T - -czf xbel.tgz

Collect ~/.config/dconf/user alongside it to document the retention settings in force.

Parsing

No dedicated parser is required; any XML tool works.

xmllint --xpath '//*[local-name()="bookmark"]/@href' recently-used.xbel
import xml.etree.ElementTree as ET, urllib.parse
NS = {"bookmark": "http://www.freedesktop.org/standards/desktop-bookmarks",
      "mime": "http://www.freedesktop.org/standards/shared-mime-info"}
for b in ET.parse("recently-used.xbel").getroot().iter("bookmark"):
    apps = [(a.get("name"), a.get("count"), a.get("modified") or a.get("timestamp"))
            for a in b.iterfind(".//bookmark:application", NS)]
    mime = b.find(".//mime:mime-type", NS)
    print(b.get("added"), b.get("modified"), b.get("visited"),
          urllib.parse.unquote(b.get("href")), mime.get("type") if mime is not None else "", apps)

Investigator tips

  • Entries with paths under /media/<user>/ or /run/media/<user>/ show files opened from removable media; correlate the time with USB device logs.
  • sftp://, smb:// and davs:// URIs reveal remote shares browsed through GVfs, often with host and user name in the URI.
  • The file is rewritten as a whole, so its inode mtime is the last registration by any app, and missing older entries may simply have been pruned by age or the 1000-item cap.
  • A tiny or missing file on an active desktop account can mean a history clear or remember-recent-files disabled: check dconf before calling it anti-forensics.
  • Combine with Tracker/LocalSearch for file metadata and with browser downloads to follow a file from download to opening.
  • For terminal editors, the equivalent evidence is in viminfo and lesshst.

See also