-
Notifications
You must be signed in to change notification settings - Fork 2
Chaos Engineering
AceGuru-mjh edited this page Oct 1, 2026
·
4 revisions
📑 本页目录
本项目有两次"混沌工程式"全仓审查(2026-02 评审、2026-09 审计),共落地 24 处修复。 这些是最值得读的"为什么这里这么写"清单。来源:
docs/chaos-engineering-review.md、docs/CHAOS_AUDIT_2026-09.md。
| 维度 | 内容 |
|---|---|
| 范围 | 全仓库 20 个 Gradle 模块(含 ComposeFoundry) |
| 方法 | 全量 grep + 逐行人工核对(行号基于 main@c3176ed) |
| 三维 | ① 生命周期竞态 ② 数据一致性与边界值陷阱 ③ 隐式依赖与版本兼容性 |
| 规模 | 评审 12 项(P0×4 / P1×4 / P2×4);审计落地 24 处修复,全部经 CI 同款 kotlinc 2.0.21 验证 |
| # | 问题 | 机制 | 修复 |
|---|---|---|---|
| P0-1 |
VIBRATE 权限缺失 → Agent 进 WAITING_HUMAN 时 triggerVibration() 在 mainHandler.post 抛 SecurityException,Android 8~15 必现进程崩溃
|
Manifest 未声明权限 + vibrator.vibrate(VibrationEffect.createOneShot(150, DEFAULT_AMPLITUDE)) 无 try/catch |
补权限 + ContextCompat.checkSelfPermission 自防御 + runCatching(防 ODM ROM 二次限制) |
| P0-2 |
压缩切在 tool_call / result 之间 → 保留窗首段出现无主 role:"tool" → OpenAI 兼容端点 400;且压缩后的损坏历史被 memory.save() 持久化 → 此后每轮都 400,只能清空对话
|
SlidingWindowCompressor 的 preserveStart 纯按条数硬切 |
while (preserveStart > systemEnd && history[preserveStart] is LlmMessage.ToolResult) preserveStart-- |
| P0-3 |
截断器反向膨胀:设置 Max Tool Output Length = 200(合法范围 200..100_000)时,head 1200 + tail 600 大于 maxChars → 250 字符输出变成 ≈2×原文,省略计数为负 [... -1550 chars omitted ...]
|
ToolOutputTruncator 未收敛 head/tail |
safeHead = minOf(headChars, (maxChars*2/3).coerceAtLeast(1))、safeTail = minOf(tailChars, (maxChars - safeHead).coerceAtLeast(0))、计数 coerceAtLeast(0)
|
| P0-4(审计) |
线程契约违规:BrowserEngine.respondFileChooser 在 Dispatchers.IO 读写 WebView ValueCallback,而 onShowFileChooser 主线程写 → Chromium 主线程契约违反(native crash 或回调丢失死锁) |
ToolRegistry flowOn(Dispatchers.IO) + 主线程写入 |
改 suspend fun + withContext(Dispatchers.Main)
|
| P0-4(评审) | 预拷贝孤儿协程:每次 new CoroutineScope(Dispatchers.IO),Job 无人持有;50MB 附件选后立即移除,拷贝仍跑完;重选同文件 → 两路并发交错写同一目录 |
— |
PreprocessResult(..., job: Job?) + cancel(uri) + 进行中条目 putIfAbsent 登记复用 |
| 问题 | 现象 | 修复 |
|---|---|---|
TerminalCore mutation 列表无界 |
每字符追加一条 ScreenMutation 且 drainMutations() 全仓库无消费者(cat 50MB ≈ 5000 万对象) |
有界累加器 BoundedMutationList(capacity = 4096),超限折叠为 ScreenMutation.FULL
|
VtParser CSI 参数无上限 |
畸形流永不出现 final byte → 无限累积 |
appendCsiParam/appendCsiIntermediate,超 MAX_CSI_BUFFER_LENGTH = 4096 转 CSI_IGNORE
|
TerminalEventLogImpl append-only |
每 session 永久泄漏 | 超 MAX_EVENTS_PER_SESSION = 10_000 批量裁剪最旧 1_000 条(均摊 O(1)) |
其他 P1:
| 问题 | 修复 |
|---|---|
三处单例内 CoroutineScope(Dispatchers.Main).launch(连点 N 次 → N 个无主协程抢 stateMutex) |
统一 mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
LogViewerScreen 导出捕获 Activity context |
改 context.applicationContext
|
resume/retry/crash 三路径覆盖 currentJob 未先 cancel()
|
补 cancel()
|
RingTerminalBuffer writePos.get().toInt() 越过 2³¹ 回绕为负 |
minOf(writePos.get(), capacityBytes.toLong()).toInt()(先 Long 域再收窄) |
CyberNeonBallManager.startPulse() 每次 new ObjectAnimator(INFINITE) 不保存引用 |
持 pulseAnimator 引用并 cancel()
|
ApexAccessibilityService 心跳在 Main 线程每 30s 做 binder IPC,无异常处理器 |
移 Dispatchers.Default + CoroutineExceptionHandler + runCatching
|
Important
MemoryGraphStoreImpl 用 Room 的 IN (:fingerprints) 一次性传整棵 UI 树的指纹。
复杂页面 >999 节点 时,Android 8–10 的 SQLITE_MAX_VARIABLE_NUMBER = 999 触发
SQLiteException: too many SQL variables —— 被 MemoryWriterActor 吞掉 →
该帧记忆全部静默丢失。
修复:chunked(500).flatMap { dao.getByFingerprints(it) }。
VT 侧同类边界:
| 输入 | 后果 | 修复 |
|---|---|---|
\e[2147483647B |
cursorRow + n 回绕为负 → ArrayIndexOutOfBounds
|
参数 coerceIn(0, rows/cols)、结果 coerceIn(0, rows-1)
|
\e[999999999S |
repeat(n) 无上限 → ANR |
clamp 到 rows |
\e[38;5;2147483647m |
灰度分支 (index-232)*10+8 Int 溢出
|
统一 coerceIn(0, 255)
|
| 问题 | 机制 | 修复 |
|---|---|---|
| 本地模型永远连不上 | 内置 Ollama / LM Studio / vLLM 预设都是 http://localhost:*,但无 networkSecurityConfig,targetSdk ≥ 28 默认禁明文 → Cleartext HTTP traffic not permitted
|
新增 res/xml/network_security_config.xml,仅对 localhost / 127.0.0.1 / 10.0.2.2 / 10.0.3.2 放行明文,云端 https 不受影响 |
| GitHub token 永久明文 |
allowBackup="true" → 云备份恢复后 Tink keyset 与硬件绑定 MasterKey 不匹配 → EncryptedSharedPreferences.create 抛 AEADBadTagException → catch 静默降级为明文 SP |
data_extraction_rules.xml(API 31+)+ legacy_backup_rules.xml(API ≤30)排除两份 prefs |
| 非流式响应解析 | 网关返回 200+HTML → SerializationException 绕过 LlmException,ErrorClassifier 失效;jsonPrimitive.int 对 128.0 抛 NumberFormatException
|
intOrNull + 归一化 LlmException.Http(-1, "unparseable response body: ${body.take(200)}")
|
| Android 13(API 33)全新安装 |
POST_NOTIFICATIONS 默认拒绝 → 前台服务通知静默不可见 |
launcher.launch(Manifest.permission.POST_NOTIFICATIONS) |
| Shizuku 版本不一致 | app 硬编码 dev.rikka.shizuku:api:13.1.0 vs catalog 13.1.5,Gradle 静默取高 → 移除 privilege 依赖会无声降级(api/provider 版本不一致 → binder 协议不匹配) |
统一 implementation(libs.shizuku.api) + libs.shizuku.provider
|
| Shizuku 未安装兜底 |
startActivity(ACTION_VIEW) 无 ActivityNotFoundException 防护 |
补防护 |
同毫秒文件名覆盖 / 未过滤 /
|
File(targetDir, "${System.currentTimeMillis()}_$fileName") |
加 uri.hashCode() + substringAfterLast('/') + 兜底名 |
- Flow 收集全部
collectAsStateWithLifecycle(0 处裸collectAsState()—— 审计后修); - PendingIntent 仅 1 处且带
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT; - API 34 前台服务
specialUse+FOREGROUND_SERVICE_SPECIAL_USE+PROPERTY_SPECIAL_USE_FGS_SUBTYPE齐全; -
java.time/java.nio.file在 minSdk 26 可用,无需 desugaring; - RootfsDownloader / RootfsExtractor:三态续传 +
fd.sync+ tar 校验和 + 路径穿越防护已加固; - okhttp 4.12.0(无 CVE-2023-3635)/ Coil 2.6.0 / Compose BOM 2024.12.01 稳定;
-
全项目纯 Compose(
grep Fragment零命中)→ Fragment-not-attached 崩溃面不存在。
| 风险 | 建议 |
|---|---|
BrowserEngine.onRenderProcessGone 会销毁正被浮窗承载的 WebView |
需引擎 ↔ 浮窗 onWebViewDestroyed 通知协议(独立 PR) |
dependencyLocking { lockAllConfigurations() } 无 lockfile
|
锁形同虚设,建议 ./gradlew :app:dependencies --write-locks 并提交 app/gradle.lockfile
|
security-crypto 1.1.0-alpha06 |
非稳定版且静默明文兜底,建议 Keystore + AES-GCM |
browser/BrowserAgentTools 用废弃 getExternalStoragePublicDirectory
|
迁移到 MediaStore / 应用目录 |
TerminalViewModel 终端屏幕流无可见性降级 |
建议 LifecycleResumeEffect
|
ApexAccessibilityService.instance 静态引用 TOCTOU |
建议 activeInstance()(AtomicBoolean bound 标志) |
adb install -r app-debug.apk
adb shell am start -n com.apex.agent/.MainActivity
adb shell am start-foreground-service -n com.apex.agent/.service.ApexCoreService
# 驱动到 WAITING_HUMAN 状态
adb logcat -d | grep -E "SecurityException|Requires VIBRATE"
# 修复验证
adb shell dumpsys package com.apex.agent | grep VIBRATE # 期望 granted=true本项目反复出现的纪律(跨 T81/T82/MIGRATION/本审计一致): 本地验证的写本地验证,无法验证的明确标 NOT VERIFIED,绝不写假 PASS。
- 进程死亡用确定性模拟
TaskRuntime.simulateCrash(),不声称真机 E2E; - androidTest 27 用例是 compile-only,真机从未执行(如实声明);
- 审计报告里"CI 从不编译该模块测试"导致死测试套件掩埋 2 个 bug,也如实记录。
-
MCP 生态总览
新 - (沙箱 MCP · 官方 Hub · 逆向 Host · 门控语义)
- 终端运行时
- 终端 API 契约
- SDK 边界
- Termux 能力矩阵
- Ubuntu rootfs 供给
- Ubuntu 生命周期
- PRoot 二进制溯源
- VT100/ANSI 模拟器
- 终端性能
- 终端迁移
- 原生层 C++/JNI