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

类型:资金安全 / 授权缺陷 · 复测日期:2026-06-02Type: Fund safety / authorization flaw · Retest date: 2026-06-02

影响:自有钱包已链上验证——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——执行路径完全绕过平台订单追踪系统,零审计记录。
[Still unfixed · confirmed on-chain]
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)
图1Fig 1交易前·无任何签名弹窗 — 发起交易前后没有出现任何用户签名 / 授权确认弹窗。Before tx · no signature prompt — no user signature / authorization-confirmation prompt appeared before or after initiating the transaction.
DB-1 图1
图2Fig 2交易后·余额真实减少 — 交易完成后自有钱包 SOL 余额减少,资金真实移动。After tx · balance actually drops — after the transaction, the own wallet’s SOL balance decreased — funds really moved.
DB-1 图2
图3Fig 3Solscan 确认上链 — 交易哈希可在 Solscan 公开查询,确认真实上链成功。Solscan confirms on-chain — the transaction hash is publicly verifiable on Solscan, confirming a real successful on-chain transaction.
DB-1 图3
图4Fig 4钱包多笔成交记录 — Solscan 显示该钱包由攻击链推进的多笔交易,签名与对手方均可在 Solscan 公开核验。Multiple trades on the wallet — Solscan shows multiple transactions on this wallet driven by the attack chain; signers and counterparties are publicly verifiable on Solscan.
DB-1 图4
图5Fig 5第1步·构建交易请求 — 发起 swap 构建——toolName / 币种 / 金额清晰可见,全程无客户端签名或授权凭证(仅接口路径打码)。Step 1 · build the transaction request — initiating the swap build — toolName / token / amount clearly visible, with no client-side signature or authorization credential at any point (only the API path is masked).
DB-1 图5
图6Fig 6第2步·后端构建成功(201) — 后端 201 构建成功、分配 tradeId 并进入执行;无需任何客户端签名或逐笔授权(仅接口路径打码)。Step 2 · backend build succeeds (201) — the backend returns 201, assigns a tradeId, and proceeds to execution; no client-side signature or per-transaction authorization is required (only the API path is masked).
DB-1 图6
图7Fig 7第3步·推进执行 — 推进交易执行——后端经 Turnkey 自动签名(仅接口路径打码)。Step 3 · advance execution — advancing transaction execution — the backend auto-signs via Turnkey (only the API path is masked).
DB-1 图7
图8Fig 8第4步·拿到上链哈希 — 后端自动签名并广播,返回上链 txHash(Solscan 可验证;与图3/4 的 4TQscat… 为两笔独立静默执行);全程无签名弹窗、无逐笔授权(仅接口路径打码)。Step 4 · obtain the on-chain hash — the backend auto-signs and broadcasts, returning an on-chain txHash (verifiable on Solscan; a separate silent execution from the 4TQscat… one in Figs 3/4); no signature prompt and no per-transaction authorization throughout (only the API path is masked).
DB-1 图8

DB-2 · 跨用户交易构建未绑定当前会话钱包归属Cross-user transaction build not bound to the current session’s wallet ownership Critical部分修复Partially fixed

类型:IDOR / 业务逻辑 · 复测日期:2026-06-02Type: IDOR / business logic · Retest date: 2026-06-02

影响:交易构建阶段不校验目标钱包是否属于当前会话用户。原始测试中,后端为一笔“把真实受害者钱包资金转给研究员”的交易成功构建了未签名交易(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.

深度证据:§2 跨用户钱包交易构建与 AUTH001

In-depth evidence: §2 Cross-user transaction build & AUTH001

复测过程(2026-06-02)Retest (2026-06-02)
【部分修复】构建(query)层 IDOR 仍在,执行(execution)层已加 Donut 自有校验。

【原始测试 — 越权构建达成】
以真实受害者钱包为 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 层校验被架空,跨钱包交易仍可执行。
[Partially fixed] The build (query)-layer IDOR remains; the execution layer now has Donut’s own check.

[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)
图1Fig 1跨钱包构建·query 请求与响应 — 用研究员 session 为未绑定的他人钱包构建交易,query 返回 201、无归属校验(越权构建成立);接口路径与对方钱包已打码。Cross-wallet build · query request & response — using the researcher’s session to build a transaction for another, unbound wallet; query returns 201 with no ownership check (unauthorized build holds); the API path and the other wallet are masked.
DB-2 图1
图2Fig 2执行被 Donut 新增校验拦下 — 用 query 返回的同一 tradeId 推进 execution,返回 WALLET_NOT_OWNED。这是 Donut 复测时新增的归属校验(部分修复);原始测试此步是透传到 Turnkey 才返回 AUTH001。接口路径与对方钱包已打码。Execution blocked by Donut’s new check — advancing execution with the same tradeId returns WALLET_NOT_OWNED — an ownership check Donut added at retest (partial fix); in the original test this step passed through to Turnkey and returned AUTH001. The API path and the other wallet are masked.
DB-2 图2

DB-3 · 任意钱包资产、持仓、交易历史 IDORIDOR on any wallet’s assets, holdings, and trade history Critical未修复Unfixed

类型:越权访问 / 信息泄露 · 复测日期:2026-06-02Type: Broken access control / info leak · Retest date: 2026-06-02

影响:任意已登录用户传入他人 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 即可无限查询。
[Still unfixed]
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)
图1Fig 1读他人钱包资产组合 — 读取非本账号钱包的资产组合(持仓明细 / 公钥已打码)。Read another wallet’s portfolio — reading the portfolio of a wallet not owned by the account (holdings detail / public key masked).
DB-3 图1
图2Fig 2读他人持仓与成本 — 读取非本账号钱包的持仓和成本数据。Read another wallet’s positions & cost — reading the positions and cost-basis data of a wallet not owned by the account.
DB-3 图2
图3Fig 3读他人交易历史 — 读取非本账号钱包的历史交易(哈希 / 链接已打码)。Read another wallet’s trade history — reading the historical trades of a wallet not owned by the account (hashes / links masked).
DB-3 图3

DB-4 · CORS 子域通配符与 credentials 组合CORS subdomain-wildcard combined with credentials Critical部分修复Partially fixed

类型:Web 配置错误 · 复测日期:2026-06-02Type: Web misconfiguration · Retest date: 2026-06-02

影响:恶意/被控来源可携带受害者登录态跨域调用敏感 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)
【部分修复】原通配符子域名 CORS 已修,localhost:3000 仍在白名单且允许 credentials。

【已修复部分】
*.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(主要攻击面已收敛,本地残余存在)
[Partially fixed] The original wildcard-subdomain CORS is fixed; localhost:3000 is still on the allowlist with credentials enabled.

[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)
图1Fig 1CORS 预检对比(部分修复) — *.donutbrowser.ai / *.donutlabs.dev 通配符已封(404、无 ACAO),但 localhost:3000 仍带 ACAC=true 放行(接口路径已打码)。CORS preflight comparison (partial fix) — the *.donutbrowser.ai / *.donutlabs.dev wildcards are closed (404, no ACAO), but localhost:3000 is still allowed with ACAC=true (API path masked).
DB-4 图1
图2Fig 2连 localhost 都被放行(残余风险) — localhost:3000 预检返回 204 + ACAO + ACAC=true,本机任意应用可携带登录态调用敏感 API(接口路径已打码)。Even localhost is allowed (residual risk) — the localhost:3000 preflight returns 204 + ACAO + ACAC=true, so any local app can carry the session to call sensitive APIs (API path masked).
DB-4 图2

DB-5 · MCP 工具层认证与权限边界不足Insufficient auth and privilege boundary in the MCP tool layer Critical未修复Unfixed

类型:认证绕过 / 工具滥用 · 复测日期:2026-06-02Type: Auth bypass / tool abuse · Retest date: 2026-06-02

影响:任意已登录用户可直接 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 层强制、工具可直接枚举调用)仍未修复;前端确认弹窗可被完全跳过。
[Core still unfixed / enumeration partially fixed]

[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)
图1Fig 1工具配置:无认证已封、登录仍可枚举 — 无认证请求 → 401;任意登录用户带认证 → 200,完整工具配置与 confirm 标志仍可枚举(接口路径已打码)。Tool config: unauth closed, login can still enumerate — unauthenticated request → 401; any authenticated logged-in user → 200, and the full tool config and confirm flags can still be enumerated (API path masked).
DB-5 图1
图2Fig 2绕前端直调 WRAP_UNWRAP_SOL — 直接 POST 调用工具、绕过 AI/前端层 → 201 + unsignedTransaction(接口路径与 unsignedTransaction 已打码,研究员自有钱包公开)。Bypass front end, call WRAP_UNWRAP_SOL directly — POST the tool directly, bypassing the AI / front-end layer → 201 + unsignedTransaction (API path and unsignedTransaction masked; the researcher’s own wallet is public).
DB-5 图2
图3Fig 3直调限价单工具 — JUPITER_CREATE_LIMIT_ORDER 直接调用 → 201(ORDER_SIZE_TOO_SMALL = 业务校验,非访问拒绝;接口路径已打码)。Directly call the limit-order tool — JUPITER_CREATE_LIMIT_ORDER called directly → 201 (ORDER_SIZE_TOO_SMALL = business validation, not access denial; API path masked).
DB-5 图3

DB-6 · Role 参数注入Role-parameter injection High未修复Unfixed

类型:输入校验 / 权限提升 · 复测日期:2026-06-02Type: Input validation / privilege escalation · Retest date: 2026-06-02

影响:消息接口对 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)
图1Fig 1role=system 注入 → 201 — 后端不校验 role,伪造 system 角色消息被接受并返回 message_id(接口路径已打码)。role=system injection → 201 — the backend does not validate role; a forged system-role message is accepted and returns a message_id (API path masked).
DB-6 图1
图2Fig 2role=tool 注入 → 201 — 同理伪造 tool 角色(assistant/developer 亦然,四角色全部 201);是否影响 AI 受读时 role→user 规范化制约,见辨析(接口路径已打码)。role=tool injection → 201 — similarly forging a tool role (assistant/developer too — all four roles return 201); whether it affects the AI is constrained by the read-time role→user normalization, see Analysis (API path masked).
DB-6 图2

DB-7 · Wallet Service 无认证或弱认证钱包创建Wallet Service wallet creation with no / weak authentication High已修复Fixed

类型:认证缺失 / 资源滥用 · 复测日期:2026-06-02Type: Missing auth / resource abuse · Retest date: 2026-06-02

影响:原始——钱包创建端点完全无认证,可无限跨 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)
图1Fig 1已修复:无认证创建被拒 — 无认证 POST 创建 Solana 钱包 → 401 AUTH0003(Invalid API key);ethereum 等其他链与 Session Cookie 同样被拒(接口路径已打码)。Fixed: unauthenticated creation rejected — an unauthenticated POST to create a Solana wallet → 401 AUTH0003 (Invalid API key); other chains like ethereum and Session Cookies are likewise rejected (API path masked).
DB-7 图1

DB-8 · Credits / 使用次数限制绕过Credits / usage-limit bypass High未修复Unfixed

类型:业务逻辑 · 复测日期:2026-06-02Type: Business logic · Retest date: 2026-06-02

影响: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)
图1Fig 1POST 正常路径被拦截 — credits=0 时 POST 新消息走正常额度校验,返回 201 INSUFFICIENT_CHATS_LIMIT,Agent 未启动——说明限制在正常路径上是生效的。POST normal path is blocked — with credits=0, posting a new message goes through the normal quota check and returns 201 INSUFFICIENT_CHATS_LIMIT with the Agent not started — showing the limit does work on the normal path.
DB-8 图1
图2Fig 2PATCH 绕过额度校验 — 同一 credits=0 状态下,PATCH 改写已有消息并置 startAgent:true,返回 200 + agent_run_id(running),credits 仍 0→0 未扣减——额度限制被完全绕过。PATCH bypasses the quota check — in the same credits=0 state, PATCHing an existing message with startAgent:true returns 200 + agent_run_id (running) and credits stay 0→0 — the quota limit is fully bypassed.
DB-8 图2

DB-9 · AI Agent 配置与 System Prompt 泄露AI Agent config and system-prompt leakage High部分修复Partially fixed

类型:信息泄露 / Prompt Injection 原料 · 复测日期:2026-06-02Type: Info leak / prompt-injection material · Retest date: 2026-06-02

影响:无认证入口已封(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)
图1Fig 1无认证已加固 — GET /aigemix/agents 匿名请求现返回 401 TOKEN_MISSING。Unauthenticated hardened — an anonymous GET /aigemix/agents now returns 401 TOKEN_MISSING.
DB-9 图1
图2Fig 2带认证仍泄露 — 带普通会话 Cookie 仍返回 4 个 Agent 完整 system_prompt(各 ~12KB);正文已打码,保留数量与字符数证明规模。Still leaks when authenticated — with an ordinary session Cookie it still returns all 4 Agents’ full system_prompt (~12KB each); the body is masked, keeping the count and character size to show the scale.
DB-9 图2

DB-10 · 跨用户限价单取消Cross-user limit-order cancellation High未修复Unfixed

类型:越权访问 · 复测日期:2026-06-02Type: Broken access control · Retest date: 2026-06-02

影响:限价单取消接口无预先归属校验;时序差异证实服务端对他人钱包会走真实订单处理路径。攻击者一旦掌握受害者真实 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)
仍未修复(行为差异证明)。POST /terminal/jupiter/limit-orders/cancel 接受任意 userWallet 参数,无预先归属校验。自有钱包([钱包已打码])+ 假 orderId → 201 INVALID_ORDER(快速响应);受害者钱包([钱包已打码])+ 同一假 orderId → 服务端持续挂起超时(>12s)。行为差异表明:服务端对受害者钱包走不同处理路径(尝试向 Jupiter/链上查询真实订单),而非预先拒绝。若攻击者获取受害者真实 orderId,可直接触发取消,无任何归属校验。
Still unfixed (proven by behavioral difference). POST /terminal/jupiter/limit-orders/cancel accepts an arbitrary userWallet parameter with no upfront ownership check. Own wallet ([wallet masked]) + a fake orderId → 201 INVALID_ORDER (fast response); victim wallet ([wallet masked]) + the same fake orderId → the server keeps hanging until timeout (>12s). The behavioral difference shows the server takes a different processing path for the victim wallet (attempting a real order lookup against Jupiter/on-chain) rather than rejecting upfront. If an attacker obtains the victim’s real orderId, they can trigger cancellation directly with no ownership check.
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
图1Fig 1自有钱包基线 — 自有钱包 + 不存在 orderId → 即时 201 INVALID_ORDER,建立时序基线(自有钱包链上公开,未打码)。Own-wallet baseline — own wallet + a non-existent orderId → instant 201 INVALID_ORDER, establishing the timing baseline (own wallet is public on-chain, not masked).
DB-10 图1
图2Fig 2受害者钱包对照 — 同一假 orderId 换成受害者钱包 → 服务端进入真实订单查询、>12s 超时;与自有钱包即时 INVALID_ORDER 的差异证明取消前无归属校验(受害者钱包已打码)。Victim-wallet comparison — the same fake orderId with the victim wallet → the server enters a real order lookup and times out after >12s; the difference from the own-wallet instant INVALID_ORDER proves there is no ownership check before cancellation (victim wallet masked).
DB-10 图2

DB-11 · DeFi 自动签名交易链路缺少用户授权DeFi auto-signing transaction path lacks user authorization Critical部分修复Partially fixed

类型:资金安全 · 复测日期:2026-06-02Type: Fund safety · Retest date: 2026-06-02

影响:无认证 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)
部分修复。无认证 query 已封(401 TOKEN_MISSING)。但带认证用户仍可调用 /action-mcp/query(KAMINO_DEPOSIT / KAMINO_WITHDRAW),均返回 HTTP 201 + unsignedTransaction + tradeId。攻击链:query→execution 中 execution 步骤可完成自动签名,全程无用户确认弹窗。链上历史:txHash=3sJBaiYQ...(DEPOSIT)/ 2LTT5Rt9...(WITHDRAW)。
Partially fixed. The unauthenticated query is closed (401 TOKEN_MISSING). But an authenticated user can still call /action-mcp/query (KAMINO_DEPOSIT / KAMINO_WITHDRAW), both returning HTTP 201 + unsignedTransaction + tradeId. Attack chain: in query→execution, the execution step can complete auto-signing with no user-confirmation dialog throughout. On-chain history: txHash=3sJBaiYQ... (DEPOSIT) / 2LTT5Rt9... (WITHDRAW).
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
图1Fig 1无认证已加固 — KAMINO_DEPOSIT query 匿名请求现返回 401 TOKEN_MISSING。Unauthenticated hardened — an anonymous KAMINO_DEPOSIT query now returns 401 TOKEN_MISSING.
DB-11 图1
图2Fig 2带认证攻击链仍通 — DEPOSIT/WITHDRAW query 均 201 + unsignedTransaction + tradeId,只差一步 execution 自动签名(自有钱包/txHash 链上公开保留,端点/unsignedTx/tradeId 已打码)。Authenticated attack chain still works — DEPOSIT/WITHDRAW queries both return 201 + unsignedTransaction + tradeId, just one execution step short of auto-signing (own wallet / txHash kept public on-chain; endpoint / unsignedTx / tradeId masked).
DB-11 图2

DB-12 · Admin Plan / 订阅计划信息泄露Admin Plan / subscription-plan info leakage High未修复Unfixed

类型:信息泄露 / 业务逻辑 · 复测日期:2026-06-02Type: Info leak / business logic · Retest date: 2026-06-02

影响: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)
仍未修复。GET /subscription/plans 返回 4 个计划,包含 Admin 计划完整信息:id=0c06c176...、taskKey="_admin"、benefits={agent:{slots:"unlimit"},terminal:{chats:"unlimit"}}、description="only for donut team now (DON'T ACTIVE!!!)"、status="unactivated"。Eligibility 端点返回 HTTP 200(响应格式含 data wrapper)。Claim 尝试返回 201 + "Plan is not active",仅由 plan status 阻止,无用户角色验证逻辑。
Still unfixed. GET /subscription/plans returns 4 plans, including the Admin plan in full: id=0c06c176..., taskKey="_admin", benefits={agent:{slots:"unlimit"},terminal:{chats:"unlimit"}}, description="only for donut team now (DON'T ACTIVE!!!)", status="unactivated". The eligibility endpoint returns HTTP 200 (the response format includes a data wrapper). A claim attempt returns 201 + "Plan is not active", blocked only by plan status, with no user-role verification logic.
证据截图(3 张,均已打码)Evidence images (3; all masked)
图1Fig 1Admin 计划泄露 — GET /subscription/plans 返回内部 Admin 计划:taskKey=_admin、benefits 全 unlimit、「only for donut team」(计划 UUID 已打码)。Admin plan leaked — GET /subscription/plans returns the internal Admin plan: taskKey=_admin, all benefits unlimit, “only for donut team” (plan UUID masked).
DB-12 图1
图2Fig 2eligibility 返回 eligible:true — 普通用户对 Admin 计划被判定合格(计划 UUID 已打码)。eligibility returns eligible:true — an ordinary user is judged eligible for the Admin plan (plan UUID masked).
DB-12 图2
图3Fig 3claim 仅被状态阻止 — 返回「Plan is not active」,拦截源于 plan status 而非角色校验;计划一旦激活即可领取 Admin(planId 已打码)。claim blocked only by status — returns “Plan is not active”; the block comes from plan status, not a role check; once the plan is activated, Admin can be claimed (planId masked).
DB-12 图3

DB-13 · SQL 查询结构与表结构泄露SQL query-structure and schema leakage High部分修复Partially fixed

类型:信息泄露 · 复测日期:2026-06-02Type: Info leak · Retest date: 2026-06-02

影响:两个端点(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)
部分修复。eligibility(非UUID参数)和 plans?status(注入payload)2个端点仍返回完整 SQL 语句("Failed query: select id,name,price,duration,benefits,status,description,task_key,created_at,updated_at from subscription_plans...")。claim 端点已修复(HTTP 400,无 SQL 泄露)。泄露确认:TypeORM+PostgreSQL,表 subscription_plans,10个列名,$1参数化查询结构。
Partially fixed. The eligibility (non-UUID parameter) and plans?status (injection payload) endpoints still return the full SQL statement ("Failed query: select id,name,price,duration,benefits,status,description,task_key,created_at,updated_at from subscription_plans..."). The claim endpoint is fixed (HTTP 400, no SQL leak). Leak confirmed: TypeORM+PostgreSQL, table subscription_plans, 10 column names, $1 parameterized query structure.
证据截图(3 张,其中打码 0 张)Evidence images (3; 0 masked)
图1Fig 1eligibility 仍泄露 — /eligibility/not-a-uuid 错误响应回显完整 SQL,暴露 subscription_plans 表 10 列。eligibility still leaks — the /eligibility/not-a-uuid error response echoes the full SQL, exposing the 10 columns of the subscription_plans table.
DB-13 图1
图2Fig 2plans?status 仍泄露 — 注入 payload 被 $1 参数化阻止,但错误仍回显完整 SQL 结构。plans?status still leaks — the injection payload is blocked by $1 parameterization, but the error still echoes the full SQL structure.
DB-13 图2
图3Fig 3综合:2/3 仍泄露、claim 已修复 — claim 现返回 400「planId must be a UUID」无泄露;附泄露 schema 汇总(表/10 列/TypeORM+PostgreSQL)。Summary: 2/3 still leak, claim fixed — claim now returns 400 “planId must be a UUID” with no leak; with a summary of the leaked schema (table / 10 columns / TypeORM+PostgreSQL).
DB-13 图3

DB-14 · 多端点输入验证不足导致 500 错误Insufficient input validation across endpoints causing 500 errors High部分修复Partially fixed

类型:输入校验 · 复测日期:2026-06-02Type: Input validation · Retest date: 2026-06-02

影响:暴露内部错误处理的 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)
部分修复。NONEXISTENT_TOOL 已修复(现返回 HTTP 201 + "Action tool not found" 而非 500)。但类型混淆仍存在:POST /messages 传入 role={"$gt":""} 或 content={text:"hello",hidden_instruction:"..."} 均返回 HTTP 201,对象被后端接受,未做字段类型校验。可用于 ORM 操作符注入探测或 Prompt 注入链路构造。
Partially fixed. NONEXISTENT_TOOL is fixed (now returns HTTP 201 + "Action tool not found" instead of 500). But type confusion remains: POST /messages with role={"$gt":""} or content={text:"hello",hidden_instruction:"..."} both return HTTP 201 — the object is accepted by the backend with no field-type validation. Usable for ORM-operator injection probing or building a prompt-injection path.
证据截图(3 张,均已打码)Evidence images (3; all masked)
图1Fig 1500 已修复 — NONEXISTENT_TOOL 现返回 201 + 友好错误(TOOL_NOT_FOUND),不再 500/堆栈(端点已打码)。500 fixed — NONEXISTENT_TOOL now returns 201 + a friendly error (TOOL_NOT_FOUND), no more 500/stack (endpoint masked).
DB-14 图1
图2Fig 2类型混淆(role) — role 传对象 {"$gt":""} 未被类型拒绝(201 而非 400),止于 chats 配额;含 ORM 操作符(端点已打码)。Type confusion (role) — role passed as the object {"$gt":""} is not type-rejected (201 instead of 400), stopping at the chat quota; contains an ORM operator (endpoint masked).
DB-14 图2
图3Fig 3类型混淆(content) — content 传含 hidden_instruction 的对象同样未被类型拒绝;2/3 端点仍有漏洞,500 已修复(端点已打码)。Type confusion (content) — content passed as an object containing hidden_instruction is likewise not type-rejected; 2/3 endpoints still vulnerable, 500 fixed (endpoint masked).
DB-14 图3

DB-15 · Solana RPC 代理无认证Unauthenticated Solana RPC proxy Medium已修复Fixed

类型:认证缺失 / 配额滥用 · 复测日期:2026-06-02Type: Missing auth / quota abuse · Retest date: 2026-06-02

影响:(已修复)原可无认证经 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)
已修复。POST /solana/rpc 现在要求有效认证,无 Cookie 请求均返回 401 TOKEN_MISSING。getBalance 和 getTokenAccountsByOwner 两个方法均验证通过。
Fixed. POST /solana/rpc now requires valid authentication, and cookieless requests all return 401 TOKEN_MISSING. Both getBalance and getTokenAccountsByOwner were verified.
证据截图(2 张,未打码)Evidence images (2; none masked)
图1Fig 1无认证已加固 — getBalance 无认证请求 → 401 TOKEN_MISSING(已修复;自有钱包链上公开未打码)。Unauthenticated hardened — an unauthenticated getBalance request → 401 TOKEN_MISSING (fixed; own wallet is public on-chain, not masked).
DB-15 图1
图2Fig 2无认证已加固 — getTokenAccountsByOwner 无认证 → 401;两方法攻击面均已封锁。Unauthenticated hardened — getTokenAccountsByOwner unauthenticated → 401; the attack surface of both methods is closed.
DB-15 图2

DB-16 · 私有分析数据泄露Private analytics-data leakage Medium已修复Fixed

类型:信息泄露 · 复测日期:2026-06-02Type: Info leak · Retest date: 2026-06-02

影响:(已修复)原可无认证读取任意钱包的私有持仓分析(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= returned private financial fields without authentication; the retest shows cookieless requests for both own and victim wallets return 401 TOKEN_MISSING — hardened.

根因:分析类接口缺访问控制。

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)
已修复。GET /users/wallets/positions?walletAddress=... 现在要求认证,无 Cookie 请求返回 401(自有钱包和受害者钱包均验证)。avgBuyPrice/totalInvestedUsd/pnl 等私有字段不再无认证暴露。
Fixed. GET /users/wallets/positions?walletAddress=... now requires authentication, and cookieless requests return 401 (verified for both own and victim wallets). Private fields like avgBuyPrice/totalInvestedUsd/pnl are no longer exposed unauthenticated.
证据截图(2 张,其中打码 1 张)Evidence images (2; 1 masked)
图1Fig 1无认证已加固 — 自有钱包 positions 无认证 → 401 TOKEN_MISSING(已修复;自有钱包链上公开未打码)。Unauthenticated hardened — own-wallet positions without auth → 401 TOKEN_MISSING (fixed; own wallet is public on-chain, not masked).
DB-16 图1
图2Fig 2无认证已加固 — 受害者钱包 positions 无认证同样 → 401;原可读对方 PnL/成本($69,105 持仓),现已封锁(受害者钱包已打码)。Unauthenticated hardened — victim-wallet positions without auth likewise → 401; previously one could read their PnL/cost basis ($69,105 holdings), now closed (victim wallet masked).
DB-16 图2

DB-17 · API 缺失速率限制Missing API rate limiting Medium部分修复Partially fixed

类型:配置错误 / 滥用防护不足 · 复测日期:2026-06-02Type: Misconfiguration / weak abuse protection · Retest date: 2026-06-02

影响:(部分修复)限流头已部署(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)
部分修复。20 次并发请求后响应头已出现 x-ratelimit-limit: 100 / x-ratelimit-remaining / x-ratelimit-reset: 60,说明限流机制已部署。但:① 阈值极为宽松(100次/60s),在单次滥用场景中仍可大量枚举;② 20 次并发请求未触发任何 429;③ 原始发现中完全无限流头的问题已修复,但高价值接口(/subscription/me、/users/profile)的限流阈值对于枚举/撞库场景仍不足够。
Partially fixed. After 20 concurrent requests the response headers show x-ratelimit-limit: 100 / x-ratelimit-remaining / x-ratelimit-reset: 60, indicating a rate-limit mechanism is deployed. But: ① the threshold is extremely loose (100 per 60s), still allowing heavy enumeration in a single abuse session; ② 20 concurrent requests trigger no 429; ③ the original finding of completely absent rate-limit headers is fixed, but the threshold on high-value endpoints (/subscription/me, /users/profile) is still insufficient for enumeration / credential-stuffing scenarios.
证据截图(2 张,均已打码)Evidence images (2; all masked)
图1Fig 120 并发无 429 — 限流头已部署(x-ratelimit-limit:100/60s)但阈值过宽,20 并发 200×19、无 429(plan UUID 已打码)。20 concurrent, no 429 — rate-limit headers deployed (x-ratelimit-limit:100/60s) but the threshold is too loose — 20 concurrent → 200×19, no 429 (plan UUID masked).
DB-17 图1
图2Fig 2/users/profile 同样可批量 — 20 并发 200×20、无 429,阈值过宽;高价值 profile 接口可枚举(userId 已打码)。/users/profile also bulk-callable — 20 concurrent → 200×20, no 429, threshold too loose; the high-value profile endpoint can be enumerated (userId masked).
DB-17 图2

DB-18 · S3 Bucket 或静态资源目录公开Public S3 bucket or static-asset directory Medium已修复Fixed

类型:信息泄露 · 复测日期:2026-06-02Type: Info leak · Retest date: 2026-06-02

影响:(已修复/未发现暴露)对 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)
已修复/未发现暴露。枚举 20 个常见 Donut S3 Bucket 命名模式:5 个 Bucket 存在(donut-ai, donut-prod, donut-dev, donut-assets, donut-backend)但均返回 HTTP 403(AccessDenied),无公开目录列表(无 ListBucketResult)。Web 服务器侧 10 个静态路径(/static/, /assets/, /uploads/ 等)无目录遍历(Index of / 未出现),无敏感文件关键词(.env/.pem/secret)暴露。
Fixed / no exposure found. Enumerating 20 common Donut S3 bucket naming patterns: 5 buckets exist (donut-ai, donut-prod, donut-dev, donut-assets, donut-backend) but all return HTTP 403 (AccessDenied) with no public directory listing (no ListBucketResult). On the web-server side, 10 static paths (/static/, /assets/, /uploads/, etc.) have no directory traversal (no “Index of /”) and no sensitive-file keywords (.env/.pem/secret) exposed.
证据截图(2 张,其中打码 0 张)Evidence images (2; 0 masked)
图1Fig 1S3 Bucket 枚举 — 20 个命名探测:5 个存在但均 403 保护、无公开目录(未发现暴露)。S3 bucket enumeration — 20 name probes: 5 exist but all 403-protected with no public directory (no exposure found).
DB-18 图1
图2Fig 2静态目录 + 综合 — 静态路径无「Index of」遍历、无敏感文件;总体未发现公开桶或遍历。Static directories + summary — static paths have no “Index of” traversal and no sensitive files; overall, no public bucket or traversal found.
DB-18 图2

DB-19 · Health / 运维端点信息泄露Health / ops-endpoint info leakage Medium部分修复Partially fixed

类型:信息泄露 · 复测日期:2026-06-02Type: Info leak · Retest date: 2026-06-02

影响:(部分修复)原 /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)
部分修复。扫描 26 个运维端点:/v1/backend/health 仍无需认证即可访问(HTTP 200),其余 25 个端点 404 或超时。当前响应已精简为 {"code":"OK","data":{"status":"ok","timestamp":"..."}},不再包含版本号、内存使用、数据库连接状态、队列状态、集群信息等原始报告中发现的敏感字段。原始漏洞核心(无认证可访问健康端点)仍未修复,但实际泄露的信息量已大幅减少,仅暴露服务存活状态与时间戳。
Partially fixed. Scanning 26 ops endpoints: /v1/backend/health is still accessible without authentication (HTTP 200), while the other 25 endpoints 404 or time out. The current response is slimmed to {"code":"OK","data":{"status":"ok","timestamp":"..."}}, no longer containing the sensitive fields found in the original report such as version number, memory usage, database connection status, queue status, and cluster info. The core of the original flaw (the health endpoint is accessible without authentication) is still unfixed, but the actual amount leaked is greatly reduced, exposing only service liveness and a timestamp.
证据截图(2 张,其中打码 0 张)Evidence images (2; 0 masked)
图1Fig 1无认证端点扫描 — 26 个运维端点仅 /v1/backend/health 无认证 200,余 25 个 404/超时。Unauthenticated endpoint scan — of 26 ops endpoints only /v1/backend/health returns 200 unauthenticated; the other 25 are 404/timeout.
DB-19 图1
图2Fig 2health 响应已精简 — 带认证/无认证均 200,响应仅 {status:ok, timestamp},敏感字段已移除;无认证访问仍未修复。health response slimmed — both authenticated and unauthenticated return 200, with the response only {status:ok, timestamp} and sensitive fields removed; unauthenticated access is still unfixed.
DB-19 图2

DB-20 · MCP 工具配置完全公开MCP tool configuration fully public Medium已修复Fixed

类型:信息泄露 · 复测日期:2026-06-02Type: Info leak · Retest date: 2026-06-02

影响:(已修复)原可无认证枚举 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)
已修复。枚举 10 个 MCP 工具配置相关端点(/action-mcp/tools、/action-mcp/list、/action-mcp/schema、/action-mcp/providers、/mcp/tools、/tools 等),无认证和带认证均返回 HTTP 404。带认证 POST /action-mcp/query 测试虚构工具名(LIST_TOOLS、GET_TOOLS)返回 TOOL_NOT_FOUND,确认工具枚举路径不存在。工具名称/Provider/参数 schema 批量枚举能力已移除。
Fixed. Enumerating 10 MCP tool-config-related endpoints (/action-mcp/tools, /action-mcp/list, /action-mcp/schema, /action-mcp/providers, /mcp/tools, /tools, etc.) returns HTTP 404 both authenticated and unauthenticated. An authenticated POST /action-mcp/query testing made-up tool names (LIST_TOOLS, GET_TOOLS) returns TOOL_NOT_FOUND, confirming the tool-enumeration path does not exist. The bulk-enumeration capability for tool names/providers/parameter schemas is removed.
证据截图(2 张,其中打码 0 张)Evidence images (2; 0 masked)
图1Fig 1无认证无法枚举 — 10 个工具目录端点无认证全 404,虚构工具名返回 TOOL_NOT_FOUND。Cannot enumerate unauthenticated — 10 tool-directory endpoints all 404 unauthenticated; a made-up tool name returns TOOL_NOT_FOUND.
DB-20 图1
图2Fig 2带认证同样 404 — 带认证 /action-mcp/tools、/list 均 404,工具配置无法枚举(已修复)。Also 404 when authenticated — authenticated /action-mcp/tools and /list both 404; tool config cannot be enumerated (fixed).
DB-20 图2

DB-21 · OpenAPI / Scalar 文档暴露OpenAPI / Scalar docs exposure Medium已修复Fixed

类型:信息泄露 · 复测日期:2026-06-02Type: Info leak · Retest date: 2026-06-02

影响:(已修复)原 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)
已修复。无认证扫描 29 个 OpenAPI/Scalar/Swagger/ReDoc 文档端点(/docs、/swagger、/openapi.json、/scalar、/reference、/v1/backend/docs 等),全部返回 404 或超时,无任何 HTTP 200 响应。同时验证第二后端 beta.donutlabs.dev 的 /docs、/reference、/openapi.json、/scalar 路径,同样全部 404。生产环境 API 文档端点已完全移除。
Fixed. Unauthenticated scan of 29 OpenAPI/Scalar/Swagger/ReDoc doc endpoints (/docs, /swagger, /openapi.json, /scalar, /reference, /v1/backend/docs, etc.) all return 404 or time out, with no HTTP 200 at all. The second backend beta.donutlabs.dev’s /docs, /reference, /openapi.json, /scalar paths were also verified — likewise all 404. The production API doc endpoints are fully removed.
证据截图(2 张,其中打码 0 张)Evidence images (2; 0 masked)
图1Fig 129 个文档端点全 404 — 无认证扫描 OpenAPI/Scalar/Swagger,未发现可访问文档。All 29 doc endpoints 404 — an unauthenticated scan of OpenAPI/Scalar/Swagger finds no accessible docs.
DB-21 图1
图2Fig 2两后端均无暴露 — api-beta + beta.donutlabs.dev 聚焦路径全 404;文档端点已移除(已修复)。Neither backend exposed — focused paths on api-beta + beta.donutlabs.dev all 404; doc endpoints removed (fixed).
DB-21 图2

DB-22 · 监控报告端点无认证Unauthenticated monitoring-report endpoint Medium已修复Fixed

类型:认证缺失 · 复测日期:2026-06-02Type: Missing auth · Retest date: 2026-06-02

影响:(已修复)原监控/报告端点(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)
已修复。无认证扫描 35 个监控/报告端点(/metrics、/bull-board、/queues、/admin、/stats、/reports、/analytics、/dashboard、/jobs、/workers、/internal/metrics 等),全部返回 HTTP 404 或超时,无任何 200/401/403 响应。Prometheus metrics、Bull 队列面板、业务统计等监控类端点均已移除或不存在。
Fixed. Unauthenticated scan of 35 monitoring/reporting endpoints (/metrics, /bull-board, /queues, /admin, /stats, /reports, /analytics, /dashboard, /jobs, /workers, /internal/metrics, etc.) all return HTTP 404 or time out, with no 200/401/403 at all. Monitoring endpoints such as Prometheus metrics, the Bull queue board, and business statistics are all removed or nonexistent.
证据截图(2 张,其中打码 0 张)Evidence images (2; 0 masked)
图1Fig 135 个监控端点全 404 — 无认证扫描 Prometheus/Bull/业务统计/管理面板,无可访问端点。All 35 monitoring endpoints 404 — an unauthenticated scan of Prometheus/Bull/business-stats/admin panels finds no accessible endpoint.
DB-22 图1
图2Fig 2重点路径全 404 — /metrics、/bull-board、/admin、/stats 等均 404;无未授权泄露(已修复)。Key paths all 404 — /metrics, /bull-board, /admin, /stats, etc. all 404; no unauthorized leak (fixed).
DB-22 图2

DB-23 · 任意钱包余额探测Arbitrary wallet-balance probing High部分修复Partially fixed

类型:信息泄露 · 复测日期:2026-06-02Type: Info leak · Retest date: 2026-06-02

影响:(部分修复)无认证侧信道余额探测已封: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= still returns 200 + balanceData, so a victim’s assets can be read by any logged-in user.

根因:余额/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)
部分修复。无认证余额探测已修复:/users/wallets/portfolio 等端点现在返回 401 TOKEN_MISSING,三个钱包(自有/受害者/虚构)响应完全一致,无侧信道差异。但带认证 IDOR 仍存在:携带有效 Cookie 访问受害者钱包 portfolio 仍返回 HTTP 200 + balanceData,受害者完整资产(SOL 余额/USD 总值)可被任意已登录用户读取。此维度与 DB-3(任意钱包 IDOR)高度重叠。
Partially fixed. Unauthenticated balance probing is fixed: endpoints like /users/wallets/portfolio now return 401 TOKEN_MISSING, and the three wallets (own/victim/made-up) have fully identical responses with no side-channel difference. But the authenticated IDOR remains: accessing a victim wallet’s portfolio with a valid Cookie still returns HTTP 200 + balanceData, so a victim’s full assets (SOL balance / total USD value) can be read by any logged-in user. This dimension heavily overlaps with DB-3 (arbitrary-wallet IDOR).
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
图1Fig 1无认证侧信道已封 — 自有/受害者/虚构钱包 portfolio 均 401、响应一致,无侧信道差异(受害者钱包已打码)。Unauthenticated side channel closed — own/victim/made-up wallet portfolios all 401 with identical responses and no side-channel difference (victim wallet masked).
DB-23 图1
图2Fig 2带认证 IDOR 仍可读 — 携 Cookie 访问受害者 portfolio 仍 200 + balanceData,受害者资产可读(与 DB-3 重叠;受害者钱包/余额已打码)。Authenticated IDOR still readable — accessing a victim’s portfolio with a Cookie still returns 200 + balanceData, so the victim’s assets are readable (overlaps with DB-3; victim wallet/balance masked).
DB-23 图2

DB-24 · risk-metrics 全平台数据泄露risk-metrics platform-wide data leakage Medium已修复Fixed

类型:信息泄露 · 复测日期:2026-06-02Type: Info leak · Retest date: 2026-06-02

影响:(已修复)原平台级风险/统计接口无需认证即返回聚合数据(活跃仓位、总钱包数、风险指标等),可用于识别高价值用户与系统状态;复测 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)
已修复。无认证和带认证均扫描 /risk-metrics、/risk-metrics/summary、/risk-metrics/platform、/risk/metrics、/users/risk-metrics、/platform/metrics、/wallets/risk 等 15 个相关端点,全部返回 HTTP 404(端点已移除)。无认证和带认证测试结果一致,平台级风险指标/黑名单/异常统计数据无法访问。
Fixed. Scanning 15 related endpoints both authenticated and unauthenticated (/risk-metrics, /risk-metrics/summary, /risk-metrics/platform, /risk/metrics, /users/risk-metrics, /platform/metrics, /wallets/risk, etc.) all return HTTP 404 (endpoints removed). Authenticated and unauthenticated results are identical; platform-level risk metrics / blocklist / anomaly statistics cannot be accessed.
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
图1Fig 1无认证全 404 — 15 个 risk-metrics 端点无认证均 404,端点已移除(受害者钱包已打码,自有揭示)。All 404 unauthenticated — 15 risk-metrics endpoints all 404 unauthenticated; endpoints removed (victim wallet masked, own revealed).
DB-24 图1
图2Fig 2带认证同样 404 — 带认证 risk-metrics(含他人钱包 IDOR 维度)全 404;平台级风险数据无法访问(已修复;受害者钱包已打码)。Also 404 when authenticated — authenticated risk-metrics (including the other-wallet IDOR dimension) all 404; platform-level risk data cannot be accessed (fixed; victim wallet masked).
DB-24 图2

DB-25 · beta.donutlabs.dev 第二后端暴露beta.donutlabs.dev second-backend exposure High未修复Unfixed

类型:攻击面暴露 · 复测日期:2026-06-02Type: Attack-surface exposure · Retest date: 2026-06-02

影响: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)
仍未修复。beta.donutlabs.dev 仍在线并运行同一套后端 API。探测结果:/v1/backend/health → HTTP 200(无认证,与主站 DB-19 相同);/v1/backend/subscription/plans → HTTP 200(无认证,暴露订阅计划数据,与主站 DB-12 同类问题);/v1/backend/subscription/me、/v1/backend/users/profile、/v1/backend/terminal/threads 等敏感端点已加认证(401)。app.donutlabs.dev 为 SPA 前端。此第二后端独立于 api-beta.donutbrowser.ai,形成独立攻击面,主站已修复的漏洞若未同步到第二后端仍可利用。
Still unfixed. beta.donutlabs.dev is still online and runs the same backend API. Probe results: /v1/backend/health → HTTP 200 (unauthenticated, same as the main site’s DB-19); /v1/backend/subscription/plans → HTTP 200 (unauthenticated, exposing subscription-plan data, the same class of issue as the main site’s DB-12); sensitive endpoints like /v1/backend/subscription/me, /v1/backend/users/profile, /v1/backend/terminal/threads now require authentication (401). app.donutlabs.dev is the SPA front end. This second backend is independent of api-beta.donutbrowser.ai, forming an independent attack surface; vulnerabilities fixed on the main site remain exploitable if not synced to the second backend.
证据截图(2 张,其中打码 1 张)Evidence images (2; 1 masked)
图1Fig 1子域连通性 — 探测 donutlabs.dev 子域:5 个主机存活(app 为 SPA 前端);beta 后端在线待深测。Subdomain connectivity — probing donutlabs.dev subdomains: 5 hosts alive (app is the SPA front end); the beta backend is online and pending deeper testing.
DB-25 图1
图2Fig 2第二后端深测 — beta.donutlabs.dev 8 端点有响应:多数 401,但 health/subscription/plans 仍无认证 200(同 DB-19/DB-12);独立攻击面(自有钱包揭示,受害者钱包已打码)。Second-backend deep test — beta.donutlabs.dev has 8 responsive endpoints: most 401, but health/subscription/plans still 200 unauthenticated (same as DB-19/DB-12); an independent attack surface (own wallet revealed, victim wallet masked).
DB-25 图2

DB-26 · wallet-service 钱包详情无认证查询wallet-service wallet-detail unauthenticated query High已修复Fixed

类型:越权访问 · 复测日期:2026-06-02Type: Broken access control · Retest date: 2026-06-02

影响:(已修复)原 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)
已修复。无认证扫描 15 个 wallet-service 端点(/wallet-service/wallets、/v1/backend/wallet-service/wallets、/v1/backend/users/wallets 等含 walletAddress 参数变体),全部返回 HTTP 404。带认证 IDOR 测试(包括受害者钱包地址查询)同样全部 404,无钱包详情字段(publicKey/walletAddress/turnkey/organizationId)暴露。端点已完全移除。
Fixed. Unauthenticated scan of 15 wallet-service endpoints (/wallet-service/wallets, /v1/backend/wallet-service/wallets, /v1/backend/users/wallets, and variants with a walletAddress parameter) all return HTTP 404. Authenticated IDOR tests (including querying the victim’s wallet address) likewise all 404, with no wallet-detail fields (publicKey/walletAddress/turnkey/organizationId) exposed. The endpoints are fully removed.
证据截图(2 张,其中打码 2 张)Evidence images (2; 2 masked)
图1Fig 1无认证全 404 — 15 个 wallet-service 端点(含钱包地址变体)无认证均 404,端点已移除(受害者钱包已打码,自有揭示)。All 404 unauthenticated — 15 wallet-service endpoints (including wallet-address variants) all 404 unauthenticated; endpoints removed (victim wallet masked, own revealed).
DB-26 图1
图2Fig 2带认证 IDOR 同样 404 — 带认证(含受害者钱包)全 404,无 publicKey/turnkey 等详情泄露(已修复;受害者钱包已打码)。Authenticated IDOR also 404 — authenticated (including victim wallet) all 404, with no publicKey/turnkey detail leaks (fixed; victim wallet masked).
DB-26 图2

DB-27 · Admin taskKey / 管理计划路径暴露Admin taskKey / management-plan path exposure High未修复Unfixed

类型:信息泄露 / 权限边界 · 复测日期:2026-06-02Type: Info leak / privilege boundary · Retest date: 2026-06-02

影响:/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)
仍未修复。/v1/backend/subscription/plans 无认证(HTTP 200)即可看到 Admin 计划:id=0c06c176-b77b-40d2-a14e-b3a6009381c1、name=Admin、price=-1、taskKey 含 admin 标识、benefits.agent.slots=unlimit(无限 Agent 槽位)。主站无认证/带认证/第二后端(beta.donutlabs.dev)三者均一致暴露,共 2 个 admin 相关计划可见。带认证 eligibility 返回 eligible=true、alreadyClaimed=false,攻击者账号已符合 Admin Plan 资格。Claim 仅被 plan 状态(Plan is not active)阻断,无任何角色级鉴权。
Still unfixed. /v1/backend/subscription/plans unauthenticated (HTTP 200) reveals the Admin plan: id=0c06c176-b77b-40d2-a14e-b3a6009381c1, name=Admin, price=-1, a taskKey containing the admin marker, benefits.agent.slots=unlimit (unlimited Agent slots). The main site (unauthenticated/authenticated) and the second backend (beta.donutlabs.dev) all expose it consistently, with 2 admin-related plans visible in total. Authenticated eligibility returns eligible=true, alreadyClaimed=false — the attacker’s account already qualifies for the Admin Plan. Claim is blocked only by plan status (Plan is not active), with no role-level authorization at all.
证据截图(2 张,均已打码)Evidence images (2; all masked)
图1Fig 1Admin 计划无认证暴露 — plans 主站(无/带认证)+第二后端均返回 _admin taskKey、unlimit、price=-1 的 Admin 计划(计划 UUID 已打码)。Admin plan exposed unauthenticated — plans on the main site (with/without auth) and the second backend all return the Admin plan with the _admin taskKey, unlimit, price=-1 (plan UUID masked).
DB-27 图1
图2Fig 2eligible=true、Claim 仅状态阻断 — /admin 路径已 404;eligibility(带认证)eligible=true、claim 仅被「Plan is not active」阻断、无角色鉴权;第二后端也暴露(计划 UUID 已打码)。eligible=true, claim blocked only by status — /admin paths now 404; eligibility (authenticated) returns eligible=true, claim is blocked only by “Plan is not active” with no role authorization; the second backend also exposes it (plan UUID masked).
DB-27 图2

DB-28 · ORM 操作符注入线索ORM operator-injection indicators Medium未修复Unfixed

类型:注入 / 查询污染 · 复测日期:2026-06-02Type: Injection / query pollution · Retest date: 2026-06-02

影响:复测确认: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)
仍未修复。POST /terminal/threads/{id}/messages 端点对 role 和 content 字段无类型验证:10 种 payload($ne/$gt/$regex/$in/$exists 操作符对象、null、int、bool)全部返回 HTTP 201(均通过验证层,被 INSUFFICIENT_CHATS_LIMIT 业务逻辑阻断而非类型校验拦截)。content=[array] 返回 HTTP 201 + INVALID_REQUEST("Expected union value"),同样被接受。对比:subscription/claim(planId 必须 UUID,400 拦截)和 action-mcp/query(toolName 必须字符串,400 VALIDATION_ERROR)已加类型验证。/messages 端点是唯一仍完全缺乏类型强制的高暴露端点,ORM 操作符可注入 role/content 字段。
Still unfixed. The POST /terminal/threads/{id}/messages endpoint does no type validation on the role and content fields: 10 payloads ($ne/$gt/$regex/$in/$exists operator objects, null, int, bool) all return HTTP 201 (all pass the validation layer, blocked by the INSUFFICIENT_CHATS_LIMIT business logic rather than by type checking). content=[array] returns HTTP 201 + INVALID_REQUEST("Expected union value"), likewise accepted. By contrast: subscription/claim (planId must be a UUID, 400 block) and action-mcp/query (toolName must be a string, 400 VALIDATION_ERROR) now have type validation. /messages is the only high-exposure endpoint still completely lacking type enforcement; ORM operators can be injected into the role/content fields.
证据截图(2 张,均已打码)Evidence images (2; all masked)
图1Fig 1/messages 无类型校验 — $ne/$gt/$regex/$in/$exists 等 10 种 payload 全 201 通过验证层(被 chats 配额阻断、非类型拦截;端点已打码)。/messages has no type validation — 10 payloads like $ne/$gt/$regex/$in/$exists all pass the validation layer with 201 (blocked by the chat quota, not by type checking; endpoint masked).
DB-28 图1
图2Fig 2对比:仅 /messages 缺校验 — claim/mcp 同类 payload 均 400 拦截,唯 /messages 全 201;ORM 操作符可进 role/content(action-mcp 端点已打码)。Comparison: only /messages lacks validation — the same payloads to claim/mcp are all 400-blocked, while only /messages returns 201; ORM operators can reach role/content (action-mcp endpoint masked).
DB-28 图2

DB-29 · Helius Webhook 路径仍可访问Helius webhook path still accessible Medium已修复Fixed

类型:认证缺失 / Webhook 暴露 · 复测日期:2026-06-02Type: Missing auth / webhook exposure · Retest date: 2026-06-02

影响:(已修复)原 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)
已修复。枚举 26 条 Helius webhook 路径(/webhook, /webhooks, /helius/webhook, /v1/backend/webhook 等变种)+ 8 条 POST fake payload 测试,全部返回 HTTP 404(端点已完全移除)。beta.donutlabs.dev 同侧 11 条路径同样全 404。带认证与无认证结果一致——无任何 webhook 接口可接受请求,无法伪造 Helius 事件。
Fixed. Enumerating 26 Helius webhook paths (/webhook, /webhooks, /helius/webhook, /v1/backend/webhook, and variants) + 8 POST fake-payload tests all return HTTP 404 (endpoints fully removed). On beta.donutlabs.dev the same 11 paths likewise all 404. Authenticated and unauthenticated results are identical — no webhook endpoint accepts requests, and Helius events cannot be forged.
证据截图(3 张,其中打码 0 张)Evidence images (3; 0 masked)
图1Fig 1主后端 30 路径全 404 — 无认证 GET 扫描 webhook 变种,全部 404_REMOVED,0 命中。Main backend 30 paths all 404 — an unauthenticated GET scan of webhook variants is all 404_REMOVED, 0 hits.
DB-29 图1
图2Fig 2POST 伪造事件全 404 — 8 条 POST fake payload 全 404,无可接受 webhook 的入口(已修复)。POST forged events all 404 — 8 POST fake payloads all 404; no entry accepts a webhook (fixed).
DB-29 图2
图3Fig 3beta + 认证对比全 404 — 第二后端 11 路径 + 带/无认证对比全 404;两后端 0 命中,webhook 已完全移除(已修复)。beta + auth comparison all 404 — the second backend’s 11 paths + authenticated/unauthenticated comparison all 404; both backends 0 hits, webhooks fully removed (fixed).
DB-29 图3

DB-30 · donutlabs.dev 通配符 DNS / 子域解析donutlabs.dev wildcard DNS / subdomain resolution Medium未修复Unfixed

类型:配置错误 · 复测日期:2026-06-03Type: Misconfiguration · Retest date: 2026-06-03

影响: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 确认存在。

【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 视为同站,通配符扩大同站请求边界
[Still unfixed] Wildcard DNS confirmed present.

[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)
图1Fig 1通配符 DNS 存在 — 6 个随机子域(evil/attacker/xss 等)全部解析成功(解析 IP 已打码)。Wildcard DNS present — 6 random subdomains (evil/attacker/xss, etc.) all resolve successfully (resolved IPs masked).
DB-30 图1
图2Fig 2任意子域 HTTP 全 404 — 随机子域 HTTP 均 404(无内容托管);仅 app.donutlabs.dev 200。Any subdomain HTTP all 404 — random subdomains all return HTTP 404 (no content hosted); only app.donutlabs.dev returns 200.
DB-30 图2
图3Fig 3CORS 已拒绝 — 所有 *.donutlabs.dev Origin 的 CORS 均被拒绝;唯一 ACAC=true 是合法前端 beta.donutbrowser.ai(属 DB-4)。CORS rejected — CORS is rejected for all *.donutlabs.dev origins; the only ACAC=true is the legitimate front end beta.donutbrowser.ai (part of DB-4).
DB-30 图3

DB-31 · MCP 工具生态完整枚举Full enumeration of the MCP tool ecosystem Medium未修复Unfixed

类型:信息泄露 · 复测日期:2026-06-03Type: Info leak · Retest date: 2026-06-03

影响:无认证已封(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 的前置信息来源)。
[Still unfixed]

[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)
图1Fig 1mcp-tool-config 带认证可读 — 无认证 401;带认证 200 返回 62 个工具名。mcp-tool-config readable when authenticated — unauthenticated 401; authenticated 200 returns 62 tool names.
DB-31 图1
图2Fig 2action-mcp/query 枚举 20 DeFi 工具 — 经 201 响应码差异区分工具存在性(端点已打码)。action-mcp/query enumerates 20 DeFi tools — distinguishes tool existence via differences within the 201 response (endpoint masked).
DB-31 图2
图3Fig 3空 args 泄露必填参数(部分 schema) — validation 错误回显工具必填参数;VALIDATION_ERROR/TOOL_NOT_FOUND 码保留,参数名与端点已打码。empty args leak required parameters (partial schema) — validation errors echo the tool’s required parameters; the VALIDATION_ERROR/TOOL_NOT_FOUND codes are kept, parameter names and endpoint masked.
DB-31 图3

DB-32 · HTTP 安全头缺失Missing HTTP security headers Low未修复Unfixed

类型:Web 加固不足 · 复测日期:2026-06-03Type: Insufficient web hardening · Retest date: 2026-06-03

影响: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 响应,无任何安全头配置
[Still unfixed] All three targets have missing security headers.

[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)
图1Fig 1API 缺 6 个 — api-beta 缺 HSTS/CSP/COOP/COEP/CORP/cache-control;XFO(DENY)/XCTO 已设(AWSALB cookie 值已打码)。API missing 6 — api-beta lacks HSTS/CSP/COOP/COEP/CORP/cache-control; XFO(DENY)/XCTO set (AWSALB cookie value masked).
DB-32 图1
图2Fig 2前端缺 5 个 — beta.donutbrowser.ai 缺 CSP(仅 report-only)/COOP/COEP/CORP/XXP;HSTS/XFO/XCTO 已设。Front end missing 5 — beta.donutbrowser.ai lacks CSP (report-only only)/COOP/COEP/CORP/XXP; HSTS/XFO/XCTO set.
DB-32 图2
图3Fig 3汇总 + 第二后端全缺 — 三目标对比;beta.donutlabs.dev 11 个安全头全缺(404 无配置)。Summary + second backend missing all — comparison across three targets; beta.donutlabs.dev lacks all 11 security headers (404, no config).
DB-32 图3

DB-33 · Cookie 安全属性不足Insufficient cookie security attributes Low未修复Unfixed

类型:会话保护不足 · 复测日期:2026-06-03Type: Insufficient session protection · Retest date: 2026-06-03

影响: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 操作。
[Still unfixed]

[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)
图1Fig 16 Cookie 属性检测 — @turnkey/session/v1 缺 HttpOnly(SameSite=Lax 已设);dws-auth-token 三属性齐全无问题;AWSALB 缺 3 项(PostHog key 已打码)。6-cookie attribute check — @turnkey/session/v1 lacks HttpOnly (SameSite=Lax set); dws-auth-token has all three and is fine; AWSALB lacks 3 (PostHog key masked).
DB-33 图1
图2Fig 2Set-Cookie:AWSALB 缺属性 — API 下发 AWSALB(缺 HttpOnly/Secure/SameSite)与 AWSALBCORS(缺 HttpOnly);前端 0 个 Set-Cookie。Set-Cookie: AWSALB missing attributes — the API issues AWSALB (lacking HttpOnly/Secure/SameSite) and AWSALBCORS (lacking HttpOnly); the front end has 0 Set-Cookie.
DB-33 图2
图3Fig 3汇总:5/6 缺 HttpOnly — 会话 Cookie 仅 @turnkey 有问题、dws-auth-token 无问题;缺 HttpOnly 共影响 5 个 Cookie(PostHog 活体 key 已打码)。Summary: 5/6 lack HttpOnly — among session cookies only @turnkey has an issue, dws-auth-token is fine; the HttpOnly gap affects 5 cookies in total (live PostHog key masked).
DB-33 图3

DB-34 · 前端敏感信息暴露Front-end sensitive-info exposure Low未修复Unfixed

类型:信息泄露 · 复测日期:2026-06-03Type: Info leak · Retest date: 2026-06-03

影响:核心发现——前端 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 文件。
[Still unfixed] All three classes of front-end sensitive-info exposure are confirmed.

[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)
图1Fig 1localStorage 存 Turnkey READ_WRITE 会话 — @turnkey/session/v3 为完整可签名会话(sessionType=READ_WRITE 保留为证;userId 与 PostHog key 已打码)。localStorage holds the Turnkey READ_WRITE session — @turnkey/session/v3 is a full signable session (sessionType=READ_WRITE kept as proof; userId and PostHog key masked).
DB-34 图1
图2Fig 2全局变量:PostHog Key — 9 项匹配中 7 项为浏览器内建 API(误报);真实暴露是 window._POSTHOG_REMOTE_CONFIG 内的 PostHog Project Key(已打码)。Global variables: PostHog key — of 9 matches, 7 are browser built-in APIs (false positives); the real exposure is the PostHog Project Key inside window._POSTHOG_REMOTE_CONFIG (masked).
DB-34 图2

DB-35 · 详细错误信息、调试端点和生产指纹暴露Verbose error messages, debug endpoints, and production-fingerprint exposure Low已修复Fixed

类型:信息泄露 · 复测日期:2026-06-03Type: Info leak · Retest date: 2026-06-03

影响:(已修复)复测三类指纹途径均无泄露——① 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)
已修复。全面复测三类指纹泄露途径,均无发现:(1)错误响应:5种畸形请求(不存在路径/无效JSON/超大参数/特殊字符/SQL关键词)全部返回友好错误(400/404),无栈追踪、无内部路径、无框架版本泄露。(2)调试/测试端点:25条路径(/debug/__debug/test/ping/info/version/env/graphql/swagger/actuator等)全部404,无未授权调试接口。(3)响应头指纹:无 Server / X-Powered-By / X-Generator 等技术栈标识。500错误无法触发。beta.donutlabs.dev 同样无指纹泄露。
Fixed. A comprehensive retest of the three fingerprint-leak avenues finds nothing: (1) Error responses: 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-version leaks. (2) Debug/test endpoints: 25 paths (/debug, /__debug, /test, /ping, /info, /version, /env, /graphql, /swagger, /actuator, etc.) all 404, with no unauthorized debug interface. (3) Response-header fingerprints: no tech-stack markers such as Server / X-Powered-By / X-Generator. A 500 error cannot be triggered. beta.donutlabs.dev likewise leaks no fingerprint.
证据截图(3 张,其中打码 2 张)Evidence images (3; 2 masked)
图1Fig 1畸形请求全友好报错 — 5 种畸形请求均返回脱敏 JSON(400/404),无栈追踪、无指纹(0/5);响应头无 Server/X-Powered-By(userId 与 AWSALB cookie 值已打码)。Malformed requests all error gracefully — 5 kinds of malformed requests all return sanitized JSON (400/404), with no stack traces and no fingerprint (0/5); response headers carry no Server/X-Powered-By (userId and AWSALB cookie value masked).
DB-35 图1
图2Fig 2调试端点全 404 — 28 条调试/测试端点(/debug、/actuator、/swagger、/graphql 等)全部 404,无可访问调试接口。Debug endpoints all 404 — 28 debug/test endpoints (/debug, /actuator, /swagger, /graphql, etc.) all 404, with no accessible debug interface.
DB-35 图2
图3Fig 3综合结论:全部已修复 — 500 无法触发、第二后端无指纹;五类指纹途径命中 0,DB-35 已修复(AWSALB cookie 值已打码)。Overall: all fixed — 500 cannot be triggered, the second backend has no fingerprint; the five fingerprint avenues have 0 hits, DB-35 is fixed (AWSALB cookie value masked).
DB-35 图3
验证边界声明Verification-boundary statement
  • 所有链上交易验证使用研究员自有 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.
点击任意处关闭放大查看