From ffded38d848635f385399c0cfb9ef70bf163b4b7 Mon Sep 17 00:00:00 2001 From: luo Date: Sat, 20 Sep 2025 01:10:03 +0800 Subject: [PATCH 01/14] add google verify --- .vscode/settings.json | 6 + src/pages/Setting/components/MCPEnvDialog.tsx | 106 +++++++++++++++--- 2 files changed, 95 insertions(+), 17 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fa6153c78..01a3e2ec6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,11 @@ ], "cSpell.words": [ "Eigent" + ], + "i18n-ally.localesPaths": [ + "backend/lang", + "server/lang", + "src/i18n", + "src/i18n/locales" ] } diff --git a/src/pages/Setting/components/MCPEnvDialog.tsx b/src/pages/Setting/components/MCPEnvDialog.tsx index 64951a1eb..d6a588e15 100644 --- a/src/pages/Setting/components/MCPEnvDialog.tsx +++ b/src/pages/Setting/components/MCPEnvDialog.tsx @@ -5,6 +5,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Bot, CircleAlert, Eye, EyeOff } from "lucide-react"; @@ -21,10 +22,62 @@ interface EnvValue { interface MCPEnvDialogProps { showEnvConfig: boolean; onClose: () => void; - onConnect: (mcp:any) => void; + onConnect: (mcp: any) => void; activeMcp?: any; } +export async function google_check(apiKey: string, searchEngineId: string) { + const query = "hello"; // rand word + const url = `https://www.googleapis.com/customsearch/v1?key=${apiKey}&cx=${searchEngineId}&q=${encodeURIComponent( + query + )}&num=1`; + + try { + const res = await fetch(url); + if (!res.ok) { + throw new Error(`Google API error: ${res.status}`); + } + const data = await res.json(); + + if ("items" in data) { + return { success: true, message: "Google key is valid ✅", sample: data.items[0] }; + } else { + return { success: false, message: "Google key invalid ❌", error: data.error }; + } + } catch (err: any) { + return { success: false, message: `Google check failed: ${err.message}` }; + } +} + +export async function exa_check(apiKey: string) { + const query = "hello"; // rand search word + + try { + const res = await fetch("https://api.exa.ai/search", { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ query }), + }); + if (!res.ok) { + const data = await res.json() + throw new Error(`Exa API error: ${res.status} ${data.error}`); + } + + const data = await res.json(); + if ("results" in data) { + return { success: true, message: "Exa key is valid ✅", sample: data.results[0] }; + } else { + return { success: false, message: "Exa key invalid ❌", error: data }; + } + } catch (err: any) { + return { success: false, message: `Exa check failed: ${err.message}` }; + } +} + + export const MCPEnvDialog: FC = ({ showEnvConfig, onClose, @@ -42,7 +95,7 @@ export const MCPEnvDialog: FC = ({ if (mcp?.install_command?.env) { const initialValues: { [key: string]: EnvValue } = {}; Object.keys(mcp.install_command.env).forEach((key) => { - + initialValues[key] = { value: "", required: true, @@ -51,11 +104,11 @@ export const MCPEnvDialog: FC = ({ ?.replace(/{{/g, "") ?.replace(/}}/g, "") || "", }; - if(key==='EXA_API_KEY'){ - initialValues[key].required=false; + if (key === 'EXA_API_KEY') { + initialValues[key].required = false; } - if(key==='GOOGLE_REFRESH_TOKEN'){ - initialValues[key].required=false; + if (key === 'GOOGLE_REFRESH_TOKEN') { + initialValues[key].required = false; } }); setEnvValues(initialValues); @@ -88,15 +141,34 @@ export const MCPEnvDialog: FC = ({ onClose(); }; - const handleConfigureMcpEnvSetting = () => { - setEnvValues({}); - setShowKeys({}); - const mcp = { ...activeMcp }; + const handleConfigureMcpEnvSetting = async () => { const env: { [key: string]: string } = {}; - Object.keys(envValues).map((key) => { + Object.keys(envValues).forEach((key) => { env[key] = envValues[key]?.value; }); - mcp.install_command.env = env; + + // 校验 Google key + if (env["GOOGLE_API_KEY"] && env["SEARCH_ENGINE_ID"]) { + const result = await google_check(env["GOOGLE_API_KEY"], env["SEARCH_ENGINE_ID"]); + if (!result.success) { + toast.error(result.message); + return; + } + } + + // 校验 Exa key + if (env["EXA_API_KEY"]) { + const result = await exa_check(env["EXA_API_KEY"]); + if (!result.success) { + toast.error(result.message); + return; + } + } + + // 如果都成功才保存 + const mcp = { ...activeMcp, install_command: { ...activeMcp.install_command, env } }; + setEnvValues({}); + setShowKeys({}); onConnect(mcp); }; return ( @@ -112,7 +184,7 @@ export const MCPEnvDialog: FC = ({
- {t("setting.configure {name} Toolkit", {name: activeMcp?.name})} + {t("setting.configure {name} Toolkit", { name: activeMcp?.name })}
@@ -152,7 +224,7 @@ export const MCPEnvDialog: FC = ({ {Object.keys(activeMcp?.install_command?.env || {}).map((key) => (
- {key}{envValues[key]?.required&&'*'} + {key}{envValues[key]?.required && '*'}
= ({ {envValues[key]?.tip} {key === 'SEARCH_ENGINE_ID' && ( )} {key === 'GOOGLE_API_KEY' && ( )} {key === 'EXA_API_KEY' && ( From 81fc14daa48300a6aee18a49ace23b6fa6e4fc09 Mon Sep 17 00:00:00 2001 From: luo Date: Sat, 20 Sep 2025 11:16:00 +0800 Subject: [PATCH 02/14] complete api key verify --- src/pages/Setting/components/MCPEnvDialog.tsx | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/pages/Setting/components/MCPEnvDialog.tsx b/src/pages/Setting/components/MCPEnvDialog.tsx index d6a588e15..d1808a6cd 100644 --- a/src/pages/Setting/components/MCPEnvDialog.tsx +++ b/src/pages/Setting/components/MCPEnvDialog.tsx @@ -13,10 +13,12 @@ import githubIcon from "@/assets/github.svg"; import { Input } from "@/components/ui/input"; import { useState, FC, useEffect } from "react"; import { useTranslation } from "react-i18next"; + interface EnvValue { value: string; required: boolean; tip: string; + error?: string; } interface MCPEnvDialogProps { @@ -141,7 +143,29 @@ export const MCPEnvDialog: FC = ({ onClose(); }; + const setFieldError = (key: string, error: string) => { + setEnvValues((prev) => ({ + ...prev, + [key]: { + ...prev[key], + error, + }, + })); + }; + + const clearFieldErrors = () => { + setEnvValues((prev) => { + const updated: typeof prev = {}; + Object.keys(prev).forEach((key) => { + updated[key] = { ...prev[key], error: "" }; + }); + return updated; + }); + }; + const handleConfigureMcpEnvSetting = async () => { + clearFieldErrors(); + const env: { [key: string]: string } = {}; Object.keys(envValues).forEach((key) => { env[key] = envValues[key]?.value; @@ -151,7 +175,8 @@ export const MCPEnvDialog: FC = ({ if (env["GOOGLE_API_KEY"] && env["SEARCH_ENGINE_ID"]) { const result = await google_check(env["GOOGLE_API_KEY"], env["SEARCH_ENGINE_ID"]); if (!result.success) { - toast.error(result.message); + setFieldError("GOOGLE_API_KEY", result.message); + setFieldError("SEARCH_ENGINE_ID", result.message); return; } } @@ -160,7 +185,7 @@ export const MCPEnvDialog: FC = ({ if (env["EXA_API_KEY"]) { const result = await exa_check(env["EXA_API_KEY"]); if (!result.success) { - toast.error(result.message); + setFieldError("EXA_API_KEY", result.message); return; } } @@ -247,6 +272,9 @@ export const MCPEnvDialog: FC = ({
{envValues[key]?.tip} + {envValues[key]?.error && ( +
{envValues[key]?.error}
+ )} {key === 'SEARCH_ENGINE_ID' && (
{t("setting.get-it-from")}: { From 50ca27104ce89bda9dd2f14bad15287dc14b005e Mon Sep 17 00:00:00 2001 From: luo Date: Sat, 20 Sep 2025 11:18:59 +0800 Subject: [PATCH 03/14] complete api key verify --- src/pages/Setting/components/MCPEnvDialog.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Setting/components/MCPEnvDialog.tsx b/src/pages/Setting/components/MCPEnvDialog.tsx index d1808a6cd..8808174c7 100644 --- a/src/pages/Setting/components/MCPEnvDialog.tsx +++ b/src/pages/Setting/components/MCPEnvDialog.tsx @@ -5,7 +5,6 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; -import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Bot, CircleAlert, Eye, EyeOff } from "lucide-react"; From 5d79fe3ec368572bfa2a57aacedbdb7f483a6bc9 Mon Sep 17 00:00:00 2001 From: Wendong-Fan Date: Sat, 20 Sep 2025 16:45:48 +0800 Subject: [PATCH 04/14] enhance: Verify the validity of google and exa api PR377 --- src/pages/Setting/components/MCPEnvDialog.tsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/pages/Setting/components/MCPEnvDialog.tsx b/src/pages/Setting/components/MCPEnvDialog.tsx index 8808174c7..b4a3bf9c7 100644 --- a/src/pages/Setting/components/MCPEnvDialog.tsx +++ b/src/pages/Setting/components/MCPEnvDialog.tsx @@ -63,7 +63,7 @@ export async function exa_check(apiKey: string) { body: JSON.stringify({ query }), }); if (!res.ok) { - const data = await res.json() + const data = await res.json(); throw new Error(`Exa API error: ${res.status} ${data.error}`); } @@ -87,6 +87,7 @@ export const MCPEnvDialog: FC = ({ }) => { const [envValues, setEnvValues] = useState<{ [key: string]: EnvValue }>({}); const [showKeys, setShowKeys] = useState<{ [key: string]: boolean }>({}); + const [isValidating, setIsValidating] = useState(false); const { t } = useTranslation(); useEffect(() => { initializeEnvValues(activeMcp); @@ -163,6 +164,9 @@ export const MCPEnvDialog: FC = ({ }; const handleConfigureMcpEnvSetting = async () => { + if (isValidating) return; + + setIsValidating(true); clearFieldErrors(); const env: { [key: string]: string } = {}; @@ -170,29 +174,32 @@ export const MCPEnvDialog: FC = ({ env[key] = envValues[key]?.value; }); - // 校验 Google key + // Validate Google API key if (env["GOOGLE_API_KEY"] && env["SEARCH_ENGINE_ID"]) { const result = await google_check(env["GOOGLE_API_KEY"], env["SEARCH_ENGINE_ID"]); if (!result.success) { setFieldError("GOOGLE_API_KEY", result.message); setFieldError("SEARCH_ENGINE_ID", result.message); + setIsValidating(false); return; } } - // 校验 Exa key + // Validate Exa API key if (env["EXA_API_KEY"]) { const result = await exa_check(env["EXA_API_KEY"]); if (!result.success) { setFieldError("EXA_API_KEY", result.message); + setIsValidating(false); return; } } - // 如果都成功才保存 + // Save only if all validations succeed const mcp = { ...activeMcp, install_command: { ...activeMcp.install_command, env } }; setEnvValues({}); setShowKeys({}); + setIsValidating(false); onConnect(mcp); }; return ( @@ -312,8 +319,9 @@ export const MCPEnvDialog: FC = ({ onClick={handleConfigureMcpEnvSetting} variant="primary" size="md" + disabled={isValidating} > - {t("setting.connect")} + {isValidating ? "Validating..." : t("setting.connect")} From 8d3a0fd9d2661a83261bba2cdaa1a0e5c4efe250 Mon Sep 17 00:00:00 2001 From: h3rrr <81402797+h3rrr@users.noreply.github.com> Date: Sun, 21 Sep 2025 13:59:26 +0800 Subject: [PATCH 05/14] Create SECURITY.md I've discovered a serious security vulnerability in my project and would like to request a report through GitHub's private channel. Alternatively, you can provide other contact information for reporting vulnerabilities. --- SECURITY.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..034e84803 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :x: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. From 589c1eec56dd95b63edf22fed8685ad1ac560b13 Mon Sep 17 00:00:00 2001 From: Wendong-Fan Date: Sun, 21 Sep 2025 20:03:36 +0800 Subject: [PATCH 06/14] update security.md --- SECURITY.md | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 034e84803..90d74b3a7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,20 +2,29 @@ ## Supported Versions -Use this section to tell people about which versions of your project are -currently being supported with security updates. +The following versions of Eigent are currently being supported with security updates: | Version | Supported | | ------- | ------------------ | -| 5.1.x | :white_check_mark: | -| 5.0.x | :x: | -| 4.0.x | :white_check_mark: | -| < 4.0 | :x: | +| 0.0.x | :white_check_mark: | +| < 0.0 | :x: | ## Reporting a Vulnerability -Use this section to tell people how to report a vulnerability. +If you discover a security vulnerability in Eigent, please report it responsibly: -Tell them where to go, how often they can expect to get an update on a -reported vulnerability, what to expect if the vulnerability is accepted or -declined, etc. +### How to Report +- **Email**: Send details to info@eigent.ai +- **GitHub**: Use GitHub's private security advisory feature +- **Include**: Detailed description, steps to reproduce, and potential impact + +### What to Expect +- **Response Time**: We aim to acknowledge reports within 48 hours +- **Updates**: We will provide updates on the investigation progress weekly +- **Resolution**: Critical vulnerabilities will be addressed within 7 days +- **Credit**: We will credit security researchers in our security advisories (if desired) + +### Security Disclosure Policy +- We follow responsible disclosure practices +- We request 90 days to address the vulnerability before public disclosure +- We will coordinate disclosure timing with the reporter From c095001da126d0ba3639b5bad010a0bfafdbe652 Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Sun, 21 Sep 2025 23:27:27 +0800 Subject: [PATCH 07/14] Update refer-a-friend invite link generation --- electron/main/init.ts | 51 +++++++++++++++++++-------------- src/components/TopBar/index.tsx | 23 +++++++++++---- src/pages/Setting/General.tsx | 4 +-- 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/electron/main/init.ts b/electron/main/init.ts index 1902fcad2..ee503d7c0 100644 --- a/electron/main/init.ts +++ b/electron/main/init.ts @@ -36,32 +36,39 @@ export async function checkToolInstalled() { */ export async function installCommandTool() { return new Promise(async (resolve, reject) => { - const ensureInstalled = async (toolName: 'uv' | 'bun', scriptName: string): Promise => { - if (await isBinaryExists(toolName)) { - return true; - } + const ensureInstalled = async (toolName: 'uv' | 'bun', scriptName: string): Promise => { + if (await isBinaryExists(toolName)) { + return true; + } - console.log(`start install ${toolName}`); - await runInstallScript(scriptName); - const installed = await isBinaryExists(toolName); - - const mainWindow = getMainWindow(); - if (mainWindow && !mainWindow.isDestroyed()) { - if (installed) { - mainWindow.webContents.send('install-dependencies-log', { - type: 'stdout', - data: `${toolName} installed successfully`, - }); - } else { + console.log(`start install ${toolName}`); + const isSuccess = await runInstallScript(scriptName); + const mainWindow = getMainWindow(); + if (mainWindow && !mainWindow.isDestroyed() && !isSuccess) { mainWindow.webContents.send('install-dependencies-complete', { success: false, code: 2, error: `${toolName} installation failed (script exit code 2)`, }); } - } + const installed = await isBinaryExists(toolName); - return installed; + if (mainWindow && !mainWindow.isDestroyed()) { + if (installed) { + mainWindow.webContents.send('install-dependencies-log', { + type: 'stdout', + data: `${toolName} installed successfully`, + }); + } else { + mainWindow.webContents.send('install-dependencies-complete', { + success: false, + code: 2, + error: `${toolName} installation failed (script exit code 2)`, + }); + } + } + + return installed; }; if (!(await ensureInstalled('uv', 'install-uv.js'))) { @@ -220,15 +227,15 @@ export async function installDependencies() { console.log('install dependencies end', code === 0) resolveInner(code === 0) }) - }catch(err) { - log.error('run install failed', err) + } catch (err) { + log.error('run install failed', err) // Clean up uv_installing.lock file if installation fails if (fs.existsSync(installingLockPath)) { fs.unlinkSync(installingLockPath); } rejectInner(err) } - + }) } @@ -317,7 +324,7 @@ export async function startBackend(setPort?: (port: number) => void): Promise { + const displayFilteredLogs = (data: String) => { if (!data) return; const msg = data.toString().trimEnd(); if (msg.toLowerCase().includes("error") || msg.toLowerCase().includes("traceback")) { diff --git a/src/components/TopBar/index.tsx b/src/components/TopBar/index.tsx index f9f8a3343..a6ce4b04b 100644 --- a/src/components/TopBar/index.tsx +++ b/src/components/TopBar/index.tsx @@ -18,8 +18,10 @@ import { useSidebarStore } from "@/store/sidebarStore"; import chevron_left from "@/assets/chevron_left.svg"; import { getAuthStore } from "@/store/authStore"; import { useTranslation } from "react-i18next"; +import { proxyFetchGet } from "@/api/http"; +import { toast } from "sonner"; function HeaderWin() { - const {t} = useTranslation(); + const { t } = useTranslation(); const titlebarRef = useRef(null); const controlsRef = useRef(null); const [platform, setPlatform] = useState(""); @@ -110,6 +112,19 @@ function HeaderWin() { chatStore.tasks[chatStore.activeTaskId as string]?.summaryTask, ]); + const getReferFriendsLink = () => { + proxyFetchGet("/api/user/invite_code").then((res: any) => { + console.log(res); + if (res?.invite_code) { + const inviteCode =`https://www.eigent.ai/signup?invite_code=${res.invite_code}`; + navigator.clipboard.writeText(inviteCode); + toast.success("Invitation code copied!"); + } else { + toast.error("Failed to get invite code"); + } + }); + }; + return (
); -} +} \ No newline at end of file From c47fbfccffcd18f85e2a84b08897331b08f869e0 Mon Sep 17 00:00:00 2001 From: Wendong-Fan Date: Mon, 22 Sep 2025 01:37:24 +0800 Subject: [PATCH 08/14] enhance: PR382 Update refer-a-friend invite link generation --- electron/main/init.ts | 11 ++--------- src/components/TopBar/index.tsx | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/electron/main/init.ts b/electron/main/init.ts index ee503d7c0..cc4b3a739 100644 --- a/electron/main/init.ts +++ b/electron/main/init.ts @@ -43,16 +43,9 @@ export async function installCommandTool() { console.log(`start install ${toolName}`); const isSuccess = await runInstallScript(scriptName); - const mainWindow = getMainWindow(); - if (mainWindow && !mainWindow.isDestroyed() && !isSuccess) { - mainWindow.webContents.send('install-dependencies-complete', { - success: false, - code: 2, - error: `${toolName} installation failed (script exit code 2)`, - }); - } const installed = await isBinaryExists(toolName); + const mainWindow = getMainWindow(); if (mainWindow && !mainWindow.isDestroyed()) { if (installed) { mainWindow.webContents.send('install-dependencies-log', { @@ -63,7 +56,7 @@ export async function installCommandTool() { mainWindow.webContents.send('install-dependencies-complete', { success: false, code: 2, - error: `${toolName} installation failed (script exit code 2)`, + error: `${toolName} installation failed`, }); } } diff --git a/src/components/TopBar/index.tsx b/src/components/TopBar/index.tsx index a6ce4b04b..c8f8e100d 100644 --- a/src/components/TopBar/index.tsx +++ b/src/components/TopBar/index.tsx @@ -112,17 +112,20 @@ function HeaderWin() { chatStore.tasks[chatStore.activeTaskId as string]?.summaryTask, ]); - const getReferFriendsLink = () => { - proxyFetchGet("/api/user/invite_code").then((res: any) => { - console.log(res); + const getReferFriendsLink = async () => { + try { + const res: any = await proxyFetchGet("/api/user/invite_code"); if (res?.invite_code) { - const inviteCode =`https://www.eigent.ai/signup?invite_code=${res.invite_code}`; - navigator.clipboard.writeText(inviteCode); - toast.success("Invitation code copied!"); + const inviteLink = `https://www.eigent.ai/signup?invite_code=${res.invite_code}`; + await navigator.clipboard.writeText(inviteLink); + toast.success("Invitation link copied!"); } else { toast.error("Failed to get invite code"); } - }); + } catch (error) { + console.error("Failed to get referral link:", error); + toast.error("Failed to get invitation link"); + } }; return ( @@ -166,7 +169,7 @@ function HeaderWin() { {location.pathname !== "/history" && ( <> - {activeTaskTitle === "New Project" ? ( + {activeTaskTitle === t("chat.new-project") ? (
) : ( -
+
+ {isDragging && ( +
+ +
+ Drop files to attach +
+ +
+ )}