CLI tool
max-baz/yubikey-touch-detector avatar
max-baz/yubikey-touch-detector

yubikey-touch-detector: telling your status bar that the key is waiting

A tool to detect when your YubiKey is waiting for a touch (to send notification or display a visual indicator on the screen)

544 stars53 forksGoISC

At a glance

What is it?
yubikey-touch-detector is a Go program that watches for the moment a YubiKey is waiting for a physical touch, and publishes that state over a Unix socket or on D-Bus so that a status bar or a notification daemon can show it. It works on Linux and macOS, and it is designed to be embedded in other user interface components rather than used on its own.
Who is it for?
yubikey-touch-detector fits a desktop Linux or macOS user who has configured their YubiKey to require a physical touch and wants the waiting state visible, since it exposes the state through two interfaces that status bars and notification daemons already understand. It is the wrong tool for a headless server with no display, for a browser based or mobile workflow, and for anyone who has not enabled the touch requirement, because there is nothing to detect in that case.
Can I use it commercially?
Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 33 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A waiting state, not a key press

The problem this solves is narrow and real. When a YubiKey is configured to require a physical touch, an operation such as a signature, a decryption or a WebAuthn login stops and waits for you. If the request comes from a command line, you see the prompt. If it comes from a desktop application, or from an SSH session that forwarded an agent, the wait can be silent, and the symptom is a hung command with no visible explanation.

yubikey-touch-detector observes that wait and republishes it, so that another component can draw a visible indicator. The project describes the tool as designed to be integrated with other UI components to display a visible indicator, and the linked example is an integration with i3wm and py3status, where the state appears in the status bar.

The set of operations that can require a touch is listed in the documentation: a sudo request through pam-u2f, WebAuthn, gpg --sign, gpg --decrypt, an ssh connection to a remote host and related operations such as scp and rsync, an ssh on a remote host to a different remote host through a forwarded ssh-agent, and HMAC operations. The app is a single Go program, released as 1.15.0 on 2026-09-20 with 1.14.0 published the same day, after 1.13.0 in June 2025. The licence is ISC, and the repository is not archived, with the last push on 2026-09-20.

Four ways to install it, and two ways to start it

The tool works on Linux and macOS, and the installation paths are laid out so that each platform has an obvious route.

On Arch Linux it is a package, installed with the pacman command for the yubikey-touch-detector package. That package also installs a systemd service and a socket unit. If you want the app to start at boot, you enable the service:

code
$ systemctl --user daemon-reload
$ systemctl --user enable --now yubikey-touch-detector.service

If you would rather the service only starts when something is listening on the Unix socket, you enable the socket unit instead, with the same daemon-reload and enable sequence and the .socket suffix. That distinction matters for a laptop, because a socket-activated service does not hold a process open when nothing is asking about the key.

The second route is the GitHub releases page, where all releases are signed with the author's PGP key. The third is a source build, which needs Go 1.26 or later, and on Linux the gpgme development package:

code
sudo apt install libgpgme-dev
code
go install github.com/maximbaz/yubikey-touch-detector@latest

The go install form puts the binary in your $GOBIN directory, which defaults to $GOPATH/bin. The fourth option is to run it from a checkout, which the repository's justfile supports with a run target that calls go run main.go.

Two notifier interfaces: a socket and a D-Bus service

The integration surface is the notifier subpackage, and it offers two mechanisms.

The unix_socket notifier lets anything connect to $XDG_RUNTIME_DIR/yubikey-touch-detector.socket on Linux or $TMPDIR/yubikey-touch-detector.socket on macOS and receive events. The events are listed as a table: GPG_1 and GPG_0 for a gpg operation starting and stopping its wait for a touch, U2F_1 and U2F_0 for a u2f operation, and MAC_1 and MAC_0 for an hmac operation. Every message has a fixed length of 5 bytes, which the documentation gives as a way to simplify the code on the receiving side. A consumer reads 5 bytes and maps the first character to an operation type and the second to the state, and the fixed length means it never has to guess where a message ends.

The dbus notifier registers a D-Bus server at the interface name com.github.maximbaz.YubikeyTouchDetector and the path /com/github/maximbaz/YubikeyTouchDetector. The properties on that interface are discoverable through introspection, they emit PropertiesChanged signals to indicate updates, and they support gobject binding, which is what makes the interface usable from a desktop toolkit without writing a client by hand.

Both interfaces are push based, and that is the right shape for this problem, because the interesting moment is a transition rather than a level. A status bar component subscribes and redraws when a property changes.

The application can also notify directly, using the freedesktop notification service on Linux or Notification Center on macOS. The title and body are Go templates, and {{.Reasons}} expands to a sorted, comma-separated list of the active event types, which are gpg, hmac and u2f. The default body is "Touch your YubiKey to continue ({{.Reasons}}).", and the documentation notes that having the event type in the body lets notification daemons such as dunst match on it.

How the detection actually works on each platform

The mechanisms differ by operating system, and the documentation is specific about both.

On Linux the detector uses hidraw, inotify and GPG agent checks. For U2F and FIDO2 operations it listens on the appropriate /dev/hidraw* device for the messages described by the FIDO specification, and the implementation details are in detector/u2f.go, which the documentation points to and says contains links to the specification. The inotify part is what covers device arrival and removal, so that the program can follow a key that is plugged in and out.

On macOS it watches the unified log for FIDO queue events and for CryptoTokenKit time-extension events, and it verifies FIDO devices against Yubico's USB vendor ID through the I/O Registry. The macOS route is therefore log based rather than device based, which is a different kind of observation with different failure modes.

The GPG detection is the most interesting part, and the documentation describes it as a transparent proxy on the normal gpg-agent socket that observes Assuan operations using private keys stored on a smartcard. It does not retain or log operation payloads, which is the property that makes it acceptable to leave running. Only the oldest active smartcard operation is checked, and operations queued behind it begin their check only after reaching the front of the queue, so a burst of cached signing operations is not mistaken for several touch requests. Time spent in a Pinentry prompt is excluded when the agent reports the Pinentry process.

And the documentation is honest about the limit: this is still a heuristic, because gpg-agent does not expose the card's actual touch state. A notification is shown when the front operation remains pending after that filtering, so the tool is inferring the wait rather than reading it.

Configuration through environment variables or flags

Every behaviour is switchable, and each switch has both an environment variable and a command line argument, with the command line taking precedence. The pairing covers verbosity, notifications, the notification title and body, standard output output, socket use and D-Bus: YUBIKEY_TOUCH_DETECTOR_VERBOSE with -v, YUBIKEY_TOUCH_DETECTOR_NOTIFY with --notify, YUBIKEY_TOUCH_DETECTOR_NOTIFY_TITLE with --notify-title, YUBIKEY_TOUCH_DETECTOR_NOTIFY_BODY with --notify-body, YUBIKEY_TOUCH_DETECTOR_STDOUT with --stdout, YUBIKEY_TOUCH_DETECTOR_NOSOCKET with --no-socket, and YUBIKEY_TOUCH_DETECTOR_DBUS with --dbus.

The verbose mode is the diagnostic entry point, because the documentation suggests running the tool in verbose mode to print every event on standard error and then trying the various commands that require a physical touch to see whether the events are detected. That is the right way to establish whether the tool works on your machine before you point a status bar at it.

For the systemd service, the same environment variables can be defined in $XDG_CONFIG_HOME/yubikey-touch-detector/service.conf, and the repository ships service.conf.example as a template. The flags exist for the manual case and the variables exist for the service case, and the environment file is the piece that makes a per-user service configurable without a unit file override.

The desktop notification itself is invoked with --notify, and the notification can be fully overridden:

code
$ yubikey-touch-detector --notify --notify-title "Security key required: {{.Reasons}}" --notify-body "Touch your YubiKey to continue."

The title and body are independent, so a component that wants the reason in the title and a fixed instruction in the body can have that.

Go modules, a Nix flake, a justfile and a signed release archive

The repository is a conventional Go project with a few extras. The module is github.com/maximbaz/yubikey-touch-detector and the go directive is 1.26.0. The runtime dependencies are few and each one is a specific need: go-systemd for the systemd integration, esiqveland/notify for the desktop notifications, godbus for the D-Bus server, proglottis/gpgme for talking to gpg-agent through gpgme, rjeczalik/notify for filesystem watching, sirupsen/logrus for logging, and pkg/errors for error wrapping.

Platform specific code is split into main_linux.go and main_darwin.go, with the detector/ directory holding the platform specific detection logic and notifier/ holding the two notifier implementations. There is a man page written in scdoc format, yubikey-touch-detector.1.scd, and the justfile builds it with scdoc into yubikey-touch-detector.1.

The release recipe is the interesting part. The justfile has a release target that cleans and vendors, then uses git archive twice: once for a normal tarball, and once for a vendored tarball that carries the vendor directory. Both are then signed with gpg --detach-sign --armor, and the unsigned tarball is removed. The result is a signed release that either fetches its dependencies at build time or carries them inside. The lint target checks that gofmt reports nothing on the tracked Go files and then runs go vet, and the test target is a plain go test.

There is also a flake.nix and a flake.lock for Nix, a .envrc for direnv, and the yubikey-touch-detector.service and yubikey-touch-detector.socket unit files at the top level, which is what the packaging installs.

Where this is the wrong tool, and what the wiki settles

The limits are clear from the design. This is a desktop tool: it draws indicators and notifications on a machine with a display, and it supports Linux and macOS only. On a headless server it has nothing to notify, and if you have not configured your YubiKey to require a physical touch there is no wait to detect, so the program is correct and silent.

There is also a hardware access question. The Linux U2F detection reads the hidraw device, and the practical detail is whether your user session can read /dev/hidraw* for your key, which depends on the udev rules on the machine. The macOS route avoids that by watching the unified log instead, and the project keeps a FAQ entry about configuring a YubiKey to require a physical touch, which is a prerequisite rather than a detail.

The GPG detection remains a heuristic by the project's own account, and a status bar that shows a false waiting state is worse than one that shows nothing, so the filtering rules matter to whoever integrates it. The queue behaviour and the Pinentry exclusion are there to reduce exactly that.

Finally, the project is explicit that it is a building block. The wiki lists the UI components that are already integrated with the app, and the documentation for integration points at the notifier subpackage rather than at the command line tool. That is the right way to use it: pick an existing integration if one fits, otherwise write a small client for the Unix socket or bind to the D-Bus properties, and keep the heavy lifting in this program rather than in your status bar.

Editorial conclusion

yubikey-touch-detector fits a desktop Linux or macOS user who has configured their YubiKey to require a physical touch and wants the waiting state visible, since it exposes the state through two interfaces that status bars and notification daemons already understand. It is the wrong tool for a headless server with no display, for a browser based or mobile workflow, and for anyone who has not enabled the touch requirement, because there is nothing to detect in that case. Before you rely on it, verify that your key is set to require a touch, read the gpg detection section in full, because that part is a documented heuristic rather than a direct read of the card state, and check that the udev access to /dev/hidraw works in your environment before you point your status bar at the socket.

Frequently asked questions

What does yubikey-touch-detector do and where does it run?

It is a Go program that detects when a YubiKey is waiting for a physical touch, so that other user interface components can show a visible indicator. It works on Linux and macOS, is released under the ISC licence, and is designed to be integrated with components such as a status bar rather than used on its own.

How do I install and start yubikey-touch-detector on Linux?

On Arch Linux, install it with pacman -S yubikey-touch-detector, which also installs a systemd service and a socket. Enable the service with systemctl --user enable --now yubikey-touch-detector.service, or the socket unit if the service should only start when a client connects, and set the environment variables in $XDG_CONFIG_HOME/yubikey-touch-detector/service.conf for per-user configuration.

How does yubikey-touch-detector detect a waiting GPG operation?

It runs as a transparent proxy on the normal gpg-agent socket and observes Assuan operations using private keys on a smartcard, without retaining or logging operation payloads. It only checks the oldest active smartcard operation and excludes time spent in a Pinentry prompt, and the documentation notes that this remains a heuristic because gpg-agent does not expose the card's actual touch state.

What events does the yubikey-touch-detector Unix socket deliver?

Six events, in pairs for the start and the end of a wait: GPG_1 and GPG_0, U2F_1 and U2F_0, and MAC_1 and MAC_0. The socket is at $XDG_RUNTIME_DIR/yubikey-touch-detector.socket on Linux and $TMPDIR/yubikey-touch-detector.socket on macOS, and every message has a fixed length of 5 bytes.

Official sources

  1. Issues
  2. License: ISC
  3. max-baz/yubikey-touch-detector on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/max-baz-yubikey-touch-detector.svg)](https://hysenlabs.com/projects/max-baz-yubikey-touch-detector)

Was this useful?