# Access Control Testing

> Use when testing whether users can access data or actions they shouldn't — IDOR/BOLA, horizontal and vertical privilege escalation, multi-tenant isolation, forced browsing, function-level authorization (BFLA), and mass assignment. The

- Skill: `mn-youssef/access-control-testing` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add mn-youssef/access-control-testing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mn-youssef/access-control-testing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: mn-youssef (https://skillmd.com/u/mn-youssef)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mn-youssef/access-control-testing

---


# Access Control Testing (FIND/EXPLOIT — OWASP A01)

## Overview
Verify that every object and every action enforces "is *this* caller allowed?" server-side.
Broken access control is the most common and highest-impact real-world flaw — and the easiest
to miss because the happy path works perfectly.

**Core principle:** Authentication proves *who you are*; authorization proves *what you may do*.
Test the second one on every object and every action — not just the UI-visible ones.

## Applies when / Skip when
- **Applies when:** the app has authentication, multiple users, roles, or multi-tenant data —
  any resource or action that should be restricted. (Almost every non-trivial app.)
- **Skip when:** the app is fully public/anonymous with no per-user or privileged data → N/A.
- **If N/A:** report "access-control-testing: N/A — no auth/multi-user surface" and stop. Don't
  invent access boundaries that don't exist.

## ⚠️ Authorization
Use test accounts you created on an app you own/are authorized to test. Two accounts (A and B)
plus one admin/one low-priv account is the cleanest setup.

## The test matrix (run every cell)
| | Read | Write/Update | Delete | Admin action |
|---|---|---|---|---|
| **As another user (horizontal)** | A reads B's object? | A edits B's? | A deletes B's? | — |
| **As lower privilege (vertical)** | user hits admin read? | user hits admin write? | — | user invokes admin fn? |
| **Unauthenticated** | no token → data? | no token → mutate? | — | no token → admin? |

If any cell succeeds, that's a finding.

## Method
1. **Capture a legitimate request** as user A (note every id, uuid, slug, filename).
2. **Swap the object reference** to B's resource, keep A's session → IDOR/BOLA.
3. **Swap the session** (remove token / use lower-priv token) on privileged endpoints → BFLA / vertical.
4. **Force-browse** hidden routes (`/admin`, `/api/internal`) with a normal account.
5. **Mass assignment** — add fields the UI never sends (`role`, `isAdmin`, `userId`, `price`).
6. **Check every method** — `GET` may be guarded but `PUT`/`DELETE`/`PATCH` forgotten.

See `references/access-control-tests.md` for concrete request recipes and edge cases.

## Quick reference — bug classes
| Class | Test |
|-------|------|
| IDOR / BOLA | Change an id/uuid/filename to another user's; do you get their data? |
| Horizontal privesc | Act on another *same-level* user's resources. |
| Vertical privesc | Low-priv account reaches admin function/data. |
| BFLA | Admin endpoint guarded in UI but callable directly by anyone. |
| Multi-tenant break | Tenant A reads tenant B (swap `org_id`/`tenant_id`). |
| Mass assignment | Inject privileged fields into the request body. |
| Forced browsing | Guess/enumerate unlinked privileged routes. |

## Output
Findings in the register: which account hit which endpoint/object, the exact swapped request,
what was exposed, and horizontal vs vertical. Severity is almost always High+.

## Hand-off
Confirmed on a running app → reproduce via **`active-pentest`**; chain with others in
**`vulnerability-chaining`**; fix via **`security-hardening`** (deny-by-default, server-side checks).

## Common mistakes
- Testing only as admin — you'll never see the bug. Always test as the *lower* privilege.
- Trusting that a hidden/unlinked endpoint is safe — enumerate and call it directly.
- Checking `GET` only — mutations are where damage happens.
- Assuming UUIDs are safe — they leak (in responses, logs, referrers) and are still IDOR-able.
- Forgetting predictable patterns: sequential ids, base64'd ids, guessable filenames.

