# DSHA 安全模型 —— 它能碰到你手机的什么 你装了一个 370MB 的 APK,让一个 AI 在里面跑 shell 命令,还可能给了它 ADB 权限。 这份文档说清楚**边界在哪**:哪些东西 agent 碰得到、哪些碰不到、哪些要你点头才行、 以及每项权限一旦给出去意味着什么。 看不懂哪一条就当它是危险的 —— 你可以只用最小配置(不给 ADB、不给文件权限、 不开局域网),DSHA 在那种状态下依然能正常跑 dsh。 > English version: [security-model.en.md](security-model.en.md) --- ## 一句话总结 **默认状态下 agent 只能动 DSHA 自己的私有目录。** 想让它碰手机的其它部分, 每一项都要你显式授权,而且都能单独关掉。 --- ## agent 默认能碰到什么 APK 装完、什么权限都不给的情况下: | 范围 | 能不能 | 说明 | |---|---|---| | 容器内的 Ubuntu 环境 | ✅ 完全可控 | 这是它的工作区。`apt install`、编译、跑服务都在这里 | | DSHA 自己的私有目录 | ✅ 可读写 | rootfs 就在这里(`/data/data/com.dsh.client/`),Android 的应用沙箱只允许 DSHA 自己访问 | | 共享存储(`/sdcard`) | ✅ 可读写 | 容器内 `/sdcard` 指向手机共享存储。**这是默认就有的** —— 照片、下载、文档它都能读 | | 其它应用的数据 | ❌ 不能 | Android 应用沙箱隔离,`/data/data/<其它包名>` 读不到 | | 系统分区 | ❌ 不能写 | 没有 root 就写不了 `/system` | | 拨号、短信、通讯录、位置、摄像头、麦克风 | ❌ 不能 | DSHA 根本没申请这些权限,可以在系统设置里自己核对 | > ⚠️ **`/sdcard` 是默认可达的**,这一条最容易被忽略。如果你不希望 agent 看到相册和下载目录, > 现在只能靠约定(在 `AGENTS.md` 里告诉它别碰)或者不在这台手机上放敏感文件。 > 容器挂载点在 `ContainerRuntime.BINDS` 里,自行构建时可以去掉。 --- ## 需要你授权的能力 每一项都是**默认关闭**的,开关都在 App 里,随时可以撤。 ### ADB 无线调试(「工作区」页) **给出去意味着什么**:agent 拿到了 `shell` 用户权限(uid 2000)。它可以点击屏幕、 滑动、截屏、装卸应用、读系统日志、看已安装应用列表、改部分系统设置。 **这是本项目能给出的第二强权限**,仅次于 root。 - 配对码只用一次,之后靠密钥对维持,DSHA 不保存你的配对码 - 撤销方式:系统设置 →「无线调试」关掉,或撤销全部调试授权 - ADB 不能做的:读其它应用的私有数据、拿 root ### root shell(「配置」页,默认关) **给出去意味着什么**:全部。有 root 就没有边界可谈了。 - 需要设备本身已 root(KernelSU / Magisk),DSHA 不提供也不申请 root - 开关是 `allow_root_shell`,默认 false - 除非你清楚自己在做什么,别开 ### 「所有文件访问」(首次启动会引导) **给出去意味着什么**:DSHA 可以读写整个共享存储。它的用途是把对话数据放到 `Documents/dshdata`,这样**卸载重装数据不丢**。 - 拒绝的后果:数据留在私有目录,**卸载即丢**(自检会明确告知当前处于哪种状态) - 它不会让 agent 拿到新的能力 —— `/sdcard` 本来就是可达的 ### 悬浮窗(「配置」页的流式悬浮条,默认关) **给出去意味着什么**:DSHA 可以在其它应用之上画东西。 - 它只用来显示 AI 输出和危险命令的批准按钮,不读屏、不截屏 - 内容会显示在屏幕上 —— **旁边的人也看得见**,这也是它默认关闭的原因 ### 局域网访问(默认关) **打开意味着什么**:同一个 Wi-Fi 下的设备可以访问你手机上的 dsh Web UI。 - 有 token 鉴权,**fail-closed**:token 缺失或不对一律拒绝,不存在「token 为空就放行」 - token 首次命中后写成 `SameSite=Strict` Cookie,之后不再出现在 URL 里 —— 防止它随外链泄漏 - 公共 Wi-Fi 下别开。token 强度够,但你的手机会在网络上暴露一个端口 --- ## agent 想干危险的事时会发生什么 `dsh-guard.sh` 会拦住这几类命令,交给你决定: - 覆盖或删除关键路径(`/` `/root` `/etc` `/data` 等) - 递归删除(`rm -rf`) - 直接写块设备、改分区 - 通过 ADB 卸载应用、恢复出厂之类的操作 拦下来之后**三条渠道**任选一条批准:通知栏、App 内弹窗、悬浮条上的按钮。 三条共用同一个确认,谁先点谁生效;60 秒没人理就当拒绝。 > ⚠️ **守门人不是沙箱。** 它是一份基于命令文本的黑名单,绕过它并不难 —— > 它防的是「AI 手滑」,不防「有人刻意写一条绕过它的命令」。 > Android 上没有 bubblewrap,dsh 因此以 `danger-full-access` 运行。 > 真正的隔离边界只有一层:**Android 的应用沙箱**。 --- ## 你的密钥和数据放在哪 | 东西 | 放在哪 | 怎么保护 | |---|---|---| | DeepSeek API key | App 的 SharedPreferences | Android Keystore 加密(AES/CBC),**密钥不出 Keystore**,别的应用拿不到 | | 备份里的 API key | 备份包内 `.dsh/.dsha-apikey` | 同一把 Keystore 密钥加密。**换设备后解不开**(这是设计如此,不是 bug),可以在「配置」页关掉「备份包含 key」 | | 对话记录 | `Documents/dshdata`(公开)或私有目录 | ⚠️ 放在公开目录时,**任何拿到存储权限的应用都能读**。这是「卸载不丢数据」的代价 | | 备份文件 | `Download/DSHA/` | ⚠️ 同上,公共目录。里面有你的**全部对话记录**。分享这个文件前请想清楚 | | dsh 自己的凭据 | `.dsh/.credentials.yaml` | 刻意留在私有目录,不迁到公开区。但它**会进备份** | | 3090 桥 token | `.dsh/.bridge_token` | 私有目录,权限 600。**已从备份里排除** —— 它属于这台机器 | **我们不收集任何数据。** DSHA 没有遥测、没有统计上报、没有崩溃收集。 唯一的对外网络请求是:你点检查更新时访问 GitHub API、热更新脚本时访问 raw.githubusercontent.com、以及安装环境时访问你自己选的那个镜像源。 API 请求由 dsh 直接发给 DeepSeek,不经过我们任何地方。 --- ## 你怎么验证装到手机上的 APK 是真的 从 GitHub 手动侧载 APK 的分发方式,来源验证是最该做的一环: ```bash # 1. 哈希(证明文件没被改过) sha256sum -c deepseekharness-arm64-vX.Y.Z.apk.sha256 # 2. 构建证明(证明它来自本仓库这个 tag 的 CI,不是别人重打的包) gh attestation verify deepseekharness-arm64-vX.Y.Z.apk --repo qiannianhuanxiang/DSHA ``` 签名证书指纹在发布流水线里逐次核对,**不匹配直接中止发布** —— 一个签名不对的包用户根本装不上(Android 只允许同签名覆盖安装), 发出去比发布失败糟得多。 --- ## 已知的弱点 不藏着: | 弱点 | 现状 | |---|---| | `danger-full-access` | Android sepolicy 挡住 bubblewrap,dsh 没有沙箱。容器内的 agent 对容器有完全控制权 | | 守门人可被绕过 | 基于命令文本的黑名单,防手滑不防刻意规避 | | 备份在公共目录 | 全部对话记录明文躺在 `Download/DSHA/`,任何有存储权限的应用可读 | | `.credentials.yaml` 进备份 | dsh 的凭据文件随备份进公共目录;是否给它一个排除开关还没定 | | `/sdcard` 默认可达 | agent 默认就能读相册和下载目录,目前没有开关 | | 签名密钥待轮换 | 线上包用的是一把 debug keystore(历史原因,换掉会让所有人无法覆盖升级)。密钥轮换按 APK Signature Scheme v3 rotation 单独排期 | 发现别的问题请开 issue,或者到 QQ 群 975836806 说。安全相关的问题优先处理。