From 8d67db1eb234b9d401e6a378796b44614cacf657 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Wed, 29 Apr 2026 16:34:19 +0800 Subject: [PATCH] fix: stt usage (#6855) * fix: stt usage * perf: skil ssrf with some api * fix:test --- packages/service/common/api/axios.ts | 1 + .../service/core/ai/audio/transcriptions.ts | 19 +++--- packages/service/core/ai/rerank/index.ts | 5 +- .../core/workflow/dispatch/tools/http468.ts | 3 +- .../core/workflow/dispatch/tools/runLaf.ts | 2 - .../service/test/core/ai/rerank/index.test.ts | 4 +- .../thirdProvider/codeSandbox/index.ts | 21 +++--- .../service/thirdProvider/textin/index.ts | 4 +- .../pages/api/aiproxy/api/createChannel.ts | 4 +- projects/app/src/service/core/ai/apiproxy.ts | 68 ------------------- .../src/service/support/wallet/usage/push.ts | 5 +- 11 files changed, 33 insertions(+), 103 deletions(-) delete mode 100644 projects/app/src/service/core/ai/apiproxy.ts diff --git a/packages/service/common/api/axios.ts b/packages/service/common/api/axios.ts index 9e3efacdf4..3a355420c5 100644 --- a/packages/service/common/api/axios.ts +++ b/packages/service/common/api/axios.ts @@ -43,6 +43,7 @@ export function createProxyAxios(config?: AxiosRequestConfig, ssrfCheck = true) /** @see https://github.com/axios/axios/issues/4531 */ export const axios = createProxyAxios(); +export const axiosWithoutSSRF = createProxyAxios(undefined, false); /** * 内部相对路径请求专用的 axios 实例: diff --git a/packages/service/core/ai/audio/transcriptions.ts b/packages/service/core/ai/audio/transcriptions.ts index 07605f097f..b6ab8cfd17 100644 --- a/packages/service/core/ai/audio/transcriptions.ts +++ b/packages/service/core/ai/audio/transcriptions.ts @@ -1,6 +1,6 @@ import type fs from 'fs'; import { getAxiosConfig } from '../config'; -import { axios } from '../../../common/api/axios'; +import { axiosWithoutSSRF } from '../../../common/api/axios'; import FormData from 'form-data'; import { type STTModelType } from '@fastgpt/global/core/ai/model.schema'; import { UserError } from '@fastgpt/global/common/error/utils'; @@ -24,22 +24,19 @@ export const aiTranscriptions = async ({ const aiAxiosConfig = getAxiosConfig(); - const { data: result } = await axios<{ text: string; usage?: { total_tokens: number } }>({ - method: 'post', - ...(modelData.requestUrl - ? { url: modelData.requestUrl } - : { - baseURL: aiAxiosConfig.baseUrl, - url: '/audio/transcriptions' - }), + // 管理员配置的 url,允许是内网 + const { data: result } = await axiosWithoutSSRF.post<{ + text: string; + usage?: { total_tokens: number }; + }>(modelData.requestUrl ? modelData.requestUrl : '/audio/transcriptions', data, { + ...(modelData.requestUrl ? {} : { baseURL: aiAxiosConfig.baseUrl }), headers: { Authorization: modelData.requestAuth ? `Bearer ${modelData.requestAuth}` : aiAxiosConfig.authorization, ...data.getHeaders(), ...headers - }, - data: data + } }); return result; diff --git a/packages/service/core/ai/rerank/index.ts b/packages/service/core/ai/rerank/index.ts index a5a65c9d4f..8268f6d0ac 100644 --- a/packages/service/core/ai/rerank/index.ts +++ b/packages/service/core/ai/rerank/index.ts @@ -1,4 +1,4 @@ -import { axios } from '../../../common/api/axios'; +import { axiosWithoutSSRF } from '../../../common/api/axios'; import { getDefaultRerankModel } from '../model'; import { getAxiosConfig } from '../config'; import { type RerankModelItemType } from '@fastgpt/global/core/ai/model.schema'; @@ -93,8 +93,9 @@ export async function reRankRecall({ const { baseUrl, authorization } = getAxiosConfig(); const start = Date.now(); + // 模型的请求 url,允许是内网 const requestUrl = model.requestUrl ? model.requestUrl : `${baseUrl}/rerank`; - const apiResult = await axios + const apiResult = await axiosWithoutSSRF .post( requestUrl, { diff --git a/packages/service/core/workflow/dispatch/tools/http468.ts b/packages/service/core/workflow/dispatch/tools/http468.ts index 60dc0ea08a..0f0c83cd0e 100644 --- a/packages/service/core/workflow/dispatch/tools/http468.ts +++ b/packages/service/core/workflow/dispatch/tools/http468.ts @@ -24,7 +24,6 @@ import { JSONPath } from 'jsonpath-plus'; import { getSecretValue } from '../../../../common/secret/utils'; import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type'; import { getLogger, LogCategories } from '../../../../common/logger'; -import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools'; import { formatHttpError } from '../utils'; import { isInternalAddress, PRIVATE_URL_TEXT } from '../../../../common/system/utils'; import { serviceRequestMaxContentLength } from '../../../../common/system/constants'; @@ -504,10 +503,10 @@ async function fetchData({ return Promise.reject(PRIVATE_URL_TEXT); } + // 都认为是用户的请求,强制 SSRF 检查 const { data: response } = await axios({ method, maxContentLength: serviceRequestMaxContentLength, - baseURL: `http://${SERVICE_LOCAL_HOST}`, url, headers: { ...headers diff --git a/packages/service/core/workflow/dispatch/tools/runLaf.ts b/packages/service/core/workflow/dispatch/tools/runLaf.ts index b0efc518c0..85223ee6b7 100644 --- a/packages/service/core/workflow/dispatch/tools/runLaf.ts +++ b/packages/service/core/workflow/dispatch/tools/runLaf.ts @@ -3,7 +3,6 @@ import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workfl import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { axios } from '../../../../common/api/axios'; import { valueTypeFormat } from '@fastgpt/global/core/workflow/runtime/utils'; -import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools'; import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { getLogger, LogCategories } from '../../../../common/logger'; @@ -122,7 +121,6 @@ async function fetchData({ }): Promise> { const { data: response } = await axios({ method, - baseURL: `http://${SERVICE_LOCAL_HOST}`, url, headers: { 'Content-Type': 'application/json' diff --git a/packages/service/test/core/ai/rerank/index.test.ts b/packages/service/test/core/ai/rerank/index.test.ts index 38595b9033..8d6bbf512b 100644 --- a/packages/service/test/core/ai/rerank/index.test.ts +++ b/packages/service/test/core/ai/rerank/index.test.ts @@ -13,9 +13,9 @@ vi.mock('@fastgpt/service/common/string/tiktoken', () => ({ countPromptTokens: mockCountPromptTokens })); -// rerank 现在改用统一 axios(带 SSRF 拦截),mock axios.post 返回 axios 风格的 { data, ... } +// rerank 走 axiosWithoutSSRF(管理员配置的 url 允许内网),mock 它的 .post 返回 axios 风格的 { data, ... } vi.mock('@fastgpt/service/common/api/axios', () => ({ - axios: { + axiosWithoutSSRF: { post: (...args: any[]) => Promise.resolve(mockAxiosPost(...args)).then((data) => ({ data })) } })); diff --git a/packages/service/thirdProvider/codeSandbox/index.ts b/packages/service/thirdProvider/codeSandbox/index.ts index 8b9d277bfd..877a4c76a3 100644 --- a/packages/service/thirdProvider/codeSandbox/index.ts +++ b/packages/service/thirdProvider/codeSandbox/index.ts @@ -1,6 +1,6 @@ import { SandboxCodeTypeEnum } from '@fastgpt/global/core/workflow/template/system/sandbox/constants'; import type { AxiosInstance } from 'axios'; -import axios from 'axios'; +import { createProxyAxios } from '../../common/api/axios'; import { getLogger, LogCategories } from '../../common/logger'; const logger = getLogger(LogCategories.MODULE.WORKFLOW.CODE_SANDBOX); @@ -17,14 +17,17 @@ export class CodeSandbox { const baseUrl = process.env.CODE_SANDBOX_URL || ''; const token = process.env.CODE_SANDBOX_TOKEN || ''; - this.client = axios.create({ - baseURL: `${baseUrl.replace(/\/$/, '')}/sandbox`, - timeout: 180000, - headers: { - 'Content-Type': 'application/json', - Authorization: token ? `Bearer ${token}` : undefined - } - }); + this.client = createProxyAxios( + { + baseURL: `${baseUrl.replace(/\/$/, '')}/sandbox`, + timeout: 180000, + headers: { + 'Content-Type': 'application/json', + Authorization: token ? `Bearer ${token}` : undefined + } + }, + false + ); this.client.interceptors.response.use( (response) => { diff --git a/packages/service/thirdProvider/textin/index.ts b/packages/service/thirdProvider/textin/index.ts index ab68282c3a..ba54d85039 100644 --- a/packages/service/thirdProvider/textin/index.ts +++ b/packages/service/thirdProvider/textin/index.ts @@ -1,12 +1,12 @@ import { matchMdImg } from '@fastgpt/global/common/string/markdown'; -import axios from 'axios'; +import { createProxyAxios } from '../../common/api/axios'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { getLogger, LogCategories } from '../../common/logger'; export const useTextinServer = ({ appId, secretCode }: { appId: string; secretCode: string }) => { const logger = getLogger(LogCategories.MODULE.DATASET.FILE); // Init request - const instance = axios.create({ + const instance = createProxyAxios({ baseURL: 'https://api.textin.com/ai/service/v1', timeout: 300000, headers: { diff --git a/projects/app/src/pages/api/aiproxy/api/createChannel.ts b/projects/app/src/pages/api/aiproxy/api/createChannel.ts index 594c96bea4..d93133e2d7 100644 --- a/projects/app/src/pages/api/aiproxy/api/createChannel.ts +++ b/projects/app/src/pages/api/aiproxy/api/createChannel.ts @@ -1,6 +1,6 @@ import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; import { authSystemAdmin } from '@fastgpt/service/support/permission/user/auth'; -import { axios } from '@fastgpt/service/common/api/axios'; +import { axiosWithoutSSRF } from '@fastgpt/service/common/api/axios'; import { getErrText } from '@fastgpt/global/common/error/utils'; const baseUrl = process.env.AIPROXY_API_ENDPOINT; @@ -14,7 +14,7 @@ async function handler(req: ApiRequestProps, res: ApiResponseType) { return Promise.reject('AIPROXY_API_ENDPOINT or AIPROXY_API_TOKEN is not set'); } - const { data } = await axios.post(`${baseUrl}/api/channel/`, req.body, { + const { data } = await axiosWithoutSSRF.post(`${baseUrl}/api/channel/`, req.body, { headers: { Authorization: `Bearer ${token}` } diff --git a/projects/app/src/service/core/ai/apiproxy.ts b/projects/app/src/service/core/ai/apiproxy.ts deleted file mode 100644 index 877fdd85e5..0000000000 --- a/projects/app/src/service/core/ai/apiproxy.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { getLogger, LogCategories } from '@fastgpt/service/common/logger'; -import { type Method } from 'axios'; -import { createProxyAxios } from '@fastgpt/service/common/api/axios'; -const logger = getLogger(LogCategories.NETWORK); - -const url = process.env.API_PROXY_URL; -const token = process.env.API_PROXY_TOKEN; - -const instance = createProxyAxios({ - baseURL: url, - timeout: 60000, // 超时时间 - headers: { - Authorization: `Bearer ${token}` - } -}); - -/** - * 响应数据检查 - */ -const checkRes = (data: any) => { - if (data === undefined) { - logger.info('api proxy data is empty'); - return Promise.reject('服务器异常'); - } - return data.data; -}; -const responseError = (err: any) => { - logger.error('API proxy request failed', { error: err }); - - if (!err) { - return Promise.reject({ message: '未知错误' }); - } - if (typeof err === 'string') { - return Promise.reject({ message: err }); - } - if (typeof err.message === 'string') { - return Promise.reject({ message: err.message }); - } - if (typeof err.data === 'string') { - return Promise.reject({ message: err.data }); - } - if (err?.response?.data) { - return Promise.reject(err?.response?.data); - } - return Promise.reject(err); -}; - -const request = (url: string, data: any, method: Method): Promise => { - /* 去空 */ - for (const key in data) { - if (data[key] === undefined) { - delete data[key]; - } - } - - return instance - .request({ - url, - method, - data: ['POST', 'PUT'].includes(method) ? data : undefined, - params: !['POST', 'PUT'].includes(method) ? data : undefined - }) - .then((res) => checkRes(res.data)) - .catch((err) => responseError(err)); -}; - -// TODO: channel crud -export const ApiProxy = {}; diff --git a/projects/app/src/service/support/wallet/usage/push.ts b/projects/app/src/service/support/wallet/usage/push.ts index a123a6ee7d..2efdc0564c 100644 --- a/projects/app/src/service/support/wallet/usage/push.ts +++ b/projects/app/src/service/support/wallet/usage/push.ts @@ -2,9 +2,8 @@ import { UsageItemTypeEnum, UsageSourceEnum } from '@fastgpt/global/support/wall import { createUsage, concatUsage } from '@fastgpt/service/support/wallet/usage/controller'; import { formatModelChars2Points } from '@fastgpt/service/support/wallet/usage/utils'; import { i18nT } from '@fastgpt/web/i18n/utils'; -import { getDefaultTTSModel } from '@fastgpt/service/core/ai/model'; +import { getDefaultSTTModel } from '@fastgpt/service/core/ai/model'; import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type'; -import type { HelperBotTypeEnumType } from '@fastgpt/global/core/chat/helperBot/type'; export const pushHelperBotUsage = ({ teamId, @@ -246,7 +245,7 @@ export const pushWhisperUsage = ({ tmbId: string; duration: number; }) => { - const whisperModel = getDefaultTTSModel(); + const whisperModel = getDefaultSTTModel(); if (!whisperModel) return;