{"record":{"id":"8d4322dc3a9d4118","repo":"HKUDS/Vibe-Trading","slug":"invalid-or-missing-api-key","errorCode":null,"errorMessage":"Invalid or missing API key","messagePattern":"Invalid or missing API key","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"critical","filePath":"agent/src/api/security.py","lineNumber":445,"sourceCode":"        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=\"Cross-site request denied\")\n\n    origin = request.headers.get(\"origin\")\n    if origin and not (_is_loopback_origin(origin) or _origin_matches_request_host(origin, request)):\n        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=\"Cross-site request denied\")\n\n\ndef _require_shutdown_authorization(\n    *,\n    request: Request,\n    cred: Optional[HTTPAuthorizationCredentials],\n) -> None:\n    \"\"\"Authorize the local shutdown control-plane action.\"\"\"\n    _reject_cross_site_browser_request(request)\n    api_key = _configured_api_key()\n    if api_key:\n        token = _auth_credential_from_header_or_query(cred, None, allow_query=False)\n        if not token or not hmac.compare_digest(token, api_key):\n            raise HTTPException(status_code=401, detail=\"Invalid or missing API key\")\n        return\n    if not _is_local_client(request):\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"API_AUTH_KEY is required for non-local API access\",\n        )\n\n\n#: Subject recorded when the caller proved possession of the shared API key.\n#: It is a role, not a person: every holder of that one secret authenticates\n#: identically, so this string must never be presented as an identity.\nSHARED_KEY_SUBJECT = \"shared-key-holder\"\n\n#: Subject recorded when no key is configured and a loopback client was trusted.\nLOOPBACK_SUBJECT = \"loopback-operator\"\n\n\ndef _validate_api_auth(","sourceCodeStart":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/api/security.py#L427-L463","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Send the exact configured API_AUTH_KEY in the Authorization header (e.g. Authorization: Bearer <key>) — query-string auth is not accepted here","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)","If the key lives in .env or a secret manager, confirm the running process actually loaded it (print/inspect env of the server process)","Rotate the key on both sides if it was recently changed, and confirm the client is not caching an old value"],"exampleFix":"# before\nimport requests\nrequests.post(\"http://localhost:8000/shutdown\", params={\"api_key\": KEY})  # 401: query auth not allowed\n\n# after\nimport requests\nrequests.post(\"http://localhost:8000/shutdown\", headers={\"Authorization\": f\"Bearer {KEY}\"})","handlingStrategy":"validation","validationCode":"import os, hmac\nKEY = os.environ.get(\"API_AUTH_KEY\", \"\")\nassert KEY, \"API_AUTH_KEY not set on client side\"\nheaders = {\"Authorization\": f\"Bearer {KEY}\"}\n# sanity: length matches what was provisioned\nassert len(KEY) > 0 and KEY == KEY.strip(), \"key has stray whitespace\"","typeGuard":null,"tryCatchPattern":"try:\n    resp = requests.post(f\"{BASE}/shutdown\", headers=headers)\n    resp.raise_for_status()\nexcept requests.HTTPError as e:\n    if e.response.status_code == 401:\n        raise RuntimeError(\"Shutdown auth failed: check API_AUTH_KEY on both sides (header-only)\") from e\n    raise","preventionTips":["Centralize key handling in one client helper that always uses the Authorization header","Strip/validate keys at config load time to catch whitespace mismatches early","Never pass keys in URLs; they leak into logs and are rejected on sensitive routes"],"tags":["api-key","auth","shutdown","http-401","security"],"backgroundTag":"api-key-auth-failed","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}