unsloth/studio/backend
Daniel Han 7f45635280
Studio: auto-shut-down an exposed first-run instance if the admin password is never changed (#6651)
* Studio: set the admin password before exposing it on the network

On first run Studio seeds the default `unsloth` admin with a random
bootstrap password and embeds it into index.html (window.__UNSLOTH_BOOTSTRAP__)
so the local user can change it without typing it. A request with no Origin
header counts as same-origin, which is what a normal top-level GET sends, so
the page hands out the password to whoever loads it. That is harmless on the
default 127.0.0.1 bind, but `--secure` (public Cloudflare tunnel) and
`--host 0.0.0.0` (raw port reachable on the network) would serve the plaintext
admin password to remote visitors during the bootstrap window.

Fix this at the source: when launching a network-exposed web UI, prompt the
operator in the terminal for a real admin password (with confirmation) before
the socket binds or the tunnel opens, and persist it via update_password (which
clears must_change_password and deletes the .bootstrap_password file). After
that there is no bootstrap secret to leak. Non-interactive launches can supply
it via UNSLOTH_STUDIO_ADMIN_PASSWORD. The masked reader echoes '*' per
character and works on Linux, macOS, and Windows (PowerShell/cmd). Loopback
binds, --api-only (no web UI), and Colab are unaffected.

As defense in depth, the index handler now embeds the bootstrap object only for
a direct local navigation: same-origin AND a loopback TCP peer with no
proxy/tunnel forwarding headers (cf-ray, cf-connecting-ip, x-forwarded-for,
x-forwarded-host, x-real-ip, forwarded). Colab stays exempt. This keeps the
password off the wire even when the prompt is skipped (no TTY and no env var).

Adds unit coverage for the prompt/confirm/decision logic, an integration test
that provisioning clears the bootstrap state, and regression tests for the
local-direct gate (loopback/IPv6/mapped/localhost peers, LAN/public peers,
missing client, each forwarding header, spoofed XFF, and the Colab exemption).

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio: fail fast on an explicitly empty admin-password env var

resolve_admin_password_source treated UNSLOTH_STUDIO_ADMIN_PASSWORD="" like
the var was unset and fell back to the bootstrap backstop. Treat any set value
(including empty) as the env source so it reaches the minimum-length guard and
refuses to expose the server instead of silently keeping the seeded password.

* Studio: apply repo kwarg-spacing format to the secure-admin-password files

* Studio: drop the pre-exposure password prompt; keep the local-direct gate

Per review, the blocking prompt added friction for --secure / 0.0.0.0 first-run
launches without extra security: the local-direct injection gate in main.py
already keeps the bootstrap password off the network for any remote request.
Remove the prompt module and its tests; the gate plus the existing
must_change_password first-login flow are the fix.

* Studio: shut down an exposed first-run instance if the admin password is never changed

The local-direct gate keeps the seeded bootstrap password off the network, but
it stays a valid credential until first login changes it. For an exposed web UI
(--secure / 0.0.0.0, not --api-only, not Colab), arm a daemon timer: if the
password is still the seeded one after the deadline (UNSLOTH_STUDIO_BOOTSTRAP_TIMEOUT,
default 3600s, 0 disables), print a message and shut Studio down via the existing
graceful-shutdown path; if it was changed, leave Studio running.

* Studio: revert the local-direct injection gate; keep the 1-hour auto-shutdown

Per maintainer decision, keep the first-run auto-fill behavior unchanged (the
bootstrap password still seeds the login form for convenience) and rely on the
exposed-instance auto-shutdown to bound the window: an exposed web UI that never
changes the seeded admin password is torn down after UNSLOTH_STUDIO_BOOTSTRAP_TIMEOUT
(default 1h). Restores studio/backend/main.py and its origin test to upstream.

* Studio: render the bootstrap-timeout shutdown message with a human duration

The message hardcoded 'minute(s)' via timeout//60, so a sub-minute timeout
(e.g. a 30s test value) printed 'within 1 minute(s)'. Add _format_duration so
it reads '30 seconds' / '1 minute 30 seconds' / '60 minutes' as appropriate.
The default 3600s still renders '60 minutes'.

* Studio: drop stale local-direct gate reference from bootstrap_timeout docstring

The gate was reverted (timer-only), so the module docstring should not describe
a main.py gate that no longer exists.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-26 01:27:27 -07:00
..
assets Studio: require signed capability tokens for /p preview links (#6666) 2026-06-25 21:40:48 -07:00
auth Studio: auto-shut-down an exposed first-run instance if the admin password is never changed (#6651) 2026-06-26 01:27:27 -07:00
core Fix offline checkpoint load/export: "tokenizer is weirdly not loaded" (#6554) 2026-06-25 23:16:53 -07:00
hub Studio: clean up empty leftover quant folders so they can be deleted (#6616) 2026-06-25 01:14:21 -07:00
loggers Studio: trim serving-log noise and surface llama-server engine stats (#6377) 2026-06-17 05:37:57 -07:00
models Studio: require signed capability tokens for /p preview links (#6666) 2026-06-25 21:40:48 -07:00
plugins Reduce and tighten code comments and docstrings repo-wide (#6095) 2026-06-08 23:09:51 -07:00
requirements Studio: remove OpenEnv and other unused packages (#6585) 2026-06-23 07:20:47 -07:00
routes Studio: require signed capability tokens for /p preview links (#6666) 2026-06-25 21:40:48 -07:00
state Studio: shareable per-checkpoint preview links (#6486) 2026-06-24 06:31:53 -07:00
storage feat: implement thread forking functionality with associated database… (#5810) 2026-06-15 14:57:39 +01:00
tests Studio: auto-shut-down an exposed first-run instance if the admin password is never changed (#6651) 2026-06-26 01:27:27 -07:00
utils Fix offline checkpoint load/export: "tokenizer is weirdly not loaded" (#6554) 2026-06-25 23:16:53 -07:00
__init__.py Final cleanup 2026-03-12 18:28:04 +00:00
_platform_compat.py Reduce and tighten code comments and docstrings repo-wide (#6095) 2026-06-08 23:09:51 -07:00
cloudflare_tunnel.py Reap Studio child processes when the parent dies abnormally (#6425) 2026-06-18 05:51:22 -07:00
colab.py Studio Colab: opt-in shareable Cloudflare tunnel link (#6684) 2026-06-26 00:56:23 -07:00
main.py Studio: shareable per-checkpoint preview links (#6486) 2026-06-24 06:31:53 -07:00
run.py Studio: auto-shut-down an exposed first-run instance if the admin password is never changed (#6651) 2026-06-26 01:27:27 -07:00
startup_banner.py Tidy verbose Studio launch messages (#6628) 2026-06-24 03:49:19 -07:00