CVE-2024-24566 (published 2024-01-31T17:15Z)

Lobe Chat is a chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. When the application is password-protected (deployed with the `ACCESS_CODE` option), it is possible to access plugins without proper authorization (without password). This vulnerability is patched in 0.122.4.

CVSS v3 base score: 5.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N)

Weakness information:
cwe_id: NVD-CWE-Other
cwe_name: Other
description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/lobehub/lobe-chat
commit_details: repo_url: https://github.com/lobehub/lobe-chat
author: Arvin Xu
author_date: 2024-01-30 23:36:02+08:00
msg: 🚑️ fix: fix plugin gateway auth (#1195)

* 🚑️ fix: fix plugin auth

* 💚 fix: fix build

* 🐛 fix: fix query of chat settings

* 🐛 fix: fix auth
repository: repo_url: https://github.com/lobehub/lobe-chat
repo_name: lobehub/lobe-chat
description: 🤯 Lobe Chat - an open-source, modern-design LLMs/AI chat framework. Supports Multi AI Providers( OpenAI / Claude 3 / Gemini / Ollama / Bedrock / Azure / Mistral / Perplexity ), Multi-Modals (Vision/TTS) and plugin system. One-click FREE deployment of your private ChatGPT chat application.
repo_language: TypeScript
owner: lobehub
file_changes: filename: createChatCompletion.ts
change_type: ModificationType.MODIFY
diff: @@ -4,7 +4,7 @@ import OpenAI from 'openai';
 import { ChatErrorType } from '@/types/fetch';
 import { OpenAIChatStreamPayload } from '@/types/openai/chat';
 
-import { createErrorResponse } from '../errorResponse';
+import { createErrorResponse } from '../../errorResponse';
 import { desensitizeUrl } from './desensitizeUrl';
 
 interface CreateChatCompletionOptions {

programming_language: TypeScript
filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -5,7 +5,7 @@ import { getServerConfig } from '@/config/server';
 import { getOpenAIAuthFromRequest } from '@/const/fetch';
 import { ChatErrorType, ErrorType } from '@/types/fetch';
 
-import { createErrorResponse } from '../errorResponse';
+import { createErrorResponse } from '../../errorResponse';
 import { createAzureOpenai } from './createAzureOpenai';
 import { createOpenai } from './createOpenai';
 

programming_language: TypeScript
filename: route.ts
change_type: ModificationType.MODIFY
diff: @@ -1,13 +1,39 @@
 import { createGatewayOnEdgeRuntime } from '@lobehub/chat-plugins-gateway';
 
-import { parserPluginSettings } from '@/app/api/plugin/gateway/settings';
+import { createErrorResponse } from '@/app/api/errorResponse';
 import { getServerConfig } from '@/config/server';
+import { getOpenAIAuthFromRequest } from '@/const/fetch';
+import { ChatErrorType, ErrorType } from '@/types/fetch';
+
+import { parserPluginSettings } from './settings';
+
+const checkAuth = (accessCode: string | null) => {
+  const { ACCESS_CODES } = getServerConfig();
+
+  // if accessCode doesn't exist
+  if (!ACCESS_CODES.length) return { auth: true };
+
+  if (!accessCode || !ACCESS_CODES.includes(accessCode)) {
+    return { auth: false, error: ChatErrorType.InvalidAccessCode };
+  }
+
+  return { auth: true };
+};
 
 const { PLUGINS_INDEX_URL: pluginsIndexUrl, PLUGIN_SETTINGS } = getServerConfig();
 
 const defaultPluginSettings = parserPluginSettings(PLUGIN_SETTINGS);
 
-export const POST = createGatewayOnEdgeRuntime({
-  defaultPluginSettings,
-  pluginsIndexUrl,
-});
+const handler = createGatewayOnEdgeRuntime({ defaultPluginSettings, pluginsIndexUrl });
+
+export const POST = async (req: Request) => {
+  const { accessCode } = getOpenAIAuthFromRequest(req);
+
+  const result = checkAuth(accessCode);
+
+  if (!result.auth) {
+    return createErrorResponse(result.error as ErrorType);
+  }
+
+  return handler(req);
+};

programming_language: TypeScript
filename: SettingButton.tsx
change_type: ModificationType.MODIFY
diff: @@ -27,7 +27,7 @@ const SettingButton = memo<{ mobile?: boolean }>(({ mobile }) => {
           });
           router.push('/settings/agent');
         } else {
-          router.push(pathString('/chat/settings', { hash: location.hash }));
+          router.push(pathString('/chat/settings', { search: location.search }));
         }
       }}
       size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE}

programming_language: TypeScript
filename: index.tsx
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@
 import dynamic from 'next/dynamic';
 import { FC, memo } from 'react';
 
+import SessionHydration from '@/app/chat/components/SessionHydration';
 import ResponsiveIndex from '@/components/ResponsiveIndex';
 
 import EditPage from '../features/EditPage';
@@ -11,11 +12,14 @@ import Layout from './layout.desktop';
 const Mobile: FC = dynamic(() => import('../(mobile)'), { ssr: false }) as FC;
 
 const ChatSettings = memo(() => (
-  <ResponsiveIndex Mobile={Mobile}>
-    <Layout>
-      <EditPage />
-    </Layout>
-  </ResponsiveIndex>
+  <>
+    <ResponsiveIndex Mobile={Mobile}>
+      <Layout>
+        <EditPage />
+      </Layout>
+    </ResponsiveIndex>
+    <SessionHydration />
+  </>
 ));
 
 export default ChatSettings;

programming_language: TypeScript
filename: chat.ts
change_type: ModificationType.MODIFY
diff: @@ -91,7 +91,8 @@ class ChatService {
 
     const res = await fetch(gatewayURL ?? PLUGINS_URLS.gateway, {
       body: JSON.stringify({ ...params, manifest }),
-      headers: createHeadersWithPluginSettings(settings),
+      // TODO: we can have a better auth way
+      headers: createHeadersWithPluginSettings(settings, createHeaderWithOpenAI()),
       method: 'POST',
       signal: options?.signal,
     });

programming_language: TypeScript
filename: selectors.test.ts.snap
change_type: ModificationType.DELETE
diff: @@ -1,246 +0,0 @@
-// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-
-exports[`settingsSelectors > CUSTOM_MODELS > custom deletion, addition, and renaming of models 1`] = `
-[
-  {
-    "displayName": "llama",
-    "name": "llama",
-  },
-  {
-    "displayName": "claude-2",
-    "name": "claude-2",
-  },
-  {
-    "displayName": "gpt-4-32k",
-    "name": "gpt-4-1106-preview",
-  },
-]
-`;
-
-exports[`settingsSelectors > CUSTOM_MODELS > duplicate naming model 1`] = `
-[
-  {
-    "displayName": "gpt-3.5-turbo",
-    "name": "gpt-3.5-turbo",
-  },
-  {
-    "displayName": "gpt-3.5-turbo-1106",
-    "name": "gpt-3.5-turbo-1106",
-  },
-  {
-    "displayName": "gpt-3.5-turbo-16k",
-    "name": "gpt-3.5-turbo-16k",
-  },
-  {
-    "displayName": "gpt-4",
-    "name": "gpt-4",
-  },
-  {
-    "displayName": "gpt-4-32k",
-    "name": "gpt-4-32k",
-  },
-  {
-    "displayName": "gpt-4-vision-preview",
-    "name": "gpt-4-vision-preview",
-  },
-  {
-    "displayName": "gpt-4-32k",
-    "name": "gpt-4-1106-preview",
-  },
-]
-`;
-
-exports[`settingsSelectors > CUSTOM_MODELS > only add the model 1`] = `
-[
-  {
-    "displayName": "gpt-3.5-turbo",
-    "name": "gpt-3.5-turbo",
-  },
-  {
-    "displayName": "gpt-3.5-turbo-1106",
-    "name": "gpt-3.5-turbo-1106",
-  },
-  {
-    "displayName": "gpt-3.5-turbo-16k",
-    "name": "gpt-3.5-turbo-16k",
-  },
-  {
-    "displayName": "gpt-4",
-    "name": "gpt-4",
-  },
-  {
-    "displayName": "gpt-4-32k",
-    "name": "gpt-4-32k",
-  },
-  {
-    "displayName": "gpt-4-1106-preview",
-    "name": "gpt-4-1106-preview",
-  },
-  {
-    "displayName": "gpt-4-vision-preview",
-    "name": "gpt-4-vision-preview",
-  },
-  {
-    "displayName": "model1",
-    "name": "model1",
-  },
-  {
-    "displayName": "model2",
-    "name": "model2",
-  },
-  {
-    "displayName": "model3",
-    "name": "model3",
-  },
-  {
-    "displayName": "model4",
-    "name": "model4",
-  },
-]
-`;
-
-exports[`settingsSelectors > currentSettings > should merge DEFAULT_SETTINGS and s.settings correctly 1`] = `
-{
-  "avatar": "avatar.jpg",
-  "defaultAgent": {
-    "config": {
-      "autoCreateTopicThreshold": 2,
-      "displayMode": "chat",
-      "enableAutoCreateTopic": true,
-      "historyCount": 1,
-      "model": "gpt-3.5-turbo",
-      "params": {
-        "frequency_penalty": 0,
-        "presence_penalty": 0,
-        "temperature": 0.6,
-        "top_p": 1,
-      },
-      "plugins": [],
-      "systemRole": "",
-      "tts": {
-        "showAllLocaleVoice": false,
-        "sttLocale": "auto",
-        "ttsService": "openai",
-        "voice": {
-          "openai": "alloy",
-        },
-      },
-    },
-    "meta": {
-      "avatar": "Default Agent",
-      "description": "Default agent for testing",
-    },
-  },
-  "fontSize": 14,
-  "language": "en-US",
-  "languageModel": {
-    "openAI": {
-      "OPENAI_API_KEY": "openai-api-key",
-      "endpoint": "https://openai-endpoint.com",
-      "models": [
-        "gpt-3.5-turbo",
-      ],
-    },
-  },
-  "neutralColor": "sand",
-  "password": "password123",
-  "primaryColor": "blue",
-  "themeMode": "light",
-  "tool": {
-    "dalle": {
-      "autoGenerate": false,
-    },
-  },
-  "tts": {
-    "openAI": {
-      "sttModel": "whisper-1",
-      "ttsModel": "tts-1",
-    },
-    "sttAutoStop": true,
-    "sttServer": "openai",
-  },
-}
-`;
-
-exports[`settingsSelectors > currentTTS > should merge DEFAULT_TTS_CONFIG and s.settings.tts correctly 1`] = `
-{
-  "openAI": {
-    "sttModel": "whisper-2",
-    "ttsModel": "tts-1",
-  },
-  "sttAutoStop": false,
-  "sttServer": "openai",
-}
-`;
-
-exports[`settingsSelectors > dalleConfig > should return the dalle configuration 1`] = `
-{
-  "apiKey": "dalle-api-key",
-  "autoGenerate": true,
-}
-`;
-
-exports[`settingsSelectors > defaultAgent > should merge DEFAULT_AGENT and s.settings.defaultAgent correctly 1`] = `
-{
-  "config": {
-    "autoCreateTopicThreshold": 2,
-    "displayMode": "chat",
-    "enableAutoCreateTopic": true,
-    "historyCount": 1,
-    "model": "gpt-3.5-turbo",
-    "params": {
-      "frequency_penalty": 0,
-      "presence_penalty": 0,
-      "temperature": 0.6,
-      "top_p": 1,
-    },
-    "plugins": [],
-    "systemRole": "user",
-    "tts": {
-      "showAllLocaleVoice": false,
-      "sttLocale": "auto",
-      "ttsService": "openai",
-      "voice": {
-        "openai": "alloy",
-      },
-    },
-  },
-  "meta": {
-    "avatar": "agent-avatar.jpg",
-    "description": "Test agent",
-  },
-}
-`;
-
-exports[`settingsSelectors > defaultAgentConfig > should merge DEFAULT_AGENT_CONFIG and defaultAgent(s).config correctly 1`] = `
-{
-  "autoCreateTopicThreshold": 2,
-  "displayMode": "chat",
-  "enableAutoCreateTopic": true,
-  "historyCount": 1,
-  "model": "gpt-3.5-turbo",
-  "params": {
-    "frequency_penalty": 0,
-    "presence_penalty": 0,
-    "temperature": 0.7,
-    "top_p": 1,
-  },
-  "plugins": [],
-  "systemRole": "user",
-  "tts": {
-    "showAllLocaleVoice": false,
-    "sttLocale": "auto",
-    "ttsService": "openai",
-    "voice": {
-      "openai": "alloy",
-    },
-  },
-}
-`;
-
-exports[`settingsSelectors > defaultAgentMeta > should merge DEFAULT_AGENT_META and defaultAgent(s).meta correctly 1`] = `
-{
-  "avatar": "agent-avatar.jpg",
-  "description": "Test agent",
-}
-`;

programming_language: unknown