{"record":{"id":"bc9d1776e4806223","repo":"Mintplex-Labs/anything-llm","slug":"forbidden","errorCode":null,"errorMessage":"Forbidden","messagePattern":"Forbidden","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","filePath":"server/endpoints/system.js","lineNumber":136,"sourceCode":"  app.get(\"/setup-complete\", async (_, response) => {\n    try {\n      const results = await SystemSettings.currentSettings();\n      response.status(200).json({ results });\n    } catch (e) {\n      console.error(e.message, e);\n      response.sendStatus(500).end();\n    }\n  });\n\n  app.get(\n    \"/system/check-token\",\n    [validatedRequest],\n    async (request, response) => {\n      try {\n        if (multiUserMode(response)) {\n          const user = await userFromSession(request, response);\n          if (!user || user.suspended) {\n            response.sendStatus(403).end();\n            return;\n          }\n\n          response.sendStatus(200).end();\n          return;\n        }\n\n        response.sendStatus(200).end();\n      } catch (e) {\n        console.error(e.message, e);\n        response.sendStatus(500).end();\n      }\n    }\n  );\n\n  /**\n   * Refreshes the user object from the session from a provided token.\n   * This does not refresh the token itself - if that is expired or invalid, the user will be logged out.","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/20f6d3546c1938bfea1ad304f58a592dddcc5948/server/endpoints/system.js#L118-L154","documentation":"This 403 'Forbidden' is a deliberate status, not an exception, sent by GET /system/check-token (server/endpoints/system.js:134) in multi-user mode. After validatedRequest passes, the handler loads the session user via userFromSession(); it sends 403 when the lookup returns null (no Authorization header, JWT invalid/expired because JWT_SECRET changed, or the user row was deleted) or when user.suspended is true (admin suspended the account). Single-user mode never returns 403 here - it always answers 200.","triggerScenarios":"Frontend polling /system/check-token with a stale session token after the admin rotated JWT_SECRET (e.g. via /system/update-password which regenerates the secret with v4()); a suspended user whose browser still holds a valid-signed JWT; a deleted user with an unexpired token; missing Authorization: Bearer header.","commonSituations":"Password update in single-user-to-multi-user transitions invalidating old sessions; admin suspending a user mid-session; clock skew or JWT_EXPIRY misconfiguration making tokens expire immediately; frontend kept a token from a previous deployment whose JWT_SECRET was regenerated.","solutions":["Treat 403 from check-token as 'log the user out': clear the stored session token and redirect to /login","If all users are logged out at once, check whether JWT_SECRET was rotated (update-password regenerates it) - users simply sign in again","If a single user is affected, verify their account is not suspended in the admin users panel","Ensure the client sends Authorization: Bearer <token> on the request"],"exampleFix":"// before (frontend)\nconst res = await fetch(\"/api/system/check-token\");\nif (!res.ok) throw new Error(\"unexpected\");\n\n// after\nconst res = await fetch(\"/api/system/check-token\", {\n  headers: { Authorization: `Bearer ${window.storage.getItem(\"token\")}` },\n});\nif (res.status === 403) {\n  window.storage.removeItem(\"token\");\n  window.location = \"/login\"; // session invalid or user suspended\n}","handlingStrategy":"validation","validationCode":"const token = window.localStorage.getItem(\"token\");\nif (!token) {\n  window.location = \"/login\"; // no session to check - skip the request entirely\n}\nconst res = await fetch(\"/api/system/check-token\", {\n  headers: { Authorization: `Bearer ${token}` },\n});\nif (res.status === 403) {\n  window.localStorage.removeItem(\"token\");\n  window.location = \"/login\";\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always send the Authorization: Bearer header on authenticated routes","Handle 403 from check-token as 'logout' - never as a retryable error","After any JWT_SECRET rotation expect all sessions to invalidate; have users re-login rather than treating it as a bug","Admins: prefer suspending users over deletion when sessions may still be active, so the UI gets a clean 403"],"tags":["express","auth","jwt","session","forbidden","multi-user"],"backgroundTag":"jwt-token-invalid","analyzedSha":"20f6d3546c1938bfea1ad304f58a592dddcc5948","analyzedAt":"2026-08-18T10:02:21.017Z","contentChangedAt":"2026-08-18T10:02:21.017Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}