HKUDS/Vibe-Trading · critical · HTTPException

Invalid or missing API key

Error message

Invalid or missing API key

What it means

Raised by the local shutdown control-plane endpoint when an API_AUTH_KEY is configured but the request did not present a matching credential in the Authorization header (query-string keys are explicitly rejected here via allow_query=False). The comparison uses hmac.compare_digest, so both a missing token and any incorrect token produce the same 401. It is a deliberate guard so that a destructive shutdown action cannot be triggered anonymously even on a key-protected deployment.

Source

Thrown at agent/src/api/security.py:445

        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Cross-site request denied")

    origin = request.headers.get("origin")
    if origin and not (_is_loopback_origin(origin) or _origin_matches_request_host(origin, request)):
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Cross-site request denied")


def _require_shutdown_authorization(
    *,
    request: Request,
    cred: Optional[HTTPAuthorizationCredentials],
) -> None:
    """Authorize the local shutdown control-plane action."""
    _reject_cross_site_browser_request(request)
    api_key = _configured_api_key()
    if api_key:
        token = _auth_credential_from_header_or_query(cred, None, allow_query=False)
        if not token or not hmac.compare_digest(token, api_key):
            raise HTTPException(status_code=401, detail="Invalid or missing API key")
        return
    if not _is_local_client(request):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="API_AUTH_KEY is required for non-local API access",
        )


#: Subject recorded when the caller proved possession of the shared API key.
#: It is a role, not a person: every holder of that one secret authenticates
#: identically, so this string must never be presented as an identity.
SHARED_KEY_SUBJECT = "shared-key-holder"

#: Subject recorded when no key is configured and a loopback client was trusted.
LOOPBACK_SUBJECT = "loopback-operator"


def _validate_api_auth(

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Send the exact configured API_AUTH_KEY in the Authorization header (e.g. Authorization: Bearer <key>) — query-string auth is not accepted here
  2. Verify the value being sent matches the server's API_AUTH_KEY byte-for-byte (print repr() of both; check for trailing newlines/whitespace from .env files)
  3. If the key lives in .env or a secret manager, confirm the running process actually loaded it (print/inspect env of the server process)
  4. Rotate the key on both sides if it was recently changed, and confirm the client is not caching an old value

Example fix

# before
import requests
requests.post("http://localhost:8000/shutdown", params={"api_key": KEY})  # 401: query auth not allowed

# after
import requests
requests.post("http://localhost:8000/shutdown", headers={"Authorization": f"Bearer {KEY}"})
Defensive patterns

Strategy: validation

Validate before calling

import os, hmac
KEY = os.environ.get("API_AUTH_KEY", "")
assert KEY, "API_AUTH_KEY not set on client side"
headers = {"Authorization": f"Bearer {KEY}"}
# sanity: length matches what was provisioned
assert len(KEY) > 0 and KEY == KEY.strip(), "key has stray whitespace"

Try / catch

try:
    resp = requests.post(f"{BASE}/shutdown", headers=headers)
    resp.raise_for_status()
except requests.HTTPError as e:
    if e.response.status_code == 401:
        raise RuntimeError("Shutdown auth failed: check API_AUTH_KEY on both sides (header-only)") from e
    raise

Prevention

When it happens

Trigger: Calling POST on the local shutdown route (shutdown_local_api -> _require_shutdown_authorization) while API_AUTH_KEY is set, with either no Authorization header, a malformed header, or a key that does not byte-for-byte match the configured API_AUTH_KEY. Passing the key as ?api_key=... also triggers it because query auth is disabled for this route.

Common situations: Env var API_AUTH_KEY has trailing whitespace or quotes in one environment but not the other; the client sends a stale key after the server key was rotated; a script tries to pass the key in the URL (works on other routes but not this one); the key was set in the server's shell but not in the service's systemd/docker environment.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/8d4322dc3a9d4118. Report an issue: GitHub.