使用 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处理权限任务的推荐流程
可以固定为:
读取现有权限模型;
输出角色—权限矩阵;
明确资源归属规则;
找到所有服务端入口;
设计最小权限变更;
修改代码;
增加允许和拒绝两类测试;
检查 Git Diff;
输出权限变更报告。
这个流程比直接说“帮我给这个页面加权限”稳定得多。
十五、Plus还是Pro?
如果主要处理:
单接口授权 简单RBAC 少量权限测试 单模块权限修复Plus 通常已经足够。
如果是大型后台系统、多角色、多服务、多仓库权限统一,并且需要连续分析接口、调用链和大量回归测试,那么可以根据实际任务连续性评估 Pro。
不过无论使用哪个版本,权限规则本身必须由项目明确,而不能让 Codex 自己猜。
总结
Codex 写权限逻辑出现越权,最常见的原因不是某一行代码完全错误,而是角色、权限、资源归属和服务端校验没有形成统一规则。
通过 RBAC、默认拒绝、最小权限、资源级授权和拒绝路径测试,可以让权限系统从“页面看起来限制住了”变成真正的服务端安全边界。
真正可靠的权限代码应该能够清楚回答:
谁,可以在什么条件下,对什么资源,执行什么操作。
只要其中任何一项无法明确,权限实现就还没有真正完成。
CSDN文章描述
本文介绍 Codex 开发权限系统时常见的越权风险,并通过 RBAC、最小权限、服务端授权、资源归属判断和权限回归测试,提高 AI 生成权限代码的安全性和可维护性。