One-command SSH into Caltech HPC with the Duo step handled silently. Built on a generalist headless-Duo-Mobile CLI for Linux, with desktop notifications and verified-push (number challenge) support.
$ calsh
$ # ...you are now on login.hpc.caltech.edu. No phone tap. No prompt.
If you SSH into a Duo-protected host a dozen times a day, calsh is what you want.
A small toolkit that turns your Linux desktop into a registered Duo Mobile device. Once enrolled (5 minutes, see docs/ENROLLMENT.md), Duo pushes for your account can be approved by your laptop instead of your phone:
calsh— Caltech HPC SSH wrapper. The headline command.duo— generalist Linux desktop wrapper. Subcommands:listen(interactive notifier),approve,deny,passcode(HOTP code generator for "type the code" prompts),status,enroll.duo-approve— bounded single-shot auto-approver, the building block undercalshand similar SSH wrappers.duo-headless— the Python CLI that speaks Duo's v2 push protocol.
It is not a daemon. It is bounded and on-demand. See why.
Caltech HPC, one command:
$ calsh 'squeue -u $USER'
JOBID PARTITION NAME ST TIME NODES NODELIST(REASON)
...
Generalist desktop UX, any Duo deployment:
$ duo listen
watching api-XXXXXXXX.duosecurity.com for 300s (poll every 2.0s, mode=interactive)
…then a system notification pops up showing the requesting service, host, and (for verified push) the 3-digit code, with Approve / Deny buttons. Click Approve → login goes through.
Verified push — both flavors:
Duo has two verified-push variants. duo listen handles both:
-
Code shown on the device (number-match) — the push includes a 3-digit code; the notification body renders it boldly so you can confirm it matches the originating site before clicking Approve:
Code: 47 app: Caltech HPC host: login.hpc.caltech.edu
-
Code typed into the device — the originating site shows the code, you have to enter it. On Approve, a
kdialog/zenityinput box pops up:Enter the code shown by Caltech HPC on the originating site: [ ] [Cancel] [OK]
You read the code off the browser, type it in, hit OK.
duo listenPOSTsapprovewith the code under the right Duo parameter names. Cancel → deny.
You can also bypass the prompt with duo approve --code 47 if you already know it.
HOTP passcode (for "type a code" prompts):
Some Duo prompts ask you to type a 6-digit code instead of (or in addition to) approving a push — older SSH banners, the "Passcode" option on the Universal Prompt, web logins where push is unavailable, etc. The activation response already gives us the HMAC secret for HOTP; duo passcode exposes it:
$ duo passcode
447293
counter=12; Duo accepts a window of ~10 codes ahead, so a missed code auto-resyncs the next time you authenticate
Counter is persisted in ~/.duo-headless/hotp_counter. If it drifts ahead of the server (you generated codes without using them and then generated >10 more), duo passcode --resync N rewinds the local counter. --peek shows the next code without advancing.
git clone https://github.com/Yubo-Cao/calsh ~/projects/calsh
cd ~/projects/calsh
./install.sh
install.sh will:
- Symlink
bin/duo,bin/duo-approve,bin/duo-headlessinto~/.local/bin/. - Symlink
examples/caltech/calshinto~/.local/bin/(only if--with-caltechor you say yes when prompted). - Create
~/.duo-headless/.venvand installpycryptodome+requestsinto it.
Then enroll the device — see docs/ENROLLMENT.md.
- Linux desktop. This project is Linux-only by design — the UX leans on
notify-send(libnotify),paplay, and standard XDG dirs. The core protocol logic works anywhere Python does, but we don't support or test on macOS or Windows. - Python 3.11+, with
pipavailable. - OpenSSH client. (You probably already have this.)
zbarimgfor QR decoding during enrollment (pacman -S zbar/apt install zbar-tools).notify-sendfrom libnotify ≥ 0.7.9 if you want desktop notifications. Older versions lack the-Aaction-button support thatduo listenuses.- An existing Duo Mobile account that lets you self-enroll additional devices via a device-management portal.
Earlier iterations of this tool ran as a systemd user service polling Duo every 5 seconds, 24/7. The Duo account got flagged and disabled after roughly three weeks of that. Detail in examples/systemd/README.md.
The current architecture polls only when you've actually triggered an auth event — typing calsh, running duo listen before clicking a login button, etc. Total request volume against Duo's endpoint becomes proportional to your actual login activity, which is what a real Duo Mobile device generates. No flagging, no spam, no risk to the account.
Treat ~/.duo-headless/key.pem like an SSH private key. Anyone with it can answer Duo pushes as you. Detailed caveats in docs/PROTOCOL.md.
This tool weakens your 2FA posture compared to a phone with biometric prompts — a logged-in attacker on your laptop can now approve Duo pushes without touching your phone. That's the trade-off you're making for the convenience. Don't run this on a shared machine, and don't run it on a machine you wouldn't trust with your SSH keys.
If your employer or institution forbids unofficial Duo clients, respect that — this is for your own device on your own account, with whatever institutional policy applies on top.
calsh/
├── README.md — you are here
├── LICENSE — MIT
├── install.sh — one-shot installer
├── bin/ — generic, install-target
│ ├── duo — desktop wrapper (listen/approve/deny/status/enroll)
│ ├── duo-approve — single-shot bounded approver
│ └── duo-headless — Python CLI (v2 push protocol)
├── examples/
│ ├── caltech/ — Caltech HPC integration
│ │ ├── calsh — the ergonomic SSH wrapper
│ │ ├── ssh-alert.sh — heavier alt with desktop-alert fallback
│ │ ├── ssh_config.sample
│ │ └── README.md
│ └── systemd/ — "we deliberately don't ship a daemon"
│ └── README.md
└── docs/
├── ARCHITECTURE.md — the pieces and why they're shaped this way
├── ENROLLMENT.md — first-time setup walkthrough
└── PROTOCOL.md — Duo v2 push protocol notes + credits
Protocol implementation owes a lot to:
- falsidge/ruo (MIT) — original headless Duo Mobile reference.
- revalo/duo-bypass — protocol notes and attribute handling.
If you like this repo, star theirs too. They did the protocol archaeology.
Issues and PRs welcome, especially:
- Verified-push response handling for deployments where the current
answer=approveflow gets rejected because it expects the code in the response body. - Support for non-Caltech
examples/(other universities, employer deployments). Drop a parameterizedcalsh-equivalent inexamples/<your-org>/and document it. - Wayland-native notification quirks (some compositors don't propagate
-Aclicks back to stdout cleanly).
MIT. See LICENSE.