使用 Codex 开发后台系统、管理平台或多角色应用时,权限功能通常是最容易“看起来正常,实际上有漏洞”的部分之一。

例如页面上已经隐藏了“删除用户”按钮,普通用户看起来无法操作,但如果直接调用后端接口,可能仍然可以完成删除。又或者管理员、运营、客服共用一套权限判断,随着功能增加,代码中逐渐出现大量 if role === ...,最后谁能访问什么已经很难说清楚。

这类问题的本质不是 Codex 不会写权限,而是权限需求如果没有明确边界,AI 很容易根据当前页面或当前接口实现“局部正确”的判断。

一、隐藏按钮不等于真正的权限控制

前端经常会出现这样的代码:

{user.role === "admin" && (
  <button onClick={deleteUser}>
    删除用户
  </button>
)}

这只能控制界面显示。

如果后端接口:

DELETE /api/users/1001

没有再次检查权限,那么普通用户完全可以绕过页面直接发送请求。

因此,权限控制必须遵循一个基本原则:

前端负责体验,服务端负责最终授权。

Codex 修改权限功能时,不能只检查页面组件,还要继续追踪到 API、Service 和数据访问层。

二、不要让角色判断散落在项目里

项目早期经常会这样写:

if (user.role === "admin") {
  // 允许操作
}

随着角色增加:

admin
operator
customer_service
finance
auditor

代码很快就会变成:

if (
  user.role === "admin" ||
  user.role === "operator"
) {
  // ...
}

另一个页面可能又使用不同条件。

长期来看,最危险的问题不是代码重复,而是权限规则逐渐不一致

更合理的方法,是将“角色”与“能力”分开。

例如:

admin
→ user.delete
→ user.edit
→ order.refund
→ report.view

customer_service
→ user.view
→ order.view
→ order.note

业务代码不再问:

你是不是管理员?

而是问:

你有没有 user.delete 权限?

三、用RBAC建立明确的权限模型

RBAC,也就是 Role-Based Access Control,可以理解为:

用户
→ 角色
→ 权限

例如:

const rolePermissions = {
  admin: [
    "user.view",
    "user.edit",
    "user.delete"
  ],

  operator: [
    "user.view",
    "user.edit"
  ],

  auditor: [
    "user.view"
  ]
};

然后统一通过:

function can(
  role: string,
  permission: string
) {
  return rolePermissions[
    role
  ]?.includes(permission) ?? false;
}

业务逻辑变成:

if (!can(user.role, "user.delete")) {
  throw new ForbiddenError();
}

这种方式最大的好处,是权限规则有了一个明确入口。

Codex 后续新增功能时,也更容易复用现有规则,而不是在不同文件中重新猜一遍。

四、默认应该是拒绝,而不是允许

权限系统中一个很重要的原则是:

没有明确允许,就应该拒绝。

错误写法通常是:

if (user.role === "guest") {
  return false;
}

return true;

这里意味着除了 guest 以外,任何未知角色都默认获得权限。

如果未来出现:

temp_user
external_user
unknown

就可能意外获得访问权限。

更安全的方式是:

return can(
  user.role,
  requiredPermission
);

没有配置的角色,默认就是 false

这就是最小权限原则的一部分:每个用户只获得完成当前工作真正需要的权限。

五、不要通过“管理员直接放行”绕开所有规则

为了方便开发,有些项目会写:

if (user.role === "admin") {
  return true;
}

这看起来合理,但长期容易产生一个问题:

管理员成为“无限权限账号”。

如果项目后续加入审计、财务、敏感数据导出等功能,并不是所有管理员都应该天然拥有所有能力。

更稳定的做法仍然是让管理员拥有明确权限集合。

例如:

admin
→ user.*
→ order.*
→ system.config

finance_admin
→ finance.*
→ report.export

权限应该来自配置,而不是代码中存在一个“万能通行证”。

六、资源权限不能只看角色

有些权限不仅取决于“你是谁”,还取决于“这条数据是谁的”。

例如普通用户可以:

查看自己的订单

但不能:

查看其他用户订单

这时简单 RBAC 就不够了。

服务端还需要检查资源所有权:

const order =
  await orderRepository.findById(orderId);

if (order.userId !== currentUser.id) {
  throw new ForbiddenError();
}

即使用户拥有:

order.view

也不代表他可以查看所有订单。

因此权限通常需要同时判断:

角色权限
+
资源归属
+
业务状态

例如退款操作可能要求:

拥有 order.refund 权限
并且
订单属于当前管理范围
并且
订单状态允许退款

七、权限判断要尽量靠近服务端入口

建议权限检查出现在 Controller、Middleware 或统一授权层,而不是等业务执行到一半才判断。

例如:

router.delete(
  "/users/:id",
  requirePermission("user.delete"),
  deleteUserHandler
);

这样调用链很清楚:

请求进入
→ 身份认证
→ 权限授权
→ 执行业务

如果授权失败,就不应该继续进入数据库修改流程。

这种结构也更方便 Codex 阅读和测试。

八、让Codex先输出权限矩阵

开始实现权限功能前,可以先让 Codex 生成一张权限矩阵,而不是立即写代码。

例如:

角色:admin
可查看用户:是
可编辑用户:是
可删除用户:是

角色:operator
可查看用户:是
可编辑用户:是
可删除用户:否

角色:auditor
可查看用户:是
可编辑用户:否
可删除用户:否

然后再将矩阵转换为代码。

这样可以先确认业务规则,再实现逻辑。

否则 Codex 很可能根据角色名称自行推断权限,而这种推断未必符合真实业务。

九、权限测试必须包含“禁止访问”

很多自动化测试只验证:

管理员可以删除用户

但没有测试:

普通用户不能删除用户

权限测试中,“拒绝路径”往往比“成功路径”更重要。

例如:

it(
  "should reject operator deleting user",
  async () => {
    const response = await request(app)
      .delete("/api/users/1001")
      .set(
        "Authorization",
        operatorToken
      );

    expect(response.status).toBe(403);
  }
);

还应该检查数据库:

const user =
  await userRepository.findById(1001);

expect(user).toBeDefined();

不能只确认接口返回403,还要确认危险操作确实没有发生。

十、修改权限后要做横向回归

一次权限修改,可能影响很多模块。

例如修改:

user.edit

可能同时影响:

用户列表
用户详情
后台审核
客服操作
批量编辑
API接口
移动管理端

因此完成修改后,不要只测试当前页面。

可以要求 Codex 输出:

本轮权限变化:
operator移除user.delete

受影响位置:
用户详情
用户列表批量操作
DELETE /api/users/:id

验证结果:
管理员删除正常
operator返回403
普通用户返回403
数据未被删除

这比简单回答“权限修改完成”更可靠。

十一、把权限规则写进AGENTS.md

对于长期项目,可以加入:

# 权限开发规则

- 前端隐藏按钮不能替代服务端授权
- 权限系统默认拒绝
- 不允许新增万能管理员绕过
- 角色判断统一转换为权限判断
- 数据访问必须检查资源归属
- 新增危险操作必须补充403测试
- 权限修改后必须检查所有调用入口
- 不允许通过删除权限校验解决403问题

这一类规则尤其适合 Codex。

因为 Codex 在修复“用户无法访问”问题时,最危险的方式就是把原来的权限判断放宽。

十二、警惕这种“快速修复”

如果某个页面返回403,下面这些修改都需要特别注意:

// 删除原来的权限判断

或者:

if (user) {
  return true;
}

甚至:

try {
  checkPermission();
} catch {
  // ignore
}

这些代码确实可能让功能恢复,但同时也可能让所有已登录用户获得不该拥有的操作权限。

遇到权限错误时,应该先确认:

当前用户是什么角色?
需要什么权限?
权限是否正确配置?
当前资源属于谁?
为什么会被拒绝?

而不是先删除限制。

十三、权限变更应该可以审计

重要系统最好记录:

谁
在什么时间
对哪个资源
执行了什么操作
结果是什么

例如:

actorId: 1008
action: user.delete
targetId: 1001
result: denied
traceId: req-xxx

这既可以帮助排查越权问题,也方便后续检查权限规则是否合理。

但日志中仍然不要记录密码、Token 或其他敏感凭据。

十四、Codex处理权限任务的推荐流程

可以固定为:

  1. 读取现有权限模型;

  2. 输出角色—权限矩阵;

  3. 明确资源归属规则;

  4. 找到所有服务端入口;

  5. 设计最小权限变更;

  6. 修改代码;

  7. 增加允许和拒绝两类测试;

  8. 检查 Git Diff;

  9. 输出权限变更报告。

这个流程比直接说“帮我给这个页面加权限”稳定得多。

十五、Plus还是Pro?

如果主要处理:

单接口授权
简单RBAC
少量权限测试
单模块权限修复

Plus 通常已经足够。

如果是大型后台系统、多角色、多服务、多仓库权限统一,并且需要连续分析接口、调用链和大量回归测试,那么可以根据实际任务连续性评估 Pro。

不过无论使用哪个版本,权限规则本身必须由项目明确,而不能让 Codex 自己猜。

总结

Codex 写权限逻辑出现越权,最常见的原因不是某一行代码完全错误,而是角色、权限、资源归属和服务端校验没有形成统一规则。

通过 RBAC、默认拒绝、最小权限、资源级授权和拒绝路径测试,可以让权限系统从“页面看起来限制住了”变成真正的服务端安全边界。

真正可靠的权限代码应该能够清楚回答:

谁,可以在什么条件下,对什么资源,执行什么操作。

只要其中任何一项无法明确,权限实现就还没有真正完成。

CSDN文章描述

本文介绍 Codex 开发权限系统时常见的越权风险,并通过 RBAC、最小权限、服务端授权、资源归属判断和权限回归测试,提高 AI 生成权限代码的安全性和可维护性。

更多推荐