Security Tip: Fix Your Leaky APIs!

[Tip#70] This is your periodic reminder to check your app for any leaky APIs and fix them ASAP, otherwise you might end up with an email from Have I Been Pwned's Troy Hunt...

Security Tip: Fix Your Leaky APIs!

We’ve covered this topic before, with Sensitive Model Attributes, and Leaking Data After Changes, but in light of the Spoutible data breach, which was recently loaded into Have I Been Pwned (HIBP), I felt it was a good time for a reminder.

Firstly, if you’re unfamiliar with the breach, Troy Hunt has written up a great post outlining what happened, and I highly recommend you give it a read. It’s one of these stories that just keeps getting worse and worse, the more you read!

Regarding the actual data breach, here’s the summary from HIBP:

In January 2024, Spoutible had 207k records scraped from a misconfigured API that inadvertently returned excessive personal information. The data included names, usernames, email and IP addresses, phone numbers (where provided to the platform), genders and bcrypt password hashes. The incident also exposed 2FA secrets and backup codes along with password reset tokens.

Yes, you read that right: Spoutible had an API endpoint which returned:

  • names
  • usernames
  • email addresses
  • IP addresses
  • phone numbers (if provided)
  • genders
  • bcrypt password hashes
  • 2FA secrets
  • 2FA backup codes
  • password reset tokens
  • And more…

We’re talking a whole boatload of Personally Identifiable Information (PII) and Authentication Data, which would be incredibly useful for stealing identities, hijacking user accounts, and a bunch of other malicious activities! The sheer amount of data available in this breach, especially via an API like this, is rarely seen.

While Troy’s post doesn’t answer the “Why was this data available?” question, it’s pretty easy to assume that the folks who built the API did something like this1:

class User
{
    public function index()
    {  
        return User::all();
    }

    public function show(User $user)
    {
        return $user;
    }
}

Unless protections are put in place, these controller actions will return everything on the user model. This may include all sorts of PII and auth data, such as what happened with Spoutible. That said, Laravel does give us some tools and helpers for avoiding this in our own apps, so protecting against it is easy - as long as you’re intentional with your data and your APIs.

Here are my recommendations:

  1. Use $hidden to protect sensitive attributes from being returned via toArray(), and toJson(), etc.
    See: Security Tip: Sensitive Model Attributes
  2. Use only() on the model or collection to limit the returned attributes on a per-request basis.
    See also: Security Tip: Leaking Data After Changes
  3. Use API Resources and manually define each returned model attribute.

Don’t forget: This doesn’t just affect public APIs, but it can also occur for internal APIs used within SPAs, or even components that serialise model data into attributes. Any time you’re sending data to the browser or over an API, be aware of who can access it and what data is being sent!


Looking to learn more?
Security Tip #51: Validate Your Webhooks!
▶️ In Depth #17: Storing Environment Variables Safely

👉 Looking to dive deeper into Laravel security? Check out Practical Laravel Security, my hands-on security course that uses interactive hacking challenges to teach you about how vulnerabilities work, so you can avoid them in your own code. 🕵️

👉 Worried about your app being hacked? Book in a Laravel Security Audit and Penetration Test! I can find the vulnerabilities before a hacker does, and help you fix them. 🕵️


  1. I don’t think it’s a Laravel app, but there is a good chance it was the equivalent to this.