API security consultant accidentally exposes own credentials: a post-mortem
2025-07-09
Yesterday, I got an automated email from Brevo telling me that someone's trying to access the Brevo API using my API key.
I've previously gotten some of these notifications. Mostly, they turned out to be false positives. I host my website on Netlify, which routes requests through different servers depending on where the visitor is located. Brevo has in the past flagged these legitimate requests, asking me to verify server IP addresses individually whenever someone tries to sign up for my newsletter. After dealing with multiple false positives, and with other business priorities demanding attention, I had started treating these alerts as routine noise.
Until yesterday's email.
This alert flagged activity from IPs outside of the US and EU. Since I knew Netlify is US-based and I don't have any infrastructure outside of the EU, this immediately raised red flags. A quick investigation revealed a critical security oversight on my part.
I had accidentally exposed my Brevo API key in client-side code whilst developing my new website. Without proper secret scanning in place, this key remained publicly accessible in my production code until I discovered and immediately revoked it.
The client-side catastrophe I should've seen coming
The problem was, in stereotypical fashion, sitting in front of the computer. When implementing a newsletter subscription component for my new website, I needed to send API requests to Brevo to handle user subscriptions. This required including my API key for authentication.
The critical error was that this newsletter component ran entirely in users' browsers, meaning the API key was embedded directly in the client-side JavaScript bundle. Anyone with basic technical knowledge could inspect the source code and extract the key. Given that Brevo API keys have a distinctive signature (prefixed with xkeysib-
), they're easily identifiable by automated scraping tools that scan websites for exposed credentials.
The potential impact was significant: With access to my Brevo API key, an attacker could have sent unlimited emails through my account, potentially damaging my sender reputation, racking up costs, or using my account for spam campaigns. They could also access my subscriber lists and campaign data.
The uncomfortable lessons I'm taking from this
When rapidly prototyping what I considered "just a marketing website," I relaxed the security standards I'd never compromise on for client projects. The mindset of this being a low-stakes project led to cutting corners on fundamental security practices.
From a user perspective, the frequent false positives from Brevo's IP monitoring created a "cry wolf" scenario. I've seen this play out countless of times. It's ultimately still my fault that I didn't pay attention to the emails, but I also learned some critical lessions about alert fatigue. In the future, I'll be more mindful of how I design user alerts in my own products to prevent alert fatigue whilst maintaining security awareness.
When you're managing multiple priorities simultaneously, from marketing, development, to business operations, it's easy to deprioritise security alerts that seem routine. To me, it once again highlighted the need for better automated tooling and processes in place to catch these critical mistakes early.
The solution wasn't complex - it just required moving the API call to a server-side endpoint with proper secret management. But under time pressure, I chose the path of least resistance rather than the secure architecture.
The actual impact was limited. I caught and addressed the exposure quickly, with no indication of malicious use beyond the initial notification. However, the potential consequences were serious enough to serve as a valuable reminder that security discipline applies to every project, regardless of perceived importance.
Below, I've written down all the technical details on the tools and processes I've implemented to prevent this from happening in the future. If that's interesting to you, read on.
Building the security guardrails I should have had from day one
Immediate response: Revoking the API key
When I spotted the API key exposed in client-side code, I immediately revoked it through Brevo's dashboard. This took about two minutes and ensured no one could send transactional emails using my credentials. Tt broke the newsletter component in the process, but given it was 11pm CEST, I was comfortable with that trade-off for a B2B audience.
Damage assessment: Reviewing suspicious emails
Next, I looked into Brevo's activity logs to identify any suspicious transactional or campaign emails. I was looking for unusual sending patterns, unexpected recipient domains, or any emails I hadn't authorised. Fortunately, the logs showed no evidence of that. It looked like I had discovered the exposed API key before it could be exploited.
Architectural fix: Moving secrets server-side
The core issue required an architectural change. Instead of handling newsletter subscriptions entirely in the browser, I rebuilt the component using SvelteKit's server actions.
Before: Newsletter component directly called Brevo API from the client, including the exposed API key
After: Newsletter component submits a form action, which calls the Brevo API server-side
This ensures the API key remains in server-side environment variables, never shipped to browsers. The user experience is identical, but the security posture is fundamentally different.
Automating security checks
To prevent future incidents, I implemented a comprehensive CI/CD security pipeline with four stages:
Code quality and secret scanning on commit:
- ESLint, Prettier, and svelte-check (failing on warnings)
- GitLeaks for secret detection
Dependency analysis:
- Syft generates a Software bill of materials (SBOM)
- Grype scans the SBOM for known vulnerabilities
- This catches risk from direct as well as third-party dependencies
- Some might consider this exotic, but I consider it a sensible default if you're already building a CI/CD pipeline
Static code analysis:
- SonarQube integration for code quality, security hotspots, and static code analysis
- Provides continuous code quality checks
Deployment gate:
- Netlify deployment only proceeds if all security checks pass
- Netlify's auto-deploy feature bypasses GitHub Actions by default, which requires an adjustment from Netlify support
As the project is already hosted on GitHub, I used GitHub Actions for this. This required repeating the checkout and build steps for every job individually, as jobs run in parallel and artifacts are not cached between runners. This is a big difference in comparison to GitLab and quite unusual.
All of these checks should've been implemented from the start, but easily slipped through the cracks as this was a "simple" marketing website and the priority was simply shipping it.
What's still missing
I consciously decided against implementing runtime monitoring (like Sentry) at this stage, as it's a marketing website with limited dynamic functionmality. I suspect, given my track record, that I'll be back to implement this sooner rather than later.
I fixed the issue within two hours of discovery, and with the comprehensive automated security checks in place, this issue is unlikely to come up again.