[Security] Empty Admin Credentials Still Allow Forged Admin JWTs
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 3/5
- Geschätzter Aufwand
- 1-3 Stunden
- Anfängerfreundlichkeit
- 72/100
- Issue-Typ
- Bug
- Klarheit
- Klar beschrieben
- Aktivitätsstatus
- Aktiv
- Tech-Stack
- node.js, typescript
Rechercherichtung
Start at server/admin/src/server/middleware/auth.ts: read the key derivation (SECRET || "tailchat" plus md5 of adminAuth) and the JWT verify/platform check at lines 37-55, and compare with the login guard in server/admin/src/server/router/api.ts lines 31-35. The fix is to reject protected requests when ADMIN_PASS/SECRET are empty or default, and mirror it in server/admin-next/src/server/middleware/auth.ts (platform 'admin-next'). Done when the issue's proof-of-concept fetch to an admin endpoint returns 401 under the shipped docker-compose.env defaults, while a login with configured credentials still works.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
Empty Admin Credentials Still Allow Forged Admin JWTs
Hello Tailchat maintainers, could you review a conditional authentication bypass in the optional admin service?
Summary
With the repository's docker-compose.env defaults, SECRET and ADMIN_PASS are empty and ADMIN_USER is tailchat. The admin signing key is therefore the publicly derivable value tailchat545497b05dba745043d8f287871bfa91. The login route rejects requests because the password is empty, but protected admin routes still accept a JWT signed with this key and containing platform: "admin".
Details
Reviewed revision: 89ab8bba033603415f46fc228b5771d99daa6a21.
auth.ts derives the signing key from SECRET || "tailchat" plus md5(JSON.stringify(adminAuth)). The shipped docker-compose.env sets SECRET=, ADMIN_USER=tailchat, and ADMIN_PASS=. These values produce the key above.
The inline comment says this MD5-derived suffix is intended to prevent token forgery when SECRET is unset and the admin credentials are changed. That protection relies on at least one credential being private. With the shipped defaults, both inputs are public; MD5 deterministically produces a public suffix and adds no secret entropy. This finding does not claim that every deployment with an unset SECRET is affected: deployments using non-public admin credentials derive a different key.
The login route refuses authentication when the admin password is empty (api.ts). However, the middleware only verifies the JWT and checks payload.platform === "admin"; it does not reject requests when admin credentials are unset (auth.ts). Protected endpoints include /callAction and user-management routes (api.ts). The admin service is optional and is exposed through the repository's docker/admin.yml.
The parallel admin-next middleware uses the same key derivation and accepts platform: "admin-next" (admin-next/auth.ts).
The admin service is optional and is not part of the base docker-compose.yml. Its deployment guide tells operators to set an independent ADMIN_PASS. Tailchat also documents that the default SECRET is public and must be replaced in production (environment documentation). Therefore, this finding applies when an operator enables the optional admin service but leaves both the shipped admin credentials and public fallback SECRET unchanged. The admin process does start in this configuration: startup validates MONGO_URL, not ADMIN_USER or ADMIN_PASS (index.ts). The login route is disabled, but protected API middleware still accepts the publicly forgeable admin JWT.
Proof of concept
For a deployment that enables the optional admin service and leaves the tracked docker-compose.env values unchanged, sign and send a token to a protected admin endpoint:
import jwt from 'jsonwebtoken';
import md5 from 'md5';
const authSecret =
'tailchat' +
md5(JSON.stringify({ username: 'tailchat', password: '' }));
const token = jwt.sign({ platform: 'admin' }, authSecret);
const response = await fetch(
'http://localhost:11000/admin/api/user/count/summary',
{ headers: { authorization: `Bearer ${token}` } },
);
console.log(response.status, await response.text());
The request is authorized without using the admin login route or knowing an admin password.
Impact
An unauthenticated network attacker who can reach an enabled admin service under these unchanged values can impersonate an administrator and access or modify data through protected admin APIs. This is a conditional default-key issue (CWE-1394: Use of Default Cryptographic Key). It requires enabling the optional admin service while ignoring the documented requirements to set an independent admin password and replace the public production SECRET; deployments following those instructions are not affected by this specific path.
Related Works
- EverShop — CVE-2023-46943 / GHSA-32r3-57hp-cgfw
- Peppermint — CVE-2023-42328 / GHSA-m4w4-j8c2-pv2w
- YourSpotify — CVE-2024-28194
- NocoBase — CVE-2025-13877 / GHSA-mv7p-34fv-4874
- FUXA — CVE-2026-25894 / GHSA-32cc-x95p-fxcg
- Claude Code UI — CVE-2026-31975 / GHSA-gv8f-wpm2-m5wr
- 9router — CVE-2026-49352 / GHSA-jphh-m39h-6gwx
- FastGPT — GHSA-w732-rq8c-chc8
This report is part of my ongoing security research. If you have any questions, please feel free to mention me. I would be glad to make even a small contribution to improving Tailchat's security.
- Vorherrschende Sprache
- TypeScript
- Sterne
- 3.6k
- Forks
- 397
- Ø Merge
- 2 Min.
- Gemergte PRs (30 T.)
- 1
Entwicklungsumgebung
- Enthält ein Dockerfile oder eine Docker-Compose-Datei
- Keine Pull-Request-Vorlage
- Kein Beitragsleitfaden
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus msgbyte/tailchat
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 65/100
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 1/100
-
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 50/100
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 48/100
-
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 35/100
Alle Issues in msgbyte/tailchat
Ähnliche Issues
-
First unknown-user login after boot is one scrypt run slower than a real user's wrong passwordOffenarea: backend bug priority: low
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
snapotter-hq/SnapOtter#2254 ·
Maintainer antworten meist innerhalb von 1 Tag
-
bug ticket
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
cratestack/cratestack#1154 ·
Maintainer antworten meist innerhalb von 1 Tag
-
server 消息处理器 cmd 分支补显式错误回报——竞态非法命令现走未处理拒绝Evtl. vergeben @openaddr hat das heute übernommen. Offenready-for-agent refactor wayfinder:task
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
openaddr/dafung-web#428 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Flaky: mongodb-memory-server 'Port already in use' when another process starts a mongod concurrentlyOffenarea:testing bug effort:S priority:P2
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 70/100
Maintainer antworten meist innerhalb von 1 Tag
-
lens:agent lens:process process
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
thebristolsound/birdbrain#1772 ·
Maintainer antworten meist innerhalb von 1 Tag