浏览代码

feat: 服务监听 0.0.0.0,只信任指定反向代理的转发头

- systemd 部署改为监听 0.0.0.0:8787,局域网可以直接访问
- 新增 AGENT_SERVER_TRUST_PROXY:默认不信任 X-Forwarded-*,
  可以填写反向代理的 IP / CIDR(逗号分隔,推荐),或填 true 信任所有来源
- 修复:原来无条件信任 X-Forwarded-For,直接访问的客户端可以伪造 IP
  绕过登录失败限流
- 经代理以 HTTPS 访问时,登录 Cookie 根据 X-Forwarded-Proto 加上 Secure 标记
- 文档:README 环境变量说明、10-deployment 部署说明

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Jerry Liang 1 周之前
父节点
当前提交
12184873b1
共有 5 个文件被更改,包括 17 次插入 和 2 次删除
  1. 2 1
      README.md
  2. 11 0
      apps/server/src/config.ts
  3. 1 1
      apps/server/src/http/server.ts
  4. 2 0
      deploy/agent-server.service
  5. 1 0
      docs/design/10-deployment.md

+ 2 - 1
README.md

@@ -50,7 +50,8 @@ pnpm dev:admin           # 后台:http://127.0.0.1:5173/admin/(代理到 878
 
 | 环境变量 | 默认值 | 说明 |
 |---|---|---|
-| `AGENT_SERVER_HOST` / `AGENT_SERVER_PORT` | `127.0.0.1` / `8787`(开发脚本为 8788) | 监听地址 |
+| `AGENT_SERVER_HOST` / `AGENT_SERVER_PORT` | `127.0.0.1` / `8787`(开发脚本为 8788;systemd 部署为 `0.0.0.0`) | 监听地址 |
+| `AGENT_SERVER_TRUST_PROXY` | `false` | 反向代理的 IP / CIDR(逗号分隔,推荐),或 `true`(信任所有来源) |
 | `AGENT_SERVER_DATA_DIR` | 生产 `/srv/agent-server`,开发 `.dev-data/` | 数据目录 |
 | `AGENT_SERVER_SHARED_DIR` | `/srv/agent-server` | 共享只读资源(runtime、skills) |
 | `AGENT_SERVER_ADMIN_PASSWORD` | (随机生成) | 仅首次启动时使用 |

+ 11 - 0
apps/server/src/config.ts

@@ -19,9 +19,20 @@ export const config = {
   adminPassword: env.AGENT_SERVER_ADMIN_PASSWORD,
   /** 后台静态资源目录(生产部署时由服务托管) */
   adminDist: env.AGENT_SERVER_ADMIN_DIST,
+  /**
+   * 信任哪些反向代理的 X-Forwarded-* 头:未设置或 false 为不信任;true 为信任所有来源(仅在服务只能经由代理访问时使用);
+   * 也可以是逗号分隔的代理 IP / CIDR(推荐),只信任来自这些地址的转发信息,直接访问的客户端无法伪造
+   */
+  trustProxy: parseTrustProxy(env.AGENT_SERVER_TRUST_PROXY),
   home: os.homedir(),
   isDev,
   logLevel: env.LOG_LEVEL ?? 'info',
 }
 
 export const dbPath = () => path.join(config.dataDir, 'data', 'agent.db')
+
+function parseTrustProxy(v: string | undefined): boolean | string[] {
+  if (!v || v === 'false') return false
+  if (v === 'true') return true
+  return v.split(',').map(s => s.trim()).filter(Boolean)
+}

+ 1 - 1
apps/server/src/http/server.ts

@@ -13,7 +13,7 @@ import adminRoutes from './routes/admin'
 import v1Routes from './routes/v1'
 
 export async function buildServer(app: App, opts: { logger: FastifyServerOptions['loggerInstance'] }) {
-  const f = Fastify({ loggerInstance: opts.logger, trustProxy: true, bodyLimit: 2 * 1048576 })
+  const f = Fastify({ loggerInstance: opts.logger, trustProxy: config.trustProxy, bodyLimit: 2 * 1048576 })
 
   await f.register(cookie)
   await f.register(multipart)

+ 2 - 0
deploy/agent-server.service

@@ -14,6 +14,8 @@ Environment=NODE_ENV=production
 Environment=HOME=/srv/agent-server/home
 Environment=AGENT_SERVER_DATA_DIR=/srv/agent-server
 Environment=AGENT_SERVER_ADMIN_DIST=/srv/agent-server/app/admin
+# 监听所有网卡(局域网可访问);前面加反向代理后改回 127.0.0.1 并设置 AGENT_SERVER_TRUST_PROXY=true
+Environment=AGENT_SERVER_HOST=0.0.0.0
 ExecStart=/usr/local/bin/node dist/main.js
 Restart=on-failure
 RestartSec=3

+ 1 - 0
docs/design/10-deployment.md

@@ -31,6 +31,7 @@
 首次安装顺序:`sudo deploy/install.sh` → `sudo deploy/setup-runtime.sh` → `deploy/deploy.sh` → 在后台"概览"页登录 Claude(或执行 `agent-claude auth login`)。
 
 ## 4. systemd 加固(deploy/agent-server.service)
+- 监听 `0.0.0.0:8787`(局域网可直接访问,HTTP 明文);以后加 HTTPS 反向代理时改回 `127.0.0.1`,并设置 `AGENT_SERVER_TRUST_PROXY=true`
 - `User=agentsvc`;`ProtectSystem=strict` + 只有 home/data/workspaces/logs/knowledge 可写;`ProtectHome=yes`;`PrivateTmp=yes`;`UMask=0077`
 - `NoNewPrivileges=yes`;`InaccessiblePaths=` docker.sock 与 LXD socket(纵深防御,agentsvc 本身也没有权限)
 - 不能开启 `RestrictNamespaces`(bubblewrap 需要)