• Agents
  • Pricing
  • Blog
Log in
Get started

Security for apps built with AI. Paste a URL, get a report, fix what matters.

Product

  • How it works
  • What we find
  • Pricing
  • Agents
  • MCP Server
  • CLI
  • GitHub Action

Resources

  • Guides
  • Blog
  • Docs
  • OWASP Top 10
  • Glossary
  • FAQ

Security

  • Supabase Security
  • Next.js Security
  • Lovable Security
  • Cursor Security
  • Bolt Security

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • Imprint
© 2026 Flowpatrol. All rights reserved.
Home/OWASP Top 10/API Top 10/API5: Broken Function Level Authorization
API5CWE-285CWE-862

The admin endpoints that forgot they were admin endpoints
Broken Function Level Authorization

When the frontend hides the admin button but the backend still answers the request.

Extremely common in AI-generated apps that split routes across many files — each one re-implements (or forgets) its own guard.

Reference: API Top 10 (2023) — API5·Last updated April 7, 2026·By Flowpatrol Team
Broken Function Level Authorization illustration

The admin panel is beautiful. It only shows up for users with role: 'admin', and the button is tucked behind a clean little feature flag. But the API routes behind those buttons don't know they're admin routes. Any logged-in user can call them directly. The guard lived in the frontend the whole time.

Function-level authorization is the question 'is this caller allowed to use this feature at all?' — as distinct from BOLA's 'is this caller allowed to touch this object?' When the answer lives in the UI instead of the route handler, any logged-in user becomes effectively an admin the moment they know the URL.

What your AI actually built

You asked for an admin dashboard with user management, org settings, and a feature toggle panel. The model built all three. It added a nice sidebar that only renders for admins, and it wrote the corresponding API routes: DELETE /api/users/:id, POST /api/orgs/:id/plan, PATCH /api/flags/:name.

What it did not do is add a role check on any of those routes. Each handler only verifies the caller is logged in. 'The UI hides the button from non-admins' was the implicit assumption. The UI is a suggestion; the API is the boundary.

On top of that, there are usually hidden endpoints the UI never even calls — /api/internal/debug/users, /api/admin/impersonate — that the model added 'just in case' and nobody added auth to because nobody was testing them.

How it gets exploited

A regular user with a paid account opens the browser dev tools and views the JavaScript bundle.

  • 1
    Read the routes off the client
    The bundled React code contains references to every admin endpoint, including the ones hidden behind conditionals. They copy-paste a list: /api/admin/users, /api/admin/impersonate, /api/admin/flags.
  • 2
    Call them directly
    They send a POST to /api/admin/impersonate with { userId: 1 }. The server issues them a token as user 1 — the root admin — because nothing in that route checked whether the caller was allowed to use it.
  • 3
    Toggle the flags
    As the root admin, they flip the 'require_payment' flag to false, grant themselves unlimited credits, and disable audit logging on their own account. Every request is authenticated, every request is logged (until the last step). Nothing looks wrong.
  • One regular account turned into full ownership of the platform. The vulnerability is not a bypass — the attacker used the admin feature exactly as built. The only thing missing was the check that said 'you are not an admin.'

    Vulnerable vs Fixed

    Vulnerable — route checks login but not role
    // app/api/admin/users/[id]/route.ts
    export async function DELETE(req, { params }) {
      const session = await getSession(req);
      if (!session) {
        return Response.json({ error: 'Unauthorized' }, { status: 401 });
      }
    
      // Any logged in user can call this. The UI hides the button, so
      // "obviously" only admins will reach it. The API does not agree.
      await db.user.delete({ where: { id: params.id } });
      return Response.json({ ok: true });
    }
    Fixed — role check at the route, not in the UI
    // app/api/admin/users/[id]/route.ts
    import { requireAdmin } from '~/lib/auth';
    
    export async function DELETE(req, { params }) {
      const admin = await requireAdmin(req); // throws 403 if not admin
      if (admin instanceof Response) return admin;
    
      await db.audit.create({
        data: { actorId: admin.id, action: 'user.delete', targetId: params.id },
      });
    
      await db.user.delete({ where: { id: params.id } });
      return Response.json({ ok: true });
    }

    Function-level authorization goes at the function — the route handler — not at the button that calls it. The `requireAdmin` helper forces every admin route to make the check explicitly, so you can't forget. The audit log is the other half: if an admin function runs, record who ran it.

    A real case

    A Venmo bug let any user list every transaction between every other user

    For years, Venmo's API exposed a public transactions endpoint that required no admin role — researchers scraped 200 million transactions before the endpoint was locked down. Same pattern: a function that was never meant for anyone to call directly was left wide open.

    Related reading

    Glossary

    Missing Admin Checks (Broken Function Level Authorization)Broken Object Level Authorization (BOLA)

    What we find

    broken access control

    References

    • API5: Broken Function Level Authorization — official OWASP entry
    • OWASP API Security Top 10 (2023) — full list
    • CWE-285 on cwe.mitre.org
    • CWE-862 on cwe.mitre.org

    Find the admin routes that never checked for admins.

    Flowpatrol discovers every endpoint in your app and tests each one against a low-privilege session. The bugs your UI hides, the scanner finds.

    Try it free