2026-08-02

Sharing an X Server Across Hosts with FamilyWild

Ever tried running an X11 application from inside a container, a chroot, or over ssh with a bind-mounted .Xauthority — only to be greeted by the ever-helpful Authorization required, but no authorization protocol specified?

The file is right there, mounted read-only where the client expects it, and yet X refuses the connection. The reason is subtle, and the fix is a single line of sed.


Why the cookie is rejected

An .Xauthority file is a list of cookies, and every entry is keyed by a family and a hostname. When a client connects, it doesn't just grab the first cookie it finds — it looks for the entry whose hostname matches the machine the client believes it's running on.

That's exactly what breaks the moment the client runs somewhere other than where the cookie was minted. Inside a container the hostname is different; over an un-forwarded socket the client resolves a different name entirely. The cookie is present and valid, but its hostname doesn't match, so the client never offers it and X falls back to "no authorization protocol."

You can see the family/hostname keying for yourself:

$ xauth list
myhost/unix:0  MIT-MAGIC-COOKIE-1  a1b2c3d4e5f6...

That leading myhost/unix:0 is the problem — it pins the cookie to myhost.


FamilyWild to the rescue

X has a wildcard family, FamilyWild, whose numeric value is 0xffff. A cookie in this family matches any hostname. So instead of trying to make the client's hostname match the cookie, we rewrite the cookie to match every host.

xauth nlist prints entries in a numeric format where the first field is the 4-hex-digit family. Overwrite it with ffff and merge the result into a fresh file:

: > /tmp/portable.Xauthority && xauth nlist :0 | \
    sed 's/^..../ffff/' | xauth -f /tmp/portable.Xauthority nmerge -
# Replace :0 with your $DISPLAY value

The change is a single field

It's worth seeing just how small the edit is. An .Xauthority entry is a packed binary record: a 2-byte family, then length-prefixed address, display number, auth name, and the cookie itself. Dump the original file and the family sits in the very first two bytes — 0100, i.e. FamilyLocal:

00000000: 0100 0006 6d79 686f 7374 0001 3000 124d  ....myhost..0..M
00000010: 4954 2d4d 4147 4943 2d43 4f4f 4b49 452d  IT-MAGIC-COOKIE-
00000020: 3100 10a1 b2c3 d4e5 f607 1829 3a4b 5c6d  1..........):K\m
00000030: 7e8f 90                                   ~..

Now dump the FamilyWild version we just built. Everything is byte-for-byte identical — same myhost address, same cookie — except those first two bytes, now flipped to ffff:

00000000: ffff 0006 6d79 686f 7374 0001 3000 124d  ....myhost..0..M
00000010: 4954 2d4d 4147 4943 2d43 4f4f 4b49 452d  IT-MAGIC-COOKIE-
00000020: 3100 10a1 b2c3 d4e5 f607 1829 3a4b 5c6d  1..........):K\m
00000030: 7e8f 90                                   ~..

That one field is the whole trick: the address bytes still spell myhost, but X no longer cares, because family 0xffff matches unconditionally.

Bind-mount (or scp) /tmp/portable.Xauthority wherever the client runs, point XAUTHORITY at it, and the connection is accepted regardless of the hostname mismatch.


What about xhost +?

You'll often see xhost + suggested as the "just make it work" answer, and it does — by turning host-based access control off entirely. Every client from every host can then connect to your display without any cookie at all.

On a single-user machine that sounds harmless, but X has no isolation between clients: anyone who can reach the server can read your keystrokes, grab the contents of any window, and inject synthetic input. xhost + hands that capability to every local user and, if your server listens on TCP, to the network. Even the narrower xhost +local: still trusts every UID on the box.

The FamilyWild cookie keeps the door locked — a client still has to present the secret — while removing only the hostname constraint that was getting in the way. Reach for it instead of xhost +, and keep the cookie file at 0600.


One caveat

A FamilyWild cookie is deliberately less specific than the one it replaces: anyone who can both reach your X socket and read this file can talk to your display. Hand it only to the environments that actually need it, and don't leave copies lying around on shared machines.

This trick is one small piece of a larger setup — I use it to forward X into unprivileged LXC containers, which I wrote up in detail in Enhancing x11 Application Security with LXC.