unslothai/unsloth · warning · HTTPException
nonce must decode to 16-128 bytes
Error message
nonce must decode to 16-128 bytes
What it means
After decoding, the /identity nonce must be between 16 and 128 bytes; anything shorter or longer returns HTTP 400 'nonce must decode to 16-128 bytes'. The floor prevents trivially guessable nonces (replay/forgery risk) and the ceiling caps HMAC input size. Length is checked on the decoded bytes, not the encoded string length.
Source
Thrown at studio/backend/routes/auth.py:408
# Sync def (not async): compute_identity_proof touches SQLite on the first call,
# so FastAPI runs it in the threadpool rather than blocking the event loop.
@router.get("/identity")
def identity(nonce: str, request: Request) -> dict:
"""Challenge-response proof this is the real local Unsloth: caller sends a nonce,
gets HMAC(install identity secret, nonce, connection address + port).
Unauthenticated and side-effect free; a process that can't read the same-user
secret can't forge a proof, and binding to the address/port the connection
landed on stops a squatter relaying a proof from the real Unsloth elsewhere."""
try:
raw = base64.urlsafe_b64decode(nonce)
except Exception:
raise HTTPException(
status_code = status.HTTP_400_BAD_REQUEST, detail = "nonce must be base64url"
)
if not 16 <= len(raw) <= 128:
raise HTTPException(
status_code = status.HTTP_400_BAD_REQUEST, detail = "nonce must decode to 16-128 bytes"
)
# The address + port the connection actually landed on, from the socket
# (request.scope is getsockname, so it is the real local address even when
# bound to 0.0.0.0), never the client-controlled Host header.
server = request.scope.get("server") or ("", 0)
host = server[0] or ""
port = server[1] if server[1] is not None else 0
return {"proof": storage.compute_identity_proof(raw, host, port)}
@router.get("/status", response_model = AuthStatusResponse)
async def auth_status() -> AuthStatusResponse:
"""Auth initialization state; ``default_username`` is exposed for first-boot UI prefill only."""
return AuthStatusResponse(
initialized = storage.is_initialized(),
default_username = storage.DEFAULT_ADMIN_USERNAME,
requires_password_change = storage.requires_password_change(storage.DEFAULT_ADMIN_USERNAME)View on GitHub (pinned to 203007d190)
Solutions
- Generate the nonce from 16-128 raw bytes: `base64.urlsafe_b64encode(secrets.token_bytes(32)).decode().rstrip('=')`
- If the nonce check fails, log the decoded byte length client-side to confirm it is in range
- For tests, use a fixed but in-range nonce (e.g. 32 bytes) rather than a short placeholder
Example fix
# before
resp = client.get("/auth/identity", params={"nonce": base64.urlsafe_b64encode(b"1234567").decode()}) # 7 bytes
# after
resp = client.get("/auth/identity", params={"nonce": base64.urlsafe_b64encode(secrets.token_bytes(32)).decode().rstrip("=")}) Defensive patterns
Strategy: validation
Validate before calling
import base64, os, secrets
def make_nonce(nbytes: int = 32) -> str:
assert 16 <= nbytes <= 128
return base64.urlsafe_b64encode(secrets.token_bytes(nbytes)).decode().rstrip("=") Try / catch
r = client.get("/auth/identity", params={"nonce": nonce})
if r.status_code == 400 and "16-128 bytes" in r.json()["detail"]:
nonce = make_nonce(32)
r = client.get("/auth/identity", params={"nonce": nonce}) Prevention
- Standardize on 32-byte nonces client-side
- Check decoded length, not encoded string length, when debugging
When it happens
Trigger: Sending a base64url nonce that decodes to fewer than 16 bytes (e.g. a 8-byte random value) or more than 128 bytes (e.g. a UUID concatenated with a timestamp and extra entropy).
Common situations: Clients using short fixed strings or 1-2 word tokens as the nonce; over-enthusiastic clients concatenating multiple entropy sources past 128 bytes; tests reusing a hardcoded tiny nonce.
Related errors
- nonce must be base64url
- New password cannot contain spaces
- New password must be different from the current password
- Request failed (${status})
- family {getattr(fam, 'name', fam)!r} declares no prequant_fi
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/a171a6426900c43a.
Report an issue: GitHub.