Donut AI Security Disclosure Evidence
Donut Browser 漏洞详情Donut Browser Vulnerability Details
钱包交易链路、1-click 静默交易、跨用户钱包归属校验、IDOR、CORS 和 MCP 工具边界相关脱敏证据集中放在本页。所有截图均为脱敏后版本,钱包公钥、Cookie、Turnkey 凭据、内部主机名按需打码。
This page collects redacted evidence on the wallet-transaction path, 1-click silent transactions, cross-user wallet-ownership checks, IDOR, CORS, and MCP tool boundaries. All screenshots are redacted versions; wallet public keys, cookies, Turnkey credentials, and internal hostnames are masked as needed.
逐条漏洞详情Per-Vulnerability Details
下面逐条列出每个漏洞的详情:编号、名称、等级、类型、复测状态、影响说明,每个漏洞卡片下可展开「证据截图」查看该漏洞的脱敏证据图。带 § 的漏洞在「攻击链」标签页另有逻辑路径说明。
Each vulnerability is listed below with its ID, name, severity, type, retest status, and impact; under each card you can expand “Evidence images” to view its redacted evidence. Items marked § also have a logical-path description in the “Attack Chains” tab.
DB-1 · 用户签名授权可伪造 / 交易静默上链User signature-authorization can be forged / transaction silently confirmed on-chain Critical未修复Unfixed
影响:自有钱包已链上验证——0.001 SOL→USDC 全程零签名弹窗、零逐笔授权,Donut 后端将交易提交给 Turnkey 完成签名上链,txHash 经 Solscan 确认 SUCCESS。跨用户测试中 Donut 后端对钱包归属零校验、照常构造并推进请求:对托管体系内非本人钱包,最终由 Turnkey 以 AUTH001(权限与钱包不符)拦截;对体系外钱包因无对应签名密钥而超时——安全边界完全压在第三方托管方,Donut 自身无防护。
Impact: Verified on-chain with the researcher’s own wallet — a 0.001 SOL→USDC swap with zero signature prompts and zero per-transaction authorization; the Donut backend submitted the transaction to Turnkey, which signed and confirmed it on-chain, with the txHash confirmed SUCCESS on Solscan. In cross-user testing the Donut backend did zero ownership checks and built and advanced the request as usual: for a non-owned wallet inside the custody system it was ultimately blocked by Turnkey with AUTH001 (privilege / wallet mismatch); for a wallet outside the system it timed out for lack of the signing key — the security boundary rests entirely on the third-party custodian, with no protection from Donut itself.
机制:交易流程为“用户在 Donut 侧完成签名授权 → Donut 后端提交给 Turnkey → Turnkey 签名并广播上链”。漏洞在 Donut 侧的授权环:本应代表用户真实意愿的“签名授权”可被伪造——攻击者无需真实用户前端确认,只要按序向后端提交请求(构建未签名交易 → 执行),后端即认定“用户已签名授权”,随后照常提交 Turnkey 完成上链,全程无签名弹窗、无 2FA、无交易预览。
Mechanism: The transaction flow is “the user completes signature authorization on the Donut side → the Donut backend submits to Turnkey → Turnkey signs and broadcasts on-chain.” The flaw is in Donut’s authorization step: the “signature authorization” that should represent the user’s real intent can be forged — without any real front-end confirmation, an attacker need only submit requests to the backend in order (build unsigned transaction → execute), and the backend treats it as “the user has signed and authorized,” then submits to Turnkey as usual, with no signature prompt, no 2FA, and no transaction preview throughout.
根因:Donut 侧“用户已签名授权”的判定不绑定不可伪造、与该笔交易绑定的用户签名,可被直接复现;后端凭这一可伪造的授权状态即把交易提交给 Turnkey。最终授权本应握在用户手里,却落在可被复现的请求序列上。
Root cause: Donut’s determination that “the user has signed and authorized” is not bound to an unforgeable, transaction-bound user signature and can be directly reproduced; the backend submits to Turnkey on the basis of this forgeable authorization state. Final authorization, which should rest in the user’s hands, instead rests on a reproducible sequence of requests.
修复建议:在 Donut 侧把每笔资金交易的提交强绑定到“仅真实用户可产生、与交易内容绑定、不可重放”的签名凭证,后端独立校验通过后才提交给 Turnkey;拒绝任何无法证明源自真实用户签名授权的请求。
Remediation: On the Donut side, strongly bind every fund-transaction submission to a signature credential that “only the real user can produce, is bound to the transaction content, and cannot be replayed”; the backend must independently verify it before submitting to Turnkey, and reject any request that cannot be proven to originate from a real user’s signature authorization.
深度证据:§1 1-click 静默交易链 · §2 跨用户钱包交易构建与 AUTH001
In-depth evidence: §1 1-click silent-transaction chain · §2 Cross-user transaction build & AUTH001
复测过程(2026-06-02)Retest (2026-06-02)
复测方法:非破坏性验证,自有账号 / 自有钱包,0.001 SOL → USDC,证据限于研究员自有资金。
关键观察:全程 Donut 界面零签名弹窗、零逐笔授权,Donut 后端将交易提交给 Turnkey 完成签名并广播上链,用户无感知完成链上交易。
txHash(Solscan 已确认 SUCCESS):4TQscatBXPbdkyAq9Hb61NKF7APrwyyqwDQhRVuSA49Nb8DWfL51h3nN7iAZDtmp4FJkhAjyMQehmC6E72BdTcQK ↗
说明:此为 2026-06-02 复测期间追加的链上确认交易,独立于原始 11 笔证据集(见 §链上交易证据(11 笔))。
附加发现:该笔交易在 Donut 自身 History(Order / DeFi / Transfer)中均不显示,DeFi history API 返回 VALIDATION_ERROR——执行路径完全绕过平台订单追踪系统,零审计记录。
Retest method: non-destructive verification, own account / own wallet, 0.001 SOL → USDC, with evidence limited to the researcher’s own funds.
Key observation: throughout, the Donut UI shows zero signature dialogs and zero per-transaction authorization; the Donut backend submits the transaction to Turnkey to sign and broadcast on-chain, completing the on-chain transaction without the user noticing.
txHash (confirmed SUCCESS on Solscan): 4TQscatBXPbdkyAq9Hb61NKF7APrwyyqwDQhRVuSA49Nb8DWfL51h3nN7iAZDtmp4FJkhAjyMQehmC6E72BdTcQK ↗
Note: this is an on-chain-confirmed transaction added during the 2026-06-02 retest, independent of the original 11-transaction evidence set (see §On-chain transaction evidence (11 txns)).
Additional finding: this transaction does not appear in any of Donut’s own History views (Order / DeFi / Transfer), and the DeFi history API returns VALIDATION_ERROR — the execution path fully bypasses the platform’s order-tracking system, with zero audit record.
证据截图(8 张,其中打码 4 张)Evidence images (8; 4 masked)
DB-2 · 跨用户交易构建未绑定当前会话钱包归属Cross-user transaction build not bound to the current session’s wallet ownership Critical部分修复Partially fixed
影响:交易构建阶段不校验目标钱包是否属于当前会话用户。原始测试中,后端为一笔“把真实受害者钱包资金转给研究员”的交易成功构建了未签名交易(query 返回 201 + unsignedTransaction),越权构建链路成立;执行阶段被拦下(原始由第三方 Turnkey,复测时由 Donut 新增的 WALLET_NOT_OWNED)。该越权入口可作为资金攻击链起点,结合会话窃取(见 DB-4)时执行侧拦截可被架空。
Impact: The transaction-build stage does not check whether the target wallet belongs to the current session user. In the original test, the backend successfully built an unsigned transaction for a “transfer the real victim’s wallet funds to the researcher” transaction (query returned 201 + unsignedTransaction) — the unauthorized-build path held; execution was stopped (originally by the third-party Turnkey, and at retest by Donut’s newly added WALLET_NOT_OWNED). This unauthorized entry can serve as the starting point of a fund attack chain, and combined with session theft (see DB-4) the execution-side block can be neutralized.
机制:交易分“构建(query)→执行(execution)”两步。构建阶段后端不校验 from_wallet 是否属于当前会话用户,会为非本账号的钱包构建交易并分配 tradeId;归属拦截被下沉到执行/签名阶段——原始测试中仅由第三方 Turnkey 以 AUTH001 兜底,复测时 Donut 在 execution 层新增 WALLET_NOT_OWNED 校验,但构建阶段的越权仍未修复。
Mechanism: A transaction has two steps: build (query) → execution. At build time the backend does not check whether from_wallet belongs to the current session user, and will build a transaction and assign a tradeId for a wallet not owned by the account; the ownership block is pushed down to the execution/signing stage — in the original test backstopped only by the third-party Turnkey via AUTH001, and at retest Donut added a WALLET_NOT_OWNED check at the execution layer, but the build-stage overreach remains unfixed.
根因:权限校验缺位于业务入口(构建阶段)——Donut 未在交易构建时强校验 from_wallet 归属当前会话用户,使越权交易得以进入后续链路;归属判断长期依赖第三方签名服务兜底,复测虽在执行层补上校验,入口处的归属缺失依旧。
Root cause: The privilege check is missing at the business entry point (build stage) — Donut does not strongly verify that from_wallet belongs to the current session user at build time, letting an unauthorized transaction enter the downstream path; ownership judgment has long relied on the third-party signing service as a backstop, and although the retest added a check at the execution layer, the ownership gap at the entry point remains.
修复建议:在交易构建入口即强制校验目标钱包归属当前会话用户,与执行入口双重校验,不把归属判断外包给签名供应商;并收敛会话窃取面(见 DB-4),避免执行侧校验被合法会话绕过。
Remediation: Enforce an ownership check at the transaction-build entry point that the target wallet belongs to the current session user, with double-checks at both build and execution entries, and do not outsource ownership judgment to the signing provider; also reduce the session-theft surface (see DB-4) so the execution-side check cannot be bypassed with a legitimate session.
In-depth evidence: §2 Cross-user transaction build & AUTH001
复测过程(2026-06-02)Retest (2026-06-02)
【原始测试 — 越权构建达成】
以真实受害者钱包为 from,构建“将其资金转给研究员”的交易,query 返回 201 + unsignedTransaction——构建阶段零归属校验。
【复测·Query 阶段 — 仍未修复】
用研究员 session,userWallet 填一个非本账号的协作测试钱包 → 201 OK,tradeId 已创建;Donut 未检查钱包归属,直接构建交易。
【复测·Execution 阶段 — 已修复(新增 Donut 层校验)】
用同一 tradeId 调 execution → 201 WALLET_NOT_OWNED,错误消息:"The requested wallet does not belong to the authenticated user"。原始测试中这一步是透传到 Turnkey 返回 AUTH001,现在 Donut 层先拦。
【对比】原始:Query 无校验 ❌ + Execution 透传 Turnkey(AUTH001)❌;当前:Query 无校验 ❌ + Execution Donut 层 WALLET_NOT_OWNED ✅(部分修复)。
【残余风险】Query 层 IDOR 仍可枚举路由、为定向攻击建模;若结合 DB-4(CORS + credentials)拿到受害者 session,execution 层校验被架空,跨钱包交易仍可执行。
[Original test — unauthorized build achieved]
Using a real victim wallet as the from, building a transaction to “send their funds to the researcher”, the query returns 201 + unsignedTransaction — zero ownership check at the build stage.
[Retest · Query stage — still unfixed]
With the researcher’s session, filling userWallet with a collaboration test wallet not belonging to this account → 201 OK, tradeId created; Donut does not check wallet ownership and builds the transaction directly.
[Retest · Execution stage — fixed (new Donut-layer check added)]
Calling execution with the same tradeId → 201 WALLET_NOT_OWNED, error message: "The requested wallet does not belong to the authenticated user". In the original test this step was passed through to Turnkey returning AUTH001; now the Donut layer blocks it first.
[Comparison] Original: Query no check ❌ + Execution passed through to Turnkey (AUTH001) ❌; current: Query no check ❌ + Execution Donut-layer WALLET_NOT_OWNED ✅ (partially fixed).
[Residual risk] The query-layer IDOR can still enumerate routes and model targeted attacks; if combined with DB-4 (CORS + credentials) to obtain a victim’s session, the execution-layer check is bypassed and cross-wallet transactions can still be executed.
证据截图(2 张,均已打码)Evidence images (2; all masked)
DB-3 · 任意钱包资产、持仓、交易历史 IDORIDOR on any wallet’s assets, holdings, and trade history Critical未修复Unfixed
影响:任意已登录用户传入他人 address,portfolio/positions/history 三端点全部 HTTP 200,返回该钱包完整且经平台聚合的财务画像(复测样本约 $69k、845 SOL);可直接作为 DB-1 自动签名盗币链的目标侦察步。
Impact: Any logged-in user passing someone else’s address gets HTTP 200 from all three endpoints (portfolio / positions / history), returning that wallet’s complete, platform-aggregated financial profile (the retest sample was ~$69k, 845 SOL); this can serve directly as the target-reconnaissance step of the DB-1 auto-signing fund-theft chain.
机制:/wallets/portfolio、/positions、/history(及 /trades)仅以 address 查询参数指定目标钱包;端点已置于登录态之后,却不校验该钱包是否属于当前会话用户,任意已登录用户即可读任意地址的完整资产视图。
Mechanism: /wallets/portfolio, /positions, /history (and /trades) specify the target wallet solely via the address query parameter; the endpoints sit behind login yet do not check whether the wallet belongs to the current session user, so any logged-in user can read the full asset view for any address.
辨析(链上公开 ≠ 此接口无害):链上数据本身公开,但区别有三——① 聚合富化即用:一次认证调用即返回预计算的美元市值、代币单价、跨协议持仓、带对手方与路由的历史,省去自行解析链上数据,等于现成的“按财富排序目标清单”;② 对象级授权失效(BOLA)才是本质:Donut 已用登录态门控此端点(即认定其受保护),却不做归属校验,与底层数据是否公开无关;③ 它是 DB-1 盗币链的侦察前置,把公开数据转成定向目标。要害不在“泄露秘密”,而在“用 auth 门控了却不按用户隔离,任意登录态即可整套拉出他人钱包的富化画像”。
Analysis (on-chain public ≠ this API harmless): On-chain data is itself public, but there are three differences — ① aggregated and enriched, ready to use: one authenticated call returns precomputed USD value, per-token prices, cross-protocol holdings, and history with counterparties and routes, sparing the attacker from parsing chain data — effectively a ready-made “targets sorted by wealth” list; ② the essence is broken object-level authorization (BOLA): Donut already gates this endpoint behind login (i.e., treats it as protected) yet performs no ownership check, regardless of whether the underlying data is public; ③ it is the reconnaissance precursor to the DB-1 fund-theft chain, turning public data into specific targets. The crux is not “leaking secrets” but “gating with auth yet not isolating by user, so any logged-in session can pull another wallet’s full enriched profile.”
根因:端点缺少对象级授权——address 参数未绑定当前会话用户,缺少“只能查本人钱包”的强校验(IDOR / BOLA)。
Root cause: The endpoints lack object-level authorization — the address parameter is not bound to the current session user, missing a strong “can only query your own wallet” check (IDOR / BOLA).
修复建议:所有按钱包取数的接口强制校验 address 归属当前会话用户;对跨用户查询返回 403,仅允许查询本人已绑定的钱包。
Remediation: All wallet-data APIs must enforce that the address belongs to the current session user; return 403 for cross-user queries, and allow querying only wallets the user has bound.
深度证据:§3 资产与交易历史 IDOR
In-depth evidence: §3 Asset & trade-history IDOR
复测过程(2026-06-02)Retest (2026-06-02)
三个端点均 HTTP 200 返回某高价值钱包(VICTIM-1,地址已打码)的完整数据,无任何归属校验。
- /wallets/portfolio:totalValueUsd≈$69k(约 845 SOL + 2034 USDC + 多种 meme 代币),完整持仓与美元市值全部返回
- /wallets/positions:持仓结构 positionId(含钱包,已打码)完整返回
- /wallets/history:完整交易历史(txHash、类型、时间、金额、对手方、签名者)返回——均为受害者链上活动,已打码
三端点仅凭合法 session cookie(任意已登录用户)、传入他人 address 即可无限查询。
All three endpoints return HTTP 200 with the full data of a high-value wallet (VICTIM-1, address masked), with no ownership check at all.
- /wallets/portfolio: totalValueUsd≈$69k (about 845 SOL + 2034 USDC + various meme tokens); the full holdings and USD market value are all returned
- /wallets/positions: the position structure positionId (containing the wallet, masked) is returned in full
- /wallets/history: the full transaction history (txHash, type, time, amount, counterparty, signer) is returned — all of it the victim’s on-chain activity, masked
With only a valid session cookie (any logged-in user) and another person’s address, all three endpoints can be queried without limit.
证据截图(3 张,其中打码 3 张)Evidence images (3; 3 masked)
DB-4 · CORS 子域通配符与 credentials 组合CORS subdomain-wildcard combined with credentials Critical部分修复Partially fixed
影响:恶意/被控来源可携带受害者登录态跨域调用敏感 API——是 DB-1 自动签名盗币链的远程投递入口。复测已封 *.donutbrowser.ai 通配符,但 localhost:3000 仍带 credentials 放行,受害者本机任意应用(含 NPM 供应链攻击)仍可冒用其 session 触发交易链。
Impact: A malicious / controlled origin can carry the victim’s logged-in session to call sensitive APIs cross-origin — the remote-delivery entry point of the DB-1 auto-signing fund-theft chain. The retest closed the *.donutbrowser.ai wildcard, but localhost:3000 is still allowed with credentials, so any app on the victim’s machine (including an NPM supply-chain attack) can still impersonate their session to trigger the transaction chain.
机制:API 对任意 *.donutbrowser.ai 子域回显 Allow-Origin 并带 Allow-Credentials: true,配合通配符 DNS(任意子域均解析、无需注册),恶意/被控子域即可携带登录 cookie 跨域调用敏感接口,使 DB-1 等需登录态的操作可被远程恶意来源触发。
Mechanism: The API echoed Allow-Origin for any *.donutbrowser.ai subdomain together with Allow-Credentials: true; combined with wildcard DNS (any subdomain resolves, no registration needed), a malicious / controlled subdomain could carry the login cookie to call sensitive endpoints cross-origin, letting login-gated operations like DB-1 be triggered from a remote malicious origin.
根因:CORS 允许来源使用子域通配符且允许携带凭据,信任边界过宽。
Root cause: CORS allowed origins to use a subdomain wildcard and to carry credentials — an overly broad trust boundary.
修复建议:CORS 白名单收敛到确切可信源,移除通配符与 localhost,谨慎使用 Allow-Credentials。
Remediation: Narrow the CORS allowlist to exact trusted origins, remove the wildcard and localhost, and use Allow-Credentials sparingly.
深度证据:§4 CORS、MCP 与组合攻击面
In-depth evidence: §4 CORS, MCP & the combined attack surface
复测过程(2026-06-02)Retest (2026-06-02)
【已修复部分】
*.donutbrowser.ai(evil/a/beta2)和 *.donutlabs.dev 全部返回 STATUS=404,无 ACAO/ACAC 头,浏览器会拦截跨域请求。
null origin 同样被拒绝。
【残余风险 — localhost:3000 仍被允许】
OPTIONS /v1/backend/action-mcp/execution + Origin: http://localhost:3000
→ HTTP 204,ACAO=http://localhost:3000,ACAC=true,ACAM=GET,POST,PUT,DELETE,PATCH,OPTIONS
任何在受害者本机 localhost:3000 运行的应用(恶意程序、被控进程、NPM 包供应链攻击等)均可携带受害者 Donut session cookie 发起完整 API 调用,包括触发 action-mcp/execution 交易链。
【原始漏洞 vs 当前状态】
原始:*.donutbrowser.ai 全部允许 ACAC=true(任意子域名攻击)
当前:仅 beta.donutbrowser.ai + localhost:3000 允许 ACAC=true(主要攻击面已收敛,本地残余存在)
[Fixed part]
*.donutbrowser.ai (evil/a/beta2) and *.donutlabs.dev all return STATUS=404 with no ACAO/ACAC headers, so the browser blocks cross-origin requests.
The null origin is likewise rejected.
[Residual risk — localhost:3000 still allowed]
OPTIONS /v1/backend/action-mcp/execution + Origin: http://localhost:3000
→ HTTP 204, ACAO=http://localhost:3000, ACAC=true, ACAM=GET,POST,PUT,DELETE,PATCH,OPTIONS
Any app running on the victim’s machine at localhost:3000 (malware, a controlled process, an NPM-package supply-chain attack, etc.) can carry the victim’s Donut session cookie to make full API calls, including triggering the action-mcp/execution transaction chain.
[Original flaw vs current state]
Original: *.donutbrowser.ai all allowed ACAC=true (any-subdomain attack)
Current: only beta.donutbrowser.ai + localhost:3000 allow ACAC=true (the main attack surface is narrowed; a local residue remains)
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
DB-5 · MCP 工具层认证与权限边界不足Insufficient auth and privilege boundary in the MCP tool layer Critical未修复Unfixed
影响:任意已登录用户可直接 POST action-mcp/query 传任意 toolName 调用工具,完全绕过 AI 对话 / Agent 决策 / 前端确认弹窗——前端确认弹窗形同虚设。WRAP_UNWRAP_SOL 等工具直接返回 201 + unsignedTransaction,可直接传入 execution 上链;与 DB-1 组合即可直接编排执行交易链。
Impact: Any logged-in user can directly POST action-mcp/query with an arbitrary toolName to invoke a tool, fully bypassing the AI chat / Agent decision / front-end confirmation dialog — the front-end confirmation dialog is effectively useless. Tools like WRAP_UNWRAP_SOL return 201 + unsignedTransaction directly, which can be passed straight to execution to go on-chain; combined with DB-1, the execution transaction chain can be orchestrated directly.
机制:工具调用入口(action-mcp/query)置于登录态之后,但不强制经 AI 对话 / Agent 决策,任意登录用户传入任意 toolName 即直接进入工具执行层;前端确认弹窗仅是 UI 层,可被直接 API 调用完全跳过。原 mcp-tool-config 无认证枚举已修(401),但登录用户仍可枚举全部工具及 confirm 标志。
Mechanism: The tool-invocation entry (action-mcp/query) sits behind login but does not require going through the AI chat / Agent decision; any logged-in user passing an arbitrary toolName enters the tool-execution layer directly. The front-end confirmation dialog is only a UI layer and can be fully skipped by calling the API directly. The original unauthenticated enumeration of mcp-tool-config is fixed (401), but a logged-in user can still enumerate all tools and their confirm flags.
根因:工具调用入口缺少服务端认证与能力授权,安全边界依赖前端。
Root cause: The tool-invocation entry lacks server-side authentication and capability authorization; the security boundary relies on the front end.
修复建议:工具执行层强制服务端认证与按角色/能力的授权,最小化可调用工具集。
Remediation: Enforce server-side authentication and role/capability-based authorization at the tool-execution layer, and minimize the set of invokable tools.
深度证据:§4 CORS、MCP 与组合攻击面
In-depth evidence: §4 CORS, MCP & the combined attack surface
复测过程(2026-06-02)Retest (2026-06-02)
【已修复】/systems/mcp-tool-config 无认证枚举
无认证请求 → 401 TOKEN_MISSING,工具清单不再公开暴露。
【仍未修复】action-mcp 直接工具调用绕过 AI 层
任意已登录用户可直接 POST action-mcp/query,传入任意 toolName 调用工具,完全绕过 AI 对话 / Agent 决策 / 前端限制层:
- WRAP_UNWRAP_SOL → HTTP 201 OK,unsignedTransaction + tradeId 完整返回,可直接传入 execution 完成链上操作
- JUPITER_CREATE_LIMIT_ORDER → HTTP 201,端点存在且响应(ORDER_SIZE_TOO_SMALL = 业务校验,非访问拒绝)
- action-mcp/execution 同样无逐笔用户授权门控(见 DB-1)
结论:核心漏洞(无 AI 层强制、工具可直接枚举调用)仍未修复;前端确认弹窗可被完全跳过。
[Fixed] Unauthenticated enumeration of /systems/mcp-tool-config
Unauthenticated request → 401 TOKEN_MISSING; the tool list is no longer publicly exposed.
[Still unfixed] action-mcp direct tool invocation bypasses the AI layer
Any logged-in user can directly POST action-mcp/query with an arbitrary toolName, fully bypassing the AI chat / Agent decision / front-end restriction layer:
- WRAP_UNWRAP_SOL → HTTP 201 OK, returning a full unsignedTransaction + tradeId, which can be passed straight to execution to complete an on-chain operation
- JUPITER_CREATE_LIMIT_ORDER → HTTP 201, the endpoint exists and responds (ORDER_SIZE_TOO_SMALL = business validation, not access denial)
- action-mcp/execution likewise has no per-transaction user-authorization gate (see DB-1)
Conclusion: the core flaw (no enforced AI layer; tools can be enumerated and invoked directly) is still unfixed; the front-end confirmation dialog can be fully skipped.
证据截图(3 张,其中打码 3 张)Evidence images (3; 3 masked)
DB-6 · Role 参数注入Role-parameter injection High未修复Unfixed
影响:消息接口对 role / content / 字段全无输入校验,可向 AI 对话上下文注入伪造角色消息(system/assistant/tool/developer 四角色均 201 + message_id),为 prompt-injection / 上下文污染提供原料;并经 PATCH + startAgent 实测触发 Agent 执行。
Impact: The message API performs no input validation on role / content / fields, allowing forged role messages to be injected into the AI chat context (all four roles — system/assistant/tool/developer — return 201 + message_id), providing material for prompt injection / context poisoning; and via PATCH + startAgent it was observed to trigger Agent execution.
机制:POST /terminal/threads/{id}/messages 接受客户端任意 role 值且后端不校验(四角色注入均 201 + message_id);进一步,role/content 传对象或 {$gt:""} 操作符、以及 function_call/tool_calls/metadata 等额外字段均被接受(无字段白名单)。PATCH 同时改 role 并置 startAgent:true,实测返回 agent_run_id(running),触发 Agent 执行。
Mechanism: POST /terminal/threads/{id}/messages accepts any client-supplied role value with no backend validation (all four role injections return 201 + message_id); further, passing role/content as objects or a {$gt:""} operator, plus extra fields like function_call/tool_calls/metadata, are all accepted (no field allowlist). A PATCH that changes role and sets startAgent:true was observed to return an agent_run_id (running), triggering Agent execution.
辨析(危害边界):需如实说明——Donut 读取消息时将 role 强制规范化为 user、并把 content 对象 JSON.stringify。因此“注入 system 指令直接覆盖 AI 安全限制”这一最重危害,很可能被读时规范化中和,本次未被干净证明,按理论风险计。已坐实的是“消息 API 全无输入校验 + PATCH 可触发 Agent 执行”。
Analysis (impact boundary): to state it honestly — when Donut reads messages it forcibly normalizes role to user and JSON.stringifies content objects. So the worst-case harm of “injecting a system instruction to directly override the AI’s safety limits” is likely neutralized by this read-time normalization and was not cleanly proven here; it is counted as a theoretical risk. What is confirmed is “the message API has no input validation + PATCH can trigger Agent execution.”
根因:消息入口信任客户端可控的 role / 类型 / 字段,缺少服务端强制校验(role 白名单、类型校验、字段白名单)。
Root cause: The message entry trusts client-controllable role / types / fields, lacking enforced server-side validation (role allowlist, type checking, field allowlist).
修复建议:后端强制 role 仅限 user、校验字段类型、按白名单过滤额外字段;PATCH 不得由客户端任意触发 Agent;读时规范化不应替代写时校验。
Remediation: The backend should force role to user only, validate field types, and filter extra fields by allowlist; PATCH must not let the client arbitrarily trigger the Agent; read-time normalization should not substitute for write-time validation.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
证据截图(2 张,均已打码)Evidence images (2; all masked)
DB-7 · Wallet Service 无认证或弱认证钱包创建Wallet Service wallet creation with no / weak authentication High已修复Fixed
影响:原始——钱包创建端点完全无认证,可无限跨 13 条链创建真实钱包(ownerId: null),造成 Turnkey 配额 / 资源滥用与洗钱等合规风险。复测已修复:端点现返回 401 AUTH0003。
Impact: Originally the wallet-creation endpoint had no authentication at all, allowing unlimited creation of real wallets across 13 chains (ownerId: null), causing Turnkey quota / resource abuse and money-laundering compliance risk. Fixed on retest: the endpoint now returns 401 AUTH0003.
机制:原始——POST /wallet-service/wallets 无需任何认证即可跨 13 链创建真实钱包,所有钱包 ownerId 为 null。复测:已切换为独立 API Key 认证,无认证与 Session Cookie 均被拒(401 AUTH0003)。
Mechanism: Originally POST /wallet-service/wallets required no authentication to create real wallets across 13 chains, with every wallet’s ownerId being null. Retest: it now uses a dedicated API-Key auth, and both unauthenticated requests and Session Cookies are rejected (401 AUTH0003).
根因:创建入口缺少身份认证与归属绑定。
Root cause: The creation entry lacked authentication and ownership binding.
修复建议:钱包创建强制认证并绑定归属,加配额与速率限制。
Remediation: Require authentication and ownership binding for wallet creation, and add quota and rate limits.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
证据截图(1 张,已打码)Evidence images (1; masked)
DB-8 · Credits / 使用次数限制绕过Credits / usage-limit bypass High未修复Unfixed
影响:POST 发新消息在 credits=0 时被正确拒绝(INSUFFICIENT_CHATS_LIMIT),但 PATCH 更新已有消息并置 startAgent:true 完全绕过额度检查——返回 agent_run_id 且 credits 不扣减。免费用户即可无限使用付费 AI Agent,直接侵蚀订阅收入并消耗 LLM API 费用;与 DB-6(role 注入)/ DB-1 组合 = 无限注入 + 无限 agent 执行。
Impact: Posting a new message is correctly rejected when credits=0 (INSUFFICIENT_CHATS_LIMIT), but PATCHing an existing message with startAgent:true fully bypasses the quota check — it returns an agent_run_id and credits are not deducted. Free users can use the paid AI Agent without limit, directly eroding subscription revenue and consuming LLM API costs; combined with DB-6 (role injection) / DB-1 = unlimited injection + unlimited agent execution.
机制:免费计划限每日 AI 聊天次数。额度校验只覆盖 POST 新消息路径(credits=0 → INSUFFICIENT_CHATS_LIMIT);但 PATCH 更新已有消息并置 startAgent:true 不经额度校验,实测返回 agent_run_id(running)且 credits 0→0 未扣减,完全绕过限制。
Mechanism: The free plan limits daily AI chats. The quota check only covers the POST new-message path (credits=0 → INSUFFICIENT_CHATS_LIMIT); but PATCHing an existing message with startAgent:true does not go through the quota check — it was observed to return an agent_run_id (running) with credits unchanged 0→0, fully bypassing the limit.
根因:额度校验未覆盖全部可触发 agent 的写入/更新路径。
Root cause: The quota check does not cover all write/update paths that can trigger the agent.
修复建议:在所有可触发 agent 的写入/更新路径施加统一的额度与订阅校验。
Remediation: Apply unified quota and subscription checks on every write/update path that can trigger the agent.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
证据截图(2 张,均已打码)Evidence images (2; all masked)
DB-9 · AI Agent 配置与 System Prompt 泄露AI Agent config and system-prompt leakage High部分修复Partially fixed
影响:无认证入口已封(401);但任意已登录普通用户(无需管理员)仍可一次性拉取全部 4 个 Agent 的完整 system_prompt(各 ~12KB)、工具开关与自定义 MCP 地址——定向 Prompt Injection 的现成原料,并暴露内部 MCP 端点。
Impact: The unauthenticated entry is closed (401); but any logged-in ordinary user (no admin needed) can still pull in one shot the full system_prompt of all 4 Agents (~12KB each), the tool toggles, and the custom MCP addresses — ready-made material for targeted prompt injection, and it exposes internal MCP endpoints.
机制:GET /aigemix/agents 带普通会话 Cookie 即返回 200,响应含每个 Agent 的 system_prompt 全文、agentpress_tools 开关与 custom_mcps URL;匿名请求已加固为 401。
Mechanism: GET /aigemix/agents with an ordinary session Cookie returns 200, and the response contains each Agent’s full system_prompt, the agentpress_tools toggles, and the custom_mcps URLs; anonymous requests are now hardened to 401.
根因:Agent 配置/Prompt 被当作前端可下发数据,认证后未做角色授权与字段最小化——普通用户本不需要后台 Agent 的完整 prompt。
Root cause: Agent config / prompts are treated as front-end-deliverable data; after authentication there is no role authorization or field minimization — an ordinary user has no need for the backend Agent’s full prompt.
修复建议:system_prompt 与工具配置列为服务端机密,不随用户级接口下发;前端只取运行所需最小字段;按角色/租户授权。
Remediation: Treat system_prompt and tool config as server-side secrets that are not delivered via user-level APIs; have the front end fetch only the minimum fields needed to run; authorize by role/tenant.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
证据截图(2 张,其中打码 1 张)Evidence images (2; 1 masked)
DB-10 · 跨用户限价单取消Cross-user limit-order cancellation High未修复Unfixed
影响:限价单取消接口无预先归属校验;时序差异证实服务端对他人钱包会走真实订单处理路径。攻击者一旦掌握受害者真实 orderId,即可取消其挂单、干扰交易策略,无任何归属拦截。
Impact: The limit-order cancel endpoint performs no upfront ownership check; a timing difference confirms the server takes a real order-processing path for other people’s wallets. Once an attacker obtains a victim’s real orderId, they can cancel the victim’s open orders and disrupt their trading strategy with no ownership barrier.
机制:接口接受任意 userWallet 且取消前不校验归属。同一不存在的 orderId——自有钱包即时返回 201 INVALID_ORDER;受害者钱包则进入真实订单查询路径、>12s 超时,证明服务端按 wallet 区分处理。
Mechanism: The endpoint accepts an arbitrary userWallet and does not check ownership before cancellation. For the same non-existent orderId, one’s own wallet returns 201 INVALID_ORDER instantly; a victim wallet enters a real order-lookup path and times out after >12s, proving the server processes differently per wallet.
根因:订单操作缺少资源归属校验(IDOR)。
Root cause: Order operations lack resource-ownership checks (IDOR).
修复建议:取消/修改订单强制校验订单归属当前会话用户。
Remediation: Require that canceling/modifying an order verify the order belongs to the current session user.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
DB-11 · DeFi 自动签名交易链路缺少用户授权DeFi auto-signing transaction path lacks user authorization Critical部分修复Partially fixed
影响:无认证 query 已封(401);但带认证用户的 KAMINO_DEPOSIT/WITHDRAW query 仍返回 201 + unsignedTransaction + tradeId,距 execution 自动签名上链仅一步,全程无用户确认。已用自有账户在链上完成 DEPOSIT/WITHDRAW 证明路径真实。
Impact: The unauthenticated query is closed (401); but for an authenticated user, KAMINO_DEPOSIT/WITHDRAW queries still return 201 + unsignedTransaction + tradeId, just one step from execution auto-signing on-chain, with no user confirmation throughout. DEPOSIT/WITHDRAW were completed on-chain with the researcher’s own account, proving the path is real.
机制:与 DB-1 同源、作用于 Kamino。无认证 query 已加固为 401;带认证 POST /action-mcp/query 对 KAMINO_DEPOSIT、KAMINO_WITHDRAW 均返回 201 + unsignedTransaction + tradeId,随后 execution 即完成自动签名,全程无确认弹窗。
Mechanism: Same origin as DB-1, applied to Kamino. The unauthenticated query is hardened to 401; an authenticated POST /action-mcp/query for KAMINO_DEPOSIT and KAMINO_WITHDRAW both return 201 + unsignedTransaction + tradeId, after which execution completes auto-signing, with no confirmation dialog throughout.
根因:与 DB-1 同源——执行链路未强制用户授权,资金操作授权下沉到后端/签名服务。
Root cause: Same origin as DB-1 — the execution path does not enforce user authorization, and authorization for fund operations is pushed down to the backend / signing service.
修复建议:对所有资金/DeFi 操作在执行入口强制用户确认,同 DB-1。
Remediation: Enforce user confirmation at the execution entry for all fund / DeFi operations, same as DB-1.
深度证据:§1 1-click 静默交易链
In-depth evidence: §1 The 1-click silent transaction chain
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
DB-12 · Admin Plan / 订阅计划信息泄露Admin Plan / subscription-plan info leakage High未修复Unfixed
影响:GET /subscription/plans 公开泄露内部 Admin 计划(taskKey=_admin、benefits 全 unlimit、标注「only for donut team」)。对该计划 eligibility 对普通用户返回 eligible:true,claim 仅被 plan status(unactivated)阻止、无任何用户角色校验——一旦该计划被激活,任意用户即可领取 Admin 权限。
Impact: GET /subscription/plans publicly leaks an internal Admin plan (taskKey=_admin, all benefits unlimit, labeled “only for donut team”). Its eligibility returns eligible:true for ordinary users, and claim is only blocked by plan status (unactivated) with no user-role check at all — once that plan is activated, any user can claim Admin privileges.
机制:订阅计划接口公开返回全部计划(含 Admin 的 benefits/taskKey/描述);eligibility 对普通用户返回 eligible:true;claim 返回 201「Plan is not active」,唯一闸门是计划状态,无角色校验。
Mechanism: The subscription-plans endpoint publicly returns all plans (including Admin’s benefits/taskKey/description); eligibility returns eligible:true for ordinary users; claim returns 201 “Plan is not active”, where the only gate is plan status, with no role check.
根因:计划数据无访问控制;claim/eligibility 缺少用户角色校验,仅以计划状态作为唯一闸门。
Root cause: Plan data has no access control; claim/eligibility lack user-role checks and use plan status as the only gate.
修复建议:隐藏内部计划,对计划领取增加用户角色与资格的服务端校验。
Remediation: Hide internal plans, and add server-side user-role and eligibility checks for plan claiming.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(3 张,均已打码)Evidence images (3; all masked)
DB-13 · SQL 查询结构与表结构泄露SQL query-structure and schema leakage High部分修复Partially fixed
影响:两个端点(eligibility 非 UUID 参数、plans?status 注入 payload)的错误响应仍回显完整 SQL,泄露表 subscription_plans 的 10 列名与 ORM/数据库(TypeORM+PostgreSQL);claim 端点已修复(400,无泄露)。注入本身被 参数化阻止——泄露的是查询结构,为后续攻击建模提供 schema 情报。
Impact: Two endpoints (eligibility with a non-UUID parameter, plans?status with an injection payload) still echo the full SQL in their error responses, leaking the 10 column names of the subscription_plans table and the ORM/database (TypeORM+PostgreSQL); the claim endpoint is fixed (400, no leak). The injection itself is blocked by parameterization — what leaks is the query structure, providing schema intelligence for modeling later attacks.
机制:参数格式/类型不匹配时,接口在错误响应中直接回显完整 SQL(select 全部列 from subscription_plans … where … = ),暴露表/字段/ORM。2/3 端点仍泄露,claim 已修复(400)。
Mechanism: When the parameter format/type mismatches, the endpoint echoes the full SQL directly in the error response (select all columns from subscription_plans … where … = ), exposing the table/fields/ORM. 2 of 3 endpoints still leak; claim is fixed (400).
根因:错误处理未脱敏,将底层查询细节(表/列/ORM)透出到响应。
Root cause: Error handling is not sanitized, exposing low-level query details (table/columns/ORM) into the response.
修复建议:统一错误处理,生产环境屏蔽底层 SQL/堆栈,返回通用错误。
Remediation: Unify error handling; in production, suppress low-level SQL/stack traces and return generic errors.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(3 张,其中打码 0 张)Evidence images (3; 0 masked)
DB-14 · 多端点输入验证不足导致 500 错误Insufficient input validation across endpoints causing 500 errors High部分修复Partially fixed
影响:暴露内部错误处理的 500/堆栈已修复;但字段类型校验仍缺失——POST /messages 传入对象型 role({"$gt":""})或 content(含 hidden_instruction)均未被类型拒绝(返回 201 而非 400),可作 ORM 操作符注入探测或 Prompt 注入原料。
Impact: The 500/stack traces that exposed internal error handling are fixed; but field type validation is still missing — POST /messages with an object-typed role ({"$gt":""}) or content (containing hidden_instruction) is not type-rejected (returns 201 instead of 400), usable as ORM-operator injection probing or prompt-injection material.
机制:NONEXISTENT_TOOL 已修复(现 201 + 友好错误,不再 500/堆栈);但 POST /messages 对 role/content 不做字段类型校验,传对象未被 400 拒绝即返回 201(本次复测因 chats 配额耗尽止于 INSUFFICIENT_CHATS_LIMIT),证明类型校验仍缺失。
Mechanism: NONEXISTENT_TOOL is fixed (now 201 + a friendly error, no more 500/stack); but POST /messages does no field-type validation on role/content — passing an object is not 400-rejected and returns 201 (this retest stopped at INSUFFICIENT_CHATS_LIMIT due to exhausted chat quota), proving type validation is still missing.
根因:字段缺少严格类型/白名单校验;异常处理已部分规范化(500 已修复)。
Root cause: Fields lack strict type/allowlist validation; exception handling is partly normalized (500 fixed).
修复建议:入参做严格类型与白名单校验(拒绝对象型 role/content);规范异常处理,避免泄露内部细节。
Remediation: Apply strict type and allowlist validation on inputs (reject object-typed role/content); normalize exception handling to avoid leaking internal details.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(3 张,均已打码)Evidence images (3; all masked)
DB-15 · Solana RPC 代理无认证Unauthenticated Solana RPC proxy Medium已修复Fixed
影响:(已修复)原可无认证经 Donut 的 Solana RPC 代理发起 getBalance/getTokenAccountsByOwner 等链上查询,借用并消耗平台 RPC 配额;现 /solana/rpc 已加认证。
Impact: (Fixed) Originally one could, without authentication, make on-chain queries like getBalance/getTokenAccountsByOwner through Donut’s Solana RPC proxy, borrowing and consuming the platform’s RPC quota; /solana/rpc now requires authentication.
机制:原 RPC 代理无需认证即可调用任意 Solana RPC 方法(借用平台 RPC 资源);复测显示无 Cookie 请求对 getBalance、getTokenAccountsByOwner 均返回 401 TOKEN_MISSING,已加固。
Mechanism: The original RPC proxy allowed calling any Solana RPC method without authentication (borrowing platform RPC resources); the retest shows that cookieless requests to getBalance and getTokenAccountsByOwner both return 401 TOKEN_MISSING — hardened.
根因:代理入口缺认证与速率限制,平台为匿名请求买单。
Root cause: The proxy entry lacked authentication and rate limiting, with the platform footing the bill for anonymous requests.
修复建议:RPC 代理加认证与速率/配额限制,限制可调用方法。
Remediation: Add authentication and rate/quota limits to the RPC proxy, and restrict the callable methods.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,未打码)Evidence images (2; none masked)
DB-16 · 私有分析数据泄露Private analytics-data leakage Medium已修复Fixed
影响:(已修复)原可无认证读取任意钱包的私有持仓分析(avgBuyPrice/totalInvestedUsd/PnL 等),含他人钱包——可用于用户画像与攻击目标筛选;现 /users/wallets/positions 已加认证,自有与受害者钱包无 Cookie 请求均返回 401。
Impact: (Fixed) Originally one could, without authentication, read any wallet’s private position analytics (avgBuyPrice/totalInvestedUsd/PnL, etc.), including other people’s wallets — usable for user profiling and target selection; /users/wallets/positions now requires authentication, and cookieless requests for both own and victim wallets return 401.
机制:原 GET /users/wallets/positions?walletAddress=任意钱包 无需认证即返回私有财务字段;复测显示无 Cookie 请求对自有与受害者钱包均返回 401 TOKEN_MISSING,已加固。
Mechanism: The original GET /users/wallets/positions?walletAddress=
根因:分析类接口缺访问控制。
Root cause: Analytics-type endpoints lacked access control.
修复建议:分析/统计接口加认证与授权,最小化对外字段。
Remediation: Add authentication and authorization to analytics/statistics endpoints, and minimize the externally exposed fields.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,其中打码 1 张)Evidence images (2; 1 masked)
DB-17 · API 缺失速率限制Missing API rate limiting Medium部分修复Partially fixed
影响:(部分修复)限流头已部署(x-ratelimit-limit:100/60s),但对高价值接口(/subscription/me、/users/profile)阈值过宽:20 并发全部 200、无 429——60s 内仍可发 100 次枚举/撞库/账号侦察。
Impact: (Partially fixed) Rate-limit headers are deployed (x-ratelimit-limit:100/60s), but the threshold is too loose for high-value endpoints (/subscription/me, /users/profile): 20 concurrent requests all return 200 with no 429 — 100 requests within 60s are still possible for enumeration / credential stuffing / account reconnaissance.
机制:原始完全无限流头已修复,响应现带 x-ratelimit-limit:100 / remaining / reset:60;但阈值 100次/60s 过宽,20 次并发未触发任何 429,敏感接口仍可批量枚举。
Mechanism: The original complete absence of rate-limit headers is fixed; responses now carry x-ratelimit-limit:100 / remaining / reset:60; but the 100-per-60s threshold is too loose — 20 concurrent requests trigger no 429, and sensitive endpoints can still be bulk-enumerated.
根因:限流阈值对高价值接口过宽,且并发未被有效约束。
Root cause: The rate-limit threshold is too loose for high-value endpoints, and concurrency is not effectively constrained.
修复建议:收紧高价值接口(登录/枚举类)阈值,约束并发,增加异常调用检测。
Remediation: Tighten thresholds on high-value endpoints (login/enumeration types), constrain concurrency, and add anomalous-call detection.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,均已打码)Evidence images (2; all masked)
DB-18 · S3 Bucket 或静态资源目录公开Public S3 bucket or static-asset directory Medium已修复Fixed
影响:(已修复/未发现暴露)对 20 个常见命名的 S3 Bucket 与 10 个静态路径做枚举:5 个 Bucket 存在但均 403(AccessDenied)、无公开目录列表,静态路径无目录遍历(无「Index of」)、无 .env/.pem/secret 等敏感文件——未发现可利用暴露。
Impact: (Fixed / no exposure found) Enumerating 20 commonly-named S3 buckets and 10 static paths: 5 buckets exist but all return 403 (AccessDenied) with no public directory listing, static paths have no directory traversal (no “Index of”), and no sensitive files like .env/.pem/secret — no exploitable exposure found.
机制:枚举 Donut S3 Bucket 命名与静态目录;存在的 Bucket 返回 403、无 ListBucketResult,目录无遍历——原对「公开桶/目录暴露」的担忧经复测未成立。
Mechanism: Enumerated Donut S3 bucket names and static directories; existing buckets return 403 with no ListBucketResult and directories have no traversal — the original concern about “public bucket / directory exposure” did not hold up on retest.
根因:(原担忧)存储桶/目录配置过松会暴露文件结构;复测显示当前配置已正确(403 + 无列举)。
Root cause: (Original concern) Overly loose bucket/directory configuration would expose the file structure; the retest shows the current config is correct (403 + no listing).
修复建议:维持关闭公开列举与目录遍历,敏感对象私有化,定期复检。
Remediation: Keep public listing and directory traversal disabled, keep sensitive objects private, and re-check periodically.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,其中打码 0 张)Evidence images (2; 0 masked)
DB-19 · Health / 运维端点信息泄露Health / ops-endpoint info leakage Medium部分修复Partially fixed
影响:(部分修复)原 /v1/backend/health 无认证即暴露 version/内存/DB/队列/集群等运维细节;现响应已精简为仅 {status:ok, timestamp},敏感字段移除——但无认证可访问这一核心仍未修复(26 个端点中仅此 1 个 200,余 404/超时)。
Impact: (Partially fixed) Originally /v1/backend/health exposed ops details like version/memory/DB/queue/cluster without authentication; the response is now slimmed to just {status:ok, timestamp} with sensitive fields removed — but the core issue of unauthenticated accessibility is still unfixed (of 26 endpoints, only this one returns 200; the rest 404/timeout).
机制:/v1/backend/health 仍无需认证返回 200;响应已精简为 {code:OK, data:{status:ok, timestamp}},不再含 version/memory/DB/queue/cluster;其余 25 个运维端点 404/超时。
Mechanism: /v1/backend/health still returns 200 without authentication; the response is slimmed to {code:OK, data:{status:ok, timestamp}}, no longer containing version/memory/DB/queue/cluster; the other 25 ops endpoints 404/timeout.
根因:健康端点未做访问控制(无认证可访问);原暴露的运维敏感字段已移除。
Root cause: The health endpoint has no access control (accessible without authentication); the previously exposed sensitive ops fields are removed.
修复建议:健康/运维端点加鉴权或内网化(敏感字段已移除,仍需收口无认证访问)。
Remediation: Add authentication to health/ops endpoints or move them to the internal network (sensitive fields are removed, but unauthenticated access still needs to be closed off).
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,其中打码 0 张)Evidence images (2; 0 masked)
DB-20 · MCP 工具配置完全公开MCP tool configuration fully public Medium已修复Fixed
影响:(已修复)原可无认证枚举 MCP 工具名/Provider/参数 schema(含 KAMINO/JUPITER 等 DeFi 工具完整调用参数),便于构造工具滥用链;现 10 个枚举端点无认证/带认证均 404,批量枚举能力已移除。
Impact: (Fixed) Originally one could enumerate, without authentication, MCP tool names/providers/parameter schemas (including the full invocation parameters of DeFi tools like KAMINO/JUPITER), making it easy to build a tool-abuse chain; now 10 enumeration endpoints all return 404 (authenticated or not), and the bulk-enumeration capability is removed.
机制:原 /action-mcp/tools 等工具目录端点可枚举完整工具配置;复测显示 10 个相关端点均 404,虚构工具名 query 返回 TOOL_NOT_FOUND——枚举路径已不存在。
Mechanism: The original tool-directory endpoints like /action-mcp/tools could enumerate the full tool config; the retest shows 10 related endpoints all 404, and a query with a made-up tool name returns TOOL_NOT_FOUND — the enumeration path no longer exists.
根因:(原)工具目录端点无访问控制;现已移除/下线,无法枚举。
Root cause: (Original) Tool-directory endpoints had no access control; they are now removed/taken offline and cannot be enumerated.
修复建议:工具目录加认证,按用户授权暴露可用工具。
Remediation: Add authentication to the tool directory and expose available tools per user authorization.
深度证据:§4 CORS、MCP 与组合攻击面
In-depth evidence: §4 CORS, MCP & the combined attack surface
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,其中打码 0 张)Evidence images (2; 0 masked)
DB-21 · OpenAPI / Scalar 文档暴露OpenAPI / Scalar docs exposure Medium已修复Fixed
影响:(已修复)原 Scalar UI 在生产环境完整暴露所有 API 路由(等于交出攻击面地图);复测 29 个文档端点(含第二后端 beta.donutlabs.dev)无认证全部 404,文档端点已完全移除。
Impact: (Fixed) Originally the Scalar UI fully exposed all API routes in production (effectively handing over an attack-surface map); on retest 29 doc endpoints (including the second backend beta.donutlabs.dev) all 404 unauthenticated — the doc endpoints are fully removed.
机制:原 /docs、/scalar、/openapi.json 等暴露完整 API 文档;复测两个后端(api-beta、beta.donutlabs.dev)共 29 个文档端点均 404,无任何 200。
Mechanism: The original /docs, /scalar, /openapi.json, etc. exposed the full API documentation; on retest the two backends (api-beta, beta.donutlabs.dev) have all 29 doc endpoints returning 404, with no 200 at all.
根因:(原)生产环境开放 API 文档端点;现已移除。
Root cause: (Original) Production left API doc endpoints open; they are now removed.
修复建议:生产关闭或鉴权 API 文档,仅在内网/开发环境开放。
Remediation: Disable or authenticate API docs in production, opening them only in internal/dev environments.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,其中打码 0 张)Evidence images (2; 0 masked)
DB-22 · 监控报告端点无认证Unauthenticated monitoring-report endpoint Medium已修复Fixed
影响:(已修复)原监控/报告端点(Prometheus metrics、Bull 队列面板、业务统计等)认证中间件未覆盖、可被未授权触达;复测 35 个端点无认证全部 404,监控类端点已移除/不存在,无未授权泄露。
Impact: (Fixed) Originally monitoring/reporting endpoints (Prometheus metrics, Bull queue board, business statistics, etc.) were not covered by the auth middleware and could be reached unauthorized; on retest 35 endpoints all 404 unauthenticated — monitoring endpoints are removed/nonexistent, with no unauthorized leak.
机制:原监控/报告端点未被认证中间件覆盖(接受未认证请求、返回 400 而非 401);复测 35 个端点(/metrics、/bull-board、/admin、/stats 等)无认证均 404,无 200/401/403——端点已移除或下线。
Mechanism: The original monitoring/reporting endpoints were not covered by the auth middleware (accepting unauthenticated requests and returning 400 instead of 401); on retest 35 endpoints (/metrics, /bull-board, /admin, /stats, etc.) all 404 unauthenticated, with no 200/401/403 — the endpoints are removed or taken offline.
根因:(原)认证中间件路由覆盖不全;现监控端点已移除。
Root cause: (Original) The auth middleware did not cover all routes; the monitoring endpoints are now removed.
修复建议:补齐认证中间件覆盖,监控端点强制鉴权。
Remediation: Complete the auth-middleware coverage and enforce authentication on monitoring endpoints.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,其中打码 0 张)Evidence images (2; 0 masked)
DB-23 · 任意钱包余额探测Arbitrary wallet-balance probing High部分修复Partially fixed
影响:(部分修复)无认证侧信道余额探测已封:portfolio 端点对自有/受害者/虚构钱包均返回 401、响应一致、无侧信道差异。但带认证 IDOR 仍存在:任意已登录用户携带 Cookie 即可读受害者钱包 portfolio(200 + balanceData),获取其 SOL/USD 资产——与 DB-3 高度重叠。
Impact: (Partially fixed) The unauthenticated side-channel balance probing is closed: the portfolio endpoint returns 401 for own/victim/made-up wallets with identical responses and no side-channel difference. But the authenticated IDOR remains: any logged-in user with a Cookie can read a victim wallet’s portfolio (200 + balanceData), obtaining its SOL/USD assets — heavily overlapping with DB-3.
机制:原可借错误响应/侧信道无认证探测任意钱包余额;复测显示无认证 portfolio 已统一 401(无侧信道)。但带认证 GET /users/wallets/portfolio?walletAddress=他人钱包 仍返回 200 + balanceData,受害者资产可被任意已登录用户读取。
Mechanism: Originally one could probe any wallet’s balance unauthenticated via error responses / side channels; the retest shows the unauthenticated portfolio is now uniformly 401 (no side channel). But authenticated GET /users/wallets/portfolio?walletAddress=
根因:余额/portfolio 接口不验证钱包归属(IDOR);无认证侧信道与错误回显已修复。
Root cause: The balance/portfolio endpoint does not verify wallet ownership (IDOR); the unauthenticated side channel and error echo are fixed.
修复建议:portfolio/余额接口强制校验钱包归属当前用户(堵 IDOR);错误信息脱敏(侧信道已修复)。
Remediation: Enforce that the portfolio/balance endpoint verifies the wallet belongs to the current user (close the IDOR); sanitize error messages (the side channel is fixed).
深度证据:§3 资产与交易历史 IDOR
In-depth evidence: §3 Assets & transaction-history IDOR
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
DB-24 · risk-metrics 全平台数据泄露risk-metrics platform-wide data leakage Medium已修复Fixed
影响:(已修复)原平台级风险/统计接口无需认证即返回聚合数据(活跃仓位、总钱包数、风险指标等),可用于识别高价值用户与系统状态;复测 15 个 risk-metrics 端点无认证/带认证均 404,端点已移除。
Impact: (Fixed) Originally the platform-level risk/statistics endpoint returned aggregated data without authentication (active positions, total wallet count, risk metrics, etc.), usable to identify high-value users and system status; on retest 15 risk-metrics endpoints all 404 (authenticated or not) — the endpoints are removed.
机制:原 /risk-metrics 等聚合接口无认证返回平台级风险/统计数据;复测 15 个相关端点无认证/带认证全部 404——端点已移除。
Mechanism: The original aggregation endpoints like /risk-metrics returned platform-level risk/statistics data without authentication; on retest 15 related endpoints all 404 (authenticated or not) — the endpoints are removed.
根因:(原)平台级聚合接口缺认证;现端点已移除。
Root cause: (Original) The platform-level aggregation endpoints lacked authentication; the endpoints are now removed.
修复建议:聚合/指标接口加认证,限制对外暴露的商业敏感数据。
Remediation: Add authentication to aggregation/metrics endpoints and limit the commercially sensitive data exposed externally.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
DB-25 · beta.donutlabs.dev 第二后端暴露beta.donutlabs.dev second-backend exposure High未修复Unfixed
影响:beta.donutlabs.dev 第二后端仍在线、运行同一套后端 API,形成独立于 api-beta.donutbrowser.ai 的攻击面。多数敏感端点已加认证(401),但 /v1/backend/health 与 /subscription/plans 仍无认证 200(与主站 DB-19/DB-12 同源)。主站已修复的漏洞若未同步到第二后端即可被绕过利用。
Impact: The second backend beta.donutlabs.dev is still online and runs the same backend API, forming an attack surface independent of api-beta.donutbrowser.ai. Most sensitive endpoints now require authentication (401), but /v1/backend/health and /subscription/plans still return 200 unauthenticated (same origin as the main site’s DB-19/DB-12). Vulnerabilities fixed on the main site can be bypassed if the fixes are not synced to the second backend.
机制:第二后端 beta.donutlabs.dev 跑同一套 API;复测 8 个有响应端点中 subscription/me、users/profile、portfolio、terminal/threads 等已 401,但 health、subscription/plans 仍无认证 200——修复未完全同步,形成绕过通道。
Mechanism: The second backend beta.donutlabs.dev runs the same API; of the 8 responsive endpoints on retest, subscription/me, users/profile, portfolio, terminal/threads, etc. now return 401, but health and subscription/plans still return 200 unauthenticated — the fixes are not fully synced, creating a bypass channel.
根因:备用后端对公网暴露且与主站独立维护,安全基线未同步。
Root cause: The backup backend is exposed to the public internet and maintained separately from the main site, with security baselines not synced.
修复建议:下线或内网化备用后端,确保所有环境同步安全基线与访问控制。
Remediation: Take the backup backend offline or move it internal, and ensure all environments share the same security baseline and access control.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,其中打码 1 张)Evidence images (2; 1 masked)
DB-26 · wallet-service 钱包详情无认证查询wallet-service wallet-detail unauthenticated query High已修复Fixed
影响:(已修复)原 wallet-service 钱包详情查询缺认证/归属校验,可枚举任意钱包详情(publicKey/turnkey/organizationId 等)、组合资金攻击链;复测 15 个端点无认证/带认证(含受害者钱包 IDOR)全部 404,端点已完全移除。
Impact: (Fixed) Originally the wallet-service wallet-detail query lacked authentication/ownership checks, allowing enumeration of any wallet’s details (publicKey/turnkey/organizationId, etc.) and assembly of a fund-attack chain; on retest 15 endpoints all 404 (authenticated or not, including victim-wallet IDOR) — the endpoints are fully removed.
机制:原 wallet-service/wallets 等可无认证/越权查询钱包详情;复测 15 个相关端点无认证与带认证 IDOR 均 404,无 publicKey/walletAddress/turnkey/organizationId 字段暴露——端点已移除。
Mechanism: The original wallet-service/wallets, etc. allowed unauthenticated/unauthorized querying of wallet details; on retest 15 related endpoints all 404 both unauthenticated and via authenticated IDOR, with no publicKey/walletAddress/turnkey/organizationId fields exposed — the endpoints are removed.
根因:(原)钱包服务接口缺对象级授权;现端点已移除。
Root cause: (Original) The wallet-service endpoints lacked object-level authorization; the endpoints are now removed.
修复建议:钱包详情查询强制认证与归属校验。
Remediation: Require authentication and ownership checks for wallet-detail queries.
深度证据:§3 资产与交易历史 IDOR
In-depth evidence: §3 Assets & transaction-history IDOR
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
DB-27 · Admin taskKey / 管理计划路径暴露Admin taskKey / management-plan path exposure High未修复Unfixed
影响:/subscription/plans 无认证(200)即暴露 Admin 计划(name=Admin、price=-1、taskKey=_admin、slots=unlimit);主站无/带认证 + 第二后端 beta.donutlabs.dev 三者一致暴露。带认证 eligibility 返回 eligible=true,Claim 仅被 plan 状态阻断、无角色鉴权。与 DB-12 同源,此处侧重无认证 + 第二后端 + _admin taskKey 维度。
Impact: /subscription/plans unauthenticated (200) exposes the Admin plan (name=Admin, price=-1, taskKey=_admin, slots=unlimit); the main site (with and without auth) plus the second backend beta.donutlabs.dev all expose it consistently. Authenticated eligibility returns eligible=true, and Claim is blocked only by plan status, with no role authorization. Same origin as DB-12; this entry focuses on the unauthenticated + second-backend + _admin taskKey dimensions.
机制:plans 公开返回含 _admin taskKey、unlimit、price=-1 的 Admin 计划(无认证即可见,第二后端同样);eligibility 对普通用户 eligible=true;claim 唯一闸门是 plan 状态、无角色校验。/admin、/admin/plans 等管理路径已 404。
Mechanism: plans publicly returns the Admin plan with the _admin taskKey, unlimit, price=-1 (visible without authentication, and on the second backend too); eligibility returns eligible=true for ordinary users; the only gate on claim is plan status, with no role check. Management paths like /admin, /admin/plans now 404.
根因:管理路径/参数未隐藏且缺权限校验。
Root cause: Management paths/parameters are not hidden and lack authorization checks.
修复建议:管理路径下沉内网、强制管理员鉴权;隐藏内部计划/taskKey,对 eligibility/claim 增加角色级校验,并同步到第二后端。
Remediation: Move management paths internal and enforce admin authentication; hide internal plans/taskKeys, add role-level checks on eligibility/claim, and sync this to the second backend.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,均已打码)Evidence images (2; all masked)
DB-28 · ORM 操作符注入线索ORM operator-injection indicators Medium未修复Unfixed
影响:复测确认:POST /messages 对 role/content 无类型校验——10 种操作符/类型 payload($ne/$gt/$regex/$in/$exists 对象 + null/int/bool/array)全部 201 通过验证层(被 chats 配额阻断、非类型拦截);claim(planId 必须 UUID)、mcp(toolName 必须字符串)已加 400 验证。/messages 是唯一仍缺类型强制的高暴露端点,ORM 操作符可进 role/content——属查询污染攻击面,实际注入影响未在配额内验证。
Impact: The retest confirms: POST /messages does no type validation on role/content — 10 operator/type payloads ($ne/$gt/$regex/$in/$exists objects + null/int/bool/array) all pass the validation layer with 201 (blocked by the chat quota, not by type checking); claim (planId must be a UUID) and mcp (toolName must be a string) now have 400 validation. /messages is the only high-exposure endpoint still lacking type enforcement, and ORM operators can reach role/content — a query-pollution attack surface; the actual injection impact was not verified within the quota.
机制:POST /messages 的 role/content 不做类型校验:10 种 payload 均返回 201(未被 400 类型拒绝,止于 INSUFFICIENT_CHATS_LIMIT、msg_id=undefined);对比 claim/mcp 同类 payload 均 400。属 ORM 查询污染攻击面/线索,非已验证的注入执行。
Mechanism: POST /messages does no type validation on role/content: all 10 payloads return 201 (not 400-rejected by type, stopping at INSUFFICIENT_CHATS_LIMIT with msg_id=undefined); by contrast, the same payloads to claim/mcp all return 400. This is an ORM query-pollution attack surface / indicator, not verified injection execution.
根因:/messages 的 role/content 未限制为标量、未防御对象操作符注入(其余端点已防御)。
Root cause: /messages does not restrict role/content to scalars or defend against object-operator injection (other endpoints already do).
修复建议:入参强制标量校验/白名单,ORM 层禁用未预期的对象操作符。
Remediation: Enforce scalar validation/allowlists on inputs, and disable unexpected object operators at the ORM layer.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(2 张,均已打码)Evidence images (2; all masked)
DB-29 · Helius Webhook 路径仍可访问Helius webhook path still accessible Medium已修复Fixed
影响:(已修复)原 Helius webhook 入口仅靠路径“保密”、无签名校验,可被探测或伪造事件;复测主后端 30 条 GET + 8 条 POST + 第二后端 11 条路径,无认证/带认证全部 404,webhook 路径已完全移除,无法伪造 Helius 事件。
Impact: (Fixed) Originally the Helius webhook entry relied only on path “secrecy” with no signature verification, allowing probing or forged events; on retest 30 GET + 8 POST paths on the main backend + 11 paths on the second backend all return 404 (authenticated or not) — the webhook paths are fully removed and Helius events cannot be forged.
机制:原 webhook 端点仅靠路径保密、无来源签名校验;复测两后端(api-beta + beta.donutlabs.dev)所有 webhook 路径变种 + POST fake payload 均 404——端点已移除。
Mechanism: The original webhook endpoint relied only on path secrecy with no source-signature verification; on retest all webhook path variants + POST fake payloads on both backends (api-beta + beta.donutlabs.dev) return 404 — the endpoints are removed.
根因:Webhook 入口仅靠路径“保密”,缺签名/密钥校验。
Root cause: The webhook entry relied only on path “secrecy”, lacking signature/secret verification.
修复建议:Webhook 强制来源签名/密钥校验,不依赖路径保密。
Remediation: Enforce source-signature/secret verification on webhooks rather than relying on path secrecy.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-02)Retest (2026-06-02)
证据截图(3 张,其中打码 0 张)Evidence images (3; 0 masked)
DB-30 · donutlabs.dev 通配符 DNS / 子域解析donutlabs.dev wildcard DNS / subdomain resolution Medium未修复Unfixed
影响:donutlabs.dev 配置通配符 DNS,任意子域均可解析(复测 6/6 随机子域成功)。但复测显示 *.donutlabs.dev 的 CORS 已拒绝、HTTP 返回 404(无内容托管)——CORS/内容托管攻击链不成立;残余风险为:① 钓鱼视觉信任(任意子域可伪装官方);② subdomain takeover(DNS 指向退役 IP/服务时可被接管);③ SameSite=Lax 将 *.donutlabs.dev 视为同站、扩大同站请求边界。
Impact: donutlabs.dev is configured with wildcard DNS, so any subdomain resolves (retest: 6/6 random subdomains succeeded). But the retest shows CORS for *.donutlabs.dev is now rejected and HTTP returns 404 (no content hosted) — the CORS / content-hosting attack chain does not hold; the residual risks are: ① phishing visual trust (any subdomain can impersonate the official site); ② subdomain takeover (if DNS points to a decommissioned IP/service, it can be taken over); ③ SameSite=Lax treats *.donutlabs.dev as same-site, widening the same-site request boundary.
机制:通配符 DNS 使任意子域解析成功;但任意子域 HTTP 返回 404(无内容)、CORS 对 *.donutlabs.dev 已拒绝(无 ACAO)。风险限于钓鱼视觉信任、subdomain takeover 与 SameSite 同站边界,而非 CORS/内容托管。
Mechanism: Wildcard DNS makes any subdomain resolve; but any subdomain’s HTTP returns 404 (no content), and CORS for *.donutlabs.dev is now rejected (no ACAO). The risk is limited to phishing visual trust, subdomain takeover, and the SameSite same-site boundary, not CORS / content hosting.
根因:通配符 DNS 解析未收敛;SameSite=Lax 将所有子域视为同站(CORS 已收紧到拒绝)。
Root cause: Wildcard DNS resolution is not narrowed; SameSite=Lax treats all subdomains as same-site (CORS has been tightened to reject).
修复建议:收敛通配符 DNS 到确切子域,清理退役 DNS 记录防 takeover,按需收紧 SameSite。
Remediation: Narrow wildcard DNS to exact subdomains, clean up decommissioned DNS records to prevent takeover, and tighten SameSite as needed.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-03)Retest (2026-06-03)
【DNS 通配符确认】
随机任意子域全部解析成功(6/6):
evil.donutlabs.dev → [IP已打码]
attacker.donutlabs.dev → [IP已打码]
test123.donutlabs.dev → [IP已打码]
xss.donutlabs.dev → [IP已打码]
notexist-abc987.donutlabs.dev → [IP已打码]
retest-probe.donutlabs.dev → [IP已打码]
【HTTP 可达性】
任意子域 HTTP 均返回 404(服务器无内容),但 DNS 已解析;app.donutlabs.dev HTTP 200。
【CORS 评估】
API 对所有 *.donutlabs.dev 任意子域 CORS 已拒绝(无 ACAO 头),完整 CORS+credentials 攻击链被阻断。
注:beta.donutbrowser.ai ACAO+ACAC=true 为合法前端,属 DB-4 问题范畴。
【残余风险】
1. 任意 *.donutlabs.dev 子域名可被 DNS 解析 → 钓鱼页面可伪装为官方子域(视觉信任风险)
2. Subdomain takeover:若 DNS 指向的 IP/服务退役但 DNS 记录未清除,攻击者可接管
3. SameSite=Lax Cookie:浏览器将 *.donutlabs.dev 视为同站,通配符扩大同站请求边界
[DNS wildcard confirmed]
All random arbitrary subdomains resolved successfully (6/6):
evil.donutlabs.dev → [IP masked]
attacker.donutlabs.dev → [IP masked]
test123.donutlabs.dev → [IP masked]
xss.donutlabs.dev → [IP masked]
notexist-abc987.donutlabs.dev → [IP masked]
retest-probe.donutlabs.dev → [IP masked]
[HTTP reachability]
Any subdomain’s HTTP returns 404 (the server has no content), but DNS resolves; app.donutlabs.dev returns HTTP 200.
[CORS assessment]
The API now rejects CORS for any *.donutlabs.dev subdomain (no ACAO header), blocking the full CORS+credentials attack chain.
Note: beta.donutbrowser.ai with ACAO+ACAC=true is the legitimate front end, within the scope of the DB-4 issue.
[Residual risk]
1. Any *.donutlabs.dev subdomain can be resolved by DNS → a phishing page can impersonate an official subdomain (visual-trust risk)
2. Subdomain takeover: if the IP/service the DNS points to is decommissioned but the DNS record is not cleared, an attacker can take it over
3. SameSite=Lax Cookie: the browser treats *.donutlabs.dev as same-site, and the wildcard widens the same-site request boundary
证据截图(3 张,其中打码 1 张)Evidence images (3; 1 masked)
DB-31 · MCP 工具生态完整枚举Full enumeration of the MCP tool ecosystem Medium未修复Unfixed
影响:无认证已封(401);但带认证(任意已登录用户)GET /systems/mcp-tool-config → 200 返回 62 个工具名;action-mcp/query 对 20 个 DeFi 工具(JUPITER/KAMINO/DRIFT/SEND_TRANSFER 等)经响应码差异可枚举存在性;对存在工具发空 args 触发 validation 错误、回显必填参数名(部分 schema)。任意已登录用户即可获取工具全名 + 调用路径 + 必填参数——DB-1/5/8 的前置信息来源。
Impact: The unauthenticated entry is closed (401); but authenticated (any logged-in user) GET /systems/mcp-tool-config → 200 returns 62 tool names; action-mcp/query can enumerate the existence of 20 DeFi tools (JUPITER/KAMINO/DRIFT/SEND_TRANSFER, etc.) via response-code differences; sending empty args to an existing tool triggers a validation error that echoes the required parameter names (partial schema). Any logged-in user can obtain tool full names + invocation paths + required parameters — the upstream information source for DB-1/5/8.
机制:无认证 401;带认证 mcp-tool-config 返回全量工具名;action-mcp/query 通过 201 响应码差异(VALIDATION_ERROR/INVALID_REQUEST=存在,TOOL_NOT_FOUND=未激活)枚举工具;空 args validation 回显必填参数。
Mechanism: Unauthenticated is 401; authenticated, mcp-tool-config returns the full set of tool names; action-mcp/query enumerates tools via differences within the 201 response (VALIDATION_ERROR/INVALID_REQUEST = exists, TOOL_NOT_FOUND = not activated); empty-args validation echoes required parameters.
根因:认证后无最小化/授权——任意已登录用户可读全量工具生态(无认证已封,带认证未收口)。
Root cause: No minimization/authorization after login — any logged-in user can read the full tool ecosystem (unauthenticated is closed, but authenticated is not locked down).
修复建议:工具枚举/配置接口按角色授权与最小化,统一错误响应避免 schema 回显。
Remediation: Authorize and minimize the tool-enumeration/config endpoints by role, and unify error responses to avoid schema echoing.
深度证据:§4 CORS、MCP 与组合攻击面
In-depth evidence: §4 CORS, MCP & the combined attack surface
复测过程(2026-06-03)Retest (2026-06-03)
【mcp-tool-config — 带认证可读】
无认证 → HTTP 401 TOKEN_MISSING ✅(无认证已封)
带认证(任意已登录用户)→ HTTP 200,返回完整工具配置(toolName、confirm 标志、inputSchema 等)❌
【action-mcp/query 工具名枚举(响应差异)】
探测 20 个工具名,全部返回 HTTP 201(非 400),通过 JSON body code 的差异可区分工具状态:
- 真实存在的工具(5个):VALIDATION_ERROR / INVALID_REQUEST
JUPITER_SWAP / JUPITER_CREATE_LIMIT_ORDER / JUPITER_CANCEL_LIMIT_ORDER / WRAP_UNWRAP_SOL / KAMINO_DEPOSIT / KAMINO_WITHDRAW
- AI层路由存在但工具未激活(多个):TOOL_NOT_FOUND(HTTP 201)
所有探测 toolName 均获得 HTTP 201,攻击者可通过暴力枚举配合响应差异系统性发现工具清单。
【影响】
任意已登录用户可获取:工具全名、调用路径(action-mcp/query→execution)、confirm 标志(是否需要弹窗确认)、inputSchema(参数类型/必填项),大幅降低攻击链构造成本(DB-1/DB-5/DB-8 的前置信息来源)。
[mcp-tool-config — readable when authenticated]
Unauthenticated → HTTP 401 TOKEN_MISSING ✅ (unauthenticated closed)
Authenticated (any logged-in user) → HTTP 200, returning the full tool config (toolName, confirm flag, inputSchema, etc.) ❌
[action-mcp/query tool-name enumeration (response difference)]
Probing 20 tool names, all return HTTP 201 (not 400); the difference in the JSON body code distinguishes tool status:
- Tools that actually exist (5): VALIDATION_ERROR / INVALID_REQUEST
JUPITER_SWAP / JUPITER_CREATE_LIMIT_ORDER / JUPITER_CANCEL_LIMIT_ORDER / WRAP_UNWRAP_SOL / KAMINO_DEPOSIT / KAMINO_WITHDRAW
- AI-layer route exists but tool not activated (several): TOOL_NOT_FOUND (HTTP 201)
Every probed toolName gets HTTP 201, so an attacker can systematically discover the tool list via brute-force enumeration combined with response differences.
[Impact]
Any logged-in user can obtain: tool full names, invocation path (action-mcp/query→execution), confirm flag (whether a confirmation dialog is required), and inputSchema (parameter types / required items), greatly lowering the cost of building an attack chain (the upstream information source for DB-1/DB-5/DB-8).
证据截图(3 张,其中打码 2 张)Evidence images (3; 2 masked)
DB-32 · HTTP 安全头缺失Missing HTTP security headers Low未修复Unfixed
影响:API 与前端缺少部分安全头(API 缺 CSP/HSTS/COOP/COEP/CORP/cache-control;前端缺 CSP/COOP/COEP/CORP/x-xss-protection;第二后端 beta.donutlabs.dev 11 个全缺)。X-Frame-Options、X-Content-Type-Options 主站均已设置(点击劫持/MIME 嗅探已缓解);真正缺口是无 CSP(XSS 无策略层)、API 无 HSTS(HTTP 降级)、跨源隔离(COOP/COEP/CORP)缺失。
Impact: The API and front end are missing some security headers (API lacks CSP/HSTS/COOP/COEP/CORP/cache-control; front end lacks CSP/COOP/COEP/CORP/x-xss-protection; the second backend beta.donutlabs.dev lacks all 11). X-Frame-Options and X-Content-Type-Options are set on the main site (clickjacking / MIME sniffing mitigated); the real gaps are no CSP (no policy layer against XSS), no HSTS on the API (HTTP downgrade), and missing cross-origin isolation (COOP/COEP/CORP).
机制:X-Frame-Options(DENY/SAMEORIGIN)与 X-Content-Type-Options(nosniff)已设置;缺失的是 CSP(High,无 XSS 策略)、HSTS(High,API 端,HTTP 降级)、COOP/COEP/CORP(跨源隔离)、cache-control;第二后端 404 无任何安全头。
Mechanism: X-Frame-Options (DENY/SAMEORIGIN) and X-Content-Type-Options (nosniff) are set; what is missing is CSP (High, no XSS policy), HSTS (High, on the API, HTTP downgrade), COOP/COEP/CORP (cross-origin isolation), and cache-control; the second backend 404s with no security headers at all.
根因:部分安全头未配置(CSP/HSTS/跨源隔离类);第二后端完全未配置。
Root cause: Some security headers are unconfigured (CSP/HSTS/cross-origin-isolation types); the second backend is entirely unconfigured.
修复建议:补齐 CSP、HSTS(API)、COOP/COEP/CORP、cache-control;第二后端配齐全部安全头(X-Frame-Options/X-Content-Type-Options 主站已设)。
Remediation: Add CSP, HSTS (API), COOP/COEP/CORP, and cache-control; configure all security headers on the second backend (X-Frame-Options/X-Content-Type-Options are already set on the main site).
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-03)Retest (2026-06-03)
【API 后端 api-beta.donutbrowser.ai — 缺失 6 个】
❌ strict-transport-security(HSTS)[High] — 缺失,HTTP 降级风险
❌ content-security-policy(CSP)[High] — 缺失,无 XSS 防护策略
❌ cross-origin-opener-policy(COOP) — 缺失
❌ cross-origin-embedder-policy(COEP) — 缺失
❌ cross-origin-resource-policy(CORP) — 缺失
❌ cache-control — 缺失,API 响应可被缓存
✅ x-frame-options: SAMEORIGIN
✅ x-content-type-options: nosniff
✅ referrer-policy 已设置
✅ permissions-policy 已设置
【前端 SPA beta.donutbrowser.ai — 缺失 5 个】
❌ content-security-policy(CSP)[High] — 缺失,XSS 无防护层
❌ cross-origin-opener-policy(COOP) — 缺失
❌ cross-origin-embedder-policy(COEP) — 缺失
❌ cross-origin-resource-policy(CORP) — 缺失
❌ x-xss-protection — 缺失(旧浏览器兼容问题)
✅ x-frame-options、x-content-type-options、HSTS 已设置
【第二后端 beta.donutlabs.dev — 缺失 11 个(全缺)】
HTTP 404 响应,无任何安全头配置
[API backend api-beta.donutbrowser.ai — 6 missing]
❌ strict-transport-security (HSTS) [High] — missing, HTTP downgrade risk
❌ content-security-policy (CSP) [High] — missing, no XSS protection policy
❌ cross-origin-opener-policy (COOP) — missing
❌ cross-origin-embedder-policy (COEP) — missing
❌ cross-origin-resource-policy (CORP) — missing
❌ cache-control — missing, API responses can be cached
✅ x-frame-options: SAMEORIGIN
✅ x-content-type-options: nosniff
✅ referrer-policy set
✅ permissions-policy set
[Front-end SPA beta.donutbrowser.ai — 5 missing]
❌ content-security-policy (CSP) [High] — missing, no XSS protection layer
❌ cross-origin-opener-policy (COOP) — missing
❌ cross-origin-embedder-policy (COEP) — missing
❌ cross-origin-resource-policy (CORP) — missing
❌ x-xss-protection — missing (legacy-browser compatibility issue)
✅ x-frame-options, x-content-type-options, HSTS set
[Second backend beta.donutlabs.dev — 11 missing (all)]
HTTP 404 response, with no security-header configuration at all
证据截图(3 张,其中打码 1 张)Evidence images (3; 1 masked)
DB-33 · Cookie 安全属性不足Insufficient cookie security attributes Low未修复Unfixed
影响:6 个 Donut Cookie 中 5 个缺 HttpOnly。关键缺口是会话 Cookie @turnkey/session/v1 缺 HttpOnly(SameSite=Lax、Secure 均已设)→ XSS 下可被 JS 读取,直接喂给 DB-1 的会话窃取链;另一会话 Cookie dws-auth-token 的 HttpOnly/Secure/SameSite 三属性全部正确、无问题。非会话 Cookie(__vdp1、PostHog、AWSALBCORS)缺 HttpOnly,AWSALB 还缺 Secure/SameSite。报告原称会话 Cookie"缺 SameSite"不准确——其 SameSite=Lax 实际已设置。
Impact: Of 6 Donut cookies, 5 lack HttpOnly. The key gap is that the session cookie @turnkey/session/v1 lacks HttpOnly (SameSite=Lax and Secure are both set) → it can be read by JS under XSS, feeding directly into DB-1’s session-theft chain; the other session cookie, dws-auth-token, has all three attributes (HttpOnly/Secure/SameSite) correct and is fine. Non-session cookies (__vdp1, PostHog, AWSALBCORS) lack HttpOnly, and AWSALB additionally lacks Secure/SameSite. The report’s original claim that the session cookie "lacks SameSite" is inaccurate — its SameSite=Lax is in fact set.
机制:CDP Network.getAllCookies 读取 6 个 Cookie 属性——@turnkey/session/v1 缺 HttpOnly(SameSite=Lax/Secure 已设);dws-auth-token 三属性齐全;__vdp1/PostHog/AWSALBCORS 缺 HttpOnly;AWSALB 缺 HttpOnly+Secure+SameSite。会话 Cookie 缺 HttpOnly 是与 DB-1 串联的关键点(XSS→JS 读 token)。
Mechanism: CDP Network.getAllCookies reads the 6 cookies’ attributes — @turnkey/session/v1 lacks HttpOnly (SameSite=Lax/Secure set); dws-auth-token has all three; __vdp1/PostHog/AWSALBCORS lack HttpOnly; AWSALB lacks HttpOnly+Secure+SameSite. A session cookie lacking HttpOnly is the key link with DB-1 (XSS → JS reads the token).
根因:部分 Cookie 下发时未设 HttpOnly(含一个会话 Cookie);AWSALB 负载均衡 Cookie 未设 Secure/SameSite。
Root cause: Some cookies are issued without HttpOnly (including one session cookie); the AWSALB load-balancer cookie is set without Secure/SameSite.
修复建议:会话 Cookie(尤其 @turnkey/session/v1)补 HttpOnly;AWSALB 补 Secure/SameSite;非必须前端读取的 Cookie 统一加 HttpOnly。
Remediation: Add HttpOnly to session cookies (especially @turnkey/session/v1); add Secure/SameSite to AWSALB; add HttpOnly uniformly to cookies the front end does not need to read.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-03)Retest (2026-06-03)
【检测范围】CDP Network.getAllCookies + Set-Cookie 响应头原始抓取
Donut 相关 Cookie 总数:6 个(domain: donutbrowser.ai / donutlabs.dev)
【Cookie 安全属性问题】
- 存在安全问题的 Cookie:5 / 6 个
- 存在问题的会话类 Cookie:1 个(会话劫持直接风险)
- API Set-Cookie 返回:2 个(健康检查端点)
【常见缺失属性】
- SameSite 未设置或弱配置:多个 Cookie 缺少 SameSite,跨站请求可携带 Cookie
- HttpOnly 缺失:部分 Cookie 可被 JS 读取(XSS 场景下 Token 可被窃取)
- Secure 属性:部分 Cookie 未强制 HTTPS 传输
【会话 Cookie 核心风险】
@turnkey/session/v1(Turnkey 会话):若缺少 HttpOnly,XSS 载荷可直接通过 document.cookie 读取并转发,再结合 action-mcp/execution 完成链上交易(与 DB-1 构成攻击链)。
dws-auth-token(平台鉴权 Token):JWT,若 SameSite 未配置,可被 CSRF 携带触发未授权 API 操作。
[Scope] CDP Network.getAllCookies + raw capture of Set-Cookie response headers
Total Donut-related cookies: 6 (domain: donutbrowser.ai / donutlabs.dev)
[Cookie security-attribute issues]
- Cookies with a security issue: 5 / 6
- Session-type cookies with an issue: 1 (direct session-hijacking risk)
- API Set-Cookie returned: 2 (health-check endpoint)
[Common missing attributes]
- SameSite unset or weak: several cookies lack SameSite, so cross-site requests can carry the cookie
- HttpOnly missing: some cookies can be read by JS (the token can be stolen under XSS)
- Secure attribute: some cookies do not enforce HTTPS transport
[Core session-cookie risk]
@turnkey/session/v1 (Turnkey session): if it lacks HttpOnly, an XSS payload can read it directly via document.cookie and forward it, then combine with action-mcp/execution to complete an on-chain transaction (forming an attack chain with DB-1).
dws-auth-token (platform auth token): a JWT; if SameSite is unconfigured, it can be carried by CSRF to trigger unauthorized API operations.
证据截图(3 张,其中打码 2 张)Evidence images (3; 2 masked)
DB-34 · 前端敏感信息暴露Front-end sensitive-info exposure Low未修复Unfixed
影响:核心发现——前端 localStorage 明文存储 Turnkey READ_WRITE 完整会话(@turnkey/session/v3,含 userId/credentialBundle)。任意 XSS 可经 localStorage.getItem('@turnkey/session/v3') 直接提取完整可签名会话,结合 action-mcp/execution 静默完成链上交易,与 DB-1 构成完整攻击链;而 Donut 既无 CSP(DB-32)、会话 Cookie 又缺 HttpOnly(DB-33),无纵深防护。次要:window._POSTHOG_REMOTE_CONFIG 暴露 PostHog Project Key(属客户端公开端 key,风险有限)。Source Map:4 条 .map 路径返回 200,但可能为 SPA catch-all 返回 index.html(未经 Content-Type 确认),不计为确认暴露。
Impact: Core finding — the front-end localStorage stores a complete Turnkey READ_WRITE session in plaintext (@turnkey/session/v3, containing userId/credentialBundle). Any XSS can directly extract the full signable session via localStorage.getItem('@turnkey/session/v3') and, combined with action-mcp/execution, silently complete an on-chain transaction — forming a complete attack chain with DB-1; yet Donut has neither CSP (DB-32) nor HttpOnly on the session cookie (DB-33), so there is no defense in depth. Secondary: window._POSTHOG_REMOTE_CONFIG exposes the PostHog Project Key (a client-side public key, limited risk). Source maps: 4 .map paths return 200, but this may be the SPA catch-all returning index.html (not confirmed by Content-Type), so it is not counted as a confirmed exposure.
机制:CDP Runtime.evaluate 遍历前端存储/全局变量——localStorage 12 项中 @turnkey/session/v3 为 READ_WRITE 完整会话明文,@turnkey/active-session-key / all-session-keys 指向它;window._POSTHOG_REMOTE_CONFIG 含 PostHog Project Key。Turnkey 设计上将会话置于 localStorage 以跨标签共享,但需前端配套 CSP + HttpOnly 才安全,Donut 两者皆缺。
Mechanism: CDP Runtime.evaluate traverses front-end storage / global variables — among 12 localStorage items, @turnkey/session/v3 is a READ_WRITE full session in plaintext, and @turnkey/active-session-key / all-session-keys point to it; window._POSTHOG_REMOTE_CONFIG contains the PostHog Project Key. Turnkey by design places the session in localStorage for cross-tab sharing, but this is only safe if the front end also has CSP + HttpOnly, both of which Donut lacks.
根因:Turnkey 会话明文存于 localStorage(其设计如此)却缺 CSP/HttpOnly 纵深防护,使 XSS 即可提取可签名会话;PostHog Project Key 注入全局变量。
Root cause: The Turnkey session is stored in plaintext in localStorage (by its design) but without CSP/HttpOnly defense in depth, so XSS alone can extract the signable session; the PostHog Project Key is injected into a global variable.
修复建议:配套 CSP(DB-32)与会话 Cookie HttpOnly(DB-33)以缓解 localStorage 会话被 XSS 提取;如条件允许缩短 Turnkey 会话有效期;移除/收敛生产环境 Source Map。
Remediation: Add CSP (DB-32) and HttpOnly on the session cookie (DB-33) to mitigate XSS extraction of the localStorage session; shorten the Turnkey session lifetime where feasible; remove/narrow source maps in production.
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-03)Retest (2026-06-03)
【localStorage — 关键发现:Turnkey 会话明文存储】
@turnkey/session/v3(READ_WRITE 权限完整会话数据)→ 存储在 localStorage ❌
值: {"sessionType":"SESSION_TYPE_READ_WRITE","userId":"d8df9c46-...",...}
风险:任意 XSS 载荷可通过 localStorage.getItem('@turnkey/session/v3') 提取完整 Turnkey 会话,结合 action-mcp/execution 可静默完成链上交易(与 DB-1 构成完整攻击链)。
Turnkey 设计上将会话存在 localStorage 以支持浏览器跨标签共享,但 Donut 未配合 CSP(DB-32)和 HttpOnly Cookie(DB-33)形成纵深防护。
@turnkey/active-session-key = "@turnkey/session/v3"
@turnkey/all-session-keys = ["@turnkey/session/v3"]
PostHog Analytics 数据(ph_phc_KenOPD6W74...)— 含初始化时间、分析配置
sessionStorage: 3 项(PostHog window_id / session / primary_window_exists)
【JS 全局变量】
window._POSTHOG_REMOTE_CONFIG — 含 PostHog Project API Key(phc_KenOPD6W74...[已打码])
window.FontAwesomeConfig — 字体库配置
其他 window.Authenticator* 等为浏览器内建 API(误报,已过滤)
【Source Map 暴露】
4 条 Source Map 路径返回 HTTP 200:
/_next/static/chunks/main.js.map
/_next/static/chunks/pages/_app.js.map
/static/js/main.chunk.js.map
/static/js/bundle.js.map
注:SPA catch-all 可能导致 200 为 index.html,需结合 Content-Type 进一步确认是否为真实 .map 文件。
[localStorage — key finding: Turnkey session stored in plaintext]
@turnkey/session/v3 (full READ_WRITE session data) → stored in localStorage ❌
Value: {"sessionType":"SESSION_TYPE_READ_WRITE","userId":"d8df9c46-...",...}
Risk: any XSS payload can extract the full Turnkey session via localStorage.getItem('@turnkey/session/v3') and, combined with action-mcp/execution, silently complete an on-chain transaction (forming a complete attack chain with DB-1).
Turnkey by design keeps the session in localStorage to support cross-tab sharing in the browser, but Donut does not pair it with CSP (DB-32) and HttpOnly cookies (DB-33) for defense in depth.
@turnkey/active-session-key = "@turnkey/session/v3"
@turnkey/all-session-keys = ["@turnkey/session/v3"]
PostHog Analytics data (ph_phc_KenOPD6W74...) — includes init time and analytics config
sessionStorage: 3 items (PostHog window_id / session / primary_window_exists)
[JS global variables]
window._POSTHOG_REMOTE_CONFIG — contains the PostHog Project API Key (phc_KenOPD6W74...[masked])
window.FontAwesomeConfig — font-library config
Others like window.Authenticator* are browser built-in APIs (false positives, filtered out)
[Source-map exposure]
4 source-map paths return HTTP 200:
/_next/static/chunks/main.js.map
/_next/static/chunks/pages/_app.js.map
/static/js/main.chunk.js.map
/static/js/bundle.js.map
Note: the SPA catch-all may cause the 200 to be index.html; confirm whether it is a real .map file using Content-Type.
证据截图(2 张,均已打码)Evidence images (2; all masked)
DB-35 · 详细错误信息、调试端点和生产指纹暴露Verbose error messages, debug endpoints, and production-fingerprint exposure Low已修复Fixed
影响:(已修复)复测三类指纹途径均无泄露——① 5 种畸形请求(不存在路径/无效 JSON/超大参数/特殊字符/SQL 关键词)全部返回友好错误(400/404),无栈追踪、无内部路径、无框架版本;② 28 条调试/测试端点(/debug、/actuator、/swagger、/graphql 等)全部 404;③ 响应头无 Server/X-Powered-By/X-Generator 指纹,500 无法触发;第二后端 beta.donutlabs.dev 同样无指纹。反而 X-Frame-Options:DENY、X-Content-Type-Options:nosniff 等安全头齐全。
Impact: (Fixed) On retest none of the three fingerprinting avenues leak — ① 5 kinds of malformed requests (nonexistent path / invalid JSON / oversized parameter / special characters / SQL keywords) all return friendly errors (400/404) with no stack traces, internal paths, or framework versions; ② 28 debug/test endpoints (/debug, /actuator, /swagger, /graphql, etc.) all 404; ③ response headers carry no Server/X-Powered-By/X-Generator fingerprints, and 500 cannot be triggered; the second backend beta.donutlabs.dev likewise has no fingerprint. On the contrary, security headers like X-Frame-Options:DENY and X-Content-Type-Options:nosniff are all present.
机制:带认证发起 5 种畸形请求 + 28 条调试端点探测 + 3 种 500 触发尝试 + 两后端响应头采集——错误响应统一为脱敏 JSON(code/message),调试端点全 404,无技术栈指纹,无法触发 500 栈泄露。
Mechanism: Authenticated requests issuing 5 kinds of malformed requests + probing 28 debug endpoints + 3 attempts to trigger a 500 + collecting both backends’ response headers — error responses are uniformly sanitized JSON (code/message), debug endpoints all 404, no tech-stack fingerprint, and a 500 stack leak cannot be triggered.
根因:原风险为生产保留调试/测试端点且错误未脱敏;复测确认生产已关闭调试/测试端点、错误响应已脱敏、已去除技术栈指纹。
Root cause: The original risk was that production kept debug/test endpoints and errors were not sanitized; the retest confirms production has closed debug/test endpoints, sanitized error responses, and removed the tech-stack fingerprint.
修复建议:已落实——生产关闭调试/测试端点、错误响应脱敏、去除版本/依赖指纹(保持现状即可)。
Remediation: Already implemented — production has closed debug/test endpoints, sanitized error responses, and removed version/dependency fingerprints (keeping the status quo is sufficient).
作为攻击链的辅助攻击面 / 信息收集组件出现,无独立深度证据章节;整体攻击链证据见下方 §1–§4。
Appears as an auxiliary attack surface / information-gathering component of the attack chain, with no standalone in-depth section; for the overall attack-chain evidence see §1–§4 below.
复测过程(2026-06-03)Retest (2026-06-03)
证据截图(3 张,其中打码 2 张)Evidence images (3; 2 masked)
- 所有链上交易验证使用研究员自有 Solana 钱包完成,已完成 11 笔自有资金范围内的真实上链。
- All on-chain transaction verification was done with the researcher’s own Solana wallet, completing 11 real on-chain transactions within the scope of the researcher’s own funds.
- 跨用户钱包归属校验测试中,请求由研究员自有账号发起、引用研究员事先构造的另一个研究员自有钱包,未涉及任何第三方真实用户资产。
- In the cross-user wallet-ownership-check tests, requests were issued from the researcher’s own account and referenced another of the researcher’s own wallets prepared in advance; no third-party real user assets were involved.
- 与 Donut 后端的全部交互均在 Donut 官方 Bug Hunt 活动及后续沟通框架内完成。
- All interactions with the Donut backend were carried out within the framework of Donut’s official Bug Hunt event and subsequent communications.
- 本页所有截图均为脱敏后版本,钱包公钥、交易哈希、Cookie、Turnkey 凭据、内部主机名按需打码。
- All screenshots on this page are redacted versions, with wallet public keys, transaction hashes, cookies, Turnkey credentials, and internal hostnames masked as needed.
Donut AI Security Disclosure Evidence
D0 AI Agent 自动化交易系统漏洞详情D0 AI-Agent Automated-Trading System — Vulnerability Details
D0 控制面访问、OpenClaw 识别、配置读取 / 修改、文件写入、自有 Pod RCE、Session Token 与容器环境信息读取相关脱敏证据集中放在本页。所有命令执行与配置修改验证均在研究员自有租户 / Pod 范围内完成,未对其他用户 Pod 进行未授权横向移动。
This page collects redacted evidence on D0 control-plane access, OpenClaw identification, config read / modify, file write, own-Pod RCE, and reading of the Session Token and container-environment information. All command-execution and config-modification verification was done within the researcher’s own tenant / Pod; no unauthorized lateral movement was performed against other users’ Pods.
逐条漏洞详情Per-Vulnerability Details
下面逐条列出每个漏洞的详情:编号、名称、等级、类型、复测状态、影响说明,每个漏洞卡片下可展开「证据截图」查看该漏洞的脱敏证据图。带 § 的漏洞在「攻击链」标签页另有逻辑路径说明。
Each vulnerability is listed below with its ID, name, severity, type, retest status, and impact; under each card you can expand “Evidence images” to view its redacted evidence. Items marked § also have a logical-path description in the “Attack Chains” tab.
D0-1 · 环境接口泄露 Gateway 连接信息与 TokenEnvironment API leaks Gateway connection info and Token Critical未修复Unfixed
影响:已登录态调用 /d0/environment 即 HTTP 200 返回该用户 OpenClaw 容器的完整控制面连接材料——gatewayToken(控制面唯一入口凭据,完整明文)+ externalIp:agentPort(公网直连,无 VPN / 二次鉴权)。任何登录用户立即获得控制面直达凭据,是 D0 攻击链的第一步(→ D0-8 反弹 Shell RCE)。
Impact: A logged-in call to /d0/environment returns HTTP 200 with the complete control-plane connection material for that user’s OpenClaw container — gatewayToken (the sole entry credential to the control plane, in full plaintext) + externalIp:agentPort (a direct public-internet connection, with no VPN / second authentication). Any logged-in user immediately obtains a direct credential to the control plane — the first step of the D0 attack chain (→ D0-8 reverse-shell RCE).
机制:登录态下 GET /v1/backend/d0/environment 返回 HTTP 200,响应体明文携带 gatewayToken、externalIp、internalIp、agentPort 等容器网络入口;控制面以明文直暴公网、未走反代 / 托管域,凭此可直达控制面并调用网关能力。
Mechanism: When logged in, GET /v1/backend/d0/environment returns HTTP 200, and the response body carries the container network entry points — gatewayToken, externalIp, internalIp, agentPort — in plaintext; the control plane is exposed to the public internet in plaintext without a reverse proxy / managed domain, so one can reach the control plane directly and invoke gateway capabilities.
根因:把控制面唯一鉴权凭据与明文入口下发到前端,控制面以明文直暴公网、未走反代/托管域。
Root cause: The sole control-plane credential and a plaintext entry point are delivered to the front end, and the control plane is exposed to the public internet in plaintext without a reverse proxy / managed domain.
修复建议:不向前端下发网关令牌与真实入口;控制面走反向代理 + 鉴权,凭据由后端持有、最小权限并短时轮换。
Remediation: Do not deliver the gateway token and real entry point to the front end; put the control plane behind a reverse proxy + authentication, with the credential held by the backend under least privilege and rotated on a short cycle.
深度证据:§1 控制面连接材料
In-depth evidence: §1 Control-plane connection material
复测过程(2026-06-03)Retest (2026-06-03)
【复测结果】
已登录状态下调用接口,HTTP 200 返回:
- 外部IP: [IP已打码]
- 内部IP: [IP已打码]
- agentPort: 19023
- gatewayToken: [已打码] (完整明文)
- sharedPodName: b39f8ea3-7a39-4cf8-a7d5-977e15a0c0d3
- agentId: agent-6fd74351abe2
- status: RUNNING
【风险】
- gatewayToken 是 OpenClaw 控制面的唯一入口凭据
- 外部IP:agentPort 为公网直连地址,无 VPN/代理保护
- 任何登录用户立即获得控制面直达凭据
- 结合 D0-8,形成完整的登录→凭据→RCE 攻击链
[Retest result]
Calling the endpoint while logged in, HTTP 200 returns:
- External IP: [IP masked]
- Internal IP: [IP masked]
- agentPort: 19023
- gatewayToken: [masked] (full plaintext)
- sharedPodName: b39f8ea3-7a39-4cf8-a7d5-977e15a0c0d3
- agentId: agent-6fd74351abe2
- status: RUNNING
[Risk]
- gatewayToken is the sole entry credential to the OpenClaw control plane
- External IP:agentPort is a direct public-internet address with no VPN/proxy protection
- Any logged-in user immediately obtains a direct control-plane credential
- Combined with D0-8, this forms a complete login→credential→RCE attack chain
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
D0-2 · WebSocket Origin / 来源边界不足Insufficient WebSocket Origin / source boundary High未修复Unfixed
影响:控制面 WebSocket 握手只校验 Origin 格式、无域名白名单——6 种 Origin 测试中 evil.example.com、http://localhost、控制面 IP 的 HTTP/HTTPS 变体均被接受(accepted:true),仅 "null" 与无 Origin 头被拒。client.id/mode 有常量白名单(伪造被 INVALID_REQUEST 拒),但 Origin 完全开放意味着任何持 gatewayToken(D0-1)的第三方网站 / 脚本均可建立控制面连接,无跨源防护。是 D0 攻击链第二步(从攻击者来源建连)。
Impact: The control-plane WebSocket handshake only checks the Origin format, with no domain allowlist — of 6 Origin tests, evil.example.com, http://localhost, and the HTTP/HTTPS variants of the control-plane IP are all accepted (accepted:true); only "null" and a missing Origin header are rejected. client.id/mode has a constant allowlist (forgeries are rejected with INVALID_REQUEST), but a fully open Origin means any third-party website / script holding the gatewayToken (D0-1) can establish a control-plane connection, with no cross-origin protection. This is the second step of the D0 attack chain (connecting from an attacker origin).
机制:以 6 种 Origin 接入控制面 WebSocket——合法 Origin ✓、evil.example.com ✓、localhost ✓、IP:PORT ✓ 全部接受;仅格式无效的 "null" / 无 Origin 头被拒。client.id/mode 字段有常量校验(custom-rogue-client / headless 被 INVALID_REQUEST 拒),但 Origin 无任何域名白名单。
Mechanism: Connecting to the control-plane WebSocket with 6 Origins — legitimate Origin ✓, evil.example.com ✓, localhost ✓, IP:PORT ✓ are all accepted; only the format-invalid "null" / missing Origin header are rejected. The client.id/mode fields have constant validation (custom-rogue-client / headless are rejected with INVALID_REQUEST), but Origin has no domain allowlist at all.
根因:WebSocket 握手只做 Origin 格式校验、无域名白名单,且未把连接绑定到合法前端来源。
Root cause: The WebSocket handshake only does Origin format validation with no domain allowlist, and does not bind the connection to a legitimate front-end origin.
修复建议:控制面连接强制 Origin 域名白名单(精确到合法前端域)+ 会话上下文绑定,拒绝非授权来源。
Remediation: Enforce an Origin domain allowlist (exact to the legitimate front-end domain) + session-context binding on control-plane connections, and reject unauthorized origins.
深度证据:§1 控制面连接材料
In-depth evidence: §1 Control-plane connection material
证据截图(2 张,其中打码 1 张)Evidence images (2; 1 masked)
D0-3 · operator/admin 高权限能力暴露或限制不足operator/admin high-privilege capabilities exposed or under-restricted Critical未修复Unfixed
影响:任意 operator 角色单一 token 即覆盖控制面全量高危能力——agents.files.list 枚举 Agent 核心配置文件、agents.files.set 覆写白名单内配置、cron.list 管理计划任务、secrets.resolve 访问密钥解析,无细粒度权限分离。任意文件名写入有白名单拦截(探针文件 d0-probe.md 被 "unsupported file" 拒),但白名单覆盖 AGENTS.md/SOUL.md 等 Agent 人格 / 指令文件,operator 无需二次鉴权即可覆写 → AI 人格劫持,正是 D0-8 RCE 链经 AGENTS.md 绕过的前置能力。
Impact: A single operator-role token covers the full set of high-risk control-plane capabilities — agents.files.list to enumerate the Agent’s core config files, agents.files.set to overwrite allowlisted configs, cron.list to manage scheduled tasks, secrets.resolve to access secret resolution — with no fine-grained privilege separation. Arbitrary-filename writes are blocked by an allowlist (the probe file d0-probe.md is rejected as "unsupported file"), but the allowlist covers Agent personality / instruction files like AGENTS.md/SOUL.md, which an operator can overwrite with no second authentication → AI personality hijacking, which is exactly the prerequisite capability the D0-8 RCE chain uses to bypass via AGENTS.md.
机制:operator 角色单 token 可调用 agents.files.list/set、cron.list、secrets.resolve 等高危方法;任意文件名(d0-probe.md)被 "unsupported file" 拒(有文件名白名单),但白名单内的 AGENTS.md/SOUL.md/TOOLS.md/IDENTITY.md/USER.md 可被覆写,无能力级最小授权。
Mechanism: A single operator-role token can invoke high-risk methods like agents.files.list/set, cron.list, and secrets.resolve; an arbitrary filename (d0-probe.md) is rejected as "unsupported file" (there is a filename allowlist), but the allowlisted AGENTS.md/SOUL.md/TOOLS.md/IDENTITY.md/USER.md can be overwritten, with no capability-level least privilege.
根因:控制面对 operator 会话授予过宽能力(读 / 写白名单文件、cron、secrets),缺少能力级最小授权与高危操作二次鉴权。
Root cause: The control plane grants the operator session overly broad capabilities (reading/writing allowlisted files, cron, secrets), lacking capability-level least privilege and second authentication for high-risk operations.
修复建议:按最小权限拆分控制面能力;Agent 核心配置文件(AGENTS.md/SOUL.md)写入需额外授权与完整性校验;secrets / cron / 文件写入等高危能力独立授权并审计。
Remediation: Split control-plane capabilities by least privilege; writing the Agent’s core config files (AGENTS.md/SOUL.md) should require additional authorization and integrity checks; high-risk capabilities like secrets / cron / file write should be independently authorized and audited.
深度证据:§3 配置读取、修改与文件写入
In-depth evidence: §3 Config read, modification & file write
证据截图(3 张,其中打码 0 张)Evidence images (3; 0 masked)
D0-4 · WebSocket 控制面配置读取WebSocket control-plane config read High未修复Unfixed
影响:持 gatewayToken 连控制面后,大量只读 RPC 无细粒度授权即可读出全量生产信息——config.get 返回完整生产配置(session-token 文件路径、内部 AWS ELB 明文 http 入口、DONUT_ENV=production、model 配置);channels.status / agents.list / sessions.list 暴露 Telegram Bot 状态、workspace 路径与会话历史;logs.tail 实时回读 286KB 生产运行日志(含 hostname、webhook 绑定、内部源码路径等 runtime 信息)。是 D0 攻击链的配置侦察环节。
Impact: After connecting to the control plane with the gatewayToken, a large set of read-only RPCs with no fine-grained authorization can read out the full production information — config.get returns the complete production config (session-token file path, internal AWS ELB plaintext http entry, DONUT_ENV=production, model config); channels.status / agents.list / sessions.list expose Telegram Bot status, workspace paths, and conversation history; logs.tail reads back 286KB of live production logs in real time (containing runtime info such as hostname, webhook bindings, internal source paths). This is the config-reconnaissance stage of the D0 attack chain.
机制:持令牌建立控制面 WebSocket 后,config.get / channels.status / agents.list / sessions.list / agents.files.list / logs.tail 等只读 RPC 均无方法级授权,任意 operator 即可读出服务端生产配置与实时运行信息。
Mechanism: After establishing the control-plane WebSocket with the token, read-only RPCs like config.get / channels.status / agents.list / sessions.list / agents.files.list / logs.tail all lack method-level authorization, so any operator can read the server’s production config and live runtime info.
根因:控制面只读 RPC 缺少方法 / 角色级授权,持令牌即获得对配置、会话、日志的广泛读权限;生产敏感配置经控制面明文下发。
Root cause: The control-plane read-only RPCs lack method/role-level authorization, so holding the token grants broad read access to config, sessions, and logs; production-sensitive config is delivered in plaintext via the control plane.
修复建议:控制面 RPC 按方法 / 角色最小授权;生产敏感配置(内部 ELB、session-token 路径等)不经控制面明文下发;日志接口限制并脱敏。
Remediation: Apply method/role least privilege to control-plane RPCs; do not deliver production-sensitive config (internal ELB, session-token path, etc.) in plaintext via the control plane; restrict and sanitize the log interface.
深度证据:§3 配置读取、修改与文件写入
In-depth evidence: §3 Config read, modification & file write
证据截图(3 张,其中打码 1 张)Evidence images (3; 1 masked)
D0-5 · agents.files.set 等文件写入能力过宽Overly broad file-write capability (agents.files.set, etc.) Critical未修复Unfixed
影响:agents.files.set 写入有文件名白名单(任意名 d0-probe.md 被 "unsupported file" 拒);但白名单覆盖 Agent 配置文件(AGENTS.md/SOUL.md 等),operator 可无二次鉴权覆写这些 → 写→执行链:覆写 AGENTS.md 为脚本 → beforeRun / heartbeat(config.patch)触发 Agent 执行 → 等效 RCE(D0-8 即经此实现)。
Impact: agents.files.set writes have a filename allowlist (the arbitrary name d0-probe.md is rejected as "unsupported file"); but the allowlist covers Agent config files (AGENTS.md/SOUL.md, etc.), which an operator can overwrite with no second authentication → a write→execute chain: overwrite AGENTS.md with a script → beforeRun / heartbeat (config.patch) triggers Agent execution → equivalent to RCE (which is how D0-8 is achieved).
机制:agents.list / config.get 确认 browser.executablePath=/usr/bin/chromium、workspace=/home/node/.openclaw/workspace、scope=per-sender;agents.files.set 对任意文件名返回 "unsupported file"(文件名白名单),但白名单内的 Agent 配置文件可被覆写,全程无二次鉴权,与 beforeRun / heartbeat 组合成写→执行链。
Mechanism: agents.list / config.get confirm browser.executablePath=/usr/bin/chromium, workspace=/home/node/.openclaw/workspace, scope=per-sender; agents.files.set returns "unsupported file" for arbitrary filenames (filename allowlist), but the allowlisted Agent config files can be overwritten with no second authentication throughout, combining with beforeRun / heartbeat into a write→execute chain.
根因:文件写入虽有文件名白名单,但白名单包含会被自动执行的 Agent 配置 / 脚本文件,且覆写无二次鉴权与完整性校验。
Root cause: Although file writes have a filename allowlist, the allowlist includes Agent config / script files that get auto-executed, and overwriting has no second authentication or integrity check.
修复建议:收紧白名单、排除会被自动执行的配置文件;对 AGENTS.md/SOUL.md 等写入加二次授权 + 完整性校验 + 审计。
Remediation: Tighten the allowlist to exclude auto-executed config files; add second authorization + integrity checks + auditing to writes of AGENTS.md/SOUL.md, etc.
深度证据:§3 配置读取、修改与文件写入
In-depth evidence: §3 Config read, modification & file write
证据截图(2 张,其中打码 1 张)Evidence images (2; 1 masked)
D0-6 · config.patch 缺乏足够审计和变更保护config.patch lacks adequate audit and change protection Medium未修复Unfixed
影响:config.patch 接口存在(需 config base hash,经 config.get 取得后即可改);探测 audit.list / config.history / changelog.list / events.list 四个审计接口全部返回 "unknown method" = 配置变更零可追溯;config.patch 无二次确认、无变更告警、无回滚。operator 可任意改写生产配置(beforeRun 脚本路径、DWS_API_URL 等)且不留审计——beforeRun 改写正是 D0-7 / D0-8 RCE 链的一步。
Impact: The config.patch endpoint exists (it needs the config base hash, obtainable via config.get, after which it can be changed); probing the four audit endpoints audit.list / config.history / changelog.list / events.list all return "unknown method" = config changes are zero-traceable; config.patch has no second confirmation, no change alert, and no rollback. An operator can arbitrarily rewrite the production config (beforeRun script path, DWS_API_URL, etc.) leaving no audit trail — rewriting beforeRun is precisely one step of the D0-7 / D0-8 RCE chain.
机制:config.patch(参数 raw + baseHash)存在且可调用;audit.list / config.history / changelog.list / events.list 均返回 unknown method;无审计日志、无变更告警、无回滚机制。
Mechanism: config.patch (parameters raw + baseHash) exists and is callable; audit.list / config.history / changelog.list / events.list all return unknown method; there is no audit log, no change alert, and no rollback mechanism.
根因:配置变更接口缺审计日志、变更审批 / 回滚与完整性校验,高危字段变更不留痕。
Root cause: The config-change endpoint lacks audit logging, change approval / rollback, and integrity checks; high-risk field changes leave no trace.
修复建议:config.patch 强制审计日志、变更审批与回滚;beforeRun / DWS_API_URL 等高危字段变更额外告警与二次确认。
Remediation: Enforce audit logging, change approval, and rollback on config.patch; add extra alerts and second confirmation for changes to high-risk fields like beforeRun / DWS_API_URL.
深度证据:§3 配置读取、修改与文件写入
In-depth evidence: §3 Config read, modification & file write
证据截图(1 张,其中打码 0 张)Evidence images (1; 0 masked)
D0-7 · heartbeat 脚本完整性校验不足Insufficient integrity checking of heartbeat scripts High未修复Unfixed
影响:set-heartbeats 接口存在、无二次鉴权,任意 operator 可配置定时执行(heartbeat);直接写 .sh 脚本被 agents.files.set 白名单拒("unsupported file"),但 heartbeat 的 beforeRun 可指向 workspace 内白名单文件(如 AGENTS.md),该文件内容无完整性 / 签名校验 → 覆写其内容 + 配 set-heartbeats = 持久化执行,绕过 AI 模型层安全审查、容器重启后仍在(D0-8 即经覆写 AGENTS.md 实现)。
Impact: The set-heartbeats endpoint exists with no second authentication, so any operator can configure scheduled execution (heartbeat); writing a .sh script directly is rejected by the agents.files.set allowlist ("unsupported file"), but the heartbeat’s beforeRun can point to an allowlisted file in the workspace (such as AGENTS.md), whose content has no integrity / signature check → overwriting its content + configuring set-heartbeats = persistent execution that bypasses the AI model-layer safety review and survives container restarts (which is how D0-8 is achieved, by overwriting AGENTS.md).
机制:config.get 确认 heartbeat 当前为空 {};set-heartbeats 存在(参数 enabled bool,无二次鉴权);agents.files.set 写 .sh 返回 "unsupported file"(文件类型 / 名白名单),故持久化执行经白名单内 markdown(beforeRun 目标)实现——该目标文件内容无签名 / 完整性校验。
Mechanism: config.get confirms heartbeat is currently empty {}; set-heartbeats exists (parameter enabled bool, no second authentication); agents.files.set writing .sh returns "unsupported file" (file-type / name allowlist), so persistent execution is achieved via an allowlisted markdown (the beforeRun target) — whose content has no signature / integrity check.
根因:heartbeat / beforeRun 目标文件内容无完整性 / 签名校验,set-heartbeats 无二次鉴权,执行不经模型层安全管控。
Root cause: The heartbeat / beforeRun target file’s content has no integrity / signature check, set-heartbeats has no second authentication, and execution bypasses the model-layer safety controls.
修复建议:set-heartbeats 加二次鉴权;beforeRun / heartbeat 目标文件做内容签名与完整性校验;执行纳入统一安全管控与审计。
Remediation: Add second authentication to set-heartbeats; apply content signing and integrity checks to beforeRun / heartbeat target files; bring execution under unified safety controls and auditing.
深度证据:§3 配置读取、修改与文件写入
In-depth evidence: §3 Config read, modification & file write
证据截图(2 张,其中打码 1 张)Evidence images (2; 1 masked)
D0-8 · OpenClaw beforeRun / 控制面机制导致 RCERCE via OpenClaw beforeRun / control-plane mechanisms Critical未修复Unfixed
影响:D0 为每位用户分配独立 OpenClaw 实例;复测以反弹 Shell PoC 实证 RCE(5 步链):
① 已登录态 GET /d0/environment 取 gatewayToken + externalIp:agentPort(D0-1)
② Ed25519 握手得 operator.admin(145 个控制面方法,D0-3)
③ agents.files.set 覆写白名单文件 AGENTS.md 为 bash 反弹脚本(.sh/.js 被拒、.md 不受限)
④ config.patch 设 beforeRun 指向 AGENTS.md
⑤ heartbeat 定时触发 → 容器内 bash 回连攻击者 VPS
拿到容器内交互式 Shell 后,攻击者可直接读取该用户的完整 dws-auth-token 会话 JWT(位于 /run/secrets/session-token),并从环境变量中取得网关令牌(GATEWAY_AUTH_TOKEN、OPENCLAW_GATEWAY_TOKEN)与内部 ELB 地址,足以冒充用户身份访问后端、并在内网横向探测。
唯一有效的安全边界来自容器自身的最小权限配置——不持有任何 Linux capability(CapEff=0)、以非 root 身份运行、且未挂载 Kubernetes 服务账号(NO_K8S),攻击者拿到 Shell 后难以进一步逃逸容器或提权至集群。
Impact: D0 assigns each user an isolated OpenClaw instance; the retest proves RCE with a reverse-shell PoC (a 5-step chain):
① logged in, GET /d0/environment to obtain gatewayToken + externalIp:agentPort (D0-1)
② an Ed25519 handshake yields operator.admin (145 control-plane methods, D0-3)
③ agents.files.set overwrites the allowlisted file AGENTS.md with a bash reverse-shell script (.sh/.js rejected, .md unrestricted)
④ config.patch sets beforeRun to point to AGENTS.md
⑤ the heartbeat timer fires → in-container bash calls back to the attacker’s VPS
Once holding an interactive in-container shell, the attacker can directly read that user’s full dws-auth-token session JWT (at /run/secrets/session-token) and obtain the gateway tokens (GATEWAY_AUTH_TOKEN, OPENCLAW_GATEWAY_TOKEN) and the internal ELB address from environment variables — enough to impersonate the user against the backend and probe laterally on the internal network.
The only effective security boundary comes from the container’s own least-privilege configuration — it holds no Linux capability (CapEff=0), runs as non-root, and mounts no Kubernetes service account (NO_K8S), so after getting a shell the attacker can hardly escape the container further or escalate to the cluster.
机制:持 gatewayToken 连控制面 WebSocket 即得 operator.admin;agents.files.set 对 .sh/.js 返 "unsupported file"(白名单),但白名单内 AGENTS.md(.md)可覆写;config.patch 改 beforeRun 指向该文件,heartbeat(约 30 分钟)定时触发 Agent 读取并执行其内 bash = 实质 RCE。
Mechanism: Connecting to the control-plane WebSocket with the gatewayToken yields operator.admin; agents.files.set returns "unsupported file" for .sh/.js (allowlist), but the allowlisted AGENTS.md (.md) can be overwritten; config.patch changes beforeRun to point to that file, and the heartbeat (about every 30 minutes) periodically triggers the Agent to read and execute the bash inside it = effectively RCE.
根因:控制面直暴公网、持令牌即 operator.admin;白名单文件(AGENTS.md)可写且其内容会被 beforeRun / heartbeat 执行,无内容完整性校验;高危能力无强隔离与二次鉴权。
Root cause: The control plane is exposed directly to the public internet and holding the token grants operator.admin; the allowlisted file (AGENTS.md) is writable and its content is executed by beforeRun / heartbeat with no content integrity check; high-risk capabilities have no strong isolation or second authentication.
修复建议:控制面不直连公网、走反向代理 + 鉴权;operator 能力最小化;beforeRun / heartbeat 目标文件内容签名 + 完整性校验;执行 / 写能力默认禁用并强授权;令牌最小权限并短时轮换。
Remediation: Do not connect the control plane directly to the public internet — put it behind a reverse proxy + authentication; minimize operator capabilities; sign and integrity-check beforeRun / heartbeat target file content; disable execute / write capabilities by default and require strong authorization; give tokens least privilege and rotate them on a short cycle.
深度证据:§3 配置读取、修改与文件写入 · §4 自有 Pod RCE 与环境信息读取
In-depth evidence: §3 Config read, modification & file write · §4 Own-Pod RCE & environment-info read
复测过程(2026-06-03)Retest (2026-06-03)
【攻击链(5步)】
1. GET /d0/environment → gatewayToken + 外部IP:agentPort(D0-1)
2. Ed25519 握手 → operator.admin 权限(145 个控制面方法)
3. agents.files.set → 覆写 AGENTS.md 为反弹 Shell bash 脚本(白名单文件绕过)
4. config.patch + config.get hash → beforeRun 指向 /home/node/.openclaw/workspace/AGENTS.md
5. heartbeat 定时器触发 → bash 脚本执行 → shell 回连 [IP已打码]:4444 ✅
【Shell 实证(2026-06-03 复测)】
- hostname: 98089f3ffbde(AWS aarch64 Ubuntu 22.04 / K8s 6.8.0-1050-aws)
- uid=10011 gid=10011(非 root,无名称)
- cat /run/secrets/session-token → 完整 dws-auth-token JWT 成功读取
- env | sort → GATEWAY_AUTH_TOKEN + OPENCLAW_GATEWAY_TOKEN + 内部 ELB 全量泄露
- CapEff=0000000000000000(最小权限容器,无逃逸能力)
- /var/run/secrets/kubernetes.io: NO_K8S(无 k8s 服务账号)
- /run/secrets tmpfs ro(session-token 挂载点,只读但容器进程可读)
- 内网网关: [IP已打码] / eth0
[Attack chain (5 steps)]
1. GET /d0/environment → gatewayToken + external IP:agentPort (D0-1)
2. Ed25519 handshake → operator.admin privilege (145 control-plane methods)
3. agents.files.set → overwrite AGENTS.md with a reverse-shell bash script (allowlisted-file bypass)
4. config.patch + config.get hash → beforeRun points to /home/node/.openclaw/workspace/AGENTS.md
5. heartbeat timer fires → bash script executes → shell calls back to [IP masked]:4444 ✅
[Shell proof (2026-06-03 retest)]
- hostname: 98089f3ffbde (AWS aarch64 Ubuntu 22.04 / K8s 6.8.0-1050-aws)
- uid=10011 gid=10011 (non-root, unnamed)
- cat /run/secrets/session-token → the full dws-auth-token JWT was read successfully
- env | sort → GATEWAY_AUTH_TOKEN + OPENCLAW_GATEWAY_TOKEN + the internal ELB fully leaked
- CapEff=0000000000000000 (least-privilege container, no escape capability)
- /var/run/secrets/kubernetes.io: NO_K8S (no k8s service account)
- /run/secrets tmpfs ro (the session-token mount point — read-only but readable by the container process)
- Internal gateway: [IP masked] / eth0
证据截图(10 张,其中打码 6 张)Evidence images (10; 6 masked)
D0-9 · Session Token 文件可由容器内进程读取Session Token file readable by in-container processes High未修复Unfixed
影响:用户的会话 JWT(dws-auth-token)以明文文件 /run/secrets/session-token 落在容器内,对容器进程完全可读。攻击者经 D0-8 拿到容器内 Shell 后,一条 cat 即可读出完整会话凭据;该凭据对后端 API 拥有该用户的全部权限——携带它调用 /d0/environment 返回 hasToken:true、完全授权,等同直接接管账户。换言之 D0-8(取得 Shell)+ D0-9(凭据可读)组合,把「拿到容器」升级为「拿到账户」。
Impact: The user’s session JWT (dws-auth-token) lands inside the container as the plaintext file /run/secrets/session-token, fully readable by container processes. After getting an in-container shell via D0-8, the attacker can read the full session credential with a single cat; that credential holds all of the user’s privileges against the backend API — calling /d0/environment with it returns hasToken:true, fully authorized, equivalent to directly taking over the account. In other words, the combination of D0-8 (getting a shell) + D0-9 (the credential being readable) upgrades “getting the container” to “getting the account”.
机制:控制面 config.get 暴露 DWS_SESSION_TOKEN_FILE=/run/secrets/session-token,明确了会话凭据的落盘路径;该文件以普通可读权限存放,容器内任意进程(含经 D0-8 注入的反弹 Shell)都能直接 cat 读取。读出 token 后,以 credentials 携带它调用 /v1/backend/d0/environment 即返回 hasToken:true,证明凭据有效且对后端完全授权。
Mechanism: The control-plane config.get exposes DWS_SESSION_TOKEN_FILE=/run/secrets/session-token, revealing the on-disk path of the session credential; the file is stored with ordinary read permissions, so any in-container process (including the reverse shell injected via D0-8) can read it directly with cat. After reading the token, carrying it as credentials to call /v1/backend/d0/environment returns hasToken:true, proving the credential is valid and fully authorized against the backend.
根因:长期有效的会话 JWT 以明文文件形式存放在容器内、且对同容器进程可读,未做加密或隔离;容器一旦被攻破即等于凭据泄露,攻击者可在 token 有效期内持续冒充用户。
Root cause: A long-lived session JWT is stored as a plaintext file inside the container and is readable by same-container processes, with no encryption or isolation; once the container is compromised it amounts to a credential leak, and the attacker can keep impersonating the user for the token’s lifetime.
修复建议:会话凭据不以明文落盘,改为加密存放或仅驻内存并最小化可读面;同时缩短 JWT 有效期并支持即时吊销,使单次容器入侵无法长期冒充用户。
Remediation: Do not store the session credential on disk in plaintext — encrypt it or keep it only in memory and minimize the readable surface; also shorten the JWT lifetime and support instant revocation, so a single container intrusion cannot impersonate the user for long.
In-depth evidence: §4 Own-Pod RCE & environment-info read
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
D0-10 · 容器环境变量泄露Container environment-variable leakage High未修复Unfixed
影响:容器把整批敏感配置以明文环境变量注入,任何已登录用户无需 RCE,经两条独立通道(HTTP API /d0/environment 与 WebSocket config.get)都能完整读出生产环境配置——生产标识(DONUT_ENV=production)、内部服务地址(DWS_API_URL)、LLM 代理入口(D0_LLM_PROXY_BASE_URL)、会话凭据落盘路径,以及明文 gatewayToken。攻击者据此可直接掌握内部拓扑并获得控制面接入材料;信息暴露在两条通道、且不需任何额外权限,使其更难收口。
Impact: The container injects the whole batch of sensitive config as plaintext environment variables, so any logged-in user — with no RCE needed — can fully read the production-environment config through two independent channels (the HTTP API /d0/environment and the WebSocket config.get): the production marker (DONUT_ENV=production), the internal service address (DWS_API_URL), the LLM proxy entry (D0_LLM_PROXY_BASE_URL), the session-credential on-disk path, and the plaintext gatewayToken. From this an attacker can directly grasp the internal topology and obtain control-plane connection material; the exposure across two channels, requiring no extra privilege, makes it harder to close off.
机制:config.get 直接返回完整 env 节,枚举出 5 个敏感环境变量;同一批信息又可经 HTTP API /d0/environment 独立读取。两条通道彼此印证、均无需 RCE——普通登录态调用受认证接口即可,gatewayToken 以明文随响应返回,可立即用于控制面接入。
Mechanism: config.get returns the full env section directly, enumerating 5 sensitive environment variables; the same batch of information can also be read independently via the HTTP API /d0/environment. The two channels corroborate each other and neither needs RCE — an ordinary logged-in call to the authenticated endpoints suffices, and the gatewayToken is returned in plaintext in the response, immediately usable for control-plane connection.
根因:生产标识、内部服务地址、网关凭据等敏感配置以明文环境变量注入容器,且经多个已认证接口对前端开放,未做密钥托管或最小化下发。
Root cause: Sensitive config such as the production marker, internal service addresses, and gateway credentials is injected into the container as plaintext environment variables and is open to the front end through multiple authenticated endpoints, with no secret management or minimized delivery.
修复建议:敏感配置改用受控密钥管理(KMS / Secrets),不以明文环境变量注入容器;最小化经接口下发到前端的配置字段,网关凭据由后端持有、最小权限并短时轮换。
Remediation: Move sensitive config to managed secret storage (KMS / Secrets) instead of injecting it as plaintext environment variables; minimize the config fields delivered to the front end via APIs, and have the backend hold the gateway credential under least privilege with short-cycle rotation.
In-depth evidence: §4 Own-Pod RCE & environment-info read
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
D0-11 · 内部 ELB / 后端入口暴露Internal ELB / backend-entry exposure Medium未修复Unfixed
影响:容器配置把内部后端入口以明文写出——DWS_API_URL 指向 AWS 悉尼区(ap-southeast-2)的内部 ELB(HTTP 明文),D0_LLM_PROXY_BASE_URL 是同一 ELB 的 /v1/backend/llm 路径,即 LLM 代理内部入口也一并暴露,相当于把内网拓扑与后端真实入口直接交给攻击者作为目标。浏览器直接访问该 ELB 会被网络/CORS 边界挡住(图2 的 Failed to fetch 即证明它是内网专用);但一旦结合 RCE(D0-8)进入容器、再带上会话 token(D0-9),即可从容器内以 HTTP 明文、绕过 CloudFront/WAF 全部边缘防护直调后端 API。
Impact: The container config writes the internal backend entry in plaintext — DWS_API_URL points to an internal ELB in the AWS Sydney region (ap-southeast-2) over plaintext HTTP, and D0_LLM_PROXY_BASE_URL is the /v1/backend/llm path on the same ELB, so the LLM proxy’s internal entry is exposed too, effectively handing the internal topology and the backend’s real entry to an attacker as a target. Accessing this ELB directly from a browser is blocked by the network/CORS boundary (the Failed to fetch in Fig 2 proves it is internal-only); but once combined with RCE (D0-8) to get into the container and then carrying the session token (D0-9), one can call the backend API directly from inside the container over plaintext HTTP, bypassing all of the CloudFront/WAF edge protections.
机制:config.get 返回的 env 节里,DWS_API_URL 与 D0_LLM_PROXY_BASE_URL 都直接写出内部 ELB 的完整域名,且不经反向代理或托管域、HTTP 明文暴露。浏览器发起的跨域探测被挡(Failed to fetch),说明它仅在内网可达;真正的可达点在容器内部——RCE 后即可绕过边缘 WAF 直连。
Mechanism: In the env section returned by config.get, both DWS_API_URL and D0_LLM_PROXY_BASE_URL write out the internal ELB’s full domain directly, exposed over plaintext HTTP with no reverse proxy or managed domain. A cross-origin probe from the browser is blocked (Failed to fetch), showing it is only reachable on the internal network; the real reachable point is inside the container — after RCE one can connect directly, bypassing the edge WAF.
根因:内部服务入口(后端 ELB、LLM 代理)以明文写入可被读取的配置,未做加密或内网隔离,等于把纵深攻击目标主动暴露。
Root cause: The internal service entries (backend ELB, LLM proxy) are written in plaintext into readable config with no encryption or internal-network isolation, which proactively exposes a deeper attack target.
修复建议:内部入口不以明文落配置;后端走反向代理/托管域并对内网做网络隔离,最小化对前端与容器的暴露面,关键路径强制经 WAF。
Remediation: Do not put internal entries in config in plaintext; place the backend behind a reverse proxy/managed domain with internal-network isolation, minimize the exposure surface to the front end and the container, and force critical paths through the WAF.
In-depth evidence: §4 Own-Pod RCE & environment-info read
证据截图(2 张,其中打码 1 张)Evidence images (2; 1 masked)
D0-12 · Pod 间网络隔离仍需进一步授权验证Pod-to-Pod network isolation needs further authorized verification High未修复Unfixed
影响:每位用户分配独立容器(scope=per-sender),本应彼此隔离;但本次确认这些容器与后端 ELB 处于同一 VPC、共享同一套 ELB 后端,且 ELB 未做容器级鉴权。这意味着在自有 Pod 内取得 RCE(D0-8)后,容器即处在可直连共享后端的网络位置——横向移动的架构条件已具备。出于白帽授权边界,本次未实际越界访问其他用户的 Pod;隔离强度需项目方进一步授权测试,才能排除跨租户横向移动风险。
Impact: Each user is assigned an isolated container (scope=per-sender) that should be mutually isolated; but this assessment confirms these containers and the backend ELB are in the same VPC, share the same ELB backend, and the ELB has no container-level authentication. This means that after getting RCE in one’s own Pod (D0-8), the container sits in a network position able to connect directly to the shared backend — the architectural conditions for lateral movement are in place. Within the white-hat authorization boundary, no other users’ Pods were actually accessed out of bounds; the isolation strength needs further authorized testing by the vendor to rule out the cross-tenant lateral-movement risk.
机制:config.get 显示 DWS_API_URL 与 D0_LLM_PROXY_BASE_URL 指向同一 AWS ELB(ap-southeast-2),容器 externalIp 直接对外可达、与 ELB 同 VPC,网络层无隔离;sessions.list 显示每用户独立容器(scope=per-sender)却共享同一 ELB 后端、ELB 未做容器级鉴权。即隔离只停留在「每人一个容器」,网络与后端仍是共享的,RCE 后容器处于可横向直连后端的位置。本次在白帽边界内未实际越界。
Mechanism: config.get shows DWS_API_URL and D0_LLM_PROXY_BASE_URL pointing to the same AWS ELB (ap-southeast-2); the container’s externalIp is directly publicly reachable and in the same VPC as the ELB, with no network-layer isolation; sessions.list shows per-user isolated containers (scope=per-sender) that nonetheless share the same ELB backend, with no container-level authentication on the ELB. So isolation stops at “one container per person” while the network and backend remain shared, and after RCE the container is positioned to connect laterally to the backend directly. This was not actually taken out of bounds, within the white-hat boundary.
根因:租户隔离只做到「每用户独立容器」,底层网络(同 VPC)与后端(共享 ELB、无容器级鉴权)未做对应隔离,横向移动的架构面因此存在。
Root cause: Tenant isolation only reaches “one isolated container per user”, while the underlying network (same VPC) and backend (shared ELB, no container-level authentication) have no corresponding isolation, so the lateral-movement architectural surface exists.
修复建议:强制 Pod 间网络隔离(NetworkPolicy)、最小化东西向连通;ELB / 后端按容器或租户做鉴权与隔离;并定期验证隔离边界的有效性。
Remediation: Enforce Pod-to-Pod network isolation (NetworkPolicy) and minimize east-west connectivity; authenticate and isolate the ELB / backend per container or tenant; and regularly verify the effectiveness of the isolation boundary.
In-depth evidence: §4 Own-Pod RCE & environment-info read
证据截图(2 张,其中打码 1 张)Evidence images (2; 1 masked)
D0-13 · Gateway Token 生命周期与轮换不足Insufficient Gateway Token lifecycle and rotation High未修复Unfixed
影响:gatewayToken 是固定 32 字符 hex(非 JWT、无 exp 字段),且跨会话静态不变——间隔 3 秒采样三次完全一致,还与会话初记录的历史值相同;同一 token 连续建立 3 次独立控制面 WebSocket 连接全部成功,无过期、无轮换、无速率限制、无单会话绑定。这意味着只要该 token 经任一路径泄露(D0-1 环境接口直吐、D0-8 RCE、D0-9 容器内读取),攻击者即获得对控制面的永久访问权,且无法靠「令牌自然过期」止损。
Impact: The gatewayToken is a fixed 32-character hex (not a JWT, no exp field) and is static across sessions — three samples taken 3 seconds apart are fully identical and also match the historical value recorded at the start of the session; the same token successfully establishes 3 consecutive independent control-plane WebSocket connections, with no expiry, no rotation, no rate limiting, and no single-session binding. This means that as soon as the token leaks via any path (D0-1 environment API spilling it, D0-8 RCE, D0-9 in-container read), an attacker gains permanent access to the control plane, and there is no way to stop the bleeding through “the token naturally expiring”.
机制:三次间隔 3s 调用 /d0/environment 取 gatewayToken,三值完全一致且 matches_historical=true,证明跨会话静态不变、无任何轮换;token 为固定 32 字符 hex(非 JWT,无 exp),用同一 token 连开 3 次控制面连接均成功,确认无过期、无使用次数限制、无单会话约束。
Mechanism: Calling /d0/environment three times at 3s intervals to fetch the gatewayToken, the three values are fully identical with matches_historical=true, proving it is static across sessions with no rotation; the token is a fixed 32-character hex (not a JWT, no exp), and opening 3 consecutive control-plane connections with the same token all succeed, confirming no expiry, no usage-count limit, and no single-session constraint.
根因:网关令牌缺少短时有效期、自动轮换与单会话绑定,泄露后无法快速失效,任意一次泄露都被放大为长期可用凭据。
Root cause: The gateway token lacks a short lifetime, automatic rotation, and single-session binding, so it cannot be quickly invalidated after a leak, and any single leak is amplified into a long-lived usable credential.
修复建议:网关令牌改为短时有效 + 自动轮换 + 单会话/单连接绑定,并支持即时吊销,使泄露后的可用窗口最小化。
Remediation: Make the gateway token short-lived + auto-rotated + bound to a single session/connection, and support instant revocation, minimizing the usable window after a leak.
深度证据:§1 控制面连接材料
In-depth evidence: §1 Control-plane connection material
证据截图(2 张,其中打码 1 张)Evidence images (2; 1 masked)
D0-14 · JWT 有效期与敏感凭据暴露风险JWT validity period and sensitive-credential exposure risk Medium未修复Unfixed
影响:多类会话与控制面凭据普遍缺乏有效的生命周期管理。dws-auth-token(JWT)有效期长达 7 天(约 167 小时剩余);浏览器 localStorage 里的会话 JWT(@turnkey/session/v3)剩余约 30 天(719 小时);gatewayToken 为固定 hex、永不过期、永不轮换(详见 D0-13);session-token 明文落盘且 RCE 后容器内可读(详见 D0-9)。综合起来,任意一类凭据一旦失窃,滥用窗口短则数天、长则永久,显著放大了前述各泄露路径的后果。
Impact: Multiple classes of session and control-plane credentials broadly lack effective lifecycle management. The dws-auth-token (JWT) has a validity of up to 7 days (about 167 hours remaining); the session JWT in browser localStorage (@turnkey/session/v3) has about 30 days remaining (719 hours); the gatewayToken is a fixed hex that never expires and never rotates (see D0-13); the session-token is stored on disk in plaintext and is readable inside the container after RCE (see D0-9). Taken together, once any class of credential is stolen, the abuse window is days at the short end and permanent at the long end, significantly amplifying the consequences of the leak paths above.
机制:提取到的凭据包括 4 个 JWT Cookie + 1 个 localStorage JWT;关键的 dws-auth-token 生命周期 7 天(约 167h 剩余),localStorage 的 @turnkey/session/v3 剩余约 719h;gatewayToken 无 exp、无轮换;session-token 落盘可读。多类凭据普遍缺乏短时有效期与轮换。
Mechanism: The extracted credentials include 4 JWT cookies + 1 localStorage JWT; the key dws-auth-token has a 7-day lifecycle (about 167h remaining), and localStorage’s @turnkey/session/v3 has about 719h remaining; the gatewayToken has no exp and no rotation; the session-token is stored on disk and readable. Multiple classes of credentials broadly lack a short lifetime and rotation.
根因:会话与控制面凭据的有效期普遍偏长、缺乏轮换,部分凭据还可被同容器进程读取,使失窃后的可用窗口从数天延伸到永久。
Root cause: Session and control-plane credentials generally have overly long validity and lack rotation, and some can be read by same-container processes, extending the usable window after theft from days to permanent.
修复建议:统一收敛各类凭据的生命周期——缩短 JWT 有效期、对网关令牌引入自动轮换与即时吊销、敏感凭据隔离存放(不可被工作进程直接读取),把任意失窃的滥用窗口降到最低。
Remediation: Uniformly tighten the lifecycle of all credential classes — shorten JWT validity, introduce automatic rotation and instant revocation for the gateway token, and store sensitive credentials in isolation (not directly readable by worker processes), minimizing the abuse window of any theft.
In-depth evidence: §4 Own-Pod RCE & environment-info read
证据截图(2 张,其中打码 0 张)Evidence images (2; 0 masked)
- D0 阶段的全部命令执行、数据读取与配置修改验证,均限定在研究员自有租户 / 自有 Pod 范围内。
- In the D0 phase, all command execution, data reads, and config-modification verification were confined to the researcher’s own tenant / own Pod.
- 没有对其他用户 Pod 进行未授权的横向移动。
- No unauthorized lateral movement was performed against other users’ Pods.
- 所有截图脱敏处理:Gateway Token、Session Token、JWT、容器 hostname、外部 IP、内部 ELB 域名、Solana 钱包公钥按需打码。
- All screenshots are redacted: Gateway Token, Session Token, JWT, container hostname, external IP, internal ELB domain, and Solana wallet public key are masked as needed.
- D0 阶段研究在 2026 年 5 月独立完成,沿用研究员对 Donut 全产品线的负责任披露框架。
- The D0-phase research was completed independently in May 2026, following the researcher’s responsible-disclosure framework for Donut’s entire product line.
攻击链详解Attack-Chain Walkthrough
把 Donut Browser 与 D0 两条线的攻击链叙事集中呈现;链内漏洞编号可点击跳转到对应证据卡片(自动切换到所属标签页)。逐条漏洞与脱敏证据图见「Donut Browser 证据」「D0 证据」两个标签页。
This brings together the attack-chain narratives for the two tracks — Donut Browser and D0; vulnerability IDs in the chains are clickable and jump to the corresponding evidence card (auto-switching to its tab). For per-vulnerability detail and redacted evidence images, see the “Donut Browser Evidence” and “D0 Evidence” tabs.
一、Donut Browser 攻击链I. Donut Browser attack chain
并行数据路径:仅凭自有会话即可越权读取他人钱包资产 / 交易历史(IDOR),形成目标画像。Parallel data path: with only one’s own session, one can read others’ wallet assets / trade history out of bounds (IDOR), building target profiles.DB-3DB-23DB-26
1. 1-click 静默交易链1. The 1-click silent transaction chain
- 用户没有进行任何手动签名或授权确认。
- The user performs no manual signing or authorization confirmation.
- 浏览器在一次访问 / 一次点击范围内携带会话向后端发起调用。
- Within a single visit / single click, the browser carries the session to make calls to the backend.
- Donut 后端在没有先要求用户重新授权的情况下,自动完成交易构建并推进执行。
- The Donut backend, without first requiring the user to re-authorize, automatically completes the transaction build and advances execution.
- 交易最终成功上链,可在 Solscan 上看到记录。
- The transaction ultimately succeeds on-chain, with a record visible on Solscan.
- 上述链路在多个交易类型(swap、wrap、transfer、deposit、withdraw)上均成立,已通过自有资金 11 笔交易验证。
- The above path holds across multiple transaction types (swap, wrap, transfer, deposit, withdraw), verified with 11 transactions using own funds.
2. 跨用户钱包交易构建与 AUTH0012. Cross-user wallet transaction build & AUTH001
- Donut 后端没有在 API 入口阶段先校验"
from_wallet是否属于当前会话用户"。 - The Donut backend does not first verify "whether
from_walletbelongs to the current session user" at the API entry stage. - 后端依然为非当前账号所属的钱包构建并提交了交易请求。
- The backend still builds and submits a transaction request for a wallet not belonging to the current account.
- 请求最终送达 Turnkey 策略判断阶段后,被 AUTH001 拦截。
- After the request finally reaches Turnkey’s policy-decision stage, it is blocked by AUTH001.
- 真正阻断资金转出的是 Turnkey,而不是 Donut 自身的权限模型。
- What actually blocks the fund transfer is Turnkey, not Donut’s own authorization model.
3. 资产与交易历史 IDOR3. Asset & trade-history IDOR
- 钱包详情、资产、持仓和交易历史接口允许在仅持有研究员会话的前提下读取非当前账号所属的钱包数据。
- The wallet-detail, assets, holdings, and trade-history endpoints allow reading wallet data not belonging to the current account while holding only the researcher’s session.
- 接口对枚举行为没有充分的速率限制和访问审计。
- The endpoints have no adequate rate limiting or access auditing against enumeration.
- 读取结果可用于筛选高价值钱包和活跃交易策略。
- The read results can be used to screen for high-value wallets and active trading strategies.
4. CORS、MCP 与组合攻击面4. CORS, MCP & the combined attack surface
- 子域通配符 CORS + credentials: true 在敏感接口上同时生效,扩大了浏览器侧信任边界。
- Subdomain-wildcard CORS + credentials: true take effect together on sensitive endpoints, widening the browser-side trust boundary.
- MCP 工具层可被枚举到完整工具生态,工具参数 schema 公开,可用于构造攻击链。
- The MCP tool layer can be enumerated down to the full tool ecosystem, with tool-parameter schemas public, usable to build an attack chain.
- 与 DB-6 ~ DB-10 等行为层漏洞组合后,远程触发成本显著下降。
- Combined with behavior-layer vulnerabilities like DB-6 ~ DB-10, the cost of remote triggering drops significantly.
与主披露的关系Relationship to the main disclosure
本页深度证据章节与主披露页的攻击链对应如下:
The in-depth evidence sections on this page map to the attack chains on the main disclosure page as follows:
- §1(1-click 静默交易)+ §2(跨用户构建与 AUTH001)+ §4(CORS 与远程触发)↔ 主披露 Chain 1(资金链路:远程可触发的服务端自动签名盗币)
- §1 (1-click silent transaction) + §2 (cross-user build & AUTH001) + §4 (CORS & remote triggering) ↔ main disclosure Chain 1 (fund path: remotely triggerable server-side auto-signing fund theft)
- §3(资产 IDOR 到目标画像)↔ 主披露 Chain 2(资产越权读取)
- §3 (asset IDOR to target profiling) ↔ main disclosure Chain 2 (unauthorized asset reading)
完整攻击链文字版见 主披露 §攻击链详解;完整 49 漏洞清单见 主披露 §漏洞总览。
For the full text version of the attack chains see Main disclosure §Attack-Chain Walkthrough; for the full list of 49 vulnerabilities see Main disclosure §Vulnerability overview.
二、D0 控制面 / 自有 Pod RCE 攻击链II. D0 control-plane / own-Pod RCE attack chain
1. 控制面连接材料1. Control-plane connection material
- D0 前端环境接口返回的字段足以让前端直接连接到底层控制面 WebSocket。
- The fields returned by the D0 front-end environment API are enough to let the front end connect directly to the underlying control-plane WebSocket.
- 控制面 WebSocket 对来源 / Origin 的约束不足,连接后即获得超出普通产品界面的操作能力。
- The control-plane WebSocket places insufficient constraints on source / Origin; once connected, one gains operations beyond the ordinary product UI.
- Gateway Token 的生命周期较长,缺少明确的强制轮换或单次使用语义。
- The Gateway Token has a relatively long lifecycle, lacking clear forced-rotation or single-use semantics.
2. OpenClaw 识别证据2. OpenClaw identification evidence
本节用于确认 D0 控制面来源。相关讨论见主披露 §安全治理观察。
This section confirms the origin of the D0 control plane. For the related discussion, see the main disclosure §Security-governance observations.
- D0 控制面来源不是基于"产品行为相似"的推断,而是通过 logo 和文档入口直接确认。
- The origin of the D0 control plane is not inferred from "similar product behavior" but confirmed directly via the logo and the docs link.
- Donut 对外宣传 D0 时,没有把 OpenClaw 作为主要产品信息呈现。
- When Donut promotes D0 externally, it does not present OpenClaw as primary product information.
- OpenClaw 项目上线时间较短,但被作为 Donut 核心 AI Agent 产品 D0 的底层。
- The OpenClaw project has been live for a relatively short time, yet is used as the foundation of Donut’s core AI Agent product, D0.
3. 配置读取、修改与文件写入3. Config read, modification & file write
- 控制面在认证通过后开放了远高于"模型对话"所需的能力:读取、修改、写入。
- After authentication, the control plane opens up capabilities far beyond what "model conversation" needs: read, modify, write.
config.patch类接口缺乏明显的审计、变更保护或回滚记录。- Endpoints like
config.patchlack obvious auditing, change protection, or rollback records. agents.files.set类接口允许向 Agent 工作区写入文件。- Endpoints like
agents.files.setallow writing files into the Agent workspace. - heartbeat / beforeRun 等运行时执行机制属于"配置一旦修改即被调度执行"的执行路径,本不应由前端可获得的普通会话材料触达。
- Runtime execution mechanisms like heartbeat / beforeRun are execution paths where "once the config is modified it gets scheduled and executed", which should not be reachable by ordinary session material obtainable from the front end.
4. 自有 Pod RCE 与环境信息读取4. Own-Pod RCE & environment-info read
- 在自有 Pod 内取得命令执行能力后,能够稳定读取到 Session Token、容器环境变量和内部服务地址。
- After gaining command-execution capability inside one’s own Pod, one can reliably read the Session Token, container environment variables, and internal service addresses.
- Session Token 文件由容器内进程可读,意味着 RCE 直接升级为"以用户身份访问后端 API"的条件。
- The Session Token file being readable by in-container processes means RCE directly upgrades into the condition for "accessing the backend API as the user".
- 容器环境变量中暴露了生产标识、内部入口和运行时配置,构成纵深攻击面。
- The container environment variables expose the production marker, internal entries, and runtime config, forming a deeper attack surface.
- Pod 间网络隔离边界尚未被未授权探测,未来如经授权扩展,仍需评估横向风险。
- The Pod-to-Pod network-isolation boundary has not been probed without authorization; if expanded with authorization in the future, the lateral risk still needs to be assessed.
与主披露的关系Relationship to the main disclosure
本页深度证据章节与主披露页的攻击链对应如下:
The in-depth evidence sections on this page map to the attack chains on the main disclosure page as follows:
- §1(控制面连接材料)+ §3(配置读取 / 修改 / 写入)+ §4(RCE 与会话 / 环境读取)↔ 主披露 Chain 3(D0 执行环境 → 资金)
- §1 (control-plane connection material) + §3 (config read / modify / write) + §4 (RCE & session / environment read) ↔ main disclosure Chain 3 (D0 execution environment → funds)
- 主披露 Chain 4(容器逃逸 → 整机接管 → 跨用户资金 / 权限)为 Chain 3 之上的潜在升级路径,未实际突破,本页未提供突破证据(对应 D0-12「Pod 间隔离需进一步授权验证」)。
- Main disclosure Chain 4 (container escape → full-host takeover → cross-user funds / privileges) is a potential escalation path on top of Chain 3; it was not actually broken through, and this page provides no breakthrough evidence (corresponding to D0-12 "Pod-to-Pod isolation needs further authorized verification").
完整攻击链文字版见 主披露 §攻击链详解;完整 49 漏洞清单见 主披露 §漏洞总览。
For the full text version of the attack chains see Main disclosure §Attack-Chain Walkthrough; for the full list of 49 vulnerabilities see Main disclosure §Vulnerability overview.
三、两条链如何合流:D0 执行环境 → Browser 资金III. How the two chains merge: D0 execution environment → Browser funds
D0-8 RCE → 读 Session Token D0-9 → 以用户身份调 Donut 后端 API → 复用 Browser 1-click 自动签名交易链 DB-1 → 用户资金损失
D0-8 RCE → read Session Token D0-9 → call the Donut backend API as the user → reuse the Browser 1-click auto-signing transaction chain DB-1 → user funds lost
这意味着 D0 的执行环境问题与 Donut Browser 的资金链路并非两个孤立问题:在自有 Pod 内取得 RCE 后即可读出会话凭据、以用户身份触达后端,从而接上浏览器侧那条“后端可在无用户确认下自动签名交易”的资金链——两套系统因此存在首尾相接、同一条通向用户资金的路径。
This means D0’s execution-environment issue and Donut Browser’s fund path are not two isolated problems: after gaining RCE inside one’s own Pod, one can read the session credential and reach the backend as the user, thereby joining the browser-side fund chain where "the backend can auto-sign transactions with no user confirmation" — so the two systems share an end-to-end path leading to user funds.
边界声明:以用户身份伪造 / 触发交易的路径已在自有环境确证可行;出于白帽授权边界,研究员未对其他用户资金实际执行转账,跨用户转账在 Browser 侧亦由第三方托管 Turnkey 在签名阶段兜底。两条链各自可独立通向资金,合流则放大整体风险。
Boundary statement: the path to forge / trigger transactions as the user has been confirmed feasible in the researcher’s own environment; out of the white-hat authorization boundary, the researcher did not actually transfer other users’ funds, and on the Browser side cross-user transfers are also backstopped at the signing stage by the third-party custodian Turnkey. Each chain can independently lead to funds, and merging them amplifies the overall risk.