본문으로 건너뛰기
cccskills
무료GitHub에 공개

access-control-and-idor

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md7.6 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Access Control and IDOR

Overview

Broken access control is the most common serious finding in real diffs, and it rarely looks dangerous: there is no exec, no string-built query — just a lookup by id with nothing after it. You find it by asking one question of every entry point the diff touches:

"If user A sends user B's id, what stops it?"

If the answer is not a line of code you can point at, it is a finding.

1. Object-Level Authorization (BOLA / IDOR — CWE-639, CWE-862)

Enumerate every id the new code accepts: path, query, body, header, nested in a JSON object, inside a batch array, in a GraphQL argument, in a WebSocket message. Follow each to the data layer.

// ❌ any authenticated user reads any invoice
inv, err := h.repo.Get(ctx, r.PathValue("id"))

// ✅ scoped to the caller's organisation in the query itself
inv, err := h.repo.GetForOrg(ctx, r.PathValue("id"), auth.OrgID(ctx))
// ❌ Spring: findById alone, the principal is never consulted
@GetMapping("/orders/{id}") fun get(@PathVariable id: UUID) = repo.findById(id)

// ✅ ownership is part of the lookup
@GetMapping("/orders/{id}") fun get(@PathVariable id: UUID, @AuthenticationPrincipal u: User) =
    repo.findByIdAndCustomerId(id, u.customerId) ?: throw NotFound()

Check the second id too: an endpoint that verifies the project in the path, then updates the task whose id is in the body, without checking the task belongs to that project.

// ❌ project ownership checked, task id from the body never tied to it
await assertProjectOwner(req.params.projectId, user.id);
await db.task.update({ where: { id: req.body.taskId }, data: { title } });

// ✅ the task is looked up inside the checked project
await db.task.update({ where: { id: req.body.taskId, projectId: req.params.projectId }, data: { title } });

2. Function-Level Authorization (BFLA — CWE-285, CWE-863)

A privileged action needs a role check, not just "logged in".

  • Spring Security: a new requestMatchers(...).permitAll() or .authenticated() on an admin path; a @PreAuthorize removed from a method; a check on the URL that a second mapping of the same handler bypasses.
  • Quarkus: a new resource method with no @RolesAllowed where its class's siblings have one; @PermitAll added.
  • Express/Nest/Fastify: a route registered before the auth middleware, or on a router that never mounts it.
  • Django/DRF: permission_classes = [AllowAny], a view missing @login_required beside decorated siblings.
# ❌ DRF: a staff-only action reachable by any authenticated user
@action(detail=True, methods=["post"])
def refund(self, request, pk=None): ...

# ✅ the action carries the same permission as the admin viewset
@action(detail=True, methods=["post"], permission_classes=[IsAdminUser])
def refund(self, request, pk=None): ...

3. Property-Level Authorization (mass assignment — CWE-915; excessive exposure — CWE-213)

Binding a request straight onto an entity lets the client set fields it should not: role, isAdmin, ownerId, orgId, price, emailVerified.

// ❌ Mongoose: whatever the client sends becomes the document
const user = await User.create(req.body);

// ✅ an explicit allowlist of client-settable fields
const { name, email } = req.body;
const user = await User.create({ name, email });

Equivalents: Django ModelForm / DRF serializer with fields = "__all__"; Spring @ModelAttribute or @RequestBody on a JPA entity; Rails params.permit!; Pydantic/request DTO that includes role; Go json.Decode into the domain struct. The response side is the mirror: returning the entity serialises passwordHash, internal flags or other users' rows in a list.

4. Tenant Scoping

In multi-tenant code, every read and write needs the tenant predicate — including the ones people forget:

  • list/search/export/count endpoints and their pagination cursors;
  • background jobs and queue consumers that receive an id from a message;
  • caches keyed by object id without tenant (one tenant warms, another reads);
  • object storage keys built from a user-supplied filename;
  • GraphQL nested resolvers (the parent was checked, the child field loads by raw id);
  • batch endpoints taking ids: [...] — each id must be checked, not the first.

5. Failing Open (CWE-280, OWASP A10:2025)

// ❌ a lookup error is treated as "no restriction"
perms, err := h.authz.For(ctx, userID)
if err == nil && !perms.CanDelete { return ErrForbidden }
return h.repo.Delete(ctx, id)

// ✅ any error denies
perms, err := h.authz.For(ctx, userID)
if err != nil || !perms.CanDelete { return ErrForbidden }

Also: a switch on role with a permissive default; a feature flag that disables the check when unset; an inverted comparison.

6. Parallel Paths to the Same Effect

The check on the route the diff adds may be right while the same state change stays reachable another way with a weaker one. For each protected effect the diff touches, list every door: the REST route and its GraphQL mutation, the v1 and v2 handlers, the single-item and the bulk endpoint, the import file, the admin tool, the background job, the WebSocket message, the agent tool. Each needs the same permission on the same resource — not authentication alone, and not a permission on a different object. Also ask whether a field in the body can override what the permission system restricts ("orgId", "ownerId", "role" in an update body) and whether bulk, export and import enforce the check per item. Tenant scoping across copies and lifecycle — caches, search, exports, soft-delete, revocation — is data-isolation-and-lifecycle.

Severity Guide

  • Reading or changing another tenant's or user's data → HIGH; CRITICAL when unauthenticated, when it reaches credentials or payment data, or when it is admin takeover.
  • Exposure of non-sensitive fields of another user → MEDIUM.

Common Mistakes

  • Reporting "ids are guessable": UUIDs are not, and guessability is not the bug — the missing check is. A sequential id with a correct check is fine.
  • Missing a check that exists one layer down: read the repository method; GetForOwner may already scope the query.
  • Reporting a missing check in client code — find the server endpoint and judge that.
  • Treating 403 vs 404 as a vulnerability. Follow the repo's convention; it is not your finding.

Red Flags

  • WHERE id = $1 with no second predicate on a caller-scoped resource.
  • findById, get_object_or_404(Model, pk=pk), Model.findByPk(id) returning straight to the response.
  • A new route that its neighbours' middleware chain does not cover.
  • A request DTO that is also the entity.
  • An authorization helper whose error path returns nil, true or falls through.

References (names and links only)

OWASP API Security Top 10 2023 — API1 BOLA, API3 BOPLA, API5 BFLA · OWASP Top 10:2025 A01 Broken Access Control · ASVS 5.0 V8 Authorization

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

Use when deciding whether a request needs an analiz task before implementation - the conditions that require the architect's analysis versus going straight to implementation

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 20일 업데이트

makifbaysal의 스킬 모두 보기

이 스킬의 문제 신고