【Bug已解决】Codex beta permission restrictions are not disabled after asking for escalation 解决方案

原始报错:Codex beta permission restrictions are not disabled after asking for escalation 场景:应用处于 beta 权限模式,对操作有额外严格限制(比如某些命令被禁)。用户主动点了"提权/升级权限"(escalation),系统提示已提升,但实际执行被限命令时依旧被 beta 限制拦下——提权没有真正解除限制。 关键词:权限状态机、提权、策略重载、权限缓存失效、授权刷新。

一、现象长什么样

操作流程:

  1. 应用处于 beta 模式,安全策略里有一组"beta 限制"(例如禁止直接执行 shell、禁止写系统目录);
  2. 用户遇到一个被拦的操作,弹窗问"是否提升权限来执行",用户点"是"(escalation);
  3. 系统返回一个"权限已提升"的提示;
  4. 用户再次执行同一个操作,依然被 beta 限制拦截,提示"受 beta 权限限制,不允许";
  5. 重启应用后,有时限制消失,有时还在。

用户感知是"提权是假的"。根因是:提权动作更新了某个"权限等级"字段,但没有让已经加载到内存的"beta 限制策略"随之失效重载,拦截逻辑还在用旧策略做判决。

二、背景:权限判决是用"策略快照"还是"实时状态"

权限系统通常分两层:

  • 身份/授权层:当前会话的权限等级(normal / escalated / admin);
  • 策略层:基于等级计算出的"允许/禁止"规则集合。

问题出在两层不同步。很多实现为了性能,会在启动时或首个请求时把"策略"算好缓存起来。当授权层因提权发生变化时,如果没有触发策略层重新计算并替换缓存,判决就会继续用旧策略——即使授权等级早已变高。

这和第 088 篇"套餐降级"、第 097 篇"升级后额度未重置"是同一类"状态变更没让相关缓存失效"的问题,只是这次变的是权限。

三、根因:提权成功但没有失效旧策略

根因拆解:

  1. 策略缓存未失效:提权只改了 self.level = "escalated",但 self.cached_policy 还是按 normal 算的旧规则。
  2. 判决读缓存:拦截器每次都读 cached_policy,不重新根据 level 计算。
  3. 提权响应误导:系统返回"已提升"是基于"等级字段变了",却没验证策略是否真的解除,给用户虚假成功感。
  4. 重启才生效:因为重启会重新走策略计算,所以"重启后有时好了"反而印证是缓存没失效。

下面用最小模型复现第 1、2 类,再给修复。

四、最小可运行复现

class PermissionSystem:
    def __init__(self):
        self.level = "normal"                       # 授权等级
        self._cached_policy = self._compute()       # 启动时缓存策略

    def _compute(self):
        # 按当前 level 计算允许集合
        if self.level == "normal":
            return {"allow_shell": False, "allow_syswrite": False}
        return {"allow_shell": True, "allow_syswrite": True}

    def escalate(self):
        self.level = "escalated"                    # 只改等级,没动缓存
        # 错误:没调用 _compute() 刷新策略

    def check(self, action: str) -> bool:
        key = {"shell": "allow_shell", "syswrite": "allow_syswrite"}[action]
        return self._cached_policy[key]             # 读旧缓存

if __name__ == "__main__":
    p = PermissionSystem()
    print("提权前 shell 允许?", p.check("shell"))    # False
    p.escalate()
    print("提权后 shell 允许?", p.check("shell"))    # 仍 False(bug!)
    print("实际等级:", p.level)                      # escalated(字段变了但策略没变)

运行后等级变了,但 check("shell") 还是 False——提权对实际判决无效,正是报错的复现。

五、方案:提权即重载策略的状态机

第一层:把权限建模成状态机,任何授权状态转移(normal → escalated)都触发策略重算,保证"判决用的策略"永远和"当前等级"一致:

class PermissionSystemV2:
    def __init__(self):
        self.level = "normal"
        self.policy = self._compute()

    def _compute(self):
        if self.level in ("escalated", "admin"):
            return {"allow_shell": True, "allow_syswrite": True}
        return {"allow_shell": False, "allow_syswrite": False}

    def transition(self, new_level: str) -> bool:
        # 状态转移函数:唯一修改等级 + 刷新策略的入口
        if new_level not in ("normal", "escalated", "admin"):
            return False
        if new_level == self.level:
            return True
        self.level = new_level
        self.policy = self._compute()        # 关键:转移即重算
        return True

    def escalate(self):
        return self.transition("escalated")

    def check(self, action: str) -> bool:
        key = {"shell": "allow_shell", "syswrite": "allow_syswrite"}[action]
        return self.policy[key]

if __name__ == "__main__":
    p = PermissionSystemV2()
    print("提权前:", p.check("shell"))        # False
    p.escalate()
    print("提权后:", p.check("shell"))        # True(限制真正解除)

通过把"改等级"和"重算策略"锁进同一个 transition,杜绝两层脱节。

六、方案:判决永远基于实时状态,不读陈旧缓存

第二层:即便有缓存,也要保证判决路径能拿到最新策略。一种做法是缓存带版本号,判决前校验版本;更简单的做法是判决直接基于 level 实时算(策略很轻量时):

class PolicyEngine:
    def decide(self, level: str, action: str) -> bool:
        # 无缓存,永远按当前 level 实时判定
        allow = level in ("escalated", "admin")
        rules = {
            "shell": allow,
            "syswrite": allow,
            "read": True,                  # 读永远允许
        }
        return rules.get(action, False)

class Session:
    def __init__(self):
        self.level = "normal"
        self.engine = PolicyEngine()

    def escalate(self):
        self.level = "escalated"

    def check(self, action: str) -> bool:
        # 每次都拿"当前 level"去判,不依赖任何缓存
        return self.engine.decide(self.level, action)

if __name__ == "__main__":
    s = Session()
    assert s.check("shell") is False
    s.escalate()
    assert s.check("shell") is True
    print("实时判决:提权后 shell =", s.check("shell"))

只要判决函数输入的 level 是最新的,中间是否有缓存都不影响正确性。

七、方案:提权响应要验证策略已生效,并提供回退

第三层:提权接口不应只返回"等级已设",而应在返回前验证关键限制确实解除;同时保留回退能力(用户取消授权时限制恢复):

class EscalationService:
    def __init__(self, session: Session):
        self.session = session

    def request(self, user_confirmed: bool) -> dict:
        if not user_confirmed:
            return {"ok": False, "reason": "用户未确认"}
        self.session.escalate()
        # 验证:提权后原本被拦的操作现在应允许
        verified = self.session.check("shell")
        if not verified:
            # 理论上不应发生;若发生则回退,避免给用户虚假成功
            self.session.level = "normal"
            return {"ok": False, "reason": "策略未生效,已回退"}
        return {"ok": True, "level": self.session.level}

    def revoke(self):
        self.session.level = "normal"

if __name__ == "__main__":
    s = Session()
    svc = EscalationService(s)
    res = svc.request(user_confirmed=True)
    print("提权结果:", res)              # {'ok': True, 'level': 'escalated'}
    svc.revoke()
    print("撤回后 shell 允许?", s.check("shell"))  # False(限制恢复)

验证 + 回退让"提权"成为可信操作:成功即真生效,失败即回退,不会留下"等级高但策略旧"的中间态。

八、验证:把"提权即解除限制、撤回即恢复"锁进测试

def test_escalation_lifts_restriction():
    s = Session(); svc = EscalationService(s)
    assert s.check("shell") is False
    assert svc.request(user_confirmed=True)["ok"] is True
    assert s.check("shell") is True

def test_revoke_restores_restriction():
    s = Session(); svc = EscalationService(s)
    svc.request(user_confirmed=True)
    svc.revoke()
    assert s.check("shell") is False

if __name__ == "__main__":
    test_escalation_lifts_restriction()
    test_revoke_restores_restriction()
    print("提权/撤回权限测试通过。")

九、排查清单("提权后限制还在"按顺序查)

  1. 两层同步:授权等级变了,判决用的策略是否同步刷新?
  2. 缓存失效:提权是否触发了策略缓存失效/重算?还是只改了等级字段?
  3. 判决来源:拦截器读的是实时 level 还是陈旧缓存?
  4. 提权响应:返回"已提升"前是否验证了限制确实解除?还是只看等级字段?
  5. 重启现象:重启后限制消失,强烈暗示是内存缓存未失效。
  6. 回退路径:用户撤回授权时,限制能否恢复?有没有残留 escalated 状态?
  7. 作用域:提权是会话级还是全局?是否泄漏到其他不该提升的会话?

十、小结

"提权后 beta 限制仍在"是授权状态变更没有传导到判决策略导致的两层脱节:等级字段变了,但拦截器还在用旧策略快照。修复三层:

  • 状态机:等级转移与策略重算锁进同一入口,转移即刷新;
  • 实时判决:判决基于当前 level 实时算,不依赖会过期的缓存;
  • 验证 + 回退:提权返回前验证限制确实解除,失败则回退,杜绝虚假成功。

核心原则:任何授权/权限变更,都必须让所有依赖它的决策点同时失效并重算。权限等级和权限判决是同一事实的两面,绝不能让其中一面滞后。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐