【Bug已解决】deny-read permission profile silently ineffective on native Windows (ACL state empty) — 0.142.5 解决方案原始报错deny-read permission profile silently ineffective on native Windows (ACL state empty) — 0.142.5 场景用户配置了一条 deny-read禁止读取某些路径权限规则。在类 Unix 平台生效但在原生 Windows 上这条规则静默失效——既不拦截读取也不报错读取被直接放行。排查发现底层 ACL 查询返回了空状态。版本 0.142.5。 关键词权限静默失效、跨平台 ACL、fail-closed、平台能力检测、默认拒绝、可见告警。一、现象长什么样用户配了 deny-read 规则明确禁止应用读取C:\secrets\下任何文件。现象在 macOS / Linux 上应用读secrets/被拦截符合预期在原生 Windows 上应用照样读到了C:\secrets\里的文件规则像不存在没有任何报错、没有任何日志说明规则未生效开启调试后发现负责查询 Windows ACL 的接口返回了空 ACL 状态而代码把空当成无限制放行了。最危险的地方是静默——用户以为自己被保护了实际没有。这类 bug 比直接报错危害更大因为它制造了虚假的安全感。二、背景deny-read 为什么依赖平台 ACLdeny-read 这类细粒度文件权限在类 Unix 上可以靠路径前缀匹配 用户态检查完成但在 Windows 上文件访问权限最终由ACL访问控制列表决定应用若要代系统执行 deny往往需要查询目标对象的 ACL 来判断是否应当拦截。当底层 ACL 查询因为某种原因权限不足、对象类型特殊、API 调用失败返回空状态时代码面临一个二选一把空当成无限制→ 放行错误且危险本 bug 就是这一种把空当成未知/不确定→ 拒绝并告警fail-closed安全。正确的安全工程原则是涉及拒绝/安全边界的决策不确定时一律收紧默认拒绝绝不该默认放行。三、根因空 ACL 状态被误判为允许根因拆解空允许的错误映射if acl_state is empty: allow把查不到当成没限制。静默放行决定放行后不打日志用户无从知晓规则没生效。平台能力没检测Windows 原生后端没声明ACL 查询可能返回空/不支持上层不知道该后端不可靠。无 fail-closed不确定性边界走了 open 而不是 closed。版本回归0.142.5 引入了 ACL 查询路径但遗漏了对空状态的兜底。下面用最小模型复现空 ACL → 静默放行再给修复。四、最小可运行复现class WindowsAclBackend: def query(self, path: str): # 模拟在原生 Windows 上 ACL 查询返回空状态 return {state: empty} # 不是 allow也不是 deny是查不到 class PermissionChecker: def __init__(self, backend): self.backend backend def is_denied(self, path: str) - bool: acl self.backend.query(path) if acl[state] deny: return True if acl[state] allow: return False # 错误空状态被当成不拒绝 - 放行 return False if __name__ __main__: checker PermissionChecker(WindowsAclBackend()) blocked checker.is_denied(rC:\secrets\key.txt) print(被 deny-read 拦截?, blocked) # False静默放行运行输出False——规则完全没生效且没人被告知。这就是报错的复现。五、方案fail-closed空/未知一律拒绝第一层也是最关键的一层任何无法确认允许的状态空、查询失败、不支持都按拒绝处理。安全边界默认收紧。class PermissionCheckerSafe: def __init__(self, backend): self.backend backend self.warnings [] def is_denied(self, path: str) - bool: acl self.backend.query(path) state acl.get(state) if state deny: return True if state allow: return False # fail-closed空 / 未知 / 查询异常 - 视为拒绝 self.warnings.append(fACL 状态未知({state})按 deny-read 拒绝: {path}) return True if __name__ __main__: checker PermissionCheckerSafe(WindowsAclBackend()) blocked checker.is_denied(rC:\secrets\key.txt) print(被拦截?, blocked) # True安全兜底 print(告警:, checker.warnings) # 说明了为何拒绝现在即便 ACL 查不到读取也被拦下且留下告警——用户至少能看到规则生效了且因为查询不确定而收紧。六、方案平台能力检测对不可靠后端明确降级第二层在初始化时就探测后端能力不可靠的平台/状态要明确标记避免假装规则有效。class BackendCapability: def __init__(self, backend): self.backend backend self.reliable self._probe() def _probe(self) - bool: # 用一个已知路径探测 ACL 查询是否返回有效状态 sample self.backend.query(__probe__) return sample.get(state) in (allow, deny) class PermissionEngine: def __init__(self, checker: PermissionCheckerSafe, cap: BackendCapability): self.checker checker self.cap cap def evaluate(self, path: str) - dict: if not self.cap.reliable: # 后端不可靠规则无法精确生效按最严格处理并提示 denied True note ACL 后端不可靠deny-read 已按最严格模式生效全部拒绝 return {denied: denied, note: note} return {denied: self.checker.is_denied(path), note: } if __name__ __main__: backend WindowsAclBackend() checker PermissionCheckerSafe(backend) cap BackendCapability(backend) engine PermissionEngine(checker, cap) print(engine.evaluate(rC:\secrets\key.txt)) # {denied: True, note: ACL 后端不可靠...}能力检测让这个平台规则到底可不可信成为显式状态而不是藏在偶然的返回里。七、方案静默失效必须可见——告警与遥测第三层安全规则的失效绝不能静默。无论是 fail-closed 还是后端不可靠都要产生可见信号日志/状态面板/遥测让运维和用户知道规则以收紧方式生效了。import logging logger logging.getLogger(perms) class ObservablePermission: def __init__(self, engine: PermissionEngine): self.engine engine def check(self, path: str) - bool: result self.engine.evaluate(path) if result[note]: # 关键可见不静默 logger.warning(deny-read 规则以非精确模式生效: %s | %s, path, result[note]) return result[denied] if __name__ __main__: logging.basicConfig(levellogging.WARNING) backend WindowsAclBackend() checker PermissionCheckerSafe(backend) cap BackendCapability(backend) engine PermissionEngine(checker, cap) obs ObservablePermission(engine) denied obs.check(rC:\secrets\key.txt) print(读取被拒绝:, denied)把规则非精确生效作为 warning 输出排查时一眼可见杜绝以为有保护其实没有。八、验证把fail-closed 可见锁进测试def test_empty_acl_is_denied_fail_closed(): checker PermissionCheckerSafe(WindowsAclBackend()) assert checker.is_denied(rC:\secrets\x) is True assert len(checker.warnings) 1 def test_unreliable_backend_forces_strict(): backend WindowsAclBackend() checker PermissionCheckerSafe(backend) cap BackendCapability(backend) engine PermissionEngine(checker, cap) res engine.evaluate(rC:\secrets\x) assert res[denied] is True assert 不可靠 in res[note] if __name__ __main__: test_empty_acl_is_denied_fail_closed() test_unreliable_backend_forces_strict() print(deny-read fail-closed 测试通过。)九、排查清单权限静默失效按顺序查决策映射空/未知/查询失败状态是被当成允许还是拒绝安全边界必须默认拒绝。静默问题规则没生效时有没有日志/告警静默放行是最高危。平台差异该规则依赖的平台机制如 Windows ACL在当前后端是否真可用能力探测初始化时是否探测后端可靠性不可靠是否显式标记fail-closed涉及拒绝的决策不确定时是否收紧而非放开版本回归最近是否引入了底层查询路径是否缺对异常状态的兜底可观测安全规则的生效/降级状态能否在面板或日志中看到十、小结deny-read 在 Windows 静默失效是把 ACL 空状态误判为允许 全程静默导致的虚假安全感。修复三层fail-closed任何无法确认允许的状态空/未知/异常一律按拒绝安全边界默认收紧能力检测初始化即探测后端可靠性不可靠者显式标记并走最严格模式可见性规则非精确生效必须产生告警/日志绝不静默。核心原则涉及拒绝与安全的决策不确定性永远向收紧倾斜并且必须让用户看见。静默失效比报错更危险——它让保护在用户不知情时消失。