Tails 7.9 issues

Hello everyone,

After upgrading to Tails 7.9 and also testing a fresh installation on a new USB drive, I noticed a couple of things that seem unusual and I’m wondering whether they are expected changes or possible bugs.

1. Unexpected “~” directory in /home/amnesia

I found a directory named ~ inside /home/amnesia.

The directory contains:

~/.local/share/applications

However, the applications directory itself is empty.

I do not remember seeing this directory in previous Tails releases. Has anyone else noticed it on Tails 7.9? Is this expected behavior or a known issue?

2. Monero GUI now reports updates available

Another thing I noticed is that Monero GUI is now informing me that an update is available. I don’t remember seeing update notifications before upgrading to Tails 7.9.

Historically, Tails has blocked non-Tor connections by default, which also prevented some applications’ automatic update checks. For example, older discussions in the Monero community suggested that Tails’ network restrictions would typically prevent the Monero GUI updater from reaching its servers.

This raises a few questions:

  • Has anything changed in Tails 7.9 regarding networking rules or application permissions?

  • Is Monero GUI now performing update checks over Tor?

  • Is the update notification expected behavior in Tails 7.9?

  • Could this indicate that some traffic is bypassing previous restrictions, or is there another explanation?

I’m not trying to jump to conclusions, but since both the unexpected ~ directory and the new update notification appeared after upgrading, I’d appreciate clarification from developers or users who have investigated these changes.

Has anyone else experienced the same behavior on Tails 7.9?

Thanks.

Issue 1: Literal “~” Directory in /home/amnesia

On Tails 7.9, a directory literally named ~ appears inside /home/amnesia/, containing the path ~/~/.local/share/applications/ (where the first ~ is the actual home directory and the second is a literal directory name). The applications subdirectory is empty. This did not exist in Tails 7.8.1.

Root Cause

This is caused by commit df27871, which introduced a new systemd user service to hide the GNOME Software launcher unless the Flatpak feature flag is enabled. The service file is tails-possibly-hide-gnome-software-launcher.service:

[Unit]
Description=Hide GNOME Software if Flatpak is not enabled
After=gnome-session.target

[Service]
Type=oneshot
ExecStart=mkdir -p ~/.local/share/applications/
ExecStart=/bin/sh -c "(cat /usr/share/applications/org.gnome.Software.desktop \
    && echo 'NoDisplay=true') >> ~/.local/share/applications/org.gnome.Software.desktop"

The problem is on the first ExecStart line. In systemd service files, the tilde (~) is not expanded to the user’s home directory. Systemd interprets it as a literal directory name. According to the systemd.exec documentation, only %h, $HOME, or wrapping the command in /bin/sh -c achieve home directory expansion.

The second ExecStart line works correctly because it wraps the command in /bin/sh -c, where the shell does expand ~ properly. But the first mkdir -p ~/.local/share/applications/ runs directly (no shell wrapper), so systemd creates a directory literally called ~ in the working directory, resulting in /home/amnesia/~/.local/share/applications/.

The fix would be to either use %h instead of ~, or wrap the mkdir command in /bin/sh -c like the second ExecStart line does.

Evidence

Item Detail
Commit df278711bc7238a8fac06d4b841b3124f711d019
Title Hide the GNOME Software .desktop entry unless the flatpak feature flag is enabled
File config/chroot_local-includes/usr/lib/systemd/user/tails-possibly-hide-gnome-software-launcher.service
Bug line ExecStart=mkdir -p ~/.local/share/applications/
Fix Use %h instead of ~, or wrap in /bin/sh -c
Link Hide the GNOME Software .desktop entry unless the flatpak feature flag is enabled (df278711) · Commits · tails / tails · GitLab

Issue 2: Monero GUI Now Reports Updates Available

On Tails 7.9, the Monero GUI shows an update notification that was not present on Tails 7.8.1. Historically, Tails’ firewall blocked non-Tor connections, which prevented applications like Monero GUI from reaching their update servers. Something changed in 7.9 that allows this to happen now.

What Changed in Tails 7.9: Reintroduction of Transparent Proxying

Tails 7.9 reintroduced transparent TCP proxying, a feature that was removed back in Tails 0.10 (circa 2012). All the relevant changes were shipped as part of Merge Request !3009 (“Support installing Flatpak apps”), merged into stable via commit 51bd13ff.

The key commit is 452facc8 (“Enable transparent proxying through tor for the live user”), which added the following rules to config/chroot_local-includes/etc/ferm/ferm.conf:

table nat chain OUTPUT {
    moduli state (ESTABLISHED RELATED) ACCEPT;
    proto tcp dport 9040 REDIRECT to-ports 9040;
}

table filter chain OUTPUT {
    moduli state (ESTABLISHED RELATED) ACCEPT;
    ... existing per-app ACCEPT rules ...
    policy DROP;
}

The nat OUTPUT chain now intercepts all outgoing TCP connections from the amnesia user and redirects them to Tor’s TransPort on port 9040 via iptables REDIRECT. Tor’s TransPort then reads the original destination using the SO_ORIGINAL_DST socket option and routes the connection through the Tor network. This means any application that makes a plain TCP connection (without configuring a SOCKS proxy) will now have that connection transparently routed through Tor, rather than being dropped by the firewall.

On Tails 7.8.1 and earlier (since 0.10), the filter OUTPUT chain had a policy DROP with specific ACCEPT rules only for applications configured to use Tor’s SOCKS port (9050). Any application that did not explicitly use SOCKS would have its connections silently dropped.

The test suite commit b6273970 confirms this is a deliberate “reintroduction” of transparent proxying. The commit message from 452facc8 explains the rationale:

“We have no other good way to torify all Flatpaks: torsocks doesn’t work; many applications do not respect the usual environment variables (HTTP_PROXY, ALL_PROXY, etc). When we originally stopped doing transparent proxying in Tails 0.10, the motivation was that we would provide high quality per-application configurations for the applications we shipped, but now that we encourage users to install arbitrary applications via Additional Software Packages and now also Flatpak, that is no longer realistic.”

How This Affects Monero GUI

The Monero GUI updater uses a two-phase update check. The first phase is a DNS TXT record query performed by the core Monero library inside Monero::WalletManager::checkUpdates(). If this DNS query succeeds, it returns a version hash, and the second phase calls Updater::fetchSignedHash() which makes HTTP requests to fetch hashes.txt and hashes.txt.sig from https://web.getmonero.org/.

The relevant code in src/libwalletqt/WalletManager.cpp shows:

void WalletManager::checkUpdatesAsync(
    const QString &software, const QString &subdir,
    const QString &buildTag, const QString &version) {
    m_scheduler.run([this, software, subdir, buildTag, version] {
        const auto updateInfo = Monero::WalletManager::checkUpdates(
            software.toStdString(), subdir.toStdString(),
            buildTag.toStdString().c_str(),
            version.toStdString().c_str());
        if (!std::get<0>(updateInfo)) {
            return;  // DNS check failed - silent abort
        }
        // Only reaches here if DNS succeeded
        const QString version = ...;
        const QByteArray hashFromDns = ...;
        Updater().fetchSignedHash(binaryFilename, hashFromDns, signers);
    });
}

The critical point: if the DNS check fails (lines 458-460), the function returns silently with no log output and no notification to the user. The fetchSignedHash() HTTP call is never reached. This means on 7.8.1, if the DNS check was failing, the update check would abort silently and the user would see nothing.

Now, inside fetchSignedHash() in src/qt/updater.cpp, the HTTP fetch uses a locally-created Network object with an empty proxy address:

QByteArray Updater::fetchSignedHash(
    const QString &binaryFilename,
    const QByteArray &hashFromDns,
    QPair<QString, QString> &signers) const {
    const Network network;  // local instance, empty proxy
    std::string hashesTxt = network.get("https://web.getmonero.org/downloads/hashes.txt");
    std::string hashesTxtSig = network.get("https://web.getmonero.org/downloads/hashes.txt.sig");
    ... verify and return ...
}

In src/qt/network.cpp, Network::get() calls set_proxy(m_proxyAddress.toStdString()). Since m_proxyAddress is empty, this sets an empty proxy string on the underlying HTTP client, meaning no explicit SOCKS or HTTP proxy is configured for these requests.

On Tails 7.8.1, these unproxied HTTP connections would be dropped by the firewall’s policy DROP in the filter OUTPUT chain. On Tails 7.9, the nat OUTPUT REDIRECT rule intercepts these connections before they reach the filter chain and redirects them to Tor’s TransPort (9040). Tor then routes them through the Tor network. The connections succeed, the update check completes, and the user sees the notification.

Why the Update Notification Appeared Now

The most likely explanation is that on Tails 7.8.1, the GUI updater’s DNS check was failing silently (aborting at the early return), so the HTTP fetch in fetchSignedHash() was never reached. On Tails 7.9, either the DNS check succeeds (possibly due to changes in how Tor resolves DNS, or it may have been a transient issue on 7.8.1), or some other factor changed. If the DNS check does succeed on 7.9, the HTTP fetch then executes and now succeeds because transparent proxying routes it through Tor, whereas on 7.8.1 it would have been dropped.

Clarification on the DNS TXT warning in logs: The warning “WARNING: no two valid DNS TXT records were received” that appears in Monero GUI logs comes from monerod (the daemon) and relates to the MoneroPulse DNS checkpoint system. This is a completely separate subsystem from the GUI updater’s version check DNS query. The GUI updater’s DNS query goes through Monero::WalletManager::checkUpdates() in the core library and its failure (or success) is not logged at the debug level the user sees. I do not have direct evidence of whether the GUI updater’s DNS query was succeeding or failing on 7.8.1.



Summary

Issue 1 (the ~ directory) is a straightforward bug in a systemd service file where the tilde character is not expanded because systemd does not perform shell expansion on ExecStart commands that are not wrapped in /bin/sh -c. The fix is to replace ~ with %h on the affected line. This is fully source-verified.

Issue 2 (Monero GUI update notifications) is a consequence of Tails 7.9’s reintroduction of transparent TCP proxying via iptables REDIRECT to Tor’s TransPort (port 9040). The Monero GUI updater creates a local Network object with an empty proxy string, meaning its HTTP requests are not explicitly configured to use Tor’s SOCKS port. On 7.8.1, these unproxied connections were silently dropped by the firewall. On 7.9, the REDIRECT rule in the nat OUTPUT chain intercepts them and routes them through Tor via TransPort, causing the update check to succeed.

A nuance I want to be upfront about: I cannot confirm from source code alone whether the GUI updater’s DNS TXT query (phase 1) was succeeding or failing on 7.8.1. The function fails silently with no logging. If it was failing, the HTTP fetch was never reached on either version. If it was succeeding on 7.9 but failing on 7.8.1, the transparent proxying change would explain why the HTTP fetch now succeeds when it reaches that stage. Runtime testing with packet capture tools would be needed to confirm the exact behavior.

If anyone has done live testing with Wireshark, sockstrace, or similar tools on Tails 7.9 to verify the actual network behavior, I would be very interested in seeing those results.