跳转至

鉴权与限额

Legacy 模式 (未配 keys.json) 行为

v1.4.86 行为变化 (partial breaking):

模式 只读 endpoint (/api/quote / /api/positions / ...) 写入 / 管理 endpoint (/api/order / /api/modify-order / /api/unlock-trade / /api/reconfirm-order / /api/cancel-all-order / /api/admin/*)
Scope 模式 (配了 keys.json) 需要对应 scope token 需要 trade:real / admin scope token
Legacy 模式 (未配 keys.json) ✅ 无 auth 放行 (backward compat) 401 UNAUTHORIZED (v1.4.86 起)

为什么: 本机任何 skill / agent / shell 脚本可以不经 auth 直接 curl POST /api/order 下单, 是安全漏洞 (外部 reviewer 2026-04-23 实证). v1.4.84 只在 startup 打 stderr warn, v1.4.86 改为硬门禁.

升级指引: 如果你原先 legacy 模式 + 用过 /api/order 等写接口:

# 1. 生成一个 API key
./futucli gen-key --id my-agent --scopes qot:read,acc:read,trade:real
# → 输出 fc_xxxxx 明文 (只显示一次, 保存到客户端)
# → 写入 ~/.futu-opend-rs/keys.json (或 --rest-keys-file 指定的路径)

# 2. 重启 daemon, 带 --rest-keys-file
./futu-opend --login-account X --login-pwd Y \
    --rest-port 22222 \
    --rest-keys-file ~/.futu-opend-rs/keys.json

# 3. 客户端调用时带 token
curl -H "Authorization: Bearer fc_xxxxx" \
    -X POST http://localhost:22222/api/order \
    -d '{"c2s": {...}}'

只读场景不受影响: 如果你只用 /api/quote / /api/positions / /api/orders 等查询接口, 可以继续不配 keys.json, daemon 仍然 legacy 放行.

五类 Scope

Scope 能做什么
qot:read 行情只读 + 订阅
acc:read 账户只读(资金 / 持仓 / 订单 / 成交)
trade:simulate 模拟账户下单 / 改单 / 撤单
trade:real 真实账户下单 / 改单 / 撤单
trade:unlock MCP futu_unlock_trade 工具(从 OS keychain 读密码,不经 LLM)
admin v1.4.32+ daemon 管理端点(/api/admin/status|reload|shutdown);只给运维 key,LLM key 不要带

keys.json 格式

{
  "version": 1,
  "keys": [
    {
      "id": "research",
      "hash": "sha256 hex",
      "scopes": ["qot:read", "acc:read"],
      "limits": {
        "allowed_markets": ["HK", "US"],
        "allowed_symbols": null,               // null = 不限
        "max_order_value": 100000.0,
        "max_daily_value": 500000.0,
        "hours_window": "09:30-16:00",
        "max_orders_per_minute": 5,
        "allowed_trd_sides": ["SELL"]
      },
      "allowed_machines": ["fp_abc123"],       // null = 不绑机器
      "created_at": "2026-04-15T10:00:00Z",
      "expires_at": "2026-05-15T10:00:00Z",    // null = 不过期
      "note": "research bot"
    }
  ]
}

字段详细见 keys.json 配置

五层限额

下单请求会依次过这几道闸门(任一拒就返回错):

1. 市场白名单   allowed_markets      —— ctx.market 在不在
2. 品种白名单   allowed_symbols      —— ctx.symbol 在不在
3. 方向白名单   allowed_trd_sides    —— BUY / SELL / SELL_SHORT / BUY_BACK
4. 时间窗口     hours_window         —— 本地时区,支持跨午夜 22:00-04:00
5. 单笔上限     max_order_value      —— qty × price
6. 速率         max_orders_per_minute —— 滑动窗口 60 秒
7. 日累计       max_daily_value      —— UTC 日滚

协调 auth 层和 handler 层

  • auth 层(middleware):市场 / symbol / side / value 都拿不到(body 没解析)。只跑 速率 + 时段 全局闸门。rate window commit timestamp
  • handler 层(业务路由):body 解析后有完整 ctx。跑 check_full_skip_rate 做剩下的检查,跳过 rate(已 commit,别重复计),但 daily 会累加

这样 rate 只计一次、daily 按金额如实累加。

软机器绑定

# 本机绑
./futucli gen-key --id bot --scopes trade:real --bind-this-machine

# 跨机器:
# 在目标机器跑
./futucli machine-id --for-key bot
# 出 fp_xxxxx

# 在签发机器
./futucli gen-key --id bot --scopes trade:real --bind-machines fp_xxxxx

指纹公式:SHA-256("futu-machine-bind:v1:" + key_id + ":" + raw_machine_id)

raw_machine_id 来源: - Linux:/etc/machine-id - macOS:IOPlatformUUID(ioreg) - Windows:尚未实现

软绑定的强度:挡不住登上目标机器的攻击者(攻击者能读 machine-id 伪造指纹);能挡 keys.json 被整体拷到其他机器的意外泄漏。 TPM / Secure Enclave 级强绑定不属于当前能力边界;不要把软绑定当成硬件级设备证明。

SIGHUP 热重载

kill -HUP <pid> 重新读 keys.json。四条入口(REST / gRPC / 核心 WS / MCP)都支持:

  • 新加的 key 秒级可用
  • 改 scope / 限额 / expires_at / allowed_machines 立刻生效(不用重启)
  • remove-key 吊销的 key 下次请求被拒(不等超时)

实现:KeyStoreArcSwap<KeysFile>,SIGHUP → 重读文件 → 原子替换 snapshot。请求路径按 get_by_id(key_id) 取当前 snapshot。

审计

每一次 allow / reject / trade 事件都走 futu_auth::audit

WARN target=futu_audit iface=rest endpoint=/api/order
     key_id=bot_a outcome=reject reason="limit: rate limit exceeded: ..."

--audit-log <PATH>target=futu_audit 的事件单独写 JSONL 文件,jq / DuckDB 后处理:

jq 'select(.outcome=="reject" and .iface=="grpc")' /var/log/futu-audit.jsonl

面向攻击者的 fail-closed 设计

场景 行为
/api/unknown-path 404(不漏接口是否存在,不误导"换 key 就能过")
tools/call 未登记工具 reject "unknown MCP tool"
gRPC 未覆盖 proto_id 默认 trade:real scope(保守)
per-call api_key 无效 (不回落 startup key,防升级攻击)
握手后 key 被 revoke 下次请求 reject "key revoked"

F3C Auth 错误码参考表(ret_type 语义)

v1.4.84 §12 新增(对应双 tester 反馈同账号跨版本 ret_type 在 11/15/20 间切换,需要语义参考)。Daemon 从 auth.futunn.com / auth.moomoo.com 收到的 POST /authority/ response 的 ret_type / error_code 含义(对齐 C++ FTLogin 约定):

ret_type 名称 含义 触发条件 Daemon 行为
0 Success 登录成功 TGTGT 验证通过 + 设备已验证 保存 tgtgt_new / device_sig_new / rand_key_new 到 credentials 文件,启 server
2 账密错 / TGTGT 签名错 pwd_md5 错 或 TGTGT 构造错(client_type / account 拆分 / svr_time endian 等任一错) Futu 协议误导:表面是"密码错",99% 实际是 TGTGT 构造问题 提示用户检查密码 + 平台(futunn vs moomoo)+ --reset-device
11 require_verify_code TGTGT 签名校验失败(不是真要图形验证码) account 拆分漏 region_no / salt URL 字段漏 / payload 字段漏 核对 POST /authority/ body 15 字段齐全 (实战教训)
15 tgtgt 过期 3 类独立来源:
(a) device_id 毒化(v1.4.16 前空 SMS 提交触发)
(b) 服务端反刷限流(同 uid 短时间重 POST /authority/)
© 账号级状态异常(需 App 登一次激活)
不是真过期,99% 是 (b) 反刷 daemon 按 3 分类顺序提示:sleep 60s retry → --reset-device → App 登一次
20 require_device_verify 正常 SMS 流程(首次新设备登录),响应带 device_verify_sig / phone_no 新 device_id / credentials 不存在 daemon 自动 call req_device_code 发 SMS → prompt 用户输 --verify-code
21 验证码错 SMS 码错 或 空码(非交互 stdin fail) 用户输错 / setup-only 非 TTY stdin 空 v1.4.76 atty check 拦截 + v1.4.81 Option B cached dcs 路径重试
23 需绑手机 账号未在 App 绑定手机 新账号未完成 App 首登激活 提示用户先在 Futu/moomoo App 完成绑定手机
45 当前应用版本过低 X-Futu-Client-Version < 800 海外账号严格校验版本 Rust hardcode 1002(= C++ OpenD FTGTW_Client_Version = 10.02 × 100),不应触发
99 网络请求参数错 body 签名校验失败 / 字段缺 / 字段非法值 sens_state 等必填字段(实战 verify, sens_state 类必填),或 body tampered 不改 daemon hardcode 字段

错误码的跨版本稳定性

同账号可能跨版本看到不同 ret_type这不一定是 daemon 回归

跨版本差异 最可能原因
从 20 → 11 device_id 变了(--reset-device 或文件被删)→ 服务端需重新 SMS 验证
从 20 → 15 反刷限流被击中(短时间 POST /authority/ 超阈值)/ 或 device_id 被 App 侧失效
从 11 → 15 TGTGT payload 差异(版本升级修了字段)+ 服务端反刷
从 2 → 11 平台切换(--platform futunnmoomoo)导致 client_type 40/60 不对
从 15 → 20 --reset-device 后服务端认为是新设备,重新 SMS 流程

建议:真机 verify 遇到跨版本 ret_type 切换时,先 sleep 300s 清反刷窗口 再试,同时确认: - 版本号(futu-opend --version) - --platform 值 - credentials 文件是否被外部修改 - 是否最近 --reset-device

2FA 错误码(/api/unlock-trade response)

Token 2FA(Futu 令牌 / moomoo token)/api/unlock-trade 后 backend 的 result_code(非 HTTP auth 路径):

result_code 含义 修法
-20011 账号需要 token 2FA 验证 security_firm 对应的 token app(Futu 令牌 app vs moomoo token app,secret 不互通)+ 账号已启用 2FA
-102 channel 不支持 账户未 unlock / 没权限 / broker 未连上
110005 合约字段不一致 核对 security_type + exchange_str + sec_market(实战 verify)