dsh-lark-bot 把本机 DeepSeek Harness(
dsh)暴露给飞书 / Lark IM。本文件说明威胁模型、 默认安全姿态与报告渠道。Security model for a bridge that exposes a local coding agent to Feishu / Lark.
- 唯一官方仓库:https://github.com/PlutoKeating/dsh-lark-bot
- 唯一官方 npm 包:
dsh-lark-bot(同源双包dsh-feishu-bot),维护者plutokeating - 唯一安装命令:
npx dsh-lark-bot@latest setup --profile dsh-lark - Releases 资产:仅两个 npm tarball(
dsh-lark-bot-<ver>.tgz/dsh-feishu-bot-<ver>.tgz), 从不发布 Windows/macOS 可执行文件 - 校验承诺:自本文档更新后的下一个 Release 起,每个发布资产随附
<asset>.sha256校验文件; 安装/使用前请核对 SHA-256,不一致即视为被篡改,请勿安装并报告 - 假冒识别:任何以本项目名义提供“下载即运行”二进制(尤其 .exe)、或使用仿冒仓库名/包名的分发 渠道均为假冒 / 恶意来源——请勿下载或运行,并截图按下方报告渠道反馈
- 取证存档:假冒仓库
tarraencompassing61/dsh-lark-bot的取证与处置约定见docs/security/2026-08-17-impostor-repo-evidence/ - 持续监控:
pnpm security:monitor(假冒仓库活动 / npm 仿冒包 / 相似包名抢注),建议每周运行或挂 cron
- 凭据泄露:飞书
app_id/app_secret、DeepSeek API key、会话内容可能在日志、卡片或进程环境中出现。 - 越权访问:未授权用户 / 群聊驱动本机 coding agent 执行命令、读写文件。
- 路径逃逸 / 符号链接逃逸:附件、worktree、
/cd相关路径穿越到 bot 状态目录之外。 - SSRF:agent 或桥接层被诱导访问内网 / 环回地址。
- 消息重放 / 过期事件:旧消息或重复事件被当作新指令处理。
- 交互工具不可达:
ask_user_question、终端类工具在 IM 场景下无法回达,应默认禁用。 - 救援通道被滥用:dsh 下线后由守护接管飞书通道,若控制信号无鉴权,任何能私聊 bot 的人 都能触发安全模式或重启完整 profile。
- 默认拒绝:
- 群聊 / 话题普通消息必须
@bot才响应;channel 以requireMention: false把事件交给 bridge,由 bridge 在匹配 pending 问答卡回复后执行 mention gate。只有精确回复问答卡可免 @。 - 配置了白名单后,私聊切换为 allowlist 模式(
dmMode: 'allowlist')。 - 可通过
DSH_LARK_ACCESS_DEFAULT_DENY=1在无白名单时也拒绝私聊(默认关闭以兼容首次扫码绑定)。
- 群聊 / 话题普通消息必须
- 密钥脱敏:结构化日志按字段名(
secret/token/password/api_key)脱敏; 自由文本日志与卡片文本对Bearer …、sk-…、api_key=…做正则脱敏(src/config/security.ts)。 - 路径 containment:媒体下载目标、git worktree 目标必须落在各自根目录内
(realpath 校验,拒绝符号链接逃逸,
isPathWithin)。 - UTF-8 安全截断:附件文本、卡片摘要按字节截断且不切断多字节字符(
truncateUtf8Safe)。 - 过期事件拒绝:消息时间戳超出窗口即拒绝(
isEventFresh)。 - SSRF 防护:仅允许 http(s) 公网地址;环回、私有、链路本地、CGNAT、IPv6 ULA 全部拒绝
(
isSafeHttpUrl)。 - 交互工具默认禁用:SDK / ACP runtime profile 禁用
user-questions;DEFAULT_DENIED_INTERACTIVE_TOOLS提供工具级黑名单。 - 审批:默认 SDK / Web 宿主在
tools/pre-execute强制阻断高风险调用并通过 dsh rc.8approval/request回调,ACP 使用session/request_permission。按隔离 scope 持久化的ask/allow/deny策略(0600,失败回滚且不报成功)决定弹一次性卡、自动放行或直接拒绝;只有管理员可修改, 显式目标仅限当前 chat 内 scope,deny会在聊天中明确告知。该策略只作用于逐工具审批,不跳过后续独立的计划门禁;run 结束或 callback 断连只结算所属 session 的挂起请求。legacy headless 无工具回调,因此不宣称受该策略保护。 SDK / ACP / Web agent 对较大或高风险动作还会通过lark_request_plan_approval暂停;同一 turn 未批准时 pre-execute 策略拒绝写入、删除、移动、命令执行与run_code。完整计划发到当前飞书 会话,只有卡片批准后才继续;继续规划会把可选文字意见返回 agent。run/callback 结束时只取消 所属 session 的挂起门禁。该门禁是人机确认层,不替代 dsh sandbox 或 ACP 的逐工具权限审批。 - 管理操作鉴权:飞书会话内对 dsh 配置与访问白名单的写操作(
/model default、/model add|remove、/provider add|update|remove、/key set|remove、/invite user|admin|group与/invite remove …)、/permission ask|allow|deny,以及群聊会话隔离模式写操作(/isolation group|topic|member)仅 profile 管理员可执行;首个扫码绑定的 operator 自动成为管理员,之后由现有 管理员经/invite admin <open_id>添加(/invite list为只读、开放)。查看类命令 (/model、/providers、/key list)开放。/doctor因包含本机运行状态与最近日志,仅管理员可执行。/replies set|default另允许当前群的群主/群管理员修改当前 scope;角色通过im.v1.chat.get的owner_id/user_manager_id_list以open_id实时校验,查询失败时拒绝写入。 - 本地回调隔离:
lark_notify、lark_send_file、lark_ask_user、lark_request_plan_approval与approval/requestanswerer 的回调 服务只绑定127.0.0.1,每次启动生成随机 token 鉴权(不落盘、不进日志),请求体限 1MB;/notify与角色 / 配置写命令同为管理员操作。 文件回传不信任 runtime 自报 cwd:bridge 以 native session 反查 scope/workspace,只允许该 workspace、该 scope 实际 worktree/归档与实例日志内的 realpath 普通文件;以 no-follow 打开后 在同一文件句柄复核文件身份并有界读取,拒绝竞态 / symlink 越界、非法文件名和默认超过 20 MiB 的文件;agent 工具目标固定为该 session 的原 chat/thread。归档跨会话转发仅管理员可用。 主动通知偏好默认关闭;普通用户只能为当前 scope 设置当前目标,跨会话目标要求管理员且必须已在ScopeDirectory登记。偏好文件为 0600,提醒发送失败不回写或改变 durable job 终态。 回复合并与近似去重默认关闭;reply-policies.json为 0600 且写失败回滚。近似去重只在同发送者、 同 immutable scope + workspace 与有限时间窗内生效,并对短文本要求规范化精确相等,降低误拦截 其他成员或不同项目任务的风险;命中时向原消息明确回执。 - 多机器人 peer 鉴权与防循环:只有
fleet.json中已启用且 identity 唯一的 bot open_id, 在群内真实 @ 当前 bot 时才可交接;未知 bot、未 @、system/anonymous 消息拒绝。bot 文本不进入 slash-command 管理管线。连续交接由跨进程handoffs.json原子计数、按 messageId 去重,超过DSH_LARK_BOT_HANDOFF_MAXfail closed;只有通过 freshness 检查的真人消息能重置计数。fleet、 handoff 与共享config.json写入均使用原子 owner 目录 + 唯一 token 子文件的 lease 锁并心跳续租; 回收/释放只删除精确 token,再对空目录rmdir,不会误删替代 owner。dead-owner / 遗弃 lease 仍可回收。附加实例拒绝无法隔离广播 session 的共享webadapter。 - 安全网守护(默认随
setup安装):- 守护是独立于 dsh / Cordis 的最小进程,只读取本地状态与进程命令行(
ps,不读内存), 不导入任何 dsh 代码、不监听公网端口; - dsh 在线时守护不连接飞书(同 app 长连接仅允许单连接,避免抢占正常通道);仅在 「曾观察 dsh 在线 且 心跳过期 + 无 dsh 进程」时接管通道;
- 控制信号默认拒绝:仅管理员(
access.admins,无管理员时回退allowedUsers)可触发/safemode系列命令,未授权消息静默丢弃; - 过期事件复用
DSH_LARK_EVENT_FRESHNESS_MS窗口拒绝; - 心跳 / 守护状态文件以
0600写入;安全模式仅挂载官方核心 bundle(headless:dsh-base+dsh-headless;SDK 流式优先:dsh-base+dsh-sdk-jsonrpc-server, 均不挂载第三方插件与 bridge 回调工具),避免把故障面带进救援通道。
- 守护是独立于 dsh / Cordis 的最小进程,只读取本地状态与进程命令行(
-
Session 投影只在飞书用户显式确认后建立;WebUI/TUI 的 open、resume、switch 或 activity 不得 自动改变 binding,也不得把外部 session 广播给所有已知 scope。
-
选择器只枚举当前 canonical workspace 的非 subagent session 元数据,不展示正文。确认卡在历史 披露前列出标题/ID、workspace、更新时间、回填数量、scope、替换/迁移;取消、超时或 operator/ scope/workspace 不匹配时不绑定也不发送历史。
-
私聊遵循 allowlist;member 仅 scope owner;共享 group/topic 和跨 scope 独占迁移仅 profile 管理员。确认副作用在原子 store 事务中复核披露时 owner 与迁移授权;owner 变化必须重新开卡。 迁移同时清除旧 scope 的兼容 session mapping。一个 DSH session 默认最多绑定一个飞书 scope, 避免跨私聊/群聊的数据泄露。
-
session-projections.json为 0600 原子状态,只保存 routing/cursor/message mapping/rpcId,不复制 transcript。DSH append-only session log 是唯一真源,bridge 不直接修改 JSONL、不启动第二 writer。 -
来源标签只依据 DSH 事件提供的可信 provenance 或 bridge prompt correlation;无法确认时显示 “其他 DSH 客户端”,不根据进程、窗口或文本相似度猜测。飞书原始用户消息不被编辑。
-
根
dsh-plugin.json的 host facet 运行于trusted-in-process:它与 dsh-TUI 宿主共享进程权限, 不是安全沙箱。可选 seam 缺失时 no-op,注册、定时器和状态均随插件 lifecycle 清理;不拦截 input/session switch,也不把 TUI observation/storage 当同步真源。 -
本地配置
~/.dsh-lark/config.json以0600权限写入。 -
飞书凭据明文保存在本机配置文件;日志与卡片不输出真实密钥。
-
dsh Web 设置页把 App Secret 声明为 Schemastery
role('secret'):Host→browser 的 resolved/base/user 层均脱敏,只允许 write payload 单向进入官方 settings provider;设置卡不读取、预填或比较旧密钥。 Web 提交触发的 bridge reload 串行等待旧 generation 完整停止,避免新旧凭据实例同时连接。 -
卡片语言由飞书/Lark 客户端根据 Card JSON 2.0 的
zh_cn/en_usvariant 本地选择;bridge 不读取、不推断也不持久化成员 locale。无法 per-viewer 选择的 Markdown/toast 直接并列中英文。 -
多机器人 registry
~/.dsh-lark/fleet.json只保存实例/profile 名与 bot open_id/name;共享handoffs.json保存 chat id、最近 message id 和轮数(均 0600)。这些标识会让本机用户看到 哪些机器人/群参与过交接;peer name/open_id 会进入每轮 agent prompt 并随任务上下文发送给 当前模型 provider,以支持精确 @ 交接。交接内容仍发送到共享群,不构成消息隐私隔离。 -
每个额外实例的 dsh provider 设置与凭据位于独立
~/.dsh-lark/bots/<name>/dsh/{settings.yaml,.credentials.yaml};service env 快照标准 DeepSeek key 与该实例配置中已引用的 credential 环境键。bot remove删除.credentials.yaml与 service env, 保留不含字面密钥的 settings/runtime session 以便恢复;其余 DSH_HOME 数据需由用户备份后手工清除。 -
桥接引擎始终在 dsh 宿主进程内运行。可选
service install会将启动所需的DSH_LARK_*、运行路径及实际 providercredentialRef环境键白名单快照到~/.dsh-lark/service/<profile>.env(POSIX 0600;Windows 用icacls移除继承并只授予当前用户);macOS plist / Windows 计划任务不嵌入密钥, 隐藏 runner 在启动时读取快照。敏感值不进日志与卡片;环境变更后需service restart刷新。 -
正常服务生命周期以 profile 级原子锁串行化;portable status 的 PID 必须同时匹配 Linux
/procstarttime、service-supervise命令和 profile 后才可发送信号,强制停止作用于已验证的 独立进程组,避免 PID 复用误杀或遗留孤儿 dsh。stop/uninstall intent 会阻止 guardian 回拉。 -
桥接引擎日志以 JSON Lines 输出到 stderr(由 dsh 宿主进程捕获),密钥字段脱敏后输出;
-
/doctor诊断文件在内存生成并直接上传,不创建临时文件;仅包含非敏感配置计数、当前 workspace 的运行摘要、服务状态与最多 64 KiB 的当前 bridge 进程内结构化事件;不读取共享 dsh 宿主 stdout。 结构化事件只保留代码内固定枚举的 category/event 与固定数值字段, 时间被规范化,所有其他字段名和值均丢弃, 因而不含消息正文/transcript/凭据标识或值。 导出前会再次对 Bearer、sk-、api_key、当前进程已知敏感环境值及主目录脱敏。群中上传的文件 对群成员可见,因此命令仅限管理员,仍建议私聊生成并由发送者转发前复核。logs/bot.log是 0.6.0 独立服务时代的遗留路径,0.7.0 起不再写入。 -
聊天命令管理的 dsh 配置按官方存储协议写入:
~/.dsh/settings.yaml(只存apiKeyEnv引用,不落字面密钥)与~/.dsh/.credentials.yaml(目录 0700、文件 0600)。bot 永不回显 密钥值;群聊中粘贴密钥会对群成员可见,建议私聊使用或改用环境变量 / dsh Web 页面录入。 -
所有数据仅在本机、飞书开放平台与 DeepSeek API 之间流转;无遥测。
-
安全网守护相关文件:
~/.dsh-lark/guardian.json与~/.dsh-lark/profiles/<profile>/guardian/heartbeat.json(均0600);守护读取的飞书凭据 来自~/.dsh-lark/config.json(0600),日志按既有规则脱敏。 -
群聊隔离模式保存在
~/.dsh-lark/profiles/<profile>/isolation.json(0600)。成员模式会把 飞书open_id作为 durable scope owner,因而该标识也会出现在对应 session、scope directory、 worktree 与 archive 的本地索引或路径中,并显示在共享群的运行卡片上。成员模式隔离的是 agent 上下文与会话数据,不是群消息可见性:任务输入、进度卡和回复仍发送到共享群,群内其他成员 仍可看到;其他成员不能操作该 member scope 的停止、审批或问答卡,缺失 operator identity 时也 拒绝操作。涉及私密内容时请改用私聊。 -
adapter 实际上报的 input/output/cache token 与 context used/limit 保存在同一 profile 的
sessions.json(0600),并按 scope + canonical workspace cwd 隔离;最近 context 快照同时保存 产生它的 native sessionId 与 canonical provider/model 身份,并可由/status卡在身份匹配时展示。未知或身份不匹配字段不估算;member scope 的刷新动作 校验 operatoropen_id与 owner,但共享群里已发送的状态卡仍对群成员可见。 -
bridge 接收的普通 agent 消息在入队前写入
profiles/<profile>/jobs.json(0600):包含原始正文、 附件/提及元数据、chat/thread/scope、workspace、状态及受控 checkpoint,最多保留 500 条终态记录。 checkpoint 不含隐藏推理正文或工具参数;/jobs展示先脱敏并按 scope + workspace 隔离。原始 prompt 仍可能包含用户主动输入的密钥,安全边界与sessions.json相同。running 崩溃后只标记 interrupted, 不自动重跑可能已有副作用的工具;显式 retry 会再次执行,用户必须先对账。 -
审批卡会把工具名、理由、调用标识及可取得的执行参数发送到当前会话;member scope 只限制谁能 点击,并不隐藏卡片正文。涉及密钥、私有路径或敏感命令时应使用私聊。
-
群消息在底层 channel 进入 bridge 后执行 mention gate:普通群消息仍需 @bot(或管理员明确开启 no-at 模式);仅当
replyToMessageId命中当前进程内 pending 问答卡时可免 @。文字答案必须属于 同 chat/topic,member scope 还要求 senderopen_id等于 owner;拒绝的回复不会结算问题或进入任务队列。 no-at 的实时事件与历史轮询都再次校验当前allowedUsers/allowedChats。scopes.json会保存 每个 scope 最近一次入站 messageId,作为 topic 问答卡的 reply anchor。
发现安全漏洞请通过 GitHub Security Advisory 私下报告,不要公开 issue。