The repo switched to AGPLv3 back on 2026-06-18 (commit 9ce9e6d), and
Cargo.toml/README have said AGPL-3.0 ever since — but that license-change
commit was never carried forward into the 0.4.x rebuild branch, so the
actual LICENSE file silently reverted to the pre-rebuild BSL 1.1 text
(with a "converts to MIT in 2030" clause that hasn't applied for months).
Restored the real AGPLv3 text from 9ce9e6d.
Also added the missing `license` field to a few crate manifests that
didn't declare one (ostp-gui/src-tauri, ostp-jni, ostp-tun-helper), and
dropped the Tauri template placeholder authors/description.
stealth_sni was never actually consumed to construct any wire bytes —
verified dead in bridge.rs (only stored, never read). It implied
TLS/HTTP SNI mimicry that this project deliberately does not do
(zapret-like: packet-level DPI obfuscation only, no protocol
mimicry). Removed from the runtime schema (config.rs, bridge.rs),
both CLI/GUI local config shapes and their JSON templates, the
Flutter profile model/UI/share-link logic, and README feature docs.
migrate.rs now drops the field from legacy configs with a note
instead of carrying it forward.
resolve-channel treated EVERY v* tag as stable, so v0.4.6-beta got published
as a non-prerelease "Latest" release, sitting on top of the release line. Now
a pushed tag is used as-is and its suffix decides the channel: v*-alpha / v*-beta
are prereleases, only a bare vX.Y.Z is stable. (A tag is never recomputed from
Cargo.toml, so the release can't upload to a different tag than the one pushed.)
Version bumped 0.4.5 -> 0.4.7 (0.4.6 is already taken by the mislabelled beta).
This commit's tip is what gets tagged v0.4.7-beta to cut the beta build.
Every process (CLI daemon, GUI, TUN helper) and every subsystem (tracing, the
core event logger, the helper IPC, panic hook) wrote its own file: ostp-cli.log
+ ostp-core.log + ostp-helper.log + ostp-crash.log — a pile per run. Now they
all funnel into a single ostp.log next to the exe.
- logging: LOG_FILE_NAME/log_file_path() as the one source of truth; init_tracing
gains a `truncate` arg. Truncation is gated twice: Windows-only (cfg!(windows))
AND daemon-only. One-shot commands (gk/check/init/-V/...) and the elevated TUN
helper pass truncate=false so they can never wipe a running daemon's log;
invocation_is_daemon() detects the daemon from argv. On Linux the server always
appends (history kept, OS-rotated) as requested.
- runner/helper manual writers + panic hook now target log_file_path(), so their
output lands in the same ostp.log instead of separate files.
- GUI launch_as_admin now matches the CLI's UAC diagnosis: detects
ERROR_CANCELLED (1223, user declined the prompt) instead of silently
treating it as success, and reports GetLastError()+exe path for any other
ShellExecuteW failure, replacing the old single opaque "denied or missing"
message that made GUI/TUI failures impossible to tell apart.
- generate-key subcommand renamed to `gk` (kept `generate-key` as an alias).
- Fixed a real short-flag collision: GenerateKey's --count used short='c',
which collides with the global --config short (propagated into every
subcommand); clap validates the whole command tree on first parse(), so
this could break parsing for the entire CLI, not just generate-key/gk.
--count is now short='n'.
- ostp-flutter/pubspec.yaml version was stuck at 0.2.97+12; bumped to 0.4.1+13.
- release.yml: added a resolve-channel job that computes one release tag per
run instead of repeating the logic in five upload steps. Rolling channel
pushes now carry the actual Cargo.toml version instead of a bare channel
name: `{version}-nightly` for the nightly branch, `{version}-beta` for
pre-release. workflow_dispatch gained a `channel` input restricted to
nightly/beta only — a manual run can never accidentally publish a "stable"
release; that still requires an explicit vX.Y.Z tag push.
security / protocol:
- Derive a PER-KEY junk marker (obfuscation.rs, info byte 0x04) instead of the
global constant [0x88,0x1A,0x93,0x5D]. A fixed marker was a universal DPI
signature identifying ALL OSTP users at once — exactly what the HKDF version
gate avoids for the handshake. Server drops junk via a new DispatchOutcome::Junk
inside the existing key-trial loop (secrets already derived → zero extra cost);
client stamps its own key's marker.
- §E: configurable junk/fragmentation params (junk_pc / junk_ps / frag_chunk / frag_sleep).
GUI (desktop):
- Light theme + toggle, GUI version footer in Settings.
- Fix mouse-wheel scroll on Settings (flex child needed min-height: 0).
- Drop the false "process exclusions unsupported in TUN mode" warning — they DO
work (native_handler maps port->process via GetExtendedTcpTable).
release / infra:
- build.ps1: add -PreRelease (tag CURRENT version as v<ver>-beta.N, no bump, no
master commit); guard the panel build when ostp-control ships no source; bump
the real ostp-gui/package.json instead of the nonexistent ostp-control one.
- release.yml: mark hyphenated tags as GitHub pre-releases; don't hard-fail the
web-panel step when there is no source (use committed dist/).
- Versions aligned to 0.4.1; README license badge BSL 1.1 -> AGPL v3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Previously start_tunnel returned early if a tunnel was already running, so
changing the server while connected silently kept the OLD connection. Per
the plan ("server change = full stop+start, not hot-reload"), tear down any
existing InProcess/Helper tunnel before starting a fresh one. For the
elevated helper, wait ~1.2s after sending stop so it releases the ostp_tun
adapter before a new helper recreates it (avoids name clashes). start_tunnel
is only invoked on an explicit connect, so restarting here is safe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The plan's share feature for the (single-config) desktop GUI. The QR is
rendered locally so the access key never leaves the device.
- src-tauri: add `qrcode = "0.14"` (features=["svg"]) + `generate_qr`
command (string -> SVG), registered in the invoke handler. Ported from
the current ostp-gui. (cargo check on src-tauri passes.)
- Frontend: "Share" button next to Import builds `ostp://KEY@HOST?sni&type`
from the current config fields, calls generate_qr, and shows a modal with
the QR + a read-only link + Copy. Added i18n keys (en/ru) so the new
data-i18n labels resolve (missing keys would render as the raw key).
Note: Rust side verified via cargo check; the frontend is syntax-checked
(node --check) but not runtime-verified — needs a Tauri build to confirm
visually.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Anti-DPI obfuscation the project wants to keep, ported from 0.3.x with the
harmful UDP behaviour designed out from the start.
- Junk: before the handshake on a UoT/TCP connection, send 2-5 random
length-prefixed frames (100-1000 B). The server reads each as a frame,
fails to authenticate it, drops it and keeps reading (drop-and-continue),
so junk perturbs DPI flow analysis without breaking the connection. Junk
is NEVER sent over UDP — there each junk would be a lone datagram
indistinguishable from a port scan (probe-flood / wasted CPU / the very
"self-ban" risk the plan calls out). Verified the server has no
probe-based ban, and the unauthorized-probe log is already rate-limited
(§B), so junk-over-UoT produces one debug line, not a flood.
- TCP fragmentation: new `transport.tcp_fragmentation` flag (default off).
When set, the writer splits the first real frame (the handshake) — length
header byte-by-byte then payload in 2-byte chunks with short gaps — so DPI
can't classify the handshake from a single read.
- Ranges are hardcoded for now; §E fine-tuning (configurable Jc/Jmin/Jmax,
S1/S2, H1..H4) is deferred.
Verified by loopback E2E: a UoT client with tcp_fragmentation=true connects
(junk logged as one rate-limited probe, then real handshake accepted) and
curl via SOCKS5 tunnels HTTPS successfully.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
When using xhttp (UoT) mode on Android, the underlying TcpStream was
not protected with VpnService.protect(fd). This caused the TCP connection
to be routed back into the TUN interface, creating an infinite routing
loop and failing the connection immediately.
Added Android-specific socket protection to the TcpStream in connect_xhttp.
This fixes xhttp/UoT mode on mobile networks.
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
DPI/TSPU resistance:
- Handshake packets now padded with 32-128 random bytes (prevents size
fingerprinting — previously every handshake was exactly 52 bytes)
- Frame header reserved bytes randomized instead of always 0 (prevents
known-plaintext oracle inside encrypted payload)
- Padding jitter cap increased from 96 to 256 bytes for better traffic
pattern masking
GUI Windows app (tunnel/proxy not starting):
- CRITICAL: Added CREATE_NO_WINDOW flag to all reg.exe calls in sysproxy.rs.
In Tauri GUI context (no console window), Command::new('reg') was silently
failing because there was no attached console. This prevented the Windows
system proxy from being enabled.
- Added ProxyOverride bypass list (localhost;127.*;10.*;192.168.*;<local>)
to prevent proxy loop for local traffic
- Added comprehensive logging for all registry operations
- Set initial connection_state to 1 (connecting) instead of 0 — prevents
UI polling from immediately flipping back to 'disconnected' before the
handshake has a chance to begin
Code quality:
- Fixed log file paths: log_to_core_file() and log_to_file() now write next
to the executable instead of CWD. In GUI context, CWD could be
C:\Windows\System32, causing write failures or misplaced log files.