
【Bug已解决】macOS detects Codex Computer Use.app as malware and deletes it! 解决方案原始报错macOS detects Codex Computer Use.app as malware and deletes it! 场景用户下载并打开Codex Computer Use.appmacOS 的 Gatekeeper / XProtect 把它判定为恶意软件直接删除或 quarantine 后阻止运行并提示已移动至废纸篓。应用根本起不来。 关键词macOS 应用包、Gatekeeper、XProtect、代码签名、公证notarization、隔离属性quarantine、应用包结构。一、现象长什么样用户操作从网页/渠道拿到Codex Computer Use.app一个.appbundle双击打开macOS 弹出无法打开因为 Apple 无法检查其是否包含恶意软件或直接由 XProtect 删掉应用消失在废纸篓无法运行用codesign --verify检查发现签名无效或不完整或根本没签名。对最终用户来说这就是我的 Mac 把应用当病毒删了。对开发者来说根因几乎都在签名/公证链条不完整或隔离属性没清掉。二、背景macOS 怎么判定一个 .app 是否安全macOS 对从网络下载的应用有多道防线针对的是.appbundle 整体代码签名Code Signing.app/Contents/MacOS/里的可执行文件、框架、资源都要用开发者证书签名且签名要覆盖整个 bundle含Contents/_CodeSignature/CodeResources清单。公证Notarization开发者把 app 上传 Apple 扫描拿到ticket用xcrun stapler staple把 ticket 钉进 app。用户打开时 Gatekeeper 校验 ticket。隔离属性quarantine从浏览器下载的 app 会被打上com.apple.quarantine扩展属性首次打开必经 Gatekeeper 检查。XProtect系统级恶意软件特征库命中即删。.appbundle 比单个 CLI 二进制复杂它是一个目录树签名必须递归覆盖每一层可执行文件、dylib、嵌套 framework、资源任何一层漏签或哈希不符整个 bundle 的校验就失败。三、根因签名/公证链有缺口根因拆解针对 app bundle整体未签名或部分签名只签了主可执行文件没签嵌套的 dylib / frameworkbundle 校验失败。未公证有签名但没上传 Apple 拿 ticketGatekeeper 在较新 macOS 上直接拦。quarantine 未清用户用curl/浏览器下载后没清com.apple.quarantine打开即触发检查。entitlements 不匹配app 需要的权限如屏幕录制、辅助功能Computer Use 类应用常需在签名时没声明运行时被拒或特征异常引 XProtect 误判。签名在打包后破损签名后又改了 bundle 内文件改 Info.plist、替换资源签名失效。下面用最小模型复现带 quarantine 且签名无效导致校验失败再给修复。四、最小可运行复现校验逻辑模拟用 Python 模拟校验一个 app bundle检查签名清单是否完整、是否带 quarantine。import hashlib, os, json class AppBundleVerifier: def __init__(self, bundle_root): self.root bundle_root def has_quarantine(self) - bool: # 模拟读取 com.apple.quarantine 扩展属性 qp os.path.join(self.root, .quarantine) return os.path.exists(qp) def signature_intact(self, code_resources: dict, actual_files: list) - bool: # code_resources 是签名时记录的哈希清单 for rel in actual_files: recorded code_resources.get(rel) if recorded is None: return False # 清单没覆盖该文件 - 签名不完整 live hashlib.sha256( open(os.path.join(self.root, rel), rb).read()).hexdigest()[:8] if live ! recorded: return False # 哈希不符 - 签名破损 return True if __name__ __main__: # 假设某文件不在签名清单里 verifier AppBundleVerifier(/tmp/Codex.app) code_resources {Contents/MacOS/Codex: abc123} # 漏了嵌套 framework actual [Contents/MacOS/Codex, Contents/Frameworks/Helper.framework/Helper] print(签名完整?, verifier.signature_intact(code_resources, actual)) # False运行输出False——嵌套 framework 没进签名清单bundle 校验失败这正是 Gatekeeper 拒绝/删除的根因之一。五、方案递归签名整个 app bundle第一层签名必须覆盖 bundle 内每一层。用codesign --deep --force --sign递归签所有可执行组件真实命令注释说明不在 Python 内编造import subprocess, shlex def sign_app_bundle(bundle_path: str, identity: str) - int: 递归签名整个 .app bundle。真实调用 codesign。 cmd [ codesign, --deep, --force, --sign, identity, bundle_path, ] print(执行:, shlex.join(cmd)) # 真实环境运行 return subprocess.call(cmd) def verify_app(bundle_path: str) - int: 校验签名。真实调用 codesign --verify --verbose。 cmd [codesign, --verify, --verbose2, bundle_path] print(执行:, shlex.join(cmd)) return subprocess.call(cmd) if __name__ __main__: # 演示调用不会真跑需真实证书与 app print(签名命令示例已生成实际需有效的 Developer ID Application 证书) sign_app_bundle(/Applications/Codex Computer Use.app, Developer ID Application: Acme) verify_app(/Applications/Codex Computer Use.app)--deep保证嵌套的 framework / dylib 都被签消除清单漏项。六、方案公证并 staple ticket第二层签名后上传 Apple 公证拿到 ticket 钉进 app让用户离线也能过 Gatekeeperdef notarize_and_staple(bundle_path: str, keychain_profile: str) - int: # 1) 上传公证 submit [ xcrun, notarytool, submit, bundle_path, --keychain-profile, keychain_profile, --wait, ] rc subprocess.call(submit) if rc ! 0: return rc # 2) 把 ticket 钉进 appstapler staple [xcrun, stapler, staple, bundle_path] return subprocess.call(staple) if __name__ __main__: print(公证流程notarytool submit - stapler staple) notarize_and_staple(/Applications/Codex Computer Use.app, my-profile)stapler 把 ticket 嵌入 app 内部用户首次打开无需联网查 AppleGatekeeper 直接放行。七、方案清除隔离属性 校验 entitlements第三层分发时清掉 quarantine或指导用户用xattr -dr清并确保 entitlements 覆盖应用所需权限Computer Use 类常需屏幕录制等def remove_quarantine(bundle_path: str) - int: 清除 com.apple.quarantine 扩展属性。 cmd [xattr, -dr, com.apple.quarantine, bundle_path] print(执行:, shlex.join(cmd)) return subprocess.call(cmd) def check_entitlements(bundle_path: str, required: list) - list: 检查 app 的 entitlements 是否声明了所需权限。 out subprocess.run( [codesign, -d, --entitlements, :-, bundle_path], capture_outputTrue, textTrue, ) missing [e for e in required if e not in out.stdout] return missing if __name__ __main__: remove_quarantine(/Applications/Codex Computer Use.app) needed [ com.apple.security.cs.allow-jit, com.apple.security.screen-capture, # 屏幕类应用常需 ] missing check_entitlements(/Applications/Codex Computer Use.app, needed) print(缺失的 entitlements:, missing)清 quarantine 让首次打开不再触发 Gatekeeper 拦entitlements 检查确保运行时权限与签名一致避免异常行为招来 XProtect 误判。八、验证本地校验脚本交付前自检def self_check(bundle_path: str) - dict: import os report {} # 1. quarantine 应已清 report[quarantine_cleared] not os.path.exists( os.path.join(bundle_path, .quarantine)) # 2. 签名可校验 report[signature_ok] (verify_app(bundle_path) 0) # 3. entitlements 覆盖所需 report[entitlements_missing] check_entitlements( bundle_path, [com.apple.security.screen-capture]) return report if __name__ __main__: rep self_check(/Applications/Codex Computer Use.app) print(交付前自检:, rep) # 全绿才发布签名 ok quarantine 清 entitlements 齐把self_check放进发布流水线签名/公证/entitlements 任一缺失都不发布从源头杜绝用户下载即被删。九、排查清单.app 被当恶意软件删除按顺序查签名完整性bundle 内每一层可执行、dylib、framework、资源是否都签了codesign -vvv是否通过公证是否上传 Apple 拿到 ticket 并stapler staple新 macOS 无 ticket 会被拦。quarantine从网络下载后com.apple.quarantine是否清除entitlements应用所需权限屏幕录制/辅助功能等是否在签名时声明打包后改动签名后是否又改过 bundle 内文件改动会废掉签名。XProtect 误判是否因行为异常如缺权限导致古怪行为触发特征库补全 entitlements 通常缓解。证书有效用的 Developer ID 证书是否仍在有效期内、未被吊销十、小结macOS 把 .app 当恶意软件删除是签名/公证/隔离属性链条有缺口导致的 Gatekeeper/XProtect 拦截。区别于单文件二进制.app是目录树签名必须递归覆盖每一层。修复三层递归签名codesign --deep覆盖可执行、dylib、framework、资源清单不漏项公证 staple上传 Apple 拿 ticket 并钉进 app离线可过 Gatekeeper清 quarantine 齐 entitlements分发时清隔离属性签名时声明应用所需权限。核心原则.app的安全校验看的是整个 bundle 的完整性不是主可执行文件一个。递归签名、公证、正确的 entitlements三者齐备用户下载打开才不会被系统当成恶意软件删掉。