Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

1. 现役 guest 用户数据由 App 私有目录持有,host backing 从 `Context.filesDir` 派生;`/data/adb/minis/rootfs` 是 Root-owned、可替换 runtime state。
2. Guest 使用设备实际 App UID/GID;进入 chroot 后必须清空 supplementary groups 与 Linux capabilities,禁止写死 `10000`。
3. `DirectRootRunner` 只允许执行 App 构造的受控基础设施动作:rootfs、namespace、bind、chroot、必要探测/修复和受控 legacy migration。按上游权限模型,本地 Agent 可使用结构化 `root.shell`(tool basename + args)调用受信 Android system 工具;该能力必须 local-only、有参数/超时/输出/进程组边界,不接受 raw command、host 文件 API 或通用 Root RPC,MCP/远程调用方不可见。另外两个 Root 代办入口都是 guest CLI:`minis-apt`(仅 `update`/`install`/`remove` 纯包名,命令由 App 构造,会移除运行时必需包的 `remove` 被拒绝)和 `android-root-cli`(Android 系统工具的整理子命令,加 `exec` 原始 root shell;权限开关默认关闭)。两者详见 `docs/contracts/04-SECURITY-CONTRACT.md`。项目只走 Root:不接入 Shizuku 或其它特权代理。
3. `DirectRootRunner` 只允许执行 App 构造的受控基础设施动作:rootfs、namespace、bind、chroot、必要探测/修复和受控 legacy migration。按上游权限模型,本地 Agent 可使用结构化 `root.shell`(tool basename + args)调用受信 Android system 工具;该能力必须 local-only、有参数/超时/输出/进程组边界,不接受 raw command、host 文件 API 或通用 Root RPC,MCP/远程调用方不可见。另外两个 Root 代办入口都是 guest CLI:`minis-apt`(仅 `update`/`install`/`remove` 纯包名,命令由 App 构造,会移除运行时必需包的 `remove` 被拒绝)和 `android-root-cli`(Android 系统工具的整理子命令,加 `exec` 原始 root shell;权限开关默认关闭)。两者详见 `docs/contracts/04-SECURITY-CONTRACT.md`。项目只走 Root:不接入 Shizuku 或其它特权代理。来自 Agent / guest CLI 的特权命令统一经 `ProtectedRoot`(设备完整性保护,Issue #182):拦会让手机无法开机或使用的操作,其余放开;App 自己构造的基础设施动作仍走 `DirectRootRunner`。
4. 网络代理和 Root/chroot 是不同概念。HTTP/CONNECT 代理协议本身不要求 Root;当前 helper 仅因 Android 出站 UID/VPN/BPF 兼容需求可由特权身份启动。不得把它扩展成命令、文件或通用 RPC 服务。
5. 产品运行时不恢复 PRoot/Alpine 双栈;不要为了兼容全局关闭 SELinux。
6. Session 执行必须保持对应 session workspace 语义,不能用全局 `/workspace` 绕过隔离。
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This changelog tracks **Minis for Android** as an independently maintained Andro

## Unreleased

- Device protection (Settings > Permissions, on by default): privileged commands from the agent, `android-root-cli` and the Android tools can no longer write partitions or the system partitions, remove `/data/system`, `/data/misc`, `/metadata` or `/data/adb`, uninstall or disable core system apps, factory-reset or remove user 0. Nothing is asked: the agent gets `DEVICE_PROTECTED: <category>: <target>` and tries another way. Everything else stays open. Refusals are listed under the switch. When `/data` is nearly full the app freezes its own terminals and refuses new bulk writes until space is freed. `android-root-cli partition read` exports a partition read-only.
- Root only: Shizuku (and AXManager / Sui) support is removed; Root is the app's one privileged path. The code before this change is kept on the branch `archive/before-root-only`.
- `android-shizuku-cli` is now `android-root-cli`: the same subcommands and JSON, run as root through `su`. Its Agent permission switch carries over.
- The virtual screen runs in a root service (libsu) instead of a Shizuku UserService. Run its compatibility check once more to switch it back on.
Expand Down
1 change: 1 addition & 0 deletions docs/contracts/04-SECURITY-CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- `DirectRootRunner` 的内部脚本动作限于确实需要权限的 rootfs 探测/修复、namespace/bind/chroot、受控 legacy 数据迁移及同类明确基础设施需求;本地 Agent 的 `root.shell` 只走结构化 Android tool/argv 合同,不得被扩展成 raw shell、host 文件或通用 RPC。
- 例外之一:`minis-apt`(guest CLI,经 `AptOffloadHandler`)。guest 以 App UID 运行、无法使用 apt,因此由 App 代为在 rootfs 的一次性 mount namespace 内以 Root 执行 `apt-get update|install|remove`。Agent 只给出包名,命令全部由 App 构造(`AptCommandPolicy`):只接受 `^[a-z0-9][a-z0-9+.-]{0,62}$` 形式的纯包名(拒绝选项、版本/发行版指定、路径、shell 字符),每次最多 20 个;`remove` 先用 apt 的模拟结果检查,若会连带移除运行时必需的包(`UbuntuProvisioner.BASE_PACKAGES` 等)则在移除前拒绝;同一时间只运行一个;走与其它 guest CLI 相同的权限开关(`apt_cli`,设置里可关闭);只影响 Ubuntu 环境,不触及 Android 系统与宿主文件。它不是通用 Root 执行入口,不接受原始命令。
- 例外之二:`android-root-cli`(guest CLI,经 `RootCliOffloadHandler` 与 `RootProcess`)。它以 root 运行 Android 系统工具:`package`/`permission`/`activity`/`display`/`settings`/`user`/`network`/`input`/`notification`/`file`/`device` 等整理过的子命令(参数向量由 App 构造),外加 `exec`——把剩余参数拼成一条命令交给 root 的 `sh -c`。`exec` 是有意给本地 Agent 的原始 root shell:2026-10 起项目只走 Root,按维护者的决定给 Agent 尽量大的权限,只防止把手机弄坏;此前同一能力是经 Shizuku 的 `android-shizuku-cli`(Shizuku 以 Root 启动时同样是 uid 0)。约束:只能从 guest shell 调用;受 `root_cli` 权限开关控制,默认关闭;su 缺失或被拒时直接返回 `SERVICE_NOT_RUNNING` / `PERMISSION_DENIED`,不运行任何命令。设备完整性保护(拦截写分区、系统分区、`/data` 系统数据、卸载核心系统包等)尚未实现,见 Issue #182;远程 MCP 调用方经 `linux.shell` 也能到达它:这是维护者接受的设计——每次调用都要用户在手机上单独批准(一次性 confirm,与「全自动」模式无关,不会被它跳过),且 `root_cli` 开关默认关闭。未经批准的远程请求拿不到 root shell。
- 设备完整性保护(Issue #182,默认开启,只有用户能在设置 → 权限里关闭):来自 Agent 或 guest CLI 的特权命令(`root.shell`、`android-root-cli` 全部子命令含 `exec`、`android.app` 等 Android 工具,即所有经 `PrivilegedCommandRunner` / `RootProcess.run` 的调用)统一经 `ProtectedRoot`。只拦"最坏结果不是重启能解决"的操作:写 `/dev/block`、写系统分区(`/system` `/system_ext` `/vendor` `/product` `/odm` `/apex`)、写 `/data/system*` `/data/misc*` `/data/vendor*` `/data/property` `/data/apex` `/data/unencrypted` `/metadata` `/data/adb`(`/data/adb/minis` 除外)、删除或改名 `/data` 等顶层节点、卸载/停用/清除/挂起/隐藏核心系统包、恢复出厂、删除用户 0、把 `device_provisioned` / `user_setup_complete` 置 0。读取、其他 app 数据、`/sdcard`、非核心预装应用、kill 进程、网络、sysctl、重启一律放开。被拦时不弹窗、不走一次性审批,固定返回 `DEVICE_PROTECTED: <类别>: <目标>` 与退出码 77,并写审计日志(设置页可见)。两道防线:`DeviceIntegrityPolicy` 在执行前按命令词判定(含 `sh -c` / `su -c` / `env` / `find -exec` 解包、`cd` 后的相对路径、`..`/`//`/大小写/`/proc/*/root` 归一化);`ProtectedView` 让命令在一次性 mount namespace 里运行(受保护目录只读 bind、`/dev/block` 换成空 `nodev` tmpfs、`su` 路径换成桩、bounding set 去掉 `CAP_SYS_ADMIN`/`CAP_MKNOD`/`CAP_SYS_MODULE`/`CAP_SYS_RAWIO`),变量、`$(…)`、脚本文件里的目标由它兜底。视图建不起来(缺 `unshare` 或 rootfs 里的 `setpriv`)时命令不执行,返回 `DEVICE_PROTECTION_UNAVAILABLE`,失败关闭。分区读取走单独的只读导出命令 `android-root-cli partition read`,由 App 构造 `dd`,不经视图。存储水位(`/data` 剩余低于 `max(3 GB, 5%)`):冻结 guest shell 进程组、拒绝新的批量写命令(`cp`/`dd`/`tar`/`unzip` 等,不含 `rm`),发通知,空间恢复 25% 余量后自动继续,不删除任何东西。App 自己构造的基础设施(`DirectRootRunner`:rootfs、namespace、bind、网络 helper、无障碍修复)不经此处。不防刻意绕过:原始 `service call` 直接发 binder 事务不受前端检查。
- 虚拟屏的 root 服务(`VirtualScreenRootService`,由 libsu 经 `su` 以 `app_process` 拉起)是专用 binder 接口:只暴露 `IVirtualScreenService` 里固定的虚拟屏操作(建屏、启动应用、点按输入、截图、帧流),不提供命令、文件或通用 RPC,也不监听网络或 socket;binder 只交回本 App。
- Guest shell 进入 chroot 后必须通过 `setpriv` 切到真实 App UID/GID,清空 supplementary groups 与 Linux capabilities。
- Root-provider identity 只是可用性信号;uid=0 不代表 SELinux、mount、namespace 或 capability 一定允许操作。
Expand Down
21 changes: 20 additions & 1 deletion docs/contracts/06-CURRENT-GAPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ config registry 暴露为 `prompt.custom`(带确认与审计回退)。内置

已知缺口:

- 设备完整性保护(Issue #182)尚未实现:`exec`、`file rm -r`、`package uninstall` 等目前没有任何保护线。
- 设备完整性保护(Issue #182)已实现(见 04 安全合同),但见下一节:全部未在真机验证。

已接受的设计(不是缺口):

Expand All @@ -236,3 +236,22 @@ config registry 暴露为 `prompt.custom`(带确认与审计回退)。内置
## PR4 — 下个版本移除旧模型组 Room 表(暂缓)

PR2/PR3 只把运行配置迁移到固定 Model Slots,并保留旧表作为迁移/回滚兼容边界;本阶段不删 `provider_model_groups` 表、不升级 Room schema。后续 PR4 才能在数据升级路径和真机备份恢复验收完成后删除该表并将 Room schema 从当前 v4 升至 v5。

## 设备完整性保护(Issue #182,2026-10-10)

只有 JVM 单测、编译和 lint 的证据;`ProtectedView` 生成的脚本、mount namespace、能力裁剪、存储水位冻结都**没有在真机上跑过**,不得据此声称设备结论。需要在小米 24129PN74C / HyperOS(KernelSU)上确认:

- `unshare -m` 在 `su` 起的进程里可用,`mount -o rprivate,bind / /`、`/dev/block` 换 tmpfs、对 `/data/adb`、`/apex` 等目录的只读 bind 与 `remount,bind,ro` 都能成功;任何一步失败会让特权命令返回 `DEVICE_PROTECTION_UNAVAILABLE`(失败关闭);
- rootfs 里的 `setpriv` 经 `ld-linux-aarch64.so.1 --library-path` 在 chroot 之外能运行,`--bounding-set` 去掉 `CAP_SYS_ADMIN` 后 `pm`/`cmd`/`settings`/`am`/`dumpsys`/`input`/`wm`/`appops` 与现有 `android-root-cli` 子命令没有回归;
- 视图里 `mount -o remount,rw /system`、`dd of=/dev/block/…`、`rm -rf /data/system` 确实失败,`su` 桩生效;
- KernelSU 的 `/data/adb/ksu/bin/su` 被桩覆盖后,视图外的 App 自身 `su` 不受影响;
- 存储水位:guest 写大文件逼近阈值时自动冻结、发通知,清理后恢复;冻结只作用于 `shells/shell-*.pid` 记录的进程组;
- 小米补充名单(`com.miui.securitycenter`、`com.lbe.security.miui`、`com.miui.home`、`com.xiaomi.xmsf`、`com.miui.system`)需真机核对,其余 OEM 没有补充名单。

已知残余:

- 原始 `service call` 直接发 binder 事务,不经 `pm`/`cmd`/`settings`/`am` 前端,绕过核心包和设备状态检查。本保护防的是 Agent 手滑,不防刻意绕过。
- 命令词判定看不穿变量、`$(…)`、脚本文件;这类只靠视图兜底,且视图只管路径类(分区、系统分区、`/data` 系统数据),管不到 binder 类(核心包、恢复出厂、用户 0)。
- `/data/adb/minis` 因例外保持可写,`rm -rf` 它会毁掉 rootfs(可重装),不会让手机变砖。
- `/apex`、`/system` 等目录在系统挂载为只读的设备上,视图的只读 bind 是第二道保险,不是第一道。
- 分区读取(`partition read`)在视图之外以 root 执行,只读块设备,目标文件走与其它命令相同的写路径检查。
2 changes: 2 additions & 0 deletions src/android/app/src/main/java/com/openminis/app/MinisApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,8 @@ class MinisApp : Application(), ImageLoaderFactory {
// the process. Mirrors iOS ConfigRegistry.shared.registerBuiltinsIfNeeded().
com.openminis.app.config.MinisConfigPermissionStore.init(this)
com.openminis.app.config.AutonomyStore.init(this)
com.openminis.app.integrity.DeviceProtectionStore.init(this)
com.openminis.app.integrity.StorageGuard.start(this)
com.openminis.app.config.audit.ConfigAuditLog.init(this)
com.openminis.app.config.ConfigRegistry.init(
this, providerRepository, envVarRepository, chatRepository,
Expand Down
Loading