Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .github/workflows/test-sandbox.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ jobs:
node-version: '20'
cache: 'pnpm'

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- uses: actions/setup-python@v5
with:
python-version: '3.11'
Expand Down
1 change: 1 addition & 0 deletions document/content/self-host/upgrading/4-15/4150.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ description: 'FastGPT V4.15.0 更新说明'
3. 非管理员/访客,触发余额不足时候,提示优化。
4. 无创建权限时,隐藏模板功能。
5. 加强第三方知识库请求的 SSRF 防护。
6. codex-sandbox 加强 AST 检查,防止绕过安全检查。

## 🐛 修复

Expand Down
2 changes: 1 addition & 1 deletion document/data/doc-last-modified.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:35:13+08:00",
"content/self-host/upgrading/4-15/4150.mdx": "2026-04-28T22:44:51+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",
Expand Down
2 changes: 1 addition & 1 deletion packages/service/core/workflow/dispatch/tools/http468.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
Object.keys(results).length > 0 ? results : rawResponse
};
} catch (error) {
logger.warn('HTTP tool request failed', { error });
logger.warn('HTTP tool request failed', { error, httpReqUrl });

// @adapt
if (node.catchError === undefined) {
Expand Down
125 changes: 54 additions & 71 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

68 changes: 36 additions & 32 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
packages:
- packages/*
- projects/*
- scripts/icon
- sdk/*

- projects/app
- projects/code-sandbox
- projects/marketplace

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching workspace package globs to an explicit list drops projects/volume-manager from the workspace, even though it contains a package.json and is referenced by CI/deploy configs. If this is not intentional, re-add it (or restore projects/*) to avoid breaking installs and lockfile consistency.

Suggested change
- projects/marketplace
- projects/marketplace
- projects/volume-manager

Copilot uses AI. Check for mistakes.
- projects/mcp_server
- pro/admin
- pro/sso

- scripts/icon
- sdk/*

catalog:
"@fastgpt-sdk/logger": 0.1.2
"@fastgpt-sdk/otel": 0.1.2
"@fastgpt-sdk/storage": 0.6.15
"@modelcontextprotocol/sdk": ^1
"@node-rs/jieba": 2.0.1
"@svgr/webpack": ^6.5.1
"@tanstack/react-query": ^4.24.10
"@types/js-yaml": ^4.0.9
"@types/jsonwebtoken": ^9.0.3
"@types/lodash": ^4
"@types/mime-types": ^3.0.1
"@types/node": ^20
"@types/react": ^18
"@types/react-dom": ^18
"@types/request-ip": ^0.0.38
"@typescript-eslint/eslint-plugin": ^6.21.0
"@typescript-eslint/parser": ^6.21.0
"@vitest/coverage-v8": ^4.1.5
'@fastgpt-sdk/logger': 0.1.2
'@fastgpt-sdk/otel': 0.1.2
'@fastgpt-sdk/storage': 0.6.15
'@modelcontextprotocol/sdk': ^1
'@node-rs/jieba': 2.0.1
'@svgr/webpack': ^6.5.1
'@tanstack/react-query': ^4.24.10
'@types/js-yaml': ^4.0.9
'@types/jsonwebtoken': ^9.0.3
'@types/lodash': ^4
'@types/mime-types': ^3.0.1
'@types/node': ^20
'@types/react': ^18
'@types/react-dom': ^18
'@types/request-ip': ^0.0.38
'@typescript-eslint/eslint-plugin': ^6.21.0
'@typescript-eslint/parser': ^6.21.0
'@vitest/coverage-v8': ^4.1.5
ahooks: ^3.9.5
"@chakra-ui/anatomy": ^2
"@chakra-ui/icons": ^2
"@chakra-ui/next-js": ^2
"@chakra-ui/react": ^2
"@chakra-ui/styled-system": ^2
"@chakra-ui/system": ^2
"@emotion/react": ^11
"@emotion/styled": ^11
'@chakra-ui/anatomy': ^2
'@chakra-ui/icons': ^2
'@chakra-ui/next-js': ^2
'@chakra-ui/react': ^2
'@chakra-ui/styled-system': ^2
'@chakra-ui/system': ^2
'@emotion/react': ^11
'@emotion/styled': ^11
axios: 1.13.6
chalk: ^5.6.2
date-fns: ^3.6.0
Expand Down Expand Up @@ -72,7 +76,7 @@ catalog:
zod: ^4

onlyBuiltDependencies:
- "@parcel/watcher"
- '@parcel/watcher'
- bufferutil
- canvas
- core-js
Expand All @@ -86,7 +90,7 @@ onlyBuiltDependencies:
- vue-demi

overrides:
"@types/react": ^18
"@types/react-dom": ^18
'@types/react': ^18
'@types/react-dom': ^18
react: ^18
react-dom: ^18
2 changes: 1 addition & 1 deletion projects/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fastgpt/app",
"version": "4.14.16",
"version": "4.15.0",
"private": false,
"scripts": {
"dev": "pnpm run build:workers && next dev",
Expand Down
34 changes: 25 additions & 9 deletions projects/code-sandbox/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# --------- Build Stage -----------
FROM oven/bun:1-alpine AS builder
FROM node:24-alpine AS builder
WORKDIR /app

ARG proxy

# 安装 pnpm
RUN apk add --no-cache nodejs npm && npm install -g pnpm@10.33.2
RUN npm install -g pnpm@10.33.2

# 复制 workspace 配置和依赖包
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json tsconfig.json ./
COPY packages/global ./packages/global
COPY packages/service ./packages/service
COPY sdk ./sdk
Expand All @@ -24,22 +24,38 @@ RUN if [ -z "$proxy" ]; then \
pnpm install --frozen-lockfile --ignore-scripts --registry=https://registry.npmmirror.com; \
fi

# 先构建 SDK workspace 包,确保 dist 入口可被 bun build 解析
# 先构建 SDK workspace 包,确保 dist 入口可被打包工具解析
RUN pnpm --filter @fastgpt-sdk/logger --filter @fastgpt-sdk/otel --filter @fastgpt-sdk/storage build

# 编译主入口文件
RUN cd /app/projects/code-sandbox && pnpm build

# ===== Runner Stage =====
FROM oven/bun:1-alpine AS runner
WORKDIR /app
FROM node:24-alpine AS runner
WORKDIR /app/code-sandbox

ARG proxy

# 复制编译产物(包含 worker 文件,不需要 node_modules)
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories

# 安装 pnpm(用于 runner 阶段安装 prod 依赖)
RUN npm install -g pnpm@10.33.2

# 复制编译产物(index/worker 已 bundle 自身静态依赖,自包含)
COPY --from=builder /app/projects/code-sandbox/dist /app/code-sandbox

RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
# 安装 worker 运行时白名单模块
# worker 通过 safeRequire(name) 按用户白名单动态加载(lodash/dayjs/moment/uuid/crypto-js/qs),
# 这是变量调用,bundler 无法静态分析,必须在 runner 中以 node_modules 形式存在。
# 单独的 runtime.package.json 与主 package.json 解耦,避免 catalog: 引用无法解析。
COPY projects/code-sandbox/runtime.package.json ./package.json

RUN if [ -z "$proxy" ]; then \
pnpm install --prod --no-frozen-lockfile --ignore-scripts; \
else \
pnpm install --prod --no-frozen-lockfile --ignore-scripts --registry=https://registry.npmmirror.com; \
fi && \
pnpm store prune || true

# 安装 Python、依赖包及工具
RUN apk add --no-cache python3 py3-pip libffi util-linux && \
Expand All @@ -60,4 +76,4 @@ ENV SANDBOX_PORT=3000

EXPOSE 3000

CMD ["bun", "/app/code-sandbox/index.js"]
CMD ["node", "/app/code-sandbox/index.js"]
29 changes: 16 additions & 13 deletions projects/code-sandbox/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# FastGPT Code Sandbox

基于 Bun + Hono 的代码执行沙盒,支持 JS 和 Python。采用进程池架构,预热长驻 worker 进程,通过 stdin/stdout JSON 协议通信,消除每次请求的进程启动开销。
基于 Node + Hono 的代码执行沙盒,支持 JS 和 Python。采用进程池架构,预热长驻 worker 进程,通过 stdin/stdout JSON 协议通信,消除每次请求的进程启动开销。

## 架构

```
HTTP Request → Hono Server → Process Pool → Worker (long-lived) → Result
┌──────────────┐
│ JS Workers │ bun run worker.ts (×N)
│ JS Workers │ node worker.js (×N)
│ Py Workers │ python3 worker.py (×N)
└──────────────┘
stdin: JSON task → stdout: JSON result
```

- **进程池**:启动时预热 N 个 worker 进程(默认 20),请求到达时直接分配空闲 worker,执行完归还池中
- **JS 执行**:Bun worker 进程 + 安全 shim(禁用 Bun API、冻结 Function 构造器、require 白名单)
- **JS 执行**:Node worker 进程 + 安全 shim(冻结 Function 构造器、危险全局对象遮蔽、require 白名单)
- **Python 执行**:python3 worker 进程 + `__import__` 拦截 + resource 资源限制
- **网络请求**:统一通过 `SystemHelper.httpRequest()` / `system_helper.http_request()` 收口,内置 SSRF 防护
- **并发控制**:请求数超过池大小时自动排队,worker 崩溃自动重启补充
Expand All @@ -40,14 +40,17 @@ HTTP Request → Hono Server → Process Pool → Worker (long-lived) → Result
## 快速开始

```bash
# 安装依赖
bun install
# 安装依赖(在 monorepo 根目录执行)
pnpm install

# 开发运行
bun run src/index.ts
# 开发运行(带 watch)
cd projects/code-sandbox && pnpm dev

# 运行测试
bun run test
cd projects/code-sandbox && pnpm test

# 构建
cd projects/code-sandbox && pnpm build && pnpm start
```

## Docker
Expand Down Expand Up @@ -194,7 +197,7 @@ test/

```bash
cd projects/code-sandbox
bun add <package-name>
pnpm add <package-name>
```

2. **加入白名单**(环境变量 `SANDBOX_JS_ALLOWED_MODULES`):
Expand Down Expand Up @@ -246,7 +249,7 @@ your-new-package
### JS

- `require()` 白名单,非白名单模块直接拒绝
- `Bun.spawn`、`Bun.write`、`Bun.serve` 等 API 禁用
- 危险全局对象(`process`、`globalThis`、`global`、`Bun` 等)通过函数参数遮蔽,用户代码无法访问
- `Function` 构造器冻结,阻止 `constructor.constructor` 逃逸
- `process.env` 清理,仅保留必要变量
- `fetch`、`XMLHttpRequest`、`WebSocket` 禁用
Expand Down Expand Up @@ -283,13 +286,13 @@ your-new-package

```bash
# 全部测试(332 cases)
bun run test
pnpm test

# 单个文件
bunx vitest run test/security/security.test.ts
pnpm exec vitest run test/unit/security.test.ts

# 带详细输出
bunx vitest run --reporter=verbose
pnpm exec vitest run --reporter=verbose

# 压测(需先启动服务)
bash test/benchmark/bench-sandbox.sh
Expand Down
26 changes: 15 additions & 11 deletions projects/code-sandbox/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,27 @@ echo "Building sandbox..."
# 清理旧的构建产物
rm -rf dist

# 编译主入口文件,打包所有依赖
echo "Building main entry..."
bun build src/index.ts --outdir dist --target bun --minify --packages=bundle
# 编译入口(配置见 tsdown.config.ts):
# - 同时打包 index 和 worker 两个独立 bundle
# - 所有 npm 依赖均打入 bundle(noExternal),仅保留 Node 内置模块外部化
# - 扁平输出到 dist 根目录
echo "Building entries..."
pnpm exec tsdown

# 编译 JS worker,打包所有依赖
echo "Building JS worker..."
bun build src/pool/worker.ts --outdir dist --target bun --minify --packages=bundle
mv dist/worker.js dist/worker.ts
# package.json 已声明 type:module,直接改后缀为 .js
mv dist/index.mjs dist/index.js
mv dist/worker.mjs dist/worker.js

# 复制 Python worker(Python 不需要编译
# Python worker 不需要编译,直接复制
echo "Copying Python worker..."
cp src/pool/worker.py dist/worker.py

echo ""
echo "Build complete!"
echo " - index.js: $(du -h dist/index.js | cut -f1)"
echo " - worker.ts: $(du -h dist/worker.ts | cut -f1)"
echo " - index.js: $(du -h dist/index.js | cut -f1)"
echo " - worker.js: $(du -h dist/worker.js | cut -f1)"
echo " - worker.py: $(du -h dist/worker.py | cut -f1)"
echo ""
echo "✅ dist 目录现在是完全独立的,不需要 node_modules"
echo "ℹ️ worker 通过 safeRequire(name) 在运行时动态加载白名单模块"
echo " (lodash/dayjs/moment/uuid/crypto-js/qs),这些依赖必须以 node_modules"
echo " 形式存在于运行时,由 runtime.package.json 在 runner 阶段安装。"
21 changes: 13 additions & 8 deletions projects/code-sandbox/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "@fastgpt/code-sandbox",
"version": "5.0.0",
"description": "FastGPT Code Sandbox - Bun + Hono + 统一子进程模型",
"description": "FastGPT Code Sandbox - Node + Hono + 统一子进程模型",
"author": "",
"private": true,
"license": "UNLICENSED",
"type": "module",
"scripts": {
"dev": "bun run --watch src/index.ts",
"start": "bun run src/index.ts",
"dev": "tsx watch src/index.ts",
"start": "node dist/index.js",
"build": "sh build.sh",
"test": "vitest run",
"test:watch": "vitest"
Expand All @@ -17,8 +18,11 @@
"pnpm": "10.x"
},
"dependencies": {
"@fastgpt-sdk/logger": "workspace:*",
"@fastgpt/service": "workspace:*",
"@fastgpt-sdk/logger": "^0.1.2",
"@hono/node-server": "^1.13.7",
"ipaddr.js": "^2.3.0",
"acorn": "^8.15.0",
"acorn-walk": "^8.3.4",
"axios": "catalog:",
"crypto-js": "^4.2.0",
"dayjs": "catalog:",
Expand All @@ -32,10 +36,11 @@
"zod": "catalog:"
},
"devDependencies": {
"@types/bun": "^1.2.4",
"@types/node": "catalog:",
"tsdown": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:",
"@vitest/coverage-v8": "catalog:",
"typescript": "catalog:"
"@vitest/coverage-v8": "catalog:"
}
}
16 changes: 16 additions & 0 deletions projects/code-sandbox/runtime.package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@fastgpt/code-sandbox-runtime",
"version": "5.0.0",
"description": "Runtime-only deps for code-sandbox worker. Index/worker bundle are self-contained; this package.json exists solely to install whitelisted modules that the worker loads at runtime via safeRequire(name).",
"private": true,
"license": "UNLICENSED",
"type": "module",
"dependencies": {
"crypto-js": "4.2.0",
"dayjs": "1.11.19",
"lodash": "4.17.23",
"moment": "2.30.1",
"qs": "6.13.1",
"uuid": "9.0.1"
}
}
Loading
Loading