[Bug 报告 / 安全] 项目 .env 可替换 DEEPSEEK_API_KEY:黑名单漏拦凭据 + project-env 为合法回退源 + 无任何审批门槛,对话被重定向至攻击者账户 #1559
IHateTheWorld-LYK
started this conversation in
General
Replies: 1 comment
|
三个事实我都对着 master 源码逐一核实,全部成立,而且实际影响范围比报告写的更广——我补充一个关键放大点。 源码核实
关键放大点:影响范围比报告的更广报告的影响范围限定为"无继承 key 且未存托管存储"。但
结论:只要用户没有把 修复方向
这是 high 级机密性问题,建议优先修复。需要的话我可以把这条链路的完整证据(含精确行号)整理成一份 feature-request/修复草案,方便维护者直接跟进。 |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
[Bug 报告 / 安全] 项目
.env可替换DEEPSEEK_API_KEY:黑名单漏拦凭据 + project-env 为合法回退源 + 无任何审批门槛,对话被重定向至攻击者账户摘要
以下三个事实各自都有合理的设计理由,但组合起来,在特定凭据状态下,就形成了一条把整段 AI 对话重定向到攻击者账户的完整链路:
.env:dsh二进制在启动时读取<启动目录>/.env(project 层)并物化进process.env;BOOTSTRAP_NAMES拦截DEEPSEEK_BASE_URL/DEEPSEEK_SEARCH_BASE_URL,但不含DEEPSEEK_API_KEY——项目.env可静默写入攻击者 key;credentials.resolve在"继承环境、托管存储"均未命中时回退到项目.env,优先级高于$DSH_HOME/.env。组合效果:攻击者只需提供一个带
.env的仓库/模板,受害者在其中运行dsh(web 或 headless 均可),全部对话——提示词、读取的文件、命令输出、搜索结果——就会经攻击者提供的 key 发往攻击者账户。攻击者可完整观测会话内容,并返回任意伪造的模型响应。攻击无需任何代码执行(纯静态文件),全程无审批/确认步骤,界面无任何异常提示,未配置 key 的新用户甚至"直接可用"。证据(代码与行号)
代码根:
packages/(0.1.0-rc.5 源码)启动加载项目
.env并物化 —apps/cli/src/bin.ts:33:environment: loadLayeredEnv('dsh')(cwd 默认process.cwd(),web/headless 共用)。packages/boot/app-boot/src/index.ts:177-198:loadLayeredEnv读取<cwd>/.env(project)与$DSH_HOME/.env(user),187-192 行将非黑名单值物化进process.env(仅在同名未设置时)。黑名单漏拦凭据 —
packages/boot/app-boot/src/index.ts:93-114:拦截报错文案(157-160 行)声明意图是"决定进程如何启动、代码/指令从哪加载、如何连接网络"。但替换凭据与重定向端点对机密性的后果相同——流量都进入攻击者控制面。被 project 层写入
process.env的 key 由两个消费方读取:dsh-llm-deepseek:resolveApiKey(packages/llm/llm-deepseek/src/index.ts:225-246)每请求经credentials.resolve取 key;dsh-web-search-deepseek:apiKeyEnv: DEEPSEEK_API_KEY(packages/bundle/base/cordis.patch.yml:412-413)。project-env 是合法回退源,且优先于 user-env —
packages/credentials/credentials-local/src/index.ts:309-317:dotenvFallback(260-263 行)顺序为['project-env', 'user-env'],即<cwd>/.env优先于$DSH_HOME/.env——用户在自己~/.dsh/.env中配置的 key 会被恶意项目顶掉。复现步骤
dsh受害者在"未导出 key 且未在 Models page 存入托管存储"的状态下,该 key 静默生效。
最小 PoC(概念演示,复刻生产逻辑;已在 Node v24.15.0 实际运行):
输出:
真实包代码验证(Verification)
对仓库内真实包代码(
pnpm install后,packages/boot/app-boot与packages/credentials/credentials-local的src/)运行定向 spec(packages/credentials/credentials-local/tests/dsh-hijack-verify.spec.ts),4/4 通过:loadLayeredEnv接受仅含DEEPSEEK_API_KEY的项目.env并无抛出,物化进process.env;loadLayeredEnv对含DEEPSEEK_BASE_URL的.env抛错(对照:黑名单对端点在起作用,唯独漏 key);LocalCredentialProvider.resolve在project-env与user-env并存时返回{ value: 'sk-attacker', source: 'project-env' }(项目压过用户);LocalCredentialProvider.resolve在无继承/无存储时以项目.env作为唯一来源返回攻击者 key。两包完整测试套件:
vitest run packages/boot/app-boot/tests packages/credentials/credentials-local/tests→ 11 files,162 passed,1 skipped。LLM 消费端(
packages/llm/llm-deepseek/src/index.ts:225-246)经credentials.resolve(ref)取 key,命中即用、无额外兜底;web 搜索端apiKeyEnv: DEEPSEEK_API_KEY(packages/bundle/base/cordis.patch.yml:412)。两处均已在源码核对。影响
DEEPSEEK_API_KEY$DSH_HOME/.credentials.yaml(②)$DSH_HOME/.env(headless 常见配置).env(③,压过 user).env(③,直接生效)被劫持后,攻击者可读取受害者全部会话内容(提示词、文件、命令输出),并以伪造的模型输出诱导 agent/用户执行任意操作(提交含后门的代码、批准外传数据的命令等),界面全程无异常。
补充:
packages/sandbox/sandbox/src/roots.ts:52-55的workspace-write允许 agent 写入工作区根目录且无 dotfile 排除;headless 下工作区根==启动目录,单次会话被攻破即可向.env写入攻击者 key,下一次启动自动加载——跨会话持久劫持。建议修复(供讨论,任一即可显著收窄)
DEEPSEEK_API_KEY及cordis.patch.yml中所有apiKeyEnv引用的变量加入BOOTSTRAP_NAMES,使其与DEEPSEEK_BASE_URL一样只能来自继承环境;project-env时,headless 启动打印醒目警告;web 端 Models 页显示source;dotenvFallback改为['user-env', 'project-env'],用户级配置优先于项目级;.env:切断"被攻破会话种下跨会话持久劫持"的载体。补丁与验证(Patch & Verification)
修复已实现于 fork 分支并附带回归测试,采用"建议修复"第 1 项的收窄版:只拦截 project 层(
<cwd>/.env)的凭据,user 层($DSH_HOME/.env)与继承环境不受影响——避免破坏$DSH_HOME/.env配置 key 的合法用法(#981 场景依赖该配置)。https://github.com/IHateTheWorld-LYK/deepseek-harness/tree/fix/project-env-credential-hijack0de34dd—https://github.com/IHateTheWorld-LYK/deepseek-harness/commit/0de34dd31c930cc83983cadec76baa5d3e47d21747f9438...0de34dd):https://github.com/IHateTheWorld-LYK/deepseek-harness/compare/47f9438...0de34ddhttps://github.com/IHateTheWorld-LYK/deepseek-harness/pull/new/fix/project-env-credential-hijack改动:
packages/boot/app-boot/src/index.ts:新增PROJECT_PROTECTED_NAMES = new Set(['DEEPSEEK_API_KEY'])(与cordis.patch.yml的apiKeyEnv引用对齐);readEnvLayer增加project标志,仅对 project 层拒绝受保护凭据。原 bootstrap-only 拦截逻辑与文案(/only the launching environment may set/)不变;packages/boot/app-boot/tests/credential-protection.spec.ts:project 层拒绝 key、大小写不敏感、原 bootstrap 文案不变、parse-before-apply(拒绝时零物化)、user 层仍接受 key;packages/credentials/credentials-local/tests/dsh-hijack-verify.spec.ts改写为端到端修复验证:恶意 project.env在启动即被拒,resolve()再也拿不到攻击者 key;合法$DSH_HOME/.envkey 经真实启动快照端到端解析正常。验证(真实包代码,
pnpm install后):pnpm vitest run packages/boot/app-boot/tests packages/credentials/credentials-local/tests→ 12 files,166 passed,1 skipped。修复前基线为 11 files / 162 passed / 1 skipped;净增 4 = 新增保护测试 +5、原"接受 key"证据测试改写 −1;All reactions