• 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/API8: Security Misconfiguration
API8CWE-2CWE-16

The defaults nobody read
Security Misconfiguration

CORS set to '*'. An admin endpoint with no auth. A management port open to the internet. All three in one deploy.

The single most common API finding in real-world scans — because defaults are never secure and nobody reads them.

Reference: API Top 10 (2023) — API8·Last updated April 7, 2026·By Flowpatrol Team
Security Misconfiguration illustration

APIs do not ship with the defaults you want. They ship with the defaults that make the tutorial work. CORS wide open so the hello-world demo runs. An internal admin handler exposed on the same router as the public one. A default credential on a management dashboard nobody remembered to turn off. None of this is code. It is settings.

Security misconfiguration is when the bug is not in your code, it is in your settings. CORS wildcards, exposed admin routes, default credentials, debug endpoints in production, management dashboards on public ports. The code compiles. The app works. The settings are wrong.

What your AI actually built

You asked for an API with authentication and a dashboard. The model gave you the API, the dashboard, and — without mentioning it — a CORS handler set to Access-Control-Allow-Origin: * with credentials enabled, because that is what the getting-started guides do.

It also wired up a few internal endpoints next to the public ones: /api/admin/users for the dashboard, /api/internal/queue for the worker, /api/debug/env for the build you did at 2am. None of them have auth, because the model assumed they would only ever be called 'from inside.' They are on the same public router.

And somewhere in your compose file there is a management service — pgAdmin, Redis Commander, a queue dashboard — that the model added because you said 'I want to see the data.' It is bound to 0.0.0.0 with the default credentials baked into the image.

How it gets exploited

The attacker fingerprints the app, scrapes the Next.js build manifest, and lists every route the client knows about.

  1. 1
    Find the extras
    Among the user-facing routes are /api/admin/users, /api/internal/queue, and /api/debug/env. None of them were meant to be public. All of them respond.
  2. 2
    Walk the debug route
    /api/debug/env returns the process env as JSON. Inside: a Stripe live key, a Resend API key, the database URL, and the NextAuth secret.
  3. 3
    Cross-origin the session
    The app allows Access-Control-Allow-Origin: * with credentials. The attacker hosts a page that fetches /api/me from the victim's browser and reads the session data — no XSS needed.
  4. 4
    Log into the admin tool
    A scan of the box finds a Redis Commander on port 8081 with admin/admin. Full read and write on the session store.

None of these were bugs in the code. They were bugs in the config. Each one on its own would have been ugly. Together, they are a complete compromise of the app — and the code-review tool has nothing to say about any of them.

Vulnerable vs Fixed

Vulnerable — permissive defaults, no gate on internal routes
// app/api/_middleware.ts
export function middleware(req: Request) {
  const res = NextResponse.next();
  res.headers.set('Access-Control-Allow-Origin', '*');
  res.headers.set('Access-Control-Allow-Credentials', 'true');
  return res;
}

// app/api/admin/users/route.ts
export async function GET() {
  // "Internal only — nobody will call this from outside"
  return Response.json(await db.user.findMany());
}

// app/api/debug/env/route.ts
export async function GET() {
  return Response.json(process.env);
}
Fixed — explicit origins, gated internal routes, no debug in prod
// app/api/_middleware.ts
const ALLOWED = new Set([
  'https://app.example.com',
  'https://www.example.com',
]);

export function middleware(req: Request) {
  const origin = req.headers.get('origin') ?? '';
  const res = NextResponse.next();
  if (ALLOWED.has(origin)) {
    res.headers.set('Access-Control-Allow-Origin', origin);
    res.headers.set('Access-Control-Allow-Credentials', 'true');
    res.headers.set('Vary', 'Origin');
  }
  return res;
}

// app/api/admin/users/route.ts
export async function GET(req: Request) {
  const session = await requireAdmin(req); // throws 403 otherwise
  return Response.json(await db.user.findMany());
}

// app/api/debug/env/route.ts — deleted in prod builds
export async function GET() {
  if (process.env.NODE_ENV === 'production') {
    return new Response('Not found', { status: 404 });
  }
  return Response.json({ env: 'dev only' });
}

Three rules. CORS is an allowlist of exact origins, never a wildcard with credentials. Every route on a public router requires auth — there is no such thing as 'internal only' on an endpoint anyone on the internet can reach. Debug endpoints do not exist in production builds. Delete them or gate them on NODE_ENV.

A real case

Exposed management consoles keep ending up in search results

Every year, researchers find hundreds of production Kibana, Redis Commander, and Elasticsearch dashboards bound to 0.0.0.0 with default credentials — discovered by simple Shodan queries, not exploits.

Related reading

Glossary

Brute Force Protection (Rate Limiting)

What we find

security misconfiguration

References

  • API8: Security Misconfiguration — official OWASP entry
  • OWASP API Security Top 10 (2023) — full list
  • CWE-2 on cwe.mitre.org
  • CWE-16 on cwe.mitre.org

Find the settings that are wrong.

Flowpatrol checks every route, every CORS header, and every management port for the defaults that should have been turned off. Five minutes. One URL.

Try it free