升级 Dify v1.16.1 后,我先把 Agent 的默认密码换了
Dify v1.16.1 引入多项安全增强:沙箱网络隔离、Squid 代理 ACL 白名单控制出站、内部服务间 Bearer Token 认证、Jinja2 模板沙箱化。但两个关键环境变量 DIFY_AGENT_API_TOKEN 和 AGENT_BACKEND_API_TOKEN 默认值仍为明文公开的 'dify-agent-run-token-for-dev-only',生产环境若未手动修改将导致 agent_backend 接口被绕过权限直接调用。
上周我在本地把 Dify 从 v1.15.x 升到 v1.16.1。之前一直拖着没升,怕迁移出问题。看了下 release note,7 月 28 号发的,四个 additive 迁移,不停机,心里踏实了点。docker compose pull && docker compose up -d,跑完,日志干净,迁移顺利。
顺手点了个 Agent 工作流跑测试。LLM 正常回了,工具调了,沙箱代码也执行了,一切正常。
然后我习惯性扫了一眼 .env。
DIFY_AGENT_API_TOKEN=dify-agent-run-token-for-dev-only

默认值。没改。
愣了两秒。这串字符明晃晃写在 Dify 的 GitHub 仓库、docker-compose 模板和官方文档里。全世界搜得到。要是生产环境——任何能摸到容器网络的人,同机器上别的容器也好、被攻破的边缘服务也好、内网里横向移动的攻击者也好——拿到这串字符就能直接调 agent_backend,绕过 API 层所有业务逻辑和权限控制。
你的 Agent 后门钥匙,挂在门把手上。
我为什么会去翻 .env?v1.16.1 的 release note 专门提到了两个新环境变量,我想看下默认值长什么样。一翻,好家伙,跟 GitHub 仓库里的一模一样。Dify 不是藏着掖着,它明明白白告诉你这是开发用的默认值。但多少人升级后会去翻 .env?大部分人 docker compose up -d 跑完看到服务起来了就走了。
这个值为什么危险
v1.16.1 改了什么?核心就俩字:安全。

沙箱网络之前跟 API、worker、数据库挤同一个默认网络。沙箱里跑的代码——用户上传的工具脚本、LLM 动态生成的工作流代码——理论上能发 HTTP 请求访问这些内部服务。经典 SSRF:沙箱本该只能出去访问外部 API,结果内网全暴露了。
举个具体场景。同一台机器上跑了 Dify 和一个不太安全的 Web 服务。那个服务被打穿了,攻击者拿到容器网络访问权。v1.16.0 及以前,他可以从那个容器直接访问 Dify 的 API、worker 甚至数据库——没有任何隔离。v1.16.1 之后,他最多碰到沙箱网络里的东西,核心业务碰不到。就算网络层面漏了,没有 Bearer Token 也调不动。
现在 local_sandbox 被挪到两个专用网络,agent_sandbox_network 和 local_sandbox_proxy_network,跟主业务网络隔开了。沙箱想出去,得过 agent_ssrf_proxy——一个 Squid 正向代理。代理用 ACL 卡死白名单:只允许访问 API 的 /files/ 端点和 agent_backend 的 /agent-stub/ 路径,其他一律 deny。
内部服务通信也改了。API、worker、agent_backend 互调全走 Bearer Token 认证。两个环境变量:DIFY_AGENT_API_TOKEN 和 AGENT_BACKEND_API_TOKEN。不带 token 调内部接口,直接 401。Jinja2 模板渲染也换成了 SandboxedEnvironment,堵工作流代码节点的模板注入——之前恶意 Jinja2 语法能调 Python 内建函数读文件、执行命令,现在属性和方法访问被严格限制了。
改动方向都对。该堵的洞堵了。
但两个 token 的默认值都是 dify-agent-run-token-for-dev-only。开发环境图方便,行。生产环境忘了改呢?Dify 不会拦你——不检测、不警告、不阻止你带着默认值跑起来。它就这么静悄悄起来了,带着一把全世界都知道的钥匙。

动手改
先把 token 换了。Python 标准库生成,不需要装额外依赖:
python -c 'import secrets; print(secrets.token_urlsafe(32))'跑两遍,拿到两个不同字符串。每个大概 43 个字符长,URL-safe,没有特殊字符,直接塞进 .env 不会有转义问题。别手写几个随机字符凑数——人写的"随机"一点都不随机。secrets.token_urlsafe 用的是系统级 CSPRNG,密码学安全的。
分别给 DIFY_AGENT_API_TOKEN 和 AGENT_BACKEND_API_TOKEN 设上。文档说两边值要一致——指的是同一个 token 在 api、worker、agent_backend 三个服务里保持一致,不是说两个不同 token 要设成同一个值。我选了两个不同的随机串,万一一个泄露不至于全军覆没。
还有一个容易漏的:这两个变量得同时传给 api、worker、agent_backend 三个服务。Dify 默认 compose 文件已经配好了,但如果自定义过服务定义,翻一下这三个服务的 environment 段,确认两个变量都在。漏了一个的话服务间认证对不上,Agent 跑起来直接报 401,排查起来挺烦的。
然后是 docker-compose。这步容易忽略。网络拓扑变了,光改 .env 不够。拉最新 compose 文件,有几件事得确认。
local_sandbox 不在默认网络了。如果之前自定义过 compose——改过端口映射、挂过额外卷、加过自定义服务——别直接拿新文件覆盖,手动把网络配置合并进去。
agent_ssrf_proxy 是新增服务。Squid 的配置文件在 ssrf_proxy/squid.conf,建议看一眼 ACL 规则。默认白名单是 API 的 /files/ 和 agent_backend 的 /agent-stub/。如果在 agent_backend 上挂了自定义 stub 路径,或者有别的需要沙箱访问的内部端点,得手动加到 ACL 里。Squid 的 ACL 语法不复杂,但改完得重启 agent_ssrf_proxy 容器才生效。
确认完,拉镜像重启:
docker compose pull
docker compose up -d验证
重启完别急着走。验一下,确认改的东西真生效了。
先验 Squid ACL。从沙箱容器内,通过代理 curl 一个不在白名单上的地址:
docker exec -it <sandbox_container> \
curl -x http://agent_ssrf_proxy:3128 http://api:5001/health返回 403 或连接被重置,ACL 拦住了,对了。再试白名单内的 /files/ 路径,应该能通。
再验 Bearer Token。直接 curl agent_backend,故意不带 token:
curl http://localhost:5002/agent-stub/返回 401,认证生效。带上正确 token 再试,应该 200。
还可以验网络隔离。docker network inspect agent_sandbox_network 看看沙箱容器是不是真跟 API、worker 不在同一个网络里。docker exec 进沙箱容器 ping 一下 API 容器,ping 不通就对了。
有个坑。我那天验的时候,第一次 curl 沙箱代理返回的不是 403 而是 connection refused。排查了十分钟,发现是因为之前自定义过 compose 的服务名,沙箱的 HTTP_PROXY 环境变量还指向旧的服务名。改成 agent_ssrf_proxy 之后就好了。如果也自定义过 compose,这个坑大概率会遇到。先检查沙箱容器的 HTTP_PROXY 有没有正确指向代理服务,再往下验。
那天验完快一点了。关电脑前又扫了眼 .env,确认两个 token 都不是默认值。踏实。
升级前后
v1.16.0 及以前v1.16.1沙箱网络与所有服务同网络隔离到agent_sandbox_network + local_sandbox_proxy_network出站控制无限制Squid 代理 + ACL 白名单(/files/、/agent-stub/)内部认证无Bearer Token模板渲染标准 Jinja2SandboxedEnvironment几句实在话
Dify 这次干得漂亮。安全从"顺带修 bug"变成了基础设施级别的变更——网络隔离、代理 ACL、Bearer 认证、模板沙箱化,该有的都有了。SandboxedEnvironment 那个改动尤其值得说一句,工作流代码节点的模板注入之前确实是个洞,堵得好。
但默认 token 这个事,说句不好听的。
dify-agent-run-token-for-dev-only,明文写在文档和 compose 模板里,GitHub 上一搜就有。给个默认值图开发方便,理解。但生产部署的时候 Dify 不强制你改——不检测、不警告、不阻止你带着默认值跑。有些项目怎么做的?首次启动自动生成随机 token 写回配置文件,或者检测到默认值直接拒绝启动、强制你改。Dify 没做这层。安全工具递到手上了,用不用靠自觉。
不是要求 Dify 做保姆。但一个带 Agent 沙箱的生产级平台,默认 token 公开这件事,至少在启动日志里打个 WARNING 行不行?成本几乎为零,效果立竿见影。安全圈有句话叫"default deny"——默认拒绝。Dify 的做法反过来了,默认放行,默认给一把公开的钥匙,改不改你自己看着办。开箱即用和安全默认之间,总得有人做选择。Dify 选了前者,后者的责任就落到了部署者头上。
这个锅用户自己背。
别把 Agent 当可信程序。沙箱里跑的代码——我自己写的工具也好、LLM 动态生成的工作流也好——本质都不可信。LLM 生成代码的时候可不会替你考虑安全。隔离网络、限制出站、加认证,三样缺一不可。Dify v1.16.1 把工具给了,改不改是我的事。
说到底这不是 Dify 一家的问题。任何提供代码沙箱能力的平台——Dify、Coze 还是别的什么——都面对同一个问题:沙箱里跑的代码不可信。隔离、限流、认证,三板斧少一斧都不行。Dify 这次把安全从"修 bug"提到了"基础设施变更"的级别,态度要肯定。但"给你工具不管你用不用"这个姿势,我觉得可以做得更好。
对了,这版还顺手把首页"继续工作"接口延迟降了约 69%。跟安全无关,但点开首页确实快了不少,体感明显。
下一步打算把 Squid 的 ACL 日志接到 Loki 里,沙箱有异常出站请求能第一时间看到。下周末的事了。
参考资料
- • Dify v1.16.1 Release Notes: https://github.com/langgenius/dify/releases/tag/v1.16.1
- • Dify Docker Compose 配置: https://github.com/langgenius/dify/blob/main/docker/docker-compose.yaml
- • Python secrets 文档: https://docs.python.org/3/library/secrets.html
- • Squid ACL 配置: http://www.squid-cache.org/Doc/config/acl/
- • Jinja2 SandboxedEnvironment: https://jinja.palletsprojects.com/en/3.1.x/api/#jinja2.SandboxedEnvironment
JOTO 企业落地观察
- 企业部署 Agent 类系统时,v1.16.1 的网络隔离与 Bearer Token 认证机制显著提升了纵深防御能力,但默认凭证未强制校验的设计,将安全责任完全前置至部署环节。这意味着企业必须在 CI/CD 流程中嵌入配置审计步骤,否则极易因疏忽引入高危暴露面。
- 这类系统的取舍在于:开箱即用的便利性与安全默认的严谨性难以兼得。Dify 将沙箱网络、代理 ACL、Token 认证等能力全部开放,但未设置启动时的默认值校验或告警,要求团队具备明确的安全配置 SOP,否则自动化部署可能固化风险。
- 对 AI 安全治理而言,v1.16.1 的改进表明:仅靠运行时防护(如沙箱)不够,必须叠加通信层认证与网络层隔离。企业需将环境变量管理纳入密钥治理体系,禁止明文配置,并通过配置扫描工具主动识别类似 'dify-agent-run-token-for-dev-only' 的硬编码凭证。
- RAG 知识工程虽未直接涉及,但 Agent 沙箱的安全加固间接保障了知识调用链路的完整性。若沙箱被突破并反向探测 RAG 后端服务,可能导致提示词泄露或向量库越权访问。因此,企业需确保 RAG 组件与 Agent 沙箱处于同等隔离等级的网络域中。

