Repository navigation
feat(integrity): 设备完整性保护 — Agent 的 Root 不能把手机弄坏 (#182) - #186
Merged
Merged
Conversation
…reen on a libsu root service Root is the app's one privileged path. The code before this change is archived on the branch archive/before-root-only (6a6b450); the rootless PRoot + Ubuntu backend is shelved as #184. - android-shizuku-cli becomes android-root-cli: the same subcommands and JSON, run as root through RootProcess -> DirectRootRunner (su). The shizuku_cli permission key migrates to root_cli; a stale android-shizuku-cli wrapper in an existing rootfs is removed on the next guest CLI install. - The virtual screen service is hosted by a libsu RootService (VirtualScreenRootService, daemon mode) instead of a Shizuku UserService, and accepts uid 0 only. The Shizuku-reserved destroy() leaves the AIDL; libsu ends the daemon on app update, so no service version is needed. Probes saved under the Shizuku host no longer count (host marker in the fingerprint): the self-check must pass again before it is re-enabled. - Accessibility repair, clearing restricted settings and one-tap authorize use Root. Settings > System gets a Root page in place of "Root & Shizuku"; the readiness checklist drops Shizuku. - Removed: ShizukuManager/ShizukuBackend, the Shizuku settings page, the Shizuku API/provider dependency, manifest provider/permission/queries, and every Shizuku string in all eight locales (new strings translated). - Contracts: AGENTS.md rule 3 and 04 list android-root-cli (with its raw `exec` root shell) and the virtual-screen root service; 05 describes the new VScreen host; 06 records what is unverified on a device and two gaps (MCP can reach android-root-cli through linux.shell; #182 not built). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3
…design The remote MCP path stays gated by the one-shot MCP_CONFIRM approval, which AutonomyMode (全自动) does not bypass, and by the default-off root_cli switch. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3
Refuses only what can leave the phone unbootable or unusable: block-device and system-partition writes, /data system state, core packages, factory reset and user 0 removal. Path tricks (case, ./.., //, /proc/*/root) and sh -c / su -c / env / find -exec wrappers are followed. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3
Agent- and guest-CLI-originated Root commands now pass ProtectedRoot: a pure DeviceIntegrityPolicy refuses what could leave the phone unbootable or unusable (partition and system-partition writes, /data system state, core packages, factory reset, user 0), with a fixed DEVICE_PROTECTED error and no prompt; the command then runs in a ProtectedView (read-only binds, no block nodes, su stub, CAP_SYS_ADMIN/MKNOD/SYS_MODULE/SYS_RAWIO dropped). Settings switch (default on, user-only), audit list, storage watermark that freezes guest shells and refuses bulk writes, and a read-only partition export. Device behaviour is unverified. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3
…ge notice Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3
Owner
Author
|
Android debug lint 在 e6c1c83 上第一次失败,原因是 CI 机器安装 NDK 28.2.13676358 时压缩包损坏("Error on ZipFile unknown archive"),与本 PR 无关(本 PR 的树与上一个通过的头 09f29a2 完全相同)。已重跑一次,全部检查通过。 Generated by Claude Code |
Slacker-LLC
pushed a commit
that referenced
this pull request
Oct 10, 2026
#186, #188 and #189 landed on main. The merge was clean (CHANGELOG, 06-CURRENT-GAPS and the string files only had adjacent additions); CI on this head tests the combination. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #182。叠在 #185 之上(base 为
feat/root-only-drop-shizuku),#185 合并后改 base 到main。做了什么
目标只有一个:本地 Agent 通过
root.shell、android-root-cli、Android 系统工具拿到的 Root,不能把手机弄到无法开机或无法使用。不弹窗、不让用户逐条确认,命中就直接拒绝,错误固定为:两层:
DeviceIntegrityPolicy,纯 Kotlin):对 argv / 脚本做检查,给出清晰的拒绝原因,主机上可测。/dev/block、/dev/mem、/dev/kmem、/system、/system_ext、/vendor、/product、/odm、/apex、/data/system*、/data/misc*、/data/vendor*、/data/property、/data/apex、/data/unencrypted、/data/adb(minis 自己的目录除外)、/metadata、/cache/recovery。/、/data、/data/data、/data/user、/data/user_de、/data/app、/data/media这些节点本身只拒绝破坏性操作。sh -c/su -c/env/busybox/timeout/nice等包装;跟踪cd;find -delete/-exec、mountremount、dd of=、cp/mv/ln、块设备工具、am恢复出厂、pm/cmd package的卸载 / 禁用 / 清数据 / 挂起 / 隐藏、remove-user 0、settings put device_provisioned / user_setup_complete。//、.、..、/proc/<pid|self>/root、大小写;glob 展开按保守处理。llc.slacker.minis)+ 设备事实(persistent、android.uid.* 共享 UID、当前桌面、默认输入法)。App 没有QUERY_ALL_PACKAGES,事实由一条固定的只读 Root 脚本取得;取不到时按核心包处理(失败关闭)。ProtectedView):字符串匹配看不穿变量和$(),所以真正的强制在这里。命令在独立 mount namespace 里运行:保护区只读 bind;/dev/block换成空的 nodev tmpfs;su被桩替换(不能再 su 一次绕过);bounding set 去掉sys_admin / mknod / sys_module / sys_rawio。去 capability 用的是 rootfs 里的setpriv,通过ld-linux-aarch64.so.1 --library-path在 chroot 之外执行(toybox 没有 setpriv)。入口覆盖:
PrivilegedCommandRunner(所有 Android 系统工具)、RootShellHandler(root.shell)、RootProcess(android-root-cli全部子命令,含exec;exec走受保护的 raw 路径)。App 自己构造的基础设施动作(rootfs、namespace、bind、chroot、网络 helper、无障碍修复)仍直接走DirectRootRunner,不经过策略。存储水位(
StorageGuard):/data剩余空间低于max(3 GB, 5%)时,冻结 guest shell 进程组(SIGSTOP),拒绝新的批量写入命令(cp / dd / tar / unzip / fallocate / rsync / wget / curl 等),发一条通知;回到阈值 +25% 后 SIGCONT 恢复。只暂停写入,从不删除,rm始终可用以便腾空间。分区读取:
android-root-cli partition read(App 构造dd,在保护视图之外,目标路径过写入检查)。读取分区是允许的,写入不允许。开关:设置 → 权限里的「设备保护」,默认开,只有用户能改。和
AutonomyStore一样:没有配置字段、不在备份白名单里、Agent 没有任何工具能碰到它。拒绝记录在设置里的诊断列表(最近 200 条)和 App 日志。已知残余(写入
06-CURRENT-GAPS.md)service call等经 Binder 的操作不在策略里,只有保护视图(capability + 只读挂载)间接限制。$()、eval拼出来的命令,策略层看不见,依赖保护视图兜底。验证
:app:testDebugUnitTest:3531 项,0 失败(LANG=C.UTF-8)。新增 58 项 integrity 测试:DeviceIntegrityPolicyTest、ProtectedRootTest、ProtectedViewTest、StorageGuardTest、RootCorePackageResolverTest。/proc/self/root拒绝;核心包取不到事实时拒绝;保护视图建不起来时 exit 125 而不是退化成不保护运行;开关关闭时才放行;存储水位下批量写拒绝但rm放行。:app:lintDebug:0 error。发现并修复一处MissingPermission(StorageGuard发通知前补上POST_NOTIFICATIONS检查)。:app:minifyReleaseWithR8:通过。未验证(需要真机)
以下全部只在主机单元测试里验证了逻辑,没有在任何真实设备上跑过:
unshare -m、只读 bind、/dev/block覆盖、rootfssetpriv经 glibc loader 的调用。sys_admin之后,android-root-cli里依赖这些能力的合法子命令是否受影响(目前判断不会,但没有实测)。dumpsys package/cmd package resolve-activity在不同 ROM 上输出格式是否与解析器一致;不一致时会按核心包处理,表现为「过度拒绝」而不是「漏拒绝」。/data与StatFs看到的卷是否一致。🤖 Generated with Claude Code
https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3
Generated by Claude Code