authz-security
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAuthorization Security Scanner
授权安全扫描器
This skill turns the model into an authorization reviewer. Read the routes, controllers, resolvers, and data models; walk the detection passes; report each missing access-control check with a severity and a concrete, framework-correct fix. No tools to install, no app to run, no credentials — the analysis is the model reading the code.
Broken access control is the OWASP Top 10's #1 web risk (A01) and the top two API risks (API1 BOLA, API5 BFLA). It tops the lists precisely because automated tools miss it: there is no dangerous function call to grep for. The bug is the absence of a check, and confirming it requires understanding the application's ownership model — which is exactly what a model reading the code can do and a signature scanner cannot.
此 skill 将模型转变为一个授权审查器。读取 route、controller、resolver 和 data model;逐项执行检测流程;报告每个缺失的访问控制检查,并附上严重级别和具体、符合框架惯例的修复方案。无需安装工具、运行应用或提供凭据——整个分析过程由模型读取代码完成。
Broken access control 是 OWASP Top 10 中排名第 1 的 Web 风险(A01),也是 API 风险中排名前两位的问题(API1 BOLA、API5 BFLA)。它之所以位居榜首,恰恰是因为自动化工具往往会漏掉它:并不存在可供 grep 检索的危险函数调用。缺陷在于检查的缺失,而不是代码错误;要确认这一点,必须理解应用的 ownership 模型——这正是读取代码的模型能够做到、而基于特征签名的扫描器无法做到的。
Mental model
思维模型
Authentication answers who are you? Authorization answers are you allowed to do this, to this specific object? This skill is only about the second question. A handler can be perfectly authenticated and still be a critical vulnerability.
Every privileged operation can be escalated along two axes:
- Horizontal — acting on another user's object at the same privilege level. Manipulate an object identifier (→
/orders/1042) and you read someone else's data. This is BOLA / IDOR (OWASP API1)./orders/1043 - Vertical — reaching a function above your privilege level. A normal user hitting an admin endpoint. This is BFLA (OWASP API5).
The defect is almost always missing code, not wrong code: a query that filters by object id but not by owner, an admin route guarded by "is logged in" but not "is admin," a body that binds straight to a model. So you are scanning for a gap, not a pattern.
When you cannot tell whether a check exists, assume it does not, and flag it for confirmation. The cost of a false positive is a review comment. The cost of a false negative is every user's data. This asymmetry governs every judgment call below.
Authentication 回答你是谁?,Authorization 回答你是否被允许对这一特定对象执行此操作? 此 skill 只处理第二个问题。handler 即便已经完成完美的 Authentication,仍可能存在严重漏洞。
每项特权操作都可能在两个维度上被提升:
- Horizontal——以相同权限级别操作其他用户的对象。操纵对象标识符(→
/orders/1042),即可读取他人的数据。这就是 BOLA / IDOR(OWASP API1)。/orders/1043 - Vertical——访问高于自身权限级别的功能。普通用户访问 admin endpoint,即属于此类问题,也就是 BFLA(OWASP API5)。
缺陷几乎总是代码缺失,而不是代码错误:查询只按 object id 过滤,却没有按 owner 过滤;admin route 仅通过“是否已登录”保护,却没有“是否为 admin”保护;request body 直接绑定到 model。因此,扫描目标是缺口,而不是某种代码模式。
如果无法确定某项检查是否存在,就假定它不存在,并将其标记出来等待确认。 误报的代价只是一条审查意见;漏报的代价则是所有用户的数据泄露。下面的每项判断都遵循这一不对称性。
Scan procedure
扫描流程
Walk these passes in order. Pass 0 builds the context the rest depend on — do not skip it.
按顺序执行以下阶段。Pass 0 用于建立后续阶段所依赖的上下文——不可跳过。
Pass 0: map the ownership and tenancy model
Pass 0:映射 ownership 与 tenancy 模型
You cannot tell that a check is missing until you know what "owning" an object means here. Before reading a single endpoint, read the data layer — ORM models, schema, migrations — and establish:
- For each resource, who owns it? Look for an owner/scope column: ,
user_id,account_id,org_id,tenant_id,team_id. A resource with such a column must never be returned or mutated without scoping to it.workspace_id - What is the principal? Where does the authenticated identity come from — from a session,
request.userset by auth middleware, a verified JWTreq.userclaim? This is the only trustworthy identity. Note it.sub - What is the tenancy strategy? Row-level (shared tables with a column) is where almost all multi-tenant BOLA hides, because isolation depends on every single query remembering the filter. Schema- or database-per-tenant pushes the boundary lower and is harder to get wrong.
tenant_id
Write this map down (even informally). Every later finding is "principal P touched object O without confirming P may access O" — you need to know O's owner to make the call.
在知道这里所说的“拥有某个对象”意味着什么之前,无法判断某项检查是否缺失。读取任何 endpoint 之前,先阅读数据层——ORM model、schema、migration——并确定:
- 每种资源由谁拥有? 查找 owner/scope 列,例如 、
user_id、account_id、org_id、tenant_id、team_id。带有此类列的资源,在返回或修改时必须始终限定在该范围内。workspace_id - principal 是什么? 已认证身份来自哪里——session 中的 、auth middleware 设置的
request.user,还是经过验证的 JWTreq.userclaim?这是唯一可信的身份来源。应将其记录下来。sub - tenancy 策略是什么? 行级(row-level)隔离,即共享表加 列,是 multi-tenant BOLA 最常藏身之处,因为隔离依赖于每一条查询都记得添加过滤条件。每个 schema 或 database 独立一个 tenant 的方式会将边界下移,因此更难出现错误。
tenant_id
将这个映射记录下来,即使只是非正式记录。后续每个发现项都可以归结为“principal P 操作了 object O,却没有确认 P 是否有权访问 O”——必须先知道 O 的 owner,才能作出判断。
Pass 1: enumerate handlers and the objects they touch
Pass 1:枚举 handler 及其访问的对象
List every entry point: REST routes, controller actions, GraphQL resolvers, RPC/gRPC methods, server actions. For each, record:
- HTTP method + path (or resolver name).
- The object(s) it reads or mutates.
- Where the object identifier comes from: path param, query string, request body, or the session/token.
Flag every handler that loads or mutates an object by a client-supplied identifier — those are the BOLA candidates for Pass 2. A handler that only ever uses the principal's own id from the session (, ) is low risk and can be deprioritized.
GET /mecurrent_user.orders列出所有入口点:REST route、controller action、GraphQL resolver、RPC/gRPC method、server action。对每个入口点,记录:
- HTTP method + path(或 resolver name)。
- 它读取或修改的 object。
- object identifier 来自哪里:path param、query string、request body,还是 session/token。
标记每个通过客户端提供的 identifier加载或修改 object 的 handler——这些是 Pass 2 的 BOLA 候选。如果某个 handler 始终只使用 session 中 principal 自己的 id(例如 、),则风险较低,可以降低优先级。
GET /mecurrent_user.ordersPass 2: object-level authorization (BOLA / IDOR) — OWASP API1
Pass 2:对象级授权(BOLA / IDOR)——OWASP API1
For every handler flagged in Pass 1, ask: is there a check that the principal owns or may access this specific object? An acceptable check takes one of three forms:
- Owner-scoped query — the lookup itself includes the owner, so a non-owned id simply isn't found:
python
invoice = Invoice.objects.get(id=id, org=request.user.org) # 404 on someone else's - Explicit post-load check — load, then compare to the principal and reject:
python
invoice = Invoice.objects.get(id=id) if invoice.org_id != request.user.org_id: raise PermissionDenied - Centralized policy / ability — a guard that encodes the rule: (Pundit/CanCanCan),
authorize! :read, invoice, an OPA/Cedar/OpenFGA decision, a policy gate.@PreAuthorize("hasPermission(#id,...)")
If the object is loaded by a client-supplied id with none of these — only the id, on the assumption that "you had to be logged in to get here" — that is the canonical IDOR. Authentication is not authorization.
javascript
// [P0] BOLA — Express: looks up by id, never checks ownership
app.get("/api/orders/:id", requireAuth, async (req, res) => {
const order = await Order.findByPk(req.params.id);
res.json(order);
});
// Fixed — scope to the authenticated principal
app.get("/api/orders/:id", requireAuth, async (req, res) => {
const order = await Order.findOne({
where: { id: req.params.id, userId: req.user.id },
});
if (!order) return res.sendStatus(404);
res.json(order);
});Severity: P0 for a sensitive read or any write/delete reachable by any authenticated user; P1 if the id space is large and unguessable and the data is low-sensitivity (still a finding — unguessable ids leak).
对于 Pass 1 标记的每个 handler,询问:是否存在检查,证明 principal 拥有或可以访问这个具体 object? 可接受的检查有以下三种形式:
- Owner-scoped query——查询本身包含 owner,因此非自有 id 不会被找到:
python
invoice = Invoice.objects.get(id=id, org=request.user.org) # 404 on someone else's - 显式 post-load check——先加载 object,再将其与 principal 比较并拒绝访问:
python
invoice = Invoice.objects.get(id=id) if invoice.org_id != request.user.org_id: raise PermissionDenied - 集中式 policy / ability——通过 guard 编码规则:(Pundit/CanCanCan)、
authorize! :read, invoice、OPA/Cedar/OpenFGA decision,或 policy gate。@PreAuthorize("hasPermission(#id,...)")
如果 object 是通过客户端提供的 id 加载,且上述检查一个都没有——只按 id 查询,并假定“能到这里就说明已登录”——那么这就是典型的 IDOR。Authentication 不等于 Authorization。
javascript
// [P0] BOLA — Express: looks up by id, never checks ownership
app.get("/api/orders/:id", requireAuth, async (req, res) => {
const order = await Order.findByPk(req.params.id);
res.json(order);
});
// Fixed — scope to the authenticated principal
app.get("/api/orders/:id", requireAuth, async (req, res) => {
const order = await Order.findOne({
where: { id: req.params.id, userId: req.user.id },
});
if (!order) return res.sendStatus(404);
res.json(order);
});严重级别:敏感数据读取,或任何 authenticated 用户均可触发的 write/delete,属于 P0;如果 id 空间很大且不可猜测,同时数据敏感度较低,则属于 P1(仍应报告——不可猜测的 id 也会泄露)。
Pass 3: function-level authorization (BFLA / vertical) — OWASP API5
Pass 3:功能级授权(BFLA / vertical)——OWASP API5
Endpoints that perform privileged actions — user management, role changes, admin dashboards, data exports, billing, internal tooling, feature flags — must check the principal's role or permission, not merely that they are authenticated.
Flag:
- An route or a privileged action guarded only by an authentication middleware, with no role/permission assertion in the handler or its middleware chain.
/admin/* - Role checks performed only in the frontend (a hidden button) while the API endpoint is open.
- "Hidden" admin endpoints relying on obscurity — undocumented paths with no guard.
python
undefined执行特权操作的 endpoint——用户管理、角色变更、admin dashboard、数据导出、billing、内部工具、feature flag 等——必须检查 principal 的 role 或 permission,而不仅仅检查其是否已完成 Authentication。
标记以下情况:
- route 或特权操作只受 authentication middleware 保护,而 handler 或其 middleware 链中没有任何 role/permission assertion。
/admin/* - Role check 仅在前端执行,例如隐藏按钮,但对应的 API endpoint 仍处于开放状态。
- 依靠 obscurity 隐藏 admin endpoint,例如使用未公开的 path,却没有设置任何 guard。
python
undefined[P1] BFLA — authenticated, but any user can promote anyone to admin
[P1] BFLA — authenticated, but any user can promote anyone to admin
@router.post("/users/{id}/role")
def set_role(id: int, body: RoleIn, user=Depends(current_user)):
User.objects.filter(id=id).update(role=body.role)
@router.post("/users/{id}/role")
def set_role(id: int, body: RoleIn, user=Depends(current_user)):
User.objects.filter(id=id).update(role=body.role)
Fixed — assert the privilege
Fixed — assert the privilege
@router.post("/users/{id}/role")
def set_role(id: int, body: RoleIn, user=Depends(current_user)):
if not user.is_admin:
raise HTTPException(403)
User.objects.filter(id=id).update(role=body.role)
undefined@router.post("/users/{id}/role")
def set_role(id: int, body: RoleIn, user=Depends(current_user)):
if not user.is_admin:
raise HTTPException(403)
User.objects.filter(id=id).update(role=body.role)
undefinedPass 4: mass assignment / excessive property binding — OWASP API3
Pass 4:mass assignment / 过度属性绑定——OWASP API3
When a handler binds the whole request body to a model — , , , — ask which fields the client should not control: , , , , , , , , , . If any reachable, the client escalates privilege or moves objects between tenants by adding a field.
User.update(req.body)Model(**request.data)user.update_attributes(params[:user])Object.assign(entity, req.body)roleis_adminis_verifiedemail_verifiedowner_idorg_idtenant_idbalancepricestatusFix: allowlist the writable fields explicitly — strong params, a serializer/DTO with named fields, a Pydantic model scoped to inputs. Never bind a raw body to a persistence model.
当 handler 将整个 request body 绑定到 model——例如 、、、——询问客户端不应控制哪些字段:、、、、、、、、、。如果其中任何字段可达,客户端就能通过添加字段提升权限,或将 object 移动到其他 tenant。
User.update(req.body)Model(**request.data)user.update_attributes(params[:user])Object.assign(entity, req.body)roleis_adminis_verifiedemail_verifiedowner_idorg_idtenant_idbalancepricestatus修复方式:显式使用 allowlist 限定可写字段——例如 strong params、包含具名字段的 serializer/DTO,或限定输入范围的 Pydantic model。绝不要将原始 body 绑定到 persistence model。
Pass 5: trusting client-supplied identity
Pass 5:信任客户端提供的身份
The principal — and the tenant — must be derived server-side from the session or a verified token, never from a client-controlled value. Flag any of these used for an authorization decision or query scoping:
- /
user_id/account_idread from the request body, query string, path, or a custom header (org_id,X-User-Id).X-Tenant - A JWT claim trusted without verifying signature, issuer, audience, and expiry.
javascript
// [P0] — scopes by a query param the caller controls
app.get("/api/invoices", requireAuth, async (req, res) => {
res.json(await Invoice.findAll({ where: { userId: req.query.userId } }));
});
// Fixed: scope by the session principal, ignore client-supplied identity
// where: { userId: req.user.id }principal——以及 tenant——必须由服务端的 session 或经过验证的 token 推导,绝不能来自客户端可控的值。如果以下值被用于授权决策或查询范围限定,应将其标记:
- 从 request body、query string、path 或自定义 header(例如 、
X-User-Id)读取的X-Tenant/user_id/account_id。org_id - 未验证 signature、issuer、audience 和 expiry 就被信任的 JWT claim。
javascript
// [P0] — scopes by a query param the caller controls
app.get("/api/invoices", requireAuth, async (req, res) => {
res.json(await Invoice.findAll({ where: { userId: req.query.userId } }));
});
// Fixed: scope by the session principal, ignore client-supplied identity
// where: { userId: req.user.id }Pass 6: consistency and edge surfaces
Pass 6:一致性与边缘场景
Authorization bugs cluster where coverage is uneven. Check each:
- Sibling verbs. checks ownership but
GET /orders/:id/PUT/PATCHon the same resource don't (or the reverse). Verify every method, not just read.DELETE - Nested resources. — is the parent→child link verified (does the project actually belong to the org, and the user to the org)? A common bug checks the leaf and trusts the path prefix.
/orgs/:orgId/projects/:projectId - Batch / bulk endpoints. They accept an array of ids — the check must run on every element, not the first. Same for GraphQL aliased queries.
- List / collection endpoints. has no id to swap, so it's easy to forget it must be scoped to the tenant. An unscoped list leaks the whole table — often a worse P0 than a single-object IDOR.
GET /invoices - GraphQL. / global object identification and every field resolver that loads a related object must enforce authorization per resolver; checking only the query root is insufficient.
node(id:) - Newly added endpoints in a diff. A route, a fresh controller action, a new field resolver — these are exactly where the guard was forgotten. Treat any new object-by-id handler in a diff as an automatic Pass-2 review.
v2
授权缺陷往往会聚集在覆盖不均的地方。逐项检查:
- Sibling verbs。 检查了 ownership,但同一资源上的
GET /orders/:id/PUT/PATCH没有检查,反之亦然。必须验证每一种 method,而不只是读取操作。DELETE - Nested resources。 对于 ,是否验证了 parent→child 关联,即 project 是否确实属于 org、user 是否属于 org?常见缺陷是只检查 leaf object,然后直接信任 path prefix。
/orgs/:orgId/projects/:projectId - Batch / bulk endpoint。 它们接收 id 数组——检查必须应用于每一个元素,而不是只检查第一个。GraphQL aliased query 同样如此。
- List / collection endpoint。 没有可供替换的 id,因此很容易忘记它必须按 tenant 限定范围。未限定范围的列表会泄露整张表,通常比单个 object 的 IDOR 造成更严重的 P0。
GET /invoices - GraphQL。 / global object identification,以及每个加载关联 object 的 field resolver,都必须分别执行授权检查;只检查 query root 并不充分。
node(id:) - Diff 中新增的 endpoint。 route、新增的 controller action、全新的 field resolver——这些正是 guard 最容易被遗忘的地方。应将 diff 中每个新增的按 id 访问 object 的 handler 自动纳入 Pass 2 审查。
v2
Pass 7: indirect or non-controls
Pass 7:间接措施或非控制措施
Things that resemble authorization but are not. Each of these, when it is the only thing between users, carries the same severity as the missing check it is standing in for:
- Middleware/route ordering. Relying on registration order so a guard runs first — trivially broken when a route is later added above it, or when a framework matches a more specific route first.
- Frontend-only enforcement. Button hidden, SPA route guarded, while the API behind it is open.
- Secret identifiers. UUIDs or slugs as the only control. IDs leak via logs, headers, error messages, and API responses, and UUIDv1 is partly predictable. Unguessable ids are defense-in-depth, never the check (P3 when authz is otherwise correct).
Referer - Default-allow config. A framework default that permits when no rule matches: a missing , Spring
permission_classes, a catch-all route, a policy engine queried but whose default decision is allow.permitAll()
以下内容看起来像授权控制,但实际并非如此。当其中任何一项是用户之间唯一的屏障时,其严重级别与它所替代的缺失检查相同:
- Middleware/route ordering。 依赖注册顺序来保证 guard 最先执行——当以后有 route 被添加到它上方,或 framework 优先匹配更具体的 route 时,这种保护很容易失效。
- 仅由前端执行。 按钮被隐藏、SPA route 受到保护,但其后方的 API 仍处于开放状态。
- Secret identifier。 将 UUID 或 slug 作为唯一控制手段。ID 会通过 log、header、error message 和 API response 泄露,而且 UUIDv1 在一定程度上可以预测。不可猜测的 id 只能作为纵深防御,绝不能充当授权检查(其他 authz 均正确时为 P3)。
Referer - Default-allow 配置。 framework 默认在没有规则匹配时允许访问:缺少 、Spring
permission_classes、catch-all route,或 policy engine 被调用,但其默认 decision 为 allow。permitAll()
Finding format
发现项格式
Report each finding in this structure. Group by severity, P0 first.
[P0] bola in app/controllers/orders_controller.rb:42
#show loads Order by params[:id] with no ownership check. Any authenticated
user can read any order by changing the id (sequential integers).
Principal: current_user. Order belongs to a user via user_id.
Vulnerable:
def show
@order = Order.find(params[:id])
end
Fix (scope the lookup to the owner; 404s on non-owned ids):
def show
@order = current_user.orders.find(params[:id])
endIf the user pastes a snippet without a path, refer to "the handler" and use line numbers within the snippet. Always name the object, its owner, and the principal — that triad is the proof the check is missing.
按以下结构报告每个发现项。按严重级别分组,P0 置顶。
[P0] bola in app/controllers/orders_controller.rb:42
#show loads Order by params[:id] with no ownership check. Any authenticated
user can read any order by changing the id (sequential integers).
Principal: current_user. Order belongs to a user via user_id.
Vulnerable:
def show
@order = Order.find(params[:id])
end
Fix (scope the lookup to the owner; 404s on non-owned ids):
def show
@order = current_user.orders.find(params[:id])
end如果用户粘贴的 snippet 没有 path,就引用“the handler”,并使用 snippet 内的行号。始终明确指出 object、其 owner 和 principal——这三者共同构成检查缺失的证据。
Severity scale
严重级别标准
- P0 — exploitable now by any authenticated (often any unauthenticated) user, no chain: a missing object-level check on a sensitive read or any write/delete, an admin function reachable by a normal user, an unscoped collection endpoint, or scoping keyed on client-supplied identity. Block merge.
- P1 — missing role check on a less-sensitive action, mass assignment of a privilege/tenant field, or a BOLA gated only by a large unguessable id space. Block merge on anything that touches other users' data.
- P2 — uneven coverage not yet exploitable: one verb scoped and a sibling ambiguous, tenant scoping present but fragile, a check that depends on middleware you can't confirm statically.
- P3 — defense-in-depth: predictable ids where authz is otherwise correct, missing authorization regression tests, reliance on id secrecy as a secondary layer.
- P0——任何 authenticated 用户(通常也包括任何 unauthenticated 用户)当前即可利用,且无需攻击链:敏感数据缺少对象级检查、任何 write/delete 缺少对象级检查、普通用户可以访问 admin 功能、collection endpoint 未限定范围,或查询范围基于客户端提供的身份。阻止 merge。
- P1——敏感度较低的操作缺少 role check;mass assignment 可写入 privilege/tenant 字段;或 BOLA 仅受范围较大且不可猜测的 id space 保护。只要涉及其他用户的数据,就阻止 merge。
- P2——覆盖不均但尚不可利用:某个 verb 已限定范围,而同类 verb 仍有歧义;tenant 范围已存在但较为脆弱;或者检查依赖无法通过静态分析确认的 middleware。
- P3——纵深防御:其他 authz 均正确,但 id 可预测;缺少授权回归测试;依赖 id 的保密性作为次要保护层。
When the code looks fine
当代码看起来没有问题时
After all passes, if nothing fires:
- Say so explicitly, and state what you verified: the ownership model from Pass 0, the handlers enumerated, and that every object-by-id path and privileged function carries a check across all verbs.
- Note what static review cannot confirm: runtime middleware wiring and ordering, the actual body of a centralized policy you didn't see, framework defaults, and whether deployed config matches the code.
- Recommend confirming at runtime with a dynamic BOLA test (two accounts, swap ids) against a running instance — the complementary offensive check this skill does not perform — and walking .
references/checklist.md
A clean static read does not prove the deployment is safe.
完成所有阶段后,如果没有任何命中:
- 明确说明这一点,并陈述已验证的内容:Pass 0 中建立的 ownership 模型、枚举出的 handler,以及每条按 id 访问 object 的路径和每项特权功能在所有 verb 上都具备相应检查。
- 说明静态审查无法确认的内容:runtime middleware 的连接和执行顺序、未看到的集中式 policy 的实际内容、framework 默认配置,以及部署配置是否与代码一致。
- 建议在运行中的实例上通过动态 BOLA 测试进行确认——使用两个账户并交换 object id——这是此 skill 不会执行的、互补的 offensive 检查;同时按照 逐项核查。
references/checklist.md
静态审查结果干净并不能证明部署是安全的。
Reference files
参考文件
- — what a correct ownership and role check looks like in each major stack (Express/NestJS, Django/DRF, Rails + Pundit/CanCanCan, Spring Security, Laravel, Go). Read it to recognize whether a check is present or absent in idiomatic code, and to write the fix in the project's own conventions.
references/frameworks.md - — "I want to do X authorization safely" recipes: owner-scoped queries, a centralized policy layer, deny-by-default, server-derived identity, secure multi-tenant scoping. Read it when proposing the fix, not just naming the gap.
references/patterns.md - — a flat per-endpoint / per-resource / per-application checklist with a triage order. Use it for a full audit, for scanning many endpoints at once, or when prioritizing findings at scale.
references/checklist.md
- ——在每个主流技术栈(Express/NestJS、Django/DRF、Rails + Pundit/CanCanCan、Spring Security、Laravel、Go)中,正确的 ownership 和 role check 应是什么样子。阅读它以判断 idiomatic code 中是否存在检查,并按照项目自身的惯例编写修复方案。
references/frameworks.md - ——“我想安全地完成 X authorization”的实现方案:owner-scoped query、集中式 policy 层、deny-by-default、服务端推导身份、安全的 multi-tenant 范围限定。提出修复方案时应阅读它,而不只是指出缺陷。
references/patterns.md - ——按 endpoint、resource 和 application 组织的扁平 checklist,并提供分诊顺序。完整审计、一次扫描大量 endpoint,或需要大规模确定优先级时,应使用它。
references/checklist.md
What this skill won't do
此 skill 不会做什么
- It won't run the app, send requests, or need credentials. It reads source. For runtime confirmation, pair it with a dynamic BOLA tester (two accounts, swap object ids) — that is the offensive complement to this defensive review.
- It won't replace an authorization engine (OPA/Rego, AWS Cedar, OpenFGA, Oso). It checks that whatever mechanism exists is actually applied on every path, including the ones added yesterday.
- It won't wave through a missing check because exploitability is unproven. When it cannot confirm a check exists, it flags it — the false-positive cost is a comment; the miss is a breach.
- It won't accept authentication as authorization. "You must be logged in" is never the answer to "may this user touch this object?"
- 它不会运行应用、发送请求或需要凭据。它只读取源代码。若要在 runtime 阶段确认,应将它与动态 BOLA tester(两个账户、交换 object id)配合使用;后者是这项 defensive review 的 offensive 补充。
- 它不会取代授权引擎(OPA/Rego、AWS Cedar、OpenFGA、Oso)。它会检查现有机制是否真正应用于每条路径,包括昨天刚添加的路径。
- 不会因为尚未证明可利用性就放过缺失的检查。如果无法确认检查存在,就会将其标记出来——误报的代价只是一条评论;漏报则可能导致数据泄露。
- 不会将 Authentication 当作 Authorization。“用户必须已登录”永远无法回答“这个用户是否有权操作这个 object?”