In The News

The Front Door Was Open: What McKinsey's AI Breach Teaches Us About API Security

McKinsey's AI breach started with a missing auth check. Here's how to make sure it doesn't happen to you, with theoretical and practical guidance.

Albert Heinle
Written by
Albert Heinle

A few days ago, CodeWall disclosed that McKinsey's internal AI platform, Lilli, had over 200 publicly exposed API endpoints, 22 of which required no authentication at all. One of those unprotected endpoints contained a SQL injection flaw that led to the exposure of 46.5 million chat messages, 728,000 files, and 57,000 user accounts.

The breach is severe. There were multiple vulnerabilities at play here: the SQL injection, IDOR flaws, exposed system prompts. Each of those deserves to be found and fixed on its own. That is defense in depth. But an attacker would not have had such an easy game if the first layer of defense, authentication on all endpoints, had been in place. Scanning those endpoints before deployment would have surfaced the problem immediately.

22 endpoints had no authentication. That was the first domino.

At CoGuard, we have seen this exact pattern surface over and over again in the infrastructure configurations we scan. It is one of the most common misconfigurations we catch, and one of the easiest to prevent.

How Does This Happen?

Most modern web frameworks work the same way. You write a function, you add a decorator or annotation to protect it, and you move on. Here is what that looks like in a typical Python framework:

@app.route("/chat/messages")
@require_auth          # <-- developer remembers to add thi
def get_messages():
...
app.route("/search/query")‍                       # <-- developer forgets, endpoint is now public‍def run_search():‍    ...

The problem is obvious in a two-endpoint example. It is invisible in a platform with 200+ endpoints built by multiple teams over months or years. Someone forgets the decorator on one function, a new hire does not know it is required, a quick prototype gets pushed to production, and suddenly you have an open door.

This is not a people problem. It is a design problem.

The Fix: Auth by Default, Opt Out Where Needed

The correct approach flips the model. Instead of requiring developers to remember to add authentication, you make authentication the default and require developers to explicitly opt out for the handful of endpoints that should be public: health checks, public status pages, login routes.

Here is the difference:

# WRONG: opt-in authentication (easy to forget)
@app.route("/internal/data")
@require_aut
h‍def internal_data():‍    

...

RIGHT: auth is on by default, opt-out where justified‍app = create_app(default_auth=True)
@app.route("/health")
@public            # deliberate, reviewable decision‍def
health_check():
    return {"status": "ok"}‍@app.route("/internal/data")   # protected automatically‍def internal_data():‍ 

...

When authentication is the default, every public endpoint becomes a conscious, reviewable decision instead of an accident of omission.

Why Code Scanners Miss This

You might assume that static analysis tools would catch an unprotected endpoint. In practice, they usually don't. The ecosystem of web frameworks is enormous: Django, Flask, FastAPI, Express, Spring Boot, .NET, and dozens more, each with its own conventions for middleware, decorators, and route definitions. A scanner that understands Flask decorators may not understand how Spring Security filters work, and vice versa.

But there is a place where all of these frameworks converge: the OpenAPI specification.

Whether you are using Python, Java, Node.js, or anything else, most modern frameworks can generate an OpenAPI spec (sometimes called a Swagger spec) as part of the build process. This is a structured, machine-readable document that describes every endpoint your application exposes, including its security requirements.

Here is a simplified snippet from an OpenAPI spec:

paths:
  /search/query:
    get:
      summary: Run a search query
      # notice: no 'security' field, this endpoint is unprotected
      responses:
        "200":
          description: Search results
  /chat/messages:
    get:
      summary: Get chat messages
      security:
        - bearerAuth: []     # protected
      responses:
        "200":
          description: Chat messages

An endpoint missing the security field is easy to detect programmatically. You do not need to understand any specific framework. You just need to read the spec.

This is exactly what CoGuard does. By scanning OpenAPI specifications and other infrastructure configurations before deployment, we catch misconfigurations like unauthenticated endpoints before they ever reach production, regardless of the language or framework used to build them.

The Deeper Lesson

After the unauthenticated endpoints were discovered in McKinsey's platform, additional vulnerabilities followed: SQL injection through unsanitized JSON keys, IDOR flaws allowing cross-user data access, exposed AI model configurations, and writable database access that could have enabled prompt manipulation.

Each of these is a real vulnerability that needs to be fixed independently. Defense in depth means you do not rely on a single layer. But the pattern we see over and over again tells a clear story:

  1. An endpoint ships without authentication.
  2. That endpoint contains a second flaw: an injection, an authorization bug, an information leak.
  3. The combination turns a minor oversight into a major breach.

If those 22 endpoints had required authentication, an attacker would still have needed valid credentials before they could even probe for SQL injection or IDOR flaws. The attack surface shrinks dramatically. You still fix the SQL injection, you still fix the IDOR, but the window of opportunity for an external attacker becomes much smaller.

The first flaw is a configuration issue. It lives not in your application logic, but in how your application is assembled and deployed. It shows up reliably in OpenAPI specs, infrastructure-as-code files, and deployment configurations, exactly the artifacts that CoGuard is built to scan.

What You Can Do Today

  • Set authentication as the default in your framework configuration. Make public endpoints the exception, not the rule.
  • Generate and scan your OpenAPI spec as part of your CI/CD pipeline. If an endpoint lacks a security definition, the build should fail.
  • Treat configuration as a security boundary. Your application code may be flawless, but if the configuration that wraps it leaves endpoints exposed, it does not matter.

The McKinsey breach is a reminder that the most consequential security failures are often the simplest ones. Not a sophisticated zero-day. Not a novel attack technique. Just an open door that nobody checked.

CoGuard scans infrastructure configurations, including OpenAPI specifications, to catch security misconfigurations before deployment. If you want to see what it finds in your setup, try it out.

Explore a test environment

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Check out and explore a test environment to run infra audits on sample repositories of web applications and view select reports on CoGuard's interative dashboard today.