RBAC scope filter přepsán URL parametrem (own → all)
import { Aside } from ‘@astrojs/starlight/components’;
Symptom
Sekce “Symptom”Uživatel s rolí, která má v RBAC scope own (smí vidět jen své záznamy), přepne v UI na testovací účet a v sekci uvidí klienty jiného uživatele. Vrátí to zpět na admin účet, zkusí znovu — stále vidí cizí. Někde v URL je ?ownerId=<id_jiného_uživatele> — ale to nemá smyslem fungovat, protože scope filter by ho měl přebít.
Root cause
Sekce “Root cause”List endpoint (GET /api/clients, /api/leads, …) staví where klauzuli ve dvou krocích:
// 1) scope filter podle rolelet where = applyScopeFilter(auth, {}, "ownerId");// pro scope=own vrátí: { ownerId: auth.user.id }
// 2) user-input parametryconst ownerId = searchParams.get("ownerId");if (ownerId) where.ownerId = ownerId;// ❌ TICHÝ PŘEPIS — scope filter je k ničemu, user vidí cizí datawhere.ownerId = ownerId přebije to, co tam dal applyScopeFilter. Útočník (nebo zvědavý kolega) napíše do URL ?ownerId=<jiné_id> a vidí cizí záznamy s normální 200 response. Žádná chyba v logu — vypadá to jako legitimní filtr.
Pattern je obvykle součást „filtrace dle vlastníka” v UI (admin si filtruje pohledem konkrétního OZ) — endpoint si parametr přijme, ale zapomene zkontrolovat, že volající ho má smět použít.
Fix
Sekce “Fix”Centrální helper v RBAC modulu:
export function violatesOwnScope( auth: PermissionResult, requestedUserId: string | null | undefined): boolean { if (!requestedUserId) return false; return auth.scope === "own" && requestedUserId !== auth.user.id;}V každém list endpointu, který přijímá ?ownerId/?creatorId/?assignedToId/?managerId, kontrolovat před přiřazením do where:
const ownerId = sp.get("ownerId");if (violatesOwnScope(auth, ownerId)) { return NextResponse.json( { error: "Nemáte oprávnění filtrovat dle jiného uživatele." }, { status: 403 } );}if (ownerId) where.ownerId = ownerId; // teď bezpečněProč 403 a ne tiché filtrování? Tiché vrácení prázdného listu maskuje útok — admin si v logu nevšimne, že někdo zkoušel scope bypass. 403 je viditelné selhání.
Alternativa — opačné pořadí v where: aplikovat applyScopeFilter až NA KONCI, tj. po user-inputu. Pak scope filter user-input přepíše. Funguje pro „silent ignore”, ale chová se jinak (útočníkův pokus nebude vidět). Pro auditovatelnost preferuj guard variantu.
Co zkontrolovat ve svém kódu
Sekce “Co zkontrolovat ve svém kódu”Najít všechny list endpointy, kde se nastavuje where pole, které je zároveň ownerField parametr applyScopeFilter:
grep -rn 'where\.\(ownerId\|assignedToId\|creatorId\|managerId\|userId\)\s*=' src/app/apiPro každý zkontrolovat pořadí + přítomnost guardu. V typickém Next.js + Prisma + custom RBAC layoutu bývá takových endpointů 6–10.
Související
Sekce “Související”- Souvislost s [[match-records-by-id-or-email]] — také o tom, jak může user-input parametr nevědomky obejít kontrolu.
- IDOR (Insecure Direct Object Reference) — OWASP Top 10 A01.