Skip to content

Security: restore Jupyter origin and XSRF protection - #654

Open
GOLDKUN wants to merge 2 commits into
chaitin:mainfrom
GOLDKUN:fix/jupyter-origin-xsrf
Open

Security: restore Jupyter origin and XSRF protection#654
GOLDKUN wants to merge 2 commits into
chaitin:mainfrom
GOLDKUN:fix/jupyter-origin-xsrf

Conversation

@GOLDKUN

@GOLDKUN GOLDKUN commented Sep 2, 2026

Copy link
Copy Markdown

问题

Jupyter guest 启动参数设置了 --ServerApp.allow_origin='*'--ServerApp.disable_check_xsrf=True。同时 daemon 的 Jupyter 代理路径不使用 daemon Bearer Token,导致泄露 Jupyter URL Token 后缺少 Origin/XSRF 保护层。

影响

恶意站点或被动攻击者在获得 Token 后,可跨站调用 Jupyter API、访问文件或操作 Kernel。涉及 CWE-352、CWE-942。

修复内容

  • 移除任意 Origin 通配配置,显式使用 Jupyter 默认的同源策略。
  • 显式设置 --ServerApp.disable_check_xsrf=False
  • 保留 Jupyter URL Token 认证机制。
  • 增加回归测试,确保命令不会重新启用通配 Origin 或关闭 XSRF。
  • 更新安全部署文档。

验证

  • go test ./pkg/driver ./pkg/agentcompose/proxy
  • git diff --check

@monkeyscan

monkeyscan Bot commented Sep 2, 2026

Copy link
Copy Markdown

评审任务执行失败。

@GOLDKUN

GOLDKUN commented Sep 2, 2026

Copy link
Copy Markdown
Author

已检查最新评论与 Review 状态。当前唯一评论是“评审任务执行失败”,没有具体代码或测试修订意见,因此没有制造无意义的代码变更或空提交。

PR 中的 Jupyter Origin/XSRF 修复及回归测试保持不变,现有 CI 检查均通过;如需要重新执行自动评审,可直接重新触发该 PR 的 Review。

@kingfs

kingfs commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

@monkeyscan review pr

@monkeyscan

monkeyscan Bot commented Sep 2, 2026

Copy link
Copy Markdown

PR Title: Security: restore Jupyter origin and XSRF protecti...

Commit: 916e2b9

本次改动为 Jupyter 安全加固:将 guest Jupyter 启动命令从 --ServerApp.allow_origin='*' --ServerApp.disable_check_xsrf=True 改为 --ServerApp.allow_origin='' --ServerApp.disable_check_xsrf=False,实质是恢复 Jupyter Server 的默认安全配置(同源校验 + XSRF 防护),修复了此前放开任意跨源且关闭 CSRF 校验的缺陷。配套改动包括:(1) 在 runtime_mount_manifest_test.go 的后台 Jupyter 启动命令测试中新增断言,防止弱安全配置(通配 origin / 关闭 XSRF)被重新引入,并校验安全配置被显式保留;(2) 更新 SECURITY.md 部署指引,说明 guest 需保持同源与 XSRF 保护。

审查评估:改动范围小、意图清晰、方向正确。前台 exec 与后台 nohup 两条路径共用同一命令构造函数,改动一致生效;driver 自身就绪探测仅发起 GET /api/kernelspecs(无 Origin 头、GET 不需 XSRF token),不受影响。潜在回归点在于同源约束是否影响经 agent-compose 代理的浏览器访问——这取决于代理是否保留原始 Host 头,属标准反代行为,但本次 diff 无法证实存在 Host 改写,故不构成高置信度缺陷。shell 空引号 allow_origin='' 语义正确,不会被丢弃。总体未发现可证明的高置信度正确性、安全或回归问题,无新增 finding。

@winterfx winterfx left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

结论

安全问题是真的,方向也对,但这个改动单独合并会让经代理访问的 JupyterLab 不可用。建议连同代理侧一行改动一起合。

我用 docker driver 起了真实沙箱做了三组对照实测(daemon 127.0.0.1:7410,guest Jupyter 发布在 127.0.0.1:32769)。

1. 漏洞确实存在,可复现

当前 main,通过代理模拟跨站攻击:

POST /jupyter/<sandbox>/api/kernels
Origin: http://evil.example
Cookie: <受害者会话 cookie>       # 不带 _xsrf
→ 201 Created

跨站页面仅凭受害者 cookie 就能创建 kernel,等价于任意代码执行。本 PR 要修的问题成立。

2. 但只改 guest 启动参数会打断 JupyterLab

打上本 PR 后,同一套流程里完全正常的浏览器请求开始失败:

请求 main 本 PR
GET /lab?token=… 200 200
GET /api/kernelspecs(cookie,无 Origin) 200 200
POST /api/kernels(cookie + 同源 Origin + _xsrf 201 404
kernel WebSocket(cookie + Origin) 101 403

guest 内 Jupyter 自己打的日志:

[W] Blocking Cross Origin API request for /jupyter/…/api/kernels.
    Origin: http://127.0.0.1:7410, Host: 127.0.0.1:32769
[W] 404 POST /jupyter/…/api/kernels
[W] Blocking Cross Origin WebSocket Attempt.  Origin: http://127.0.0.1:7410, Host: 127.0.0.1:32769
[W] 403 GET /jupyter/…/api/kernels/…/channels

表现是 Lab 页面能打开,但建不了 kernel、连不上 websocket,实际不可用。

根因在代理侧 pkg/agentcompose/proxy/proxy.go:71

req.Out.Host = target.Host   // 重写为 guest 地址 127.0.0.1:32769

浏览器的 Originhttp://127.0.0.1:7410)原样透传,于是 Jupyter 看到 Origin != Host。jupyter_server 2.18.2 中 base/handlers.py:445 的绕过分支只对 token 认证的请求生效(auth/identity.py:542 should_check_origin),而首次 ?token= 跳转之后浏览器改用 cookie 认证,绕过失效,APIHandler.prepare 直接 404,websocket 403。

--ServerApp.allow_origin='*' 当初大概率正是为了绕开这个 Host 重写才加上的。

3. 建议:代理侧一并改

 // pkg/agentcompose/proxy/proxy.go:71
-req.Out.Host = target.Host
+req.Out.Host = req.In.Host

SetURL 会把 Out.Host 置空,所以必须在其后显式赋值。)Host 透传后 Origin == Host 自然成立,Jupyter 默认同源策略直接通过;连接目标仍由 Out.URL.Host 决定,不受影响。

带上这一行后重测:

请求 结果
GET /lab?token=… / GET /api/kernelspecs 200 ✅
POST /api/kernels(正常浏览器请求) 201 ✅
kernel WebSocket 101 ✅
Origin: http://evil.example + cookie 404 🔒
同源但无 _xsrf 403 🔒
无 Origin 无 _xsrf 403 🔒

功能完全恢复,且三种攻击形态全部被挡住。go test ./pkg/driver/... ./pkg/agentcompose/proxy/... 通过。

driver 自身的就绪探测(pkg/driver/jupyter_guest.go:74GET /api/kernelspecs?token=…)走 token 认证绕过路径,两种改法下都不受影响。

4. 关于 CI 全绿

CI 绿不能作为本改动的验证依据:仓库里对 Jupyter 唯一的运行时接触就是上面那条 token 认证的就绪探测,.github/workflows/ 下没有任何浏览器级 e2e。本 PR 新增的断言也只校验启动命令字符串,覆盖不到行为。

5. 测试的小意见

新增断言放进了 TestBackgroundJupyterLaunchCommandStartsBeforeJupyterLabImportProbe(一个测启动顺序的用例),而且是整串精确匹配 --ServerApp.allow_origin='' --ServerApp.disable_check_xsrf=False,参数顺序一动就红。建议拆成独立测试,两个 flag 分别断言。

@GOLDKUN

GOLDKUN commented Sep 3, 2026

Copy link
Copy Markdown
Author

已按最新评论完成修订:

  • Jupyter ReverseProxy 在 SetURL 后将 req.Out.Host 设置为入口请求的 req.In.Host,而不是 guest target host。
  • 这样浏览器的 Origin 与 Jupyter 看到的 Host 保持一致,恢复 cookie 认证后的 POST API 和 Kernel WebSocket 可用性,同时不改变实际连接目标 Out.URL.Host
  • 新增代理 Host/Origin 透传测试,使用真实 backend 验证 Host 和 Origin 到达后端。
  • 将 Jupyter 安全 flag 断言拆为独立测试,并分别验证 Origin 通配符未启用、XSRF 未关闭;同时覆盖前台和后台启动命令。

本地验证:

  • go test ./pkg/agentcompose/proxy ./pkg/driver -run 'TestJupyterProxyPreservesPublicHostForSameOriginChecks|TestJupyterLaunchCommandKeepsOriginAndXSRFProtection' -count=1
  • git diff --check

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants