Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
225cb7e6
authored
Apr 28, 2026
by
Archer
Committed by
GitHub
Apr 28, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
perf: catch error toast (#6849)
* perf: catch error toast * fix: submodule * perf: ssrf * perf: readfiles
parent
9f5e1d08
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
170 additions
and
59 deletions
+170
-59
document/content/self-host/upgrading/4-15/4150.mdx
+1
-0
document/data/doc-last-modified.json
+1
-1
packages/service/common/api/axios.ts
+33
-11
packages/service/common/api/plusRequest.ts
+11
-8
packages/service/common/api/serverRequest.ts
+10
-7
packages/service/core/app/mcp.ts
+2
-0
packages/service/core/dataset/read.ts
+1
-1
packages/service/core/workflow/dispatch/ai/toolcall/toolCall.ts
+3
-2
packages/service/core/workflow/dispatch/ai/toolcall/tools/file.ts
+31
-12
packages/service/support/outLink/runtime/utils.ts
+6
-2
packages/service/test/common/api/axios.test.ts
+64
-0
packages/service/test/common/system/utils.test.ts
+4
-12
packages/service/test/core/app/mcp.test.ts
+1
-1
pro
+1
-1
projects/app/src/web/core/dataset/api.ts
+1
-1
No files found.
document/content/self-host/upgrading/4-15/4150.mdx
View file @
225cb7e6
...
...
@@ -15,6 +15,7 @@ description: 'FastGPT V4.15.0 更新说明'
2. 调整文件注入 messages 位置,从 system 调整至 user,便于命中缓存。
3. 非管理员/访客,触发余额不足时候,提示优化。
4. 无创建权限时,隐藏模板功能。
5. 加强第三方知识库请求的 SSRF 防护。
## 🐛 修复
...
...
document/data/doc-last-modified.json
View file @
225cb7e6
...
...
@@ -252,7 +252,7 @@
"content/self-host/upgrading/4-14/41481.mdx"
:
"2026-04-26T21:08:47+08:00"
,
"content/self-host/upgrading/4-14/4149.en.mdx"
:
"2026-04-26T21:08:47+08:00"
,
"content/self-host/upgrading/4-14/4149.mdx"
:
"2026-04-26T21:08:47+08:00"
,
"content/self-host/upgrading/4-15/4150.mdx"
:
"2026-04-28T21:
27:07
+08:00"
,
"content/self-host/upgrading/4-15/4150.mdx"
:
"2026-04-28T21:
35:13
+08:00"
,
"content/self-host/upgrading/outdated/40.en.mdx"
:
"2026-04-26T21:08:47+08:00"
,
"content/self-host/upgrading/outdated/40.mdx"
:
"2026-04-26T21:08:47+08:00"
,
"content/self-host/upgrading/outdated/41.en.mdx"
:
"2026-04-26T21:08:47+08:00"
,
...
...
packages/service/common/api/axios.ts
View file @
225cb7e6
import
_
,
{
type
AxiosRequestConfig
}
from
'axios'
;
import
_
,
{
type
Axios
Instance
,
type
Axios
RequestConfig
}
from
'axios'
;
import
{
ProxyAgent
}
from
'proxy-agent'
;
import
{
isDevEnv
}
from
'@fastgpt/global/common/system/constants'
;
import
{
isInternalAddress
,
PRIVATE_URL_TEXT
}
from
'../system/utils'
;
export
function
createProxyAxios
(
config
?:
AxiosRequestConfig
)
{
const
agent
=
new
ProxyAgent
();
const
addSSRFInterceptor
=
(
instance
:
AxiosInstance
)
=>
{
instance
.
interceptors
.
request
.
use
(
async
(
config
)
=>
{
const
requestUrl
=
(()
=>
{
try
{
return
new
URL
(
config
.
url
||
''
,
config
.
baseURL
).
toString
();
}
catch
{
return
;
}
})();
if
(
!
requestUrl
)
return
config
;
if
(
isDevEnv
)
{
return
_
.
create
(
config
);
}
if
(
await
isInternalAddress
(
requestUrl
)
)
{
return
Promise
.
reject
(
new
Error
(
PRIVATE_URL_TEXT
)
);
}
return
_
.
create
({
proxy
:
false
,
httpAgent
:
agent
,
httpsAgent
:
agent
,
...
config
return
config
;
});
return
instance
;
};
export
function
createProxyAxios
(
config
?:
AxiosRequestConfig
,
ssrfCheck
=
true
)
{
const
agent
=
new
ProxyAgent
();
const
instance
=
isDevEnv
?
_
.
create
(
config
)
:
_
.
create
({
proxy
:
false
,
httpAgent
:
agent
,
httpsAgent
:
agent
,
...
config
});
return
ssrfCheck
?
addSSRFInterceptor
(
instance
)
:
instance
;
}
/** @see https://github.com/axios/axios/issues/4531 */
...
...
packages/service/common/api/plusRequest.ts
View file @
225cb7e6
...
...
@@ -69,14 +69,17 @@ function responseError(err: any) {
}
/* 创建请求实例 */
const
instance
=
createProxyAxios
({
timeout
:
60000
,
headers
:
{
'content-type'
:
'application/json'
,
'Cache-Control'
:
'no-cache'
,
rootkey
:
process
.
env
.
ROOT_KEY
}
});
const
instance
=
createProxyAxios
(
{
timeout
:
60000
,
headers
:
{
'content-type'
:
'application/json'
,
'Cache-Control'
:
'no-cache'
,
rootkey
:
process
.
env
.
ROOT_KEY
}
},
false
);
/* 请求拦截 */
instance
.
interceptors
.
request
.
use
(
requestStart
,
(
err
)
=>
Promise
.
reject
(
err
));
...
...
packages/service/common/api/serverRequest.ts
View file @
225cb7e6
...
...
@@ -60,13 +60,16 @@ function responseError(err: any) {
}
/* 创建请求实例 */
const
instance
=
createProxyAxios
({
timeout
:
60000
,
// 超时时间
headers
:
{
'content-type'
:
'application/json'
,
'Cache-Control'
:
'no-cache'
}
});
const
instance
=
createProxyAxios
(
{
timeout
:
60000
,
// 超时时间
headers
:
{
'content-type'
:
'application/json'
,
'Cache-Control'
:
'no-cache'
}
},
false
);
export
const
serverRequestBaseUrl
=
`http://
${
SERVICE_LOCAL_HOST
}
`
;
/* 请求拦截 */
...
...
packages/service/core/app/mcp.ts
View file @
225cb7e6
...
...
@@ -50,6 +50,8 @@ export class MCPClient {
}
private
async
doConnect
():
Promise
<
Client
>
{
await
assertMCPUrlNotInternal
(
this
.
url
);
// 避免连接重复,强制关闭一次
await
this
.
client
.
close
().
catch
(()
=>
{});
...
...
packages/service/core/dataset/read.ts
View file @
225cb7e6
...
...
@@ -106,7 +106,7 @@ export const readFileRawTextByUrl = async ({
try
{
// 合并所有 chunks 为单个 buffer
const
buffer
=
Buffer
.
concat
(
chunks
);
const
buffer
=
Buffer
.
concat
(
chunks
as
unknown
as
Uint8Array
[]
);
// 立即清理 chunks 数组释放内存
chunks
.
length
=
0
;
...
...
packages/service/core/workflow/dispatch/ai/toolcall/toolCall.ts
View file @
225cb7e6
...
...
@@ -314,8 +314,9 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
return
{
response
,
flowResponse
};
}
else
if
(
toolInfo
.
type
===
'file'
)
{
const
{
ids
}
=
ReadFileToolParamsSchema
.
parse
(
parseJsonArgs
(
call
.
function
.
arguments
));
const
{
response
,
usages
,
node
Response
}
=
await
dispatchReadFileTool
({
const
{
response
,
usages
,
flow
Response
}
=
await
dispatchReadFileTool
({
files
:
ids
.
map
((
id
)
=>
({
id
,
url
:
allFiles
.
get
(
id
)?.
url
!
})),
toolCallId
:
call
.
id
,
teamId
:
workflowProps
.
runningUserInfo
.
teamId
,
tmbId
:
workflowProps
.
runningUserInfo
.
tmbId
,
customPdfParse
:
workflowProps
.
chatConfig
?.
fileSelectConfig
?.
customPdfParse
,
...
...
@@ -324,7 +325,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
return
{
response
,
usages
,
node
Response
flow
Response
};
}
else
{
const
toolNode
=
toolInfo
.
rawData
;
...
...
packages/service/core/workflow/dispatch/ai/toolcall/tools/file.ts
View file @
225cb7e6
...
...
@@ -7,6 +7,7 @@ import { LogCategories } from '../../../../../../common/logger';
import
{
FlowNodeTypeEnum
}
from
'@fastgpt/global/core/workflow/node/constant'
;
import
{
i18nT
}
from
'../../../../../../../web/i18n/utils'
;
import
z
from
'zod'
;
import
type
{
ChildResponseItemType
}
from
'../type'
;
const
logger
=
getLogger
(
LogCategories
.
MODULE
.
AI
.
TOOL_CALL
);
...
...
@@ -39,6 +40,7 @@ export const ReadFileToolParamsSchema = z.object({
});
type
FileReadParams
=
{
files
:
{
id
:
string
;
url
:
string
}[];
toolCallId
:
string
;
teamId
:
string
;
tmbId
:
string
;
...
...
@@ -47,13 +49,32 @@ type FileReadParams = {
};
export
const
dispatchReadFileTool
=
async
({
files
,
toolCallId
,
teamId
,
tmbId
,
customPdfParse
,
usageId
}:
FileReadParams
)
=>
{
const
startTime
=
Date
.
now
();
const
usages
:
ChatNodeUsageType
[]
=
[];
const
getFlowResponse
=
(
nodeResponse
:
Record
<
string
,
any
>
=
{}):
ChildResponseItemType
=>
({
flowResponses
:
[
{
...
nodeResponse
,
moduleType
:
FlowNodeTypeEnum
.
readFiles
,
moduleName
:
i18nT
(
'chat:read_file'
),
moduleLogo
:
ReadFileTooData
.
avatar
,
id
:
toolCallId
,
nodeId
:
toolCallId
,
runningTime
:
+
((
Date
.
now
()
-
startTime
)
/
1000
).
toFixed
(
2
),
totalPoints
:
usages
.
reduce
((
sum
,
item
)
=>
sum
+
item
.
totalPoints
,
0
)
}
],
flowUsages
:
usages
,
runTimes
:
0
});
try
{
const
usages
:
ChatNodeUsageType
[]
=
[];
const
readFilesResult
=
await
Promise
.
all
(
files
.
map
(
async
({
url
,
id
})
=>
{
try
{
...
...
@@ -93,21 +114,19 @@ export const dispatchReadFileTool = async ({
return
{
response
,
usages
,
nodeResponse
:
{
moduleType
:
FlowNodeTypeEnum
.
readFiles
,
moduleName
:
i18nT
(
'chat:read_file'
)
}
flowResponse
:
getFlowResponse
()
};
}
catch
(
error
)
{
logger
.
error
(
'[File Read] Compression failed, using original content'
,
{
error
});
const
response
=
`Failed to read file:
${
getErrText
(
error
)}
`
;
const
nodeResponse
=
{
errorText
:
response
};
return
{
response
:
`Failed to read file:
${
getErrText
(
error
)}
`
,
usages
:
[],
nodeResponse
:
{
moduleType
:
FlowNodeTypeEnum
.
readFiles
,
moduleName
:
i18nT
(
'chat:read_file'
),
errorText
:
`Failed to read file:
${
getErrText
(
error
)}
`
}
response
,
usages
,
flowResponse
:
getFlowResponse
(
nodeResponse
)
};
}
};
packages/service/support/outLink/runtime/utils.ts
View file @
225cb7e6
...
...
@@ -23,7 +23,10 @@ import { getLogger, LogCategories } from '../../../common/logger';
import
{
appendRedisCache
}
from
'../../../common/redis/cache'
;
import
{
getErrResponse
,
getErrText
}
from
'@fastgpt/global/common/error/utils'
;
import
{
getUsageSourceByPublishChannel
}
from
'@fastgpt/global/support/wallet/usage/tools'
;
import
{
getChatSourceByPublishChannel
}
from
'@fastgpt/global/core/chat/utils'
;
import
{
getChatSourceByPublishChannel
,
removeAIResponseCite
}
from
'@fastgpt/global/core/chat/utils'
;
import
{
WORKFLOW_MAX_RUN_TIMES
}
from
'../../../core/workflow/constants'
;
import
{
mongoSessionRun
}
from
'../../../common/mongo/sessionRun'
;
import
{
MongoChat
}
from
'../../../core/chat/chatSchema'
;
...
...
@@ -226,7 +229,8 @@ export async function outlinkInvokeChat<T extends OutlinkAppType>({
});
// Format results
let
responseContent
=
assistantResponses
const
formatAssistantResponses
=
removeAIResponseCite
(
assistantResponses
,
false
);
let
responseContent
=
formatAssistantResponses
.
map
((
response
)
=>
{
return
response
.
text
?.
content
;
})
...
...
packages/service/test/common/api/axios.test.ts
View file @
225cb7e6
import
{
describe
,
it
,
expect
,
vi
,
beforeEach
}
from
'vitest'
;
import
{
createProxyAxios
}
from
'@fastgpt/service/common/api/axios'
;
import
{
PRIVATE_URL_TEXT
}
from
'@fastgpt/service/common/system/utils'
;
type
AxiosRequestConfig
=
{
timeout
?:
number
;
...
...
@@ -86,6 +87,69 @@ describe('axios.ts', () => {
expect
(
typeof
instance
.
delete
).
toBe
(
'function'
);
expect
(
typeof
instance
.
request
).
toBe
(
'function'
);
});
it
(
'应该在请求前阻止内网地址'
,
async
()
=>
{
const
adapter
=
vi
.
fn
().
mockResolvedValue
({
data
:
{},
status
:
200
,
statusText
:
'OK'
,
headers
:
{},
config
:
{}
});
const
instance
=
createProxyAxios
({
adapter
});
await
expect
(
instance
.
get
(
'http://127.0.0.1/admin'
)).
rejects
.
toThrow
(
PRIVATE_URL_TEXT
);
expect
(
adapter
).
not
.
toHaveBeenCalled
();
});
it
(
'应该校验 baseURL 和相对路径合成后的地址'
,
async
()
=>
{
const
adapter
=
vi
.
fn
().
mockResolvedValue
({
data
:
{},
status
:
200
,
statusText
:
'OK'
,
headers
:
{},
config
:
{}
});
const
instance
=
createProxyAxios
({
baseURL
:
'http://169.254.169.254'
,
adapter
});
await
expect
(
instance
.
get
(
'/latest/meta-data/'
)).
rejects
.
toThrow
(
PRIVATE_URL_TEXT
);
expect
(
adapter
).
not
.
toHaveBeenCalled
();
});
it
(
'应该允许公网地址继续进入 adapter'
,
async
()
=>
{
const
adapter
=
vi
.
fn
().
mockResolvedValue
({
data
:
{
ok
:
true
},
status
:
200
,
statusText
:
'OK'
,
headers
:
{},
config
:
{}
});
const
instance
=
createProxyAxios
({
adapter
});
const
response
=
await
instance
.
get
(
'https://example.com/api'
);
expect
(
response
.
data
).
toEqual
({
ok
:
true
});
expect
(
adapter
).
toHaveBeenCalled
();
});
it
(
'应该允许显式关闭 SSRF 检查'
,
async
()
=>
{
const
adapter
=
vi
.
fn
().
mockResolvedValue
({
data
:
{
ok
:
true
},
status
:
200
,
statusText
:
'OK'
,
headers
:
{},
config
:
{}
});
const
instance
=
createProxyAxios
({
adapter
},
false
);
const
response
=
await
instance
.
get
(
'http://127.0.0.1/admin'
);
expect
(
response
.
data
).
toEqual
({
ok
:
true
});
expect
(
adapter
).
toHaveBeenCalled
();
});
});
describe
(
'axios 导出实例'
,
()
=>
{
...
...
packages/service/test/common/system/utils.test.ts
View file @
225cb7e6
import
{
describe
,
test
,
expect
,
beforeEach
,
afterEach
,
vi
}
from
'vitest'
;
import
{
isInternalAddress
}
from
'@fastgpt/service/common/system/utils'
;
// Mock dns module
vi
.
mock
(
'dns/promises'
,
()
=>
({
default
:
{
resolve4
:
vi
.
fn
(),
resolve6
:
vi
.
fn
()
}
}));
// Import mocked dns after mock setup
import
dns
from
'dns/promises'
;
describe
(
'SSRF Protection - isInternalAddress'
,
()
=>
{
...
...
@@ -17,8 +7,10 @@ describe('SSRF Protection - isInternalAddress', () => {
beforeEach
(()
=>
{
process
.
env
.
CHECK_INTERNAL_IP
=
'true'
;
// 清除所有 mock
vi
.
clearAllMocks
();
// 重建 DNS spy,避免真实 DNS 解析和用例之间的 mock 实现串味
vi
.
restoreAllMocks
();
vi
.
spyOn
(
dns
,
'resolve4'
).
mockRejectedValue
(
new
Error
(
'No A records'
));
vi
.
spyOn
(
dns
,
'resolve6'
).
mockRejectedValue
(
new
Error
(
'No AAAA records'
));
});
afterEach
(()
=>
{
...
...
packages/service/test/core/app/mcp.test.ts
View file @
225cb7e6
...
...
@@ -36,7 +36,7 @@ beforeEach(() => {
});
describe
(
'MCPClient'
,
()
=>
{
const
config
=
{
url
:
'http
://localhost:3000
/mcp'
,
headers
:
{
Authorization
:
'Bearer test'
}
};
const
config
=
{
url
:
'http
s://example.com
/mcp'
,
headers
:
{
Authorization
:
'Bearer test'
}
};
describe
(
'assertMCPUrlNotInternal'
,
()
=>
{
it
(
'should reject localhost MCP endpoints'
,
async
()
=>
{
...
...
pro
@
1a56dfc0
Subproject commit
70380fe53914fff72e256be0e3b4eb649ae1ad90
Subproject commit
1a56dfc0d3c2fc1dc3e4a2f23ad1847926c804a8
projects/app/src/web/core/dataset/api.ts
View file @
225cb7e6
...
...
@@ -48,7 +48,7 @@ export const delDatasetById = (id: string) => DELETE(`/core/dataset/delete?id=${
export
const
postDatasetSync
=
(
data
:
PostDatasetSyncParams
)
=>
POST
(
`/proApi/core/dataset/datasetSync`
,
data
,
{
timeout
:
600000
})
.
catch
()
;
});
export
const
postCreateDatasetFolder
=
(
data
:
CreateDatasetFolderBody
)
=>
POST
(
`/core/dataset/folder/create`
,
data
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment