unslothai/unsloth · warning · HTTPException
nonce must be base64url
Error message
nonce must be base64url
What it means
GET /auth/identity is a challenge-response endpoint that HMACs a caller-supplied nonce with the install identity secret. The nonce must be base64url-encoded; if `base64.urlsafe_b64decode` raises, the endpoint returns HTTP 400 with detail 'nonce must be base64url'. This guards the proof protocol against malformed input before any cryptographic work.
Source
Thrown at studio/backend/routes/auth.py:404
# A successful login resets the IP's throttle, including any overflow it
# accumulated during saturation (drop only this IP's entry, so a
# shard-mate's throttle is untouched).
_overflow_shard(ip).pop(ip, None)
# 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."""View on GitHub (pinned to 203007d190)
Solutions
- Encode the nonce with URL-safe base64: `base64.urlsafe_b64encode(os.urandom(32)).decode().rstrip('=')` on the client
- If sending standard base64, convert it: replace '+' with '-' and '/' with '_' before sending
- Verify the nonce string contains only [A-Za-z0-9_-] characters before making the request
Example fix
# before
import secrets
resp = client.get("/auth/identity", params={"nonce": secrets.token_hex(32)})
# after
import base64, os
resp = client.get("/auth/identity", params={"nonce": base64.urlsafe_b64encode(os.urandom(32)).decode().rstrip("=")}) Defensive patterns
Strategy: validation
Validate before calling
import base64, re
def is_base64url(s: str) -> bool:
return bool(re.fullmatch(r"[A-Za-z0-9_-]+={0,2}", s)) and _can_decode(s)
def _can_decode(s: str):
try:
base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
return True
except Exception:
return False Try / catch
r = client.get("/auth/identity", params={"nonce": nonce})
if r.status_code == 400 and "base64url" in r.json()["detail"]:
nonce = base64.urlsafe_b64encode(os.urandom(32)).decode().rstrip("=")
r = client.get("/auth/identity", params={"nonce": nonce}) Prevention
- Always generate nonces with base64.urlsafe_b64encode on os.urandom/secrets
- Never hand-type nonces in tests; use a fixed in-range encoded value
When it happens
Trigger: Calling GET /identity with a `nonce` query parameter that is not valid base64url: raw hex strings, plain ASCII text, standard-base64 with `+`/`/` characters, or truncated padding (e.g. a 17-byte nonce encoded then chopped).
Common situations: Client libraries encoding the nonce with the wrong alphabet (base64 instead of base64url), hand-crafted curl tests using arbitrary strings, or non-ASCII bytes injected into the query string.
Related errors
- nonce must decode to 16-128 bytes
- New password cannot contain spaces
- New password must be different from the current password
- Invalid base64 image data: {exc}
- A reference decoded to no data.
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/8ac31574f14187d2.
Report an issue: GitHub.