Elevation belongs to install time. Registering a task that runs elevated is
itself privileged, so an unprivileged GUI can only obtain one by raising the
very prompt we are trying to remove. There was nowhere to put it: the Windows
GUI ships as a portable zip built with --no-bundle, so the project had no
installer at all. Adds an NSIS one, whose POSTINSTALL hook registers the task
while already elevated. Connecting then prompts zero times.
NSIS over WiX because installerHooks is an NSIS feature; the MSI equivalent
needs a custom action, which is more bespoke machinery, not less. installMode
is perMachine — the default, currentUser, does not run elevated, and the hook
would fail exactly as the in-app attempt did.
The task's principal is the SID S-1-5-32-545 (BUILTIN\Users) with
InteractiveToken rather than the installing user, so a machine-wide install
serves every account instead of only whoever ran the installer; the name is
localized and would not resolve. %LOCALAPPDATA% in the arguments is left
unexpanded for the same reason — Task Scheduler expands it per running user.
Also fixes the in-app fallback, which the portable zip still needs and which
had never once worked. It trusted the exit code of an elevated schtasks, but
-Verb RunAs launches through ShellExecute and a non-elevated parent generally
cannot read the child's exit code: $p.ExitCode yields $null, and `exit $null`
leaves PowerShell reporting 0 (measured, not assumed). Failure was arriving
disguised as success. -Wait does not reliably block either, so deleting the
task XML afterwards raced schtasks reading it. It now waits for the task to
actually appear before deleting anything, and treats the exit code as advisory
except for 1223, a declined prompt, which is worth failing fast on.
Corrects one comment that asserted the opposite of the truth: schtasks writes
UTF-16 to a console but UTF-8 with no BOM into a redirected pipe, which is the
case that matters here. Only the fallback made the path check work at all.
wintun.dll rides along as a bundled resource and the hook copies it beside the
executables, since the helper loads it with a plain LoadLibrary. The uninstall
hook removes both it and the task, so no stale registration is left pointing at
a deleted binary.
ostp-upload.jks was sitting untracked but NOT ignored in the repo root, so a
single `git add -A` would have swept the private upload key into a public
repository. That key is unrecoverable-by-design: Android refuses to update an
app across a signing-key change, so leaking it (or losing it) means every
existing install is stranded and can only be fixed by a manual uninstall.
Ignores *.jks, *.keystore and key.properties at the root. The ostp-lab/ line
in this diff is not mine - it was already in the working tree and is carried
along because it shares the file.
No source ever existed in this tree for it - only a built dist/ and a
full node_modules/ dump (6200+ files), and nothing in the codebase
references "ostp-license" anywhere. Leftover from the old commercial-
license-gated era before the AGPLv3 switch; pure bloat since.
Also added **/node_modules/ to .gitignore - its absence is exactly how
this got committed in the first place.
- .ostp_public_ip: a server RUNTIME cache file (its own detected public IP),
never meant to be tracked — got committed by accident from a dev run in
the repo root. Removed and gitignored so it can't happen again.
- test.json (2 bytes), test_addr.rs (95 bytes): leftover scratch files with
no references anywhere in code, CI, or docs.
- refactor.py: a one-off AST-surgery script hardcoding an absolute path to
a specific dev machine (d:/ospab-projects/ostp/...); its job (splitting
up bridge.rs::run()) is long done, and it isn't invoked by anything.
- server.json: duplicate/stale example config at repo root — the real
canonical example already lives at docs/relay-config-example.json, and
this one still had a "reality" section for the TLS-mimicry feature we
removed in §A.
- ostp-wiki/: duplicate in-repo copy of wiki content. The old 0.3.x
lineage already deleted this once ("remove useless ostp-wiki folder from
root") before this rebuild branched off an earlier point that predates
that cleanup — removing it here brings v0.4.x back in line with that
decision.
Clean-rebuild on the stably-working v0.2.98 base. The project's stealth
path is zapret-like (packet obfuscation / junk / fragmentation), NOT
TLS-mimicry, so WSS and Reality are dropped entirely.
- Delete dead orphan files: ostp-client/src/transport/xhttp.rs and
ostp-core/src/crypto/reality.rs (never declared as modules → not even
compiled), plus ostp-core/src/framing/wss.rs.
- Scrub the `wss` transport field from client config/bridge, the unified
CLI (ostp/src/main.rs), the Tauri GUI backend, the GUI frontend
(index.html/main.js), and the Flutter UI; also drop the Reality
pbk/sid plumbing and XTLS auto-search modes from both frontends.
- Drop now-unused client deps (x25519-dalek, chacha20poly1305, hex).
- Bump workspace to version 0.4.0 and license AGPL-3.0; make ostp's
ostp-core dep path-only so the version bump resolves.
- gitignore ostp-control/ (panel assets built separately; a dummy dist
is created for the rust-embed build).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The core bug: server sent 5 TLS records in server_hello but client only
read the first one (ServerHello), then passed remaining bytes (CCS + fake
records) into RealityStream. RealityStream saw 0x14 (CCS) != 0x17 and
immediately returned an error, killing the connection.
Changes:
- reality.rs: append ChangeCipherSpec after ClientHello (RFC 8446 D.4)
export REALITY_SERVER_HANDSHAKE_RECORDS=5 constant
- xhttp.rs: drain all 5 server handshake records before creating RealityStream
- uot.rs: rebuild server_hello as proper 5-record TLS 1.3 flight:
ServerHello + CCS + fake EE (108B) + fake Cert (812B) + fake Fin (52B)
drain client CCS from raw stream before wrapping in RealityStream