v0.1.7
Changelog
All notable changes to Iceslab are documented here. Format loosely follows
Keep a Changelog; versions are git tags.
v0.1.7
The censorship-survival work from v0.1.6 made functional and field-ready: the
REALITY self-steal vertical actually works end to end now, traffic accounting is
zero-loss across restarts, and admin two-factor is hardened against code replay.
Plus the full advanced Xray option surface and an opt-in probe-resistance knob,
TLS-fragment, per-user and China routing presets, a Shadowsocks cascade link, a
realistic self-steal fallback, and live Shadowsocks user management.
Self-steal and the node-hardening toggles ship functional with real-network
field-validation in progress (the maturity bar v0.1.6 used for its experimental
features).
Security
- TOTP replay rejected within the validity window. A one-time code can no
longer be reused inside its 30-second step: the last-accepted step is recorded
and a login presenting a step at or below it is refused. Closes the small
replay window the initial TOTP work left open.
Added
- Advanced Xray options, tabbed. The profile form's Xray section moved to
tabs (REALITY / TLS / Transport) and exposes the previously hidden option
surface: REALITY xver and max-time-diff, TLS reject-unknown-SNI, XHTTP mode and
request-padding, gRPC multi-mode. - REALITY fallback rate-limit (probe resistance, opt-in). An optional
per-direction throttle on unverified fallback connections, so a scanner that
fails REALITY auth is forwarded to the target slowly and sees a slow site
rather than a full-speed proxy. Off by default: the throttle is itself a
detectable pattern, so it stays an expert opt-in. - TLS-fragment (opt-in, Xray-JSON). A subscription can fragment the client
ClientHello so SNI-based DPI cannot cleanly match the handshake. Off by
default; toggle via a panel setting or the?fragment=query. Emitted only
into the Xray-JSON format (a freedom fragment outbound the proxy dials
through); intentionally not sing-box (the upstream field is unstable). - Per-user routing override + operator custom domain lists (R3). A routing
preset can now be set per user (winning over the squad and global defaults),
and an operator can define direct / proxy / block domain lists emitted into the
Xray-JSON and Clash routing rules. Empty or unset leaves output unchanged. - China routing preset (cn-split, H2). A China-direct mirror of ru-split:
China domains and IPs resolve and egress direct (clean DNS via AliDNS),
everything else tunneled. Across Xray-JSON, Clash and sing-box. - Shadowsocks cascade link cell (C3b). A multi-hop cascade can use a
Shadowsocks-2022 inter-hop link in addition to VLESS, including mixed chains.
Node-to-node link only; the entry hop still does the DPI evasion. - Realistic self-steal fallback (G1, opt-in). A self-steal node can
reverse-proxy unverified probe requests to a real upstream site instead of the
stub page, so a deep prober sees genuine content. Off by default (static
landing); any upstream error falls back to the static page.
Changed
- Zero-loss traffic accounting. Per-user stats are now drained
non-destructively: the node reports cumulative counters and the panel computes
the delta against a stored per-node-per-user snapshot in the same transaction
as the increment. A failed or retried stats write can no longer lose a slice of
a user's traffic the way the previous reset-on-read drain could. - Frontend API base URL defaults to same-origin in production. Production
builds talk to their own origin instead of a hardcoded localhost, so the panel
works served from anywhere without a build-time override. - Dependency audit gate kept clean. Two transitive advisories pulled to
patched versions via overrides so the production audit gate stays green. - Live Shadowsocks user management (N1-SS). Adding or removing a Shadowsocks
user now goes through the running core's runtime API with no restart (live
connections preserved), matching the Xray live-management path, with the same
config-restart fallback when the runtime call cannot be made. - Stricter REALITY target check (H1). The pre-deploy test-connect now also
verifies the masquerade target negotiates HTTP/2, not just TLS 1.3, and
surfaces a single health note: a CDN-grade dest should speak both.
Fixed
- REALITY self-steal now works end to end. Two wiring defects made self-steal
silently degrade to borrowing an external TLS identity (the mode that
mismatches under aggressive DPI): the camouflage-mode field was dropped by the
config schema before it reached the node, and the per-node domain was pushed
under the wrong wire key so the node received an empty server-name list. Both
fixed, plus the node is re-pushed when its domain changes. The self-steal
vertical is now functional (real-network validation pending). - Add and remove of a user converge under rapid status changes. A user
toggled active and limited in quick succession could leave the node user set
out of sync with the panel; the sync now gates on the live desired status so
the node converges to the correct set. - Node firewall self-heals on boot. UFW was only opened for an inbound's port
inside the applyInbounds push handler, so a node that restarted (or whose rule
was lost to a reimage, or to a transientufw allowthat has no retry) could
run its core with the port closed until the next push. The agent now re-ensures
UFW for every persisted inbound on startup. Caught live: xray reachable from
abroad but the binding port was firewalled.
v0.1.6
The largest release since the alpha opened: a censorship-survival toolkit for
hostile networks (routing presets, multi-hop cascades, REALITY self-steal), live
user management with no restarts, admin two-factor auth, operator analytics and a
Telegram bot, far broader client-app coverage, and a deep performance and
reliability audit across both the panel and the node-agent.
Security
- Admin two-factor auth (TOTP). Optional RFC6238 TOTP on the admin login,
with a guided enrollment (enable requires a confirmed code, so you cannot lock
yourself out) and a disable flow. Recovery is a single SQL update if a device
is lost. Additive: existing logins are untouched until an admin opts in.
Added
- Routing presets with split-DNS. A subscription can carry a
ru-split
preset (ads and local destinations resolve and egress direct, everything else
is tunneled), rendered correctly into the Xray-JSON, Clash and sing-box
formats with a matching split-DNS block so lookups do not leak. Selectable per
subscription, per squad (override), or via a?routing=query, plus a raw
custom-rules editor for hand-written Xray routing rules. - Multi-hop cascades (experimental). Chain nodes entry -> transit -> exit:
the client connects to an entry node and traffic is forwarded hop to hop to an
exit that egresses direct. Full operator UI (hop builder, reorder, validation)
plus node-agent forwarding for the Xray vless cell. Built for networks where a
single foreign hop is blocked; field validation is in progress. - REALITY self-steal (experimental). A REALITY mode where the node runs its
own local TLS fallback and presents its own domain, so the SNI and the server
IP stay consistent (the mismatch that gets a borrowed-SNI REALITY connection
mangled on aggressive DPI). Selectable per profile. - Live user add and remove with no restart. Adding or removing an Xray or
Shadowsocks user now goes through the core's runtime management API, so live
connections are never dropped. It falls back to the previous config-restart
path only when the runtime call cannot be made, so it can only improve on the
old behaviour. - Operator analytics. Dashboard bandwidth now shows deltas against the prior
period on every window, plus a new Insights page: a subscription-request
breakdown by client app and a HWID device-count distribution, both computed
from already-stored data with no new tracking. - Operator Telegram bot. A read-only bot answering
/statusand
/user <name>to the operator chat, plus a daily digest of users near expiry
or near their traffic cap. - Signed outbound webhooks. User, profile and node events are forwarded to
configured URLs with an HMAC-SHA256 signature over the payload. - Broader client-app coverage. New subscription formats: XKeen (Xray confdir
for Keenetic routers), Outline / SIP008, Surge, Quantumult X and Loon. - Multi-core node UX. Add a second protocol to an existing node from the node
view, with an auto-picked free port and a human-readable message when a port is
already taken. Plus a masquerade REALITY recipe and a test-connect that probes
the REALITY dest for resolvability and TLS 1.3. - Per-squad defaults. A squad can carry a routing-preset override and a
default HWID device limit.
Changed
- Panel performance pass. Response schemas for fast JSON serialization on the
hot dashboard and user-list endpoints, in-process caches for subscription
settings, squad bindings and blacklist lookups (all write-busted), bulk
single-statement traffic upserts and AmneziaWG peer pre-allocation, cursor
pagination on backfill, and lazy-loaded frontend routes (initial bundle cut by
about a third). - Node-agent reliability. Adapter locks are split so a multi-second core
restart no longer blocks health checks or the panel's push workers, a bounded
restart-on-crash supervisor backs every spawned core, subprocesses are
group-killed so no orphans leak, and stats and health probes run concurrently
with cached AmneziaWG and UFW reads plus zero-user short-circuits.
Fixed
- AmneziaWG runaway traffic. AWG reported kernel-cumulative counters where the
panel expected per-poll deltas, so a peer's lifetime total was re-billed on
every poll and drained quotas. The agent now emits true deltas (baseline on
first sight, so an agent restart never re-bills the backlog). - Editing a limited or expired user no longer fails. Saving such a user
returned 400 on every attempt; it now reactivates correctly, and a 0 GB
traffic limit is read as unlimited. - Per-user stats no longer error on multi-inbound users. A user present on
more than one inbound of a node tripped a Postgres conflict (21000); per-user
rows are aggregated before the bulk upsert. - Smaller audit fixes. IPv6-aware subscription host parsing, a human-readable
port-conflict 409 naming the node and profile, an online-aware node status dot,
a flag-emoji guard for non-ISO country codes, a bounded Hysteria auth-callback
body, and a settings form that re-seeds from the server after save.
v0.1.5
Full Xray protocol matrix: VLESS, VMess and Trojan over any transport and any
security mode, behind a guided picker. Plus an update-available indicator and a
round of VPS hardening.
Security
- nginx ships hardening headers.
X-Frame-Options,X-Content-Type-Options,
Referrer-Policyandserver_tokens offon every response. CSP is left to the
operator so a wrong policy can't silently break the SPA.
Added
- Full Xray protocol matrix. A profile can now run VLESS, VMess or Trojan over
any of the six transports (raw, WebSocket, gRPC, xHTTP, HTTPUpgrade, mKCP) and
any security mode: REALITY, plainnone(for a CDN that terminates TLS itself),
or node-terminated TLS with your own certificate. A guided three-step picker
(protocol, then transport, then security) reveals only the fields each
combination needs, and every combination is emitted correctly into the
raw/base64, Clash, sing-box and Xray-JSON subscription formats. - Update-available indicator. The sidebar shows an accent dot linking to the
release when a newer version ships. The panel checks the latest GitHub release
(cached 6h, best-effort: it never blocks a request or breaks if GitHub is
unreachable, and needs no token on the public repo).
Changed
- Subscription formatters are security-aware. Clash, sing-box and Xray-JSON
hardcoded REALITY for every Xray endpoint, so anoneortlsprofile would
have produced a broken client config. They now render the correct security
block per endpoint and carry all three subprotocols. - VPS resource and secret safety. Redis is capped (
--maxmemorywith
noeviction, so a runaway can't OOM a small host and queued jobs are never
silently dropped), anddeploy.shsnapshots.env.production(the only on-host
copy of the JWT secret, DB password and node mTLS CA) to a timestamped backup
ring before each deploy.
v0.1.4
Reliability hardening after a deep code audit: the bug-fix campaign, a deploy/
update fix so operators stop getting stuck on stale code, and a round of UI polish.
Fixed
- node-agent no longer stalls during a core restart. Adapter mutexes were
held across the multi-second subprocess restart (xray / shadowsocks / mtproto /
mieru / naive), blocking/healthzand the panel's push workers, plus a data
race on the Hysteria auth-callback. Locks are split so restarts run lock-free. - deploy/update no longer silently rebuilds stale code.
deploy*.shran
git pull --ff-only, a no-op on the tag-pinned detached HEAD the installer
leaves behind, so re-deploys quietly rebuilt the old version. They now sync to
ICESLAB_REF(branch or tag), fetch all refs, and fail loudly on an ambiguous
detached HEAD. The installer is a full clone so updates stay reachable. - deploy applies new migrations. Migrations ran against the previous image
before the rebuild, so a deploy that added a migration silently skipped it.
Reordered to build, then migrate, then start. - multi-profile deploy to a fresh node. Deploying several profiles to a new
node assigned port 443 to every one, so all but the first failed with
PORT_IN_USE. Each profile now gets a distinct port. - "Top users today" dashboard card was always empty (the table it reads was
never written); the stats poll now records per-user daily usage. - Subscription endpoint name de-dup, inline port-edit collision check, bounded
AmneziaWG IP allocation, and other audit fixes.
Changed
- Protocol dropdown lists Xray first with a disabled "sing-box (soon)" teaser
everywhere a protocol is chosen. - UI polish. The Users status chips are now the single filter (dropped a
duplicate control); node cards show the node address; filter chips are
keyboard-operable. - Build resilience.
prisma generateretries on a flaky network during the
Docker build, and the Prisma update-check call is disabled.
v0.1.3
Subscription self-service for end users, plus a dashboard CPU-reporting fix
and an ops-script papercut.
Added
- Human-readable subscription page. Opening
/sub/<token>in a browser
used to dump raw base64 (#1).
It now serves a self-contained landing page: status (traffic / expiry /
protocols), a copy-able subscription link, deep-link import buttons
(Hiddify / Streisand / v2rayNG / Clash), and per-format download buttons
including the AmneziaWG.conf. RU/EN by Accept-Language. VPN clients are
unaffected (an explicit?format=always wins). - QR codes on the subscription page. One QR for the subscription URL
(scan to import in proxy clients) and, when an AmneziaWG endpoint exists, a
QR of the wg-quick config text (scan straight into AmneziaVPN). Generated
server-side as inline SVG viaqrcode-svg(zero external requests).
Fixed
- Dashboard CPU headline. The host CPU card showed a 200ms instantaneous
sample taken while the backend builds the overview, so a 1-vCPU host saw
its own work as an 80%+ spike. The headline now uses the 1-minute
load-average percentage (sustained busy-ness); the sample stays as
secondary detail. - Ops scripts run from anywhere.
deploy.sh/restore.sh/ etc. errored
when run fromscripts/instead of the project root. They now auto-resolve
the root, socd scripts && ./deploy.shworks too.
Performance
- Dashboard overview cache TTL 8s → 30s. At 8s the cache expired before
almost every poll, so the ~20-query recompute ran every ~10s and pegged
small hosts. Now throttled to at most twice a minute regardless of tab count.
v0.1.2
Stabilization release: security hardening across the node-agent and installers,
a batch of panel performance fixes, and completion of the per-protocol port
wiring so port changes from the UI take effect on every core.
Security
- node-agent: config-injection guards. AmneziaWG renders peer keys / AllowedIPs
into the awg-quick INI and Hysteria renders obfs password / masquerade URL into
YAML. Both now whitelist input (base64 WG keys, CIDR, no YAML metacharacters) so
a hostile or buggy panel push can't break out of the config and inject
PostUp=/ top-level directives that run as root. - node-agent: constant-time panel-cert fingerprint compare, mtproto Secret
hex validation, Shadowsocks config now written via the atomic fsync helper
(was a non-durable WriteFile+rename). - panel-auth: per-(IP, username) login lockout. Username-only lockout let any
bot lock out the real admin from a different IP; confirmed live during a
distributed brute-force. Lockout state is now keyed on the source IP too. - panel-auth:
cookie.securedriven byNODE_ENV, admin usernames redacted
in Telegram login alerts. - panel-backend: per-route auth. Plugin-level
addHookauth replaced with
per-routeonRequestacross 9 route plugins so a future public route can't
silently inherit no-auth (Fastify v5 quirk). - installer: supply-chain + token-leak hardening.
--bootstrap-filekeeps
the bootstrap token out of/proc/cmdline; optionalICESLAB_REF_SHApins the
expected commit so a re-pointed tag aborts the install.fail2banjails for
auth brute-force + probe scanners in domain mode.
Fixed
- installer pinned to v0.1.0. Default
ICESLAB_REF/ICESLAB_NODE_REFwere
stillv0.1.0, so fresh installs pulled stale code with bugs already fixed in
later releases. Now pinned tov0.1.2. (#1) - per-protocol port wiring.
ApplyInboundnow receives the panel binding
port and every adapter (hysteria, amneziawg, xray, naive, shadowsocks, mtproto,
mieru) rebinds to it. Previously the port was install-time only and UI port
changes were silently dropped. - node-agent default port 8443 → 1337. 8443 is the first port every scanner
probes after 443; 1337 stays out of standard scanner profiles and frees 8443
for a normal user-protocol binding. Existing nodes keep their pinned port. - quick-deploy picks the first free port from
[443, 8443, 2053, 2083, 2087, 2096]instead of hardcoding 443 (which 409'd on any second binding). The UI
flags a collision with the node-agent's own mTLS port. - node-agent fanout is best-effort. A dormant/not-yet-Healthy adapter failing
during addUser/removeUser no longer 500s the whole request (was breaking
first-time backfill on fresh nodes). - heartbeat trusts system CAs + panel CA so an LE-fronted public panel stops
loggingcertificate signed by unknown authorityon every heartbeat. - cron clears stale
lastStatusMessagewhen a node recovers fromdegraded
(the old guard never re-wrote when the new message was empty). - MTProto tg:// URI no longer carries a
#fragmentthat strict Telegram
parsers rejected.
Performance
- AWG IP allocation in one SQL round-trip (was 3 queries + a 254-candidate
JS scan per user). - inbounds-sync batches addUser in bounded-parallel chunks instead of N
serial mTLS round-trips. - UsersPage server-side pagination + debounced search (was fetching up to 500
users and paging in JS, silently truncating larger installs). - AppLayout reads sidebar counts from the dashboard cache - one request
instead of four full-list queries on every page transition. - removeUser cron enqueues deduped by jobId (a stable orphan was re-enqueued
~144×/day). - dropped a redundant 10s dashboard poll inside the node edit modal.
Docs
- README: "Running multiple protocols on one node" section and an installer
env-var table (ICESLAB_REF,SKIP_SWAP,NODE_PORT,FRONTEND_PORT).
Russian README kept in parity.
v0.1.1
First post-publication stabilization pass: docker-compose healthcheck path fix
(the first-install blocker), pnpm-install OOM mitigation on 2 GB VPS, dashboard
heap-percentage fix, login-lockout default relaxation, Subscription sidebar
section, and assorted i18n cleanup.
v0.1.0
Initial public release: multi-core proxy operator panel (Hysteria 2 / Xray
REALITY / AmneziaWG / NaiveProxy / Shadowsocks 2022 / MTProto / Mieru) with
TypeScript Fastify backend, React Mantine SPA, and a Go node-agent over mTLS.