Ships the extension small (~50 KB tarball). No binaries in it — the
platform-matched sing-box is downloaded on first "Turn on" from
bns/theseus.x/vpn-binaries/<platform>/, sha256-verified against the
manifest that ships inside this operator-signed tarball, and cached
under extensions-data/vpn/bin/. Every subsequent launch re-verifies
before spawning; a mismatch redownloads rather than trusts what is on
disk.
Config generator produces a sing-box config from a vless:// URL (the
shape a 3x-UI VLESS+Reality inbound produces), plus a SOCKS5 inbound
on 127.0.0.1:<ephemeral>. api.setSessionProxy points every Theseus
request at that port while the tunnel is up; child.on("exit") clears
it if sing-box dies. Off again clears the proxy back to whatever the
browser had.
Panel is a big on/off toggle with a status pill, a paste-and-save
endpoint box, and an Advanced disclosure with "auto-on at browser
start", "re-download binary" and "clear cache". Any user with a
vless:// URL can flip it on today; the free tier and the Silent Mode
exit inbound are the server-side half, documented under DESIGN.md.
Binary manifest ships with PENDING sha256s until the binaries are
uploaded to Sia — ensureBinary refuses to activate on a platform whose
sha256 is PENDING, so a user cannot flip it on against an unverified
download.
6.7 KiB
VPN extension — design notes
Client is done in v0.1.0. This file covers what the client expects the server side to look like, so the two halves can be built independently without either side inventing a contract the other did not agree to.
Model, in one line
3x-UI + xray-core on our VPS provides a VLESS+Reality inbound; the client runs sing-box locally and speaks that inbound; a small key-issuer service on our gateway mints per-user client entries in the 3x-UI panel on demand. This is the SoloBot / YAK VPN pattern with the payment stack, the referral program and the Telegram-bot UI stripped out — everything Silent Mode does not care about — and with the client wired to Theseus's session proxy instead of a system-wide tunnel.
Anchors:
D:\Dev\VPN\vpn_bot— the original bot the code and inbound layout are adapted from.client.py(itspy3xuicalls) is the reference for our key-issuer.D:\Dev\VPN\VPN module— the YAK web frontend the payment-less flow descends from.
Server side (VPS)
- Install 3x-UI as a Docker container or systemd service. One VLESS
inbound, Reality mode, port 443,
flow=xtls-rprx-vision, one Reality short-id per operator server. - Provision at least one inbound per exit country (
nl-1,de-1, …) and record itsinbound_idin the key-issuer's config. Multiserver is phase-two; v1 ships one exit. - Key-issuer — small FastAPI service (or Node in Argus, matching our
stack) that:
- Reads 3x-UI's local API on
127.0.0.1:2053. - Exposes one POST endpoint the extension calls (below).
- Talks to Aegis-side wallet signatures when a request claims to be paid.
- Reads 3x-UI's local API on
POST /api/vpn/new-client
Request body:
{
"identity": "bch:<address>" | "anon:<128-bit random>",
"tier": "free" | "paid",
"duration_days": 1, // free tier ignores this; paid honours it
"signature": "…" // wallet signature over the JSON, iff tier=paid
}
Response:
{
"vless": "vless://…",
"expires_at": 1727000000,
"quota_bytes": 524288000 // 500 MB for free tier per day
}
Rules:
anon:requests get one key per source IP per 24h, hard-capped at 500 MB / 30 minutes of active use (enforced in the xray inbound'stotalandexpiry). One key at a time — reissuing supersedes.bch:requests without a signature get the same free-tier treatment.bch:requests with a valid signature and atier=paidclaim get a key sized to what their signed message says.duration_daysand the price paid live in the signed message. Payment verification is a separate concern (see next section).
Paid tier
BCH-native, no Stripe. Options in decreasing order of Silent Mode-fit:
- BCH invoice covenant on Aegis — the extension posts to
/api/vpn/invoicewhich returns an address + memo. User pays from Aegis. Gateway watches the address, unlocks the corresponding key when the tx clears. Same on-chain-watcher pattern the marketplace already uses in Argus. - Sirius Studio hosted key sale — a page under
theseus.x/vpn/buythat runs an offer covenant (like the name-sale covenants shipped 2026-09-17); paying it produces a signed receipt the extension shows to/api/vpn/new-client. Consistent with the name-marketplace flow.
Either way, no card processor, no email, no account. A signed receipt +
a fresh bch: identity is the whole login.
Binary hosting
sing-box binaries live at:
bns/theseus.x/vpn-binaries/win32-x64/sing-box-<version>.exe
bns/theseus.x/vpn-binaries/linux-x64/sing-box-<version>
bns/theseus.x/vpn-binaries/darwin-arm64/sing-box-<version>
Publisher: operator, uploaded with sia-upload.js. The addon's
binary-manifest.json pins the sha256 for each platform; that manifest
ships inside the operator-Ed25519-signed addon tarball, so an attacker
who compromises Sia cannot swap a binary without matching the pinned
hash, and cannot change the pinned hash without the operator OTA key.
To publish a new sing-box version:
- Download upstream from
github.com/SagerNet/sing-box/releases, record the upstream sha256. sia-upload.js sing-box-<version>.exe bns/theseus.x/vpn-binaries/win32-x64(and the two other platforms).- Bump
bundled-addons/vpn/binary-manifest.json— new sha256s, new URLs, new version. - Ship a new addon tarball via the Ed25519 OTA channel — same
sign-addon-update.mjsflow as translate and pdf-editor.
Users get the new manifest via OTA, notice their cached sha256 no longer matches, and re-download.
System-wide vs Theseus-only
v1 = Theseus-only. api.setSessionProxy routes only the Chromium session
Theseus owns. Other apps on the machine are untouched.
For system-wide, sing-box supports TUN inbounds. This requires:
- Admin / sudo on first run (Windows: WinTUN driver install, prompt).
- Different sing-box config (a
tuninbound instead ofsocks). - A different "placement" mode in the addon panel — currently stubbed
as "Turn on at browser start" but the same UI slot could take a
three-way selector:
off | Theseus only | System-wide.
Not in v1. When we add it, the extension activates the TUN inbound only after a fresh admin-elevation prompt, and only if the user has ticked "system-wide" explicitly. Default forever is browser-scoped — a leaky TUN interface on a mistyped click is not a Silent Mode failure mode.
Threat model, first pass
- Operator OTA key compromise ⇒ attacker can push a malicious
sing-box binary. Same threat model the Ed25519 OTA channel already
has; the mitigations (multi-sig, on-BCNR pubkey publication) are
logged in
docs/ADDON-UPDATES.mdunder Roadmap. - Malicious VPS operator ⇒ can see all VPN traffic that leaves the
exit. Standard VPN threat model. Users who don't want to trust
Silent Mode's operator paste their own
vless://URL in the panel; the sing-box config is built locally from whatever URL they give it, no server-side involvement. - Sia mirror MITM ⇒ swaps the sing-box download. Blocked by the sha256 check against the OTA-signed manifest.
- Reality parameter leak ⇒ someone who learns the inbound's private key can decrypt captured traffic. Same as any Reality deployment; key rotation is a 3x-UI operational task, not a Theseus concern.
What is deliberately not in v1
- Server selector — one exit for now, no city/country picker.
- Kill switch — Chromium's session either has the proxy or it does not;
if sing-box dies mid-session,
child.on("exit")clears the proxy but live requests may drift briefly. A real kill switch needs the TUN inbound. - Split tunneling by domain — belongs in
api.setSessionProxy'sbypassList; UI for it later. - Telemetry, quota display, referrals — deliberately out.