5 min read

Handy: Free Offline Dictation for Any App (GitHub, Scanned)

Hold a shortcut, speak, and your words are typed into any app, transcribed offline on your machine.

Handy logo
✅
Scan: safe. Nothing to warn about. Handy contacts the network only to download models, check for signed updates, and, if you turn it on, send transcripts to an LLM for clean-up. Scanned Oct 3, 2026; the full report is below.

Handy is push-to-talk dictation for the whole desktop. Hold a keyboard shortcut (or tap it to toggle), speak, release, and the transcription is pasted into whatever text field has focus: an email, a chat box, a code editor. Silero voice activity detection trims the silence, and transcription runs on your own computer with your choice of OpenAI's Whisper models, from Small to Large and GPU-accelerated where available, or NVIDIA's Parakeet V3, which is tuned for CPUs and detects the language automatically. No audio is sent anywhere.

It is the open-source pick in most lists of alternatives to paid dictation apps such as Wispr Flow, and it deliberately does one job, with the stated aim of being a simple base that others extend. Around that job sit a transcript history, a custom dictionary, command-line flags for controlling a running instance (which is how Wayland users bind it to a desktop shortcut) and a community Raycast extension.

Handy is written by CJ Pais in Rust with Tauri, MIT-licensed, at about 32,700 stars and version 0.9.8, with signed builds for Windows, macOS on Apple Silicon and Intel, and Linux.

  • Repository: github.com/cjpais/Handy
  • Licence: MIT (MIT License)
  • Language: Rust. Stars: 32.7K. Forks: 3,027. Last push: Oct 3, 2026.
  • Scan: safe, Oct 3, 2026, commit 8605699

Who it is for

Anyone who would rather talk than type, people with RSI or other accessibility needs, and privacy-minded users who want dictation that works on a plane and never uploads a recording.

Getting started

1. macOS, with Homebrew (the cask is community-maintained)

brew install --cask handy

2. Windows, with winget (also community-maintained; installers are on the releases page)

winget install cjpais.Handy

3. Debian or Ubuntu: download the .deb from the releases page and install it with APT

sudo apt install ./Handy_*.deb

On first launch Handy asks for microphone and accessibility permissions, the second so it can paste into other apps, and you set your shortcut in Settings. On Linux it needs xdotool (X11) or wtype or dotool (Wayland) to type text, and on Wayland the global shortcut is set in your desktop environment to run handy --toggle-transcription.

Safety scan

We cloned cjpais/Handy at commit 8605699 on Oct 3, 2026 and ran the checks described on the GitHub Tools page: credential patterns, decode-and-execute code, install-time scripts, committed binaries, risky CI workflows, every host the code talks to, known vulnerabilities in pinned dependencies, and project hygiene. A person read every hit. This is what we found.

  • No secrets and no bare-IP URLs across 387 files and about 72,500 lines of Rust and TypeScript. The two pattern hits are in src-tauri/src/autostart.rs: a comment and a test path for the macOS LaunchAgent that the Launch at login setting writes. That setting is off by default.
  • The npm postinstall hook runs scripts/check-nix-deps.ts, a developer check that the Nix build's dependency hashes are current; the one Cargo build script is Tauri's standard one. Neither runs when you install the app.
  • We read the network code. Models download from blob.handy.computer, update checks are on by default and updates are verified against the Tauri signing key in tauri.conf.json, and the only other hosts are the LLM providers (OpenAI, OpenRouter, Anthropic, Groq, DeepSeek, Z.AI and others) for the optional post-processing step, which is off by default and sends transcript text, not audio, to the provider you choose. No telemetry.
  • 38 known advisories (8 high, none critical) in src-tauri/Cargo.lock's 852 crates, in openssl, quick-xml and rustls-webpki, which arrive through Tauri and its HTTP stack. The traffic they could matter for is model downloads and update checks.
  • Nine workflows, none using pull_request_target; 1 of 12 third-party actions is pinned to a commit. Licence and contributing guide present; no security policy, Dependabot or CodeQL.

What the scanner counted

CheckResult
SecretsNone found.
Suspicious code2 pattern hits found and read; every one is listed under the raw findings.
Install-time code1 npm lifecycle script. 1 Cargo build script
Committed binariesNone.
CI workflows9 workflows. None use pull_request_target. 11 of 12 third-party actions pinned to a tag rather than a commit.
Network hosts14 distinct hosts referenced from source; most often blob.handy.computer, github.com, openrouter.ai, api.deepseek.com. No URLs to bare IP addresses.
Known vulnerabilities38 advisories across 852 pinned packages: 0 critical, 8 high, 9 moderate, 5 low, 16 unrated. src-tauri/Cargo.lock: 852 packages, 38 advisories.
Project hygieneHas licence file, contributing guide. Missing security policy, automated dependency updates, CodeQL.
OpenSSF ScorecardNot scored: the project is not in Scorecard's weekly index.

The raw findings

Every hit the scanner wrote out, with a link to the exact line at the scanned commit. Secrets candidates are redacted.

Pattern hits (2)
WhereRuleMatch
src-tauri/src/autostart.rs:102persistence/// `~/Library/LaunchAgents/{app name}.plist`.
src-tauri/src/autostart.rs:135persistencePath::new("/Users/someone/Library/LaunchAgents/Handy.plist")
npm lifecycle scripts (1)
  • package.json postinstall: bun scripts/check-nix-deps.ts
Worst known vulnerabilities (24 of 38)
AdvisorySeverityPackageSummary
GHSA-8c75-8mhr-p7r9highopenssl@0.10.75rust-openssl has incorrect bounds assertion in aes key wrap
GHSA-ghm9-cr32-g9qjhighopenssl@0.10.75rust-openssl: rustMdCtxRef::digest_final() writes past caller buffer with no length check
GHSA-hppc-g8h3-xhp3highopenssl@0.10.75rust-openssl: Unchecked callback length in PSK/cookie trampolines leaks adjacent memory to peer
GHSA-pqf5-4pqq-29f5highopenssl@0.10.75rust-openssl: Deriver::derive and PkeyCtxRef::derive can overflow short buffers on OpenSSL 1.1.1
GHSA-xp3w-r5p5-63rrhighopenssl@0.10.75rust-openssl has undefined behavior in X509Ref::ocsp_responders for certificates with non-UTF-8 OCSP URLs
RUSTSEC-2026-0194highquick-xml@0.38.4Quadratic run time when checking a start tag for duplicate attribute names
RUSTSEC-2026-0195highquick-xml@0.38.4Unbounded namespace-declaration allocation in `NsReader` enables memory-exhaustion denial of service
GHSA-82j2-j2ch-gfr8highrustls-webpki@0.103.9rustls-webpki: Denial of service via panic on malformed CRL BIT STRING
GHSA-wrw7-89jp-8q8gmoderateglib@0.18.5Unsoundness in `Iterator` and `DoubleEndedIterator` impls for `glib::VariantStrIter`
GHSA-phqj-4mhp-q6mqmoderateopenssl@0.10.75rust-openssl: Potential out-of-bounds write in `CipherCtxRef::cipher_update_inplace` for AES-KW-PAD ciphers
GHSA-xv59-967r-8726moderateopenssl@0.10.75rust-openssl vulnerable to heap buffer overflow when encrypting with AES key-wrap-with-padding
GHSA-2mjx-qc3c-rqvcmoderaterustls@0.23.36TLS 1.3 handshake messages incorrectly accepted across encryption level boundaries
GHSA-pwjx-qhcg-rvj4moderaterustls-webpki@0.103.9webpki: CRLs not considered authoritative by Distribution Point due to faulty matching logic
GHSA-7gcf-g7xr-8hxjmoderateserde_with@3.16.1serde_with: KeyValueMap serialization panics on empty sequence or map entries
GHSA-3pv8-6f4r-ffg2moderatetar@0.4.44tar has a PAX header desynchronization issue
GHSA-gchp-q4r4-x4ffmoderatetar@0.4.44tar-rs incorrectly ignores PAX size headers if header size is nonzero
GHSA-j4xf-2g29-59phmoderatetar@0.4.44tar-rs `unpack_in` can chmod arbitrary directories by following symlinks
GHSA-xmgf-hq76-4vx2lowopenssl@0.10.75rust-opennssl has an Out-of-bounds read in PEM password callback when returning an oversized length
GHSA-cq8v-f236-94qclowrand@0.7.3Rand is unsound with a custom logger using rand::rng()
GHSA-cq8v-f236-94qclowrand@0.8.5Rand is unsound with a custom logger using rand::rng()
GHSA-965h-392x-2mh5lowrustls-webpki@0.103.9webpki: Name constraints for URI names were incorrectly accepted
GHSA-xgp8-3hg3-c2mhlowrustls-webpki@0.103.9webpki: Name constraints were accepted for certificates asserting a wildcard name
RUSTSEC-2026-0190unknownanyhow@1.0.102Unsoundness in `Error::downcast_mut()`
RUSTSEC-2025-0141unknownbincode@2.0.1Bincode is unmaintained

By the numbers

Stars32.7K
Forks3,027
Contributors173
Commits872
Open issues87
Open pull requests98
Releases66
Latest releasev0.9.8
LicenceMIT
Main languageRust
Project age1 year
Last pushOct 3, 2026
Tracked files387
Lines of code72.5K
Checkout size8 MB

Lines by language: Rust 29.9K, JSON 21.4K, TypeScript 16.2K, Markdown 1,739, YAML 1,452, CSS 658.

Questions

Is Handy free?

Yes. Handy is MIT-licensed and free on Windows, macOS and Linux, with no account, subscription or usage limit. The project is supported by sponsors rather than a paid tier.

Does Handy work offline?

Yes. Once a model is downloaded, recording, voice detection and transcription all run on your computer and no audio goes to the cloud. A connection is only needed for models and update checks, and for the optional post-processing step, off by default, which sends transcript text to an LLM provider you choose.

Which model should I pick in Handy?

Parakeet V3 is the README's choice for machines without a strong GPU: it is optimized for CPUs and detects the language automatically. Whisper models (Small, Medium, Turbo and Large) use the GPU when one is available; the larger ones are more accurate but slower and need more memory.


This post is part of GitHub Tools, where every repository is cloned and scanned before it is written up. The scan is a snapshot of one commit on one day; the repository has moved on since, so check it before you install.