Tenebra is an open-source VPN client for Windows designed to provide secure and flexible network connections using the sing-box core. It offers a desktop interface optimized for managing multiple protocols, including VLESS/REALITY, Hysteria2, AmneziaWG, Shadowsocks, Trojan, and VMess.
Key Features:
Multi-protocol support with a unified configuration model.
Smart routing that keeps Russian traffic direct while tunneling the rest.
Protocol fallback for seamless switching between blocked or throttled connections.
Per-app split tunneling to exclude or include specific applications.
Honest leak detection with no false positives, ensuring accurate security insights.
Desktop niceties like system tray integration, notifications, and a clean UI.
Audience & Benefit:
Ideal for privacy-conscious users who need flexibility in managing VPN connections. Tenebra is particularly beneficial for those seeking to bypass regional restrictions while maintaining low latency for local traffic.
Tenebra can be installed on Windows via winget and includes an updater for seamless updates.
README
A cross-platform VPN client built on sing-box.
Desktop first — Windows is user-ready; macOS and Linux ship but are for advanced users (see below). A shared Go core is meant to extend to Android and iOS.
> Project status — early development. The desktop client is the current
> focus. The core, the control protocol and the UI are in good shape and well
> tested, but the real tunnel path (wintun + sing-box, which needs an elevated
> live run) is still being validated end to end. Treat this as pre-release:
> not yet "production-ready", and expect things to move around. See
> Project status for the honest breakdown.
Why another client
Most clients either lock you into a single protocol or are vague about what they
do with your traffic. Tenebra:
routes Russian destinations directly and sends everything else through the
tunnel, so latency-sensitive local traffic stays local;
falls back between protocols when one gets throttled or blocked, and remembers
what worked;
ships no telemetry, no accounts and no bundled servers — you import your own
subscription.
What it does
Everything below is implemented in this repo today (the UI features are desktop):
Many protocols, one model. Import VLESS (incl. REALITY), Hysteria2,
AmneziaWG, Shadowsocks, Trojan and VMess. A single normalized node model feeds
a from-scratch sing-box config generator. (AmneziaWG links import and connect,
but the bundled stock sing-box applies none of the AWG obfuscation parameters —
the tunnel runs as plain WireGuard; full AmneziaWG obfuscation is on the
.)
Import the way you have it. Subscription URL, a raw share link, a .txt
file of links, clipboard paste, or a QR code (image file or pasted image).
Subscription bodies handle a Clash/Mihomo YAML config, base64, or plaintext
link lists and read the Subscription-Userinfo header for traffic used / total
and expiry.
Smart RU routing.Smart keeps Russian domains and IPs (and your LAN)
direct and tunnels the rest; Global tunnels everything; Direct is the
proxy off. Geodata is pulled from the official public sing-geoip / sing-geosite
rule-sets at runtime — the client ships none of its own.
Protocol fallback. A pure state machine walks the last known-good node
first, then by protocol preference (REALITY → Hysteria2 → AmneziaWG), so a
blocked or throttled protocol is retried as another. The last good node leads
the next launch.
Per-app split tunnelling.Exclude sends chosen apps around the tunnel;
Include sends only chosen apps through it. Matched by executable name and
persisted across restarts.
Honest leak check. Observes the machine's public IP from redundant echo
services and runs a best-effort DNS probe, then reports a verdict that never
fakes a pass — it tells you what it could not measure rather than claiming
"safe". See docs/control-protocol.md.
Desktop niceties. System tray that reflects the connection state (with quick
connect/disconnect), desktop notifications on state changes, tenebra:// deep
links (import a subscription or connect a profile), launch at login (optionally
minimized to the tray), single-instance, live traffic graphs, light/dark themes,
and English / Russian UI.
The kill-switch (drop proxied traffic instead of leaking when the tunnel drops) is a
UI toggle — best-effort by design, with the exact guarantee described in the
changelog; LAN bypass is a core routing option.
or grab Tenebra_x.y.z_x64-setup.exe from the
latest release. Either
way the installer sets up the background service and the in-app updater keeps
everything current.
macOS — download the universal DMG from the
latest release, then
read the macOS note first —
the build currently needs a hand-installed root daemon.
Getting a server
Tenebra is a client — it ships no servers and hard-codes nothing. You bring
your own endpoint and import it as a subscription or a share link. Two ways to
get one:
Run your own. Any sing-box or Xray
server works; point Tenebra at its subscription URL.
Use a provider. Any service that hands you a subscription or a share link
will do. I run one at vpsxd.pro.
Project status
Area
State
Go core (parsing, profiles, routing, config gen, fallback, leak logic)
Implemented, unit-tested, no third-party deps
Control protocol (core ↔ UI)
Implemented; covered by Go tests and a real-binary e2e
Desktop UI (Tauri 2 + React)
Implemented: all screens, reactive tray, notifications, deep links, autostart, i18n, themes
Windows tunnel (wintun + sing-box)
Implemented — a background service runs the tunnel, so the app connects without an elevated GUI; installer sets it up, the in-app updater refreshes both app and service
macOS tunnel (utun + sing-box)
Builds and runs — universal .app/DMG — but see the macOS note below: it needs a hand-installed root daemon and is not yet a click-to-run product
Linux tunnel (/dev/net/tun + sing-box)
Builds and runs — a root systemd service owns the tunnel, installed by an Arch package or a sudo script; see the Linux note below. No live-tunnel sign-off yet
Android / iOS
Planned — the core is shared and platform-agnostic
Release pipeline
Tag-triggered release workflow builds the Windows and macOS bundles, minisign-signs the in-app updater artifacts, and publishes a GitHub release
Code-signing
Not set up — the Windows installer is Authenticode-unsigned (SmartScreen warns) and the macOS build is unsigned/un-notarized (Gatekeeper needs a manual "Open Anyway")
macOS note — read before downloading the DMG
The macOS build is for advanced users right now, not a finished product. Two
things are not yet in place, so a plain "download the DMG and drag to
Applications" will not give you a working tunnel:
The tunnel needs a privileged helper. macOS only lets root open the utun
device, so the app talks to a small root LaunchDaemon that owns the tunnel.
That daemon is currently installed by hand with a sudo script
(scripts/macos/install-daemon.sh) — there
is no in-app installer for it yet. Without it, the app runs but cannot connect.
The build is unsigned and un-notarized. First launch needs
System Settings → Privacy & Security → Open Anyway, and updates to the
daemon are a manual step (the in-app updater refreshes only the app, not the
root daemon). Since 0.4.4 the app warns with a banner when the daemon has
fallen behind it; re-run the install script from your checkout to update:
sudo bash scripts/macos/install-daemon.sh --from-app /Applications/Tenebra.app --allow-unsigned.
The click-to-run macOS path — a signed, notarized build with an SMAppService
daemon bundled inside the app (so it installs and updates like the Windows
service) — needs an Apple Developer ID and is planned, not done. Until then,
use the DMG only if you're comfortable running the install script yourself.
Windows users are unaffected — the Windows installer sets up the service and
the updater keeps everything current automatically.
Linux note — the tunnel needs a root service
Linux is the same shape as macOS: only a privileged process may open
/dev/net/tun and install routes, so the app talks to a small root systemd
service that owns the tunnel and serves the control protocol on
/run/tenebra.sock. The app alone cannot connect. Two ways to set it up:
Arch Linux — build the package.packaging/arch/PKGBUILD
builds the core, the desktop app and the unit from source and installs them
with pacman:
cd packaging/arch && makepkg -si
sudo systemctl enable --now tenebra.service
Updates come from pacman, not the in-app updater — it can only replace an
AppImage, never files a package manager owns.
Any other distribution — the install script. Fetch the bundled resources,
then install the daemon from your checkout:
It is safe to re-run to upgrade, rolls back if an upgrade fails, and
scripts/linux/uninstall-daemon.sh
removes it. The GUI is a separate .deb/AppImage build.
Two limits worth knowing before you install: system-proxy mode does nothing on
Linux (it needs per-desktop settings a root daemon cannot reach, so it stays
quietly disarmed — tun mode, the default, is unaffected), and the bundled
sing-box binaries are glibc-linked, so musl distributions need their own.
Full detail, including the systemd sandbox and what is deliberately left out of
it, is in docs/porting/linux.md.
If you want to help close the gap, the macOS SMAppService path and the
non-desktop adapters are the highest-leverage places — see
CONTRIBUTING.md.
Repository layout
tenebra/
├── core/ Go. Platform-agnostic, stdlib-only, fully unit-tested.
│ ├── model/ Normalized proxy node + config types.
│ ├── subscription/ Parse vless/hysteria2/ss/trojan/vmess links + sub bodies.
│ ├── profile/ Named profiles and their atomic on-disk store.
│ ├── routing/ smart/global/direct + per-app split -> sing-box route/dns.
│ ├── singbox/ Build a full sing-box config as plain JSON (no sing-box dep).
│ ├── fallback/ Pure REALITY->Hysteria2->AmneziaWG fallback state machine.
│ └── control/ The line-delimited JSON protocol + the daemon.
├── adapters/
│ └── windows/ Spawn & supervise sing-box; traffic via its clash API.
├── cmd/
│ └── tenebra-core/ The sidecar entry point (talks the protocol on stdin/stdout).
├── ui-desktop/ Tauri 2 app: Rust shell (src-tauri) + React/TS front end (src).
├── deploy/ The privileged daemon's service definitions per platform.
├── packaging/
│ └── arch/ PKGBUILD building the whole thing for Arch Linux.
├── scripts/
│ ├── fetch-resources.ps1 Download pinned sing-box + wintun for bundling (Windows).
│ ├── fetch-resources.sh The same for macOS and Linux.
│ ├── macos/ Install/remove the root LaunchDaemon.
│ └── linux/ Install/remove the root systemd service.
└── docs/ Architecture, control protocol, and the dev guide.
Building
Requirements: Go 1.24+, Node 22+, and the Rust toolchain (for the
desktop UI). Full walkthrough and troubleshooting in
docs/development.md.
Core tests:
go test ./...
Desktop app (Windows):
# fetch the sing-box binary and wintun.dll into src-tauri/resources
powershell -File scripts/fetch-resources.ps1
# build the core sidecar where Tauri bundles it
go build -o ui-desktop/src-tauri/binaries/tenebra-core-x86_64-pc-windows-msvc.exe ./cmd/tenebra-core
# build the bundle
cd ui-desktop
npm install
npm run tauri build
Desktop app (Linux):
# fetch the sing-box binary and the rule-sets into src-tauri/resources
bash scripts/fetch-resources.sh
# build the core sidecar where Tauri bundles it
go build -o ui-desktop/src-tauri/binaries/tenebra-core-x86_64-unknown-linux-gnu ./cmd/tenebra-core
# build the .deb and AppImage
cd ui-desktop
npm install
npm run tauri build
On Arch, cd packaging/arch && makepkg -si does all of the above and installs
the result — see the Linux note.