{"record":{"id":"31a8f217d017dd5f","repo":"HKUDS/Vibe-Trading","slug":"settings-writes-require-api-auth-key-or-a-local-lo","errorCode":null,"errorMessage":"Settings writes require API_AUTH_KEY or a local loopback client","messagePattern":"Settings writes require API_AUTH_KEY or a local loopback client","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"agent/src/api/security.py","lineNumber":653,"sourceCode":"            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"Settings access requires API_AUTH_KEY or a local loopback client\",\n        )\n\n\nasync def require_settings_write_auth(\n    request: Request,\n    cred: Optional[HTTPAuthorizationCredentials] = Security(_security),\n) -> None:\n    \"\"\"Require explicit authorization before changing credential-routing settings.\"\"\"\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\n    if not _is_local_client(request):\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"Settings writes require API_AUTH_KEY or a local loopback client\",\n        )\n\n\n_LEGACY_LAZY_NAMES = {\n    \"_API_KEY\": _get_api_key,\n    \"_CORS_ORIGINS\": _get_cors_origins,\n    \"_EXTRA_LOOPBACK_HOSTS\": _get_extra_loopback_hosts,\n}\n\n\ndef __getattr__(name: str):\n    if name in _LEGACY_LAZY_NAMES:\n        return _LEGACY_LAZY_NAMES[name]()\n    raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")\n","sourceCodeStart":635,"sourceCodeEnd":670,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/api/security.py#L635-L670","documentation":"When no API_AUTH_KEY is configured, settings writes fall back to loopback-only trust: any non-local client attempting to modify settings gets 403. Because writes change credential routing, the key-less mode is intentionally stricter than simple reads that might be public.","triggerScenarios":"A settings write (PATCH/POST) from a non-loopback client address on a server where API_AUTH_KEY is unset/empty.","commonSituations":"Remote admin script or dashboard writing settings on a dev box without a key; containerized control plane calling the API across the bridge network; accessing the API through its LAN hostname; proxy making requests appear non-local.","solutions":["Set API_AUTH_KEY on the server and send it in the Authorization header for any remote settings write","Perform key-less writes only from the same host via 127.0.0.1","Fix proxy/container networking so the real client IP is visible if loopback trust is intended","Bind dev servers to loopback so remote write attempts can't happen by accident"],"exampleFix":"# before (server key-less, remote client)\ncurl -X PATCH http://10.0.0.5:8000/settings/routing -d '{...}'   # 403\n\n# after\n# on server: export API_AUTH_KEY=...; restart\ncurl -X PATCH http://10.0.0.5:8000/settings/routing -H \"Authorization: Bearer $API_AUTH_KEY\" -d '{...}'","handlingStrategy":"validation","validationCode":"import os\nfrom urllib.parse import urlparse\nassert os.environ.get(\"API_AUTH_KEY\") or urlparse(BASE).hostname in (\"127.0.0.1\", \"localhost\"), \\\n    \"Remote settings writes are 403 without API_AUTH_KEY\"","typeGuard":null,"tryCatchPattern":"try:\n    r = client.patch(\"/settings/routing\", json=payload); r.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 403:\n        raise RuntimeError(\"Loopback-only writes: set API_AUTH_KEY for remote writes\") from e\n    raise","preventionTips":["Perform key-less writes only from the server host itself","Provision API_AUTH_KEY in every non-local environment via secret manager","Make CI assert that remote environments always have a key configured"],"tags":["settings","auth","http-403","loopback","write-protection"],"backgroundTag":"api-key-required-non-local","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}