The Security Scanner That Leaked the Secrets It Was Built to Find
A security scanner wrote customers' plaintext API keys into a public bucket. That was finding one of nine. Here is the full chain, the retest, and what is still open.
Target: Undisclosed — an enterprise AI automation SaaS platform. Reported privately to the vendor. The company is not named here, by policy.
A security scanner built to detect leaked credentials wrote the real credentials into its reports, then stored those reports in a public bucket.
Anyone on the internet could read them. No login. One request.
That was the first thing I found. It was not the last.
I started with no target bug, just a question: what does the outside of this company look like? The company sells AI automation agents to businesses, hosted or self-hosted. Over one recon session the answer grew into nine findings, from Critical down to Medium, covering other people's cloud keys, a competitor-grade strategy dump, and an install script that doubled as an architecture diagram for attackers.
Everything was reported privately. I retested about two weeks later, and I'm publishing the honest result: some things were fixed fast, some were half-fixed, and some are still open. That's why this post names no company, domain or endpoint, in line with my disclosure policy.
The scoreboard
| # | Finding | Severity | Status at retest |
|---|---|---|---|
| 1 | Public bucket full of plaintext API keys | Critical | Fixed |
| 2 | Installer bucket with infrastructure secrets | High | Partly fixed |
| 3 | Internal competitive strategy in the same bucket | High | Fixed |
| 4 | Customer emails leaking through the sitemap | High | Fixed (618 down to 2) |
| 5 | OAuth token manager with open signup | High | Partly fixed |
| 6 | CI/CD server on the public internet | Medium-High | Partly fixed |
| 7 | Internal API docs and wildcard CORS | Medium | Fixed |
| 8 | "Internal" portal facing the internet | Medium | Not fixed |
| 9 | 15+ non-production environments public | Medium | Not fixed |
Four fixed, three partly fixed, two not fixed. The details follow.
How I found it
Before touching anything actively, I mapped the footprint passively:
- Subdomain enumeration, then DNS resolution to see which names were live.
- Fingerprinting each live host for stack, headers and exposed paths.
- Content discovery with the boring stuff: sitemaps,
robots.txt, common paths.
The subdomain list was already a finding. A download subdomain pointed straight at a cloud storage backend. Several hosts sat directly on a web server instead of behind the CDN/WAF that protected the main app, and a number of services returned Access-Control-Allow-Origin: *. Two leads stood out: the sitemap and the buckets. Both were things the company had published deliberately, without thinking about what else they published.
1. The bucket that centralized everyone's secrets (Critical)
One cloud storage bucket allowed public LIST and READ. No authentication, any object.
It held validation reports: JSON output from an automated scanner that reviewed customer-submitted code for problems. The scanner's job was to flag hardcoded credentials. When it found one, it wrote the actual value into the report.
| Credential type | Owner | Risk |
|---|---|---|
| LLM provider API key | The vendor's own organization | Billing abuse on the vendor's account |
| Cloud IAM key pair | A customer | Access to that customer's cloud infrastructure |
| Code repository access token | A customer | Read/write access to private repositories |
| AI inference API key | A customer | LLM abuse at the customer's expense |
| Several more service tokens | Various customers | Third-party service access |
The reports covered 804 functions across 41 customer organizations. One misconfigured bucket became a third-party data breach spanning dozens of companies.
The scanner worked perfectly. It found every hardcoded key, and then it collected them into one downloadable file. A tool that logs the secrets it finds needs the same protection as the secrets themselves, or it should never record them at all (a masked value is enough).
Status fixed. Listing and direct reads now return 403.
2. The install script that doubles as a blueprint (High)
The self-hosted product shipped from a second bucket with the same flaw. It held 90+ artifacts, including 48 production binary builds (up to 764MB each) and full container images for every service in the stack: API, frontend, database, message queue and integration connector. There were also versioned installers and "latest version" pointer files used by the update system.
The install script was the most revealing file. It laid out every secret category the platform depends on:
[Auth] authentication provider credentials
[Payments] payment processor secret keys and webhook secrets
[AI/LLM] multiple LLM provider API keys
[Email] email service API keys
[Analytics] CRM and analytics tokens
[Database] superuser and application passwords
[Cache] cache authentication password
[Cloud] cloud storage credentials
The product also shipped with default credentials, so any customer who deployed without changing them left their database and message queue open.
If your deployment method is curl | bash, that URL deserves the same access control as your production secrets.
Status partly fixed. Listing is blocked, but at retest the individual files were still reachable.
3. The strategy documents sitting next to the keys (High)
The first bucket held more than validation reports. A sibling path contained the company's internal competitive-intelligence analysis, dated just weeks before I found it. It included threat assessments against the major players in AI infrastructure, a severity rating for each competitor move, recommendations on what to build next, and a product gap analysis.
This isn't a technical vulnerability. It's a business-intelligence breach. Any competitor, or any journalist, could read the company's strategic playbook: where it felt threatened and which moves it considered most dangerous.
Lesson: audit every prefix in a bucket, not just the one you were looking for. The customer keys were the loud finding, and the strategy documents sat quietly next to them.
Status fixed when the bucket was locked down.
4. A sitemap full of email addresses (High)
The organization sitemap existed for SEO. It listed over a thousand organizations, and many slugs were derived directly from the owner's email address:
/organization/{email-with-special-chars-replaced}-s-organization
→ john-doe-company-com → john.doe@company.com
Parsing it yielded 618 real email addresses: over 10 employee emails at the company itself, corporate emails from enterprise customers, student emails from schools in several countries, and more than 500 personal accounts. That's a ready-made phishing list, sorted by organization and served to every search crawler on the internet.
Never build a public slug from PII. Use opaque IDs. A sitemap is a public document.
Status fixed. At retest the sitemap had shrunk by half and the email-derived entries went from 618 to 2.
5. An OAuth token manager with the door open (High)
The platform ran a self-hosted open-source tool that manages OAuth tokens for third-party integrations. The instance was publicly reachable, open to self-registration, returned a wildcard CORS header, and exposed its exact build identifier on a config path.
I created an account to confirm the registration was open, and stopped there. I never touched any stored token, because that is where discovery ends and exploitation begins. But open signup plus permissive CORS in front of a token store is a serious risk by itself.
Status partly fixed. Signup is disabled. At retest the instance was still public, with the wildcard CORS header and config details still visible.
6. A CI/CD server facing the internet (Medium-High)
The CI server was public and leaked version numbers in its headers, treated anonymous visitors as an authenticated principal, and exposed its instance identity and CSRF values. For an attacker that is a map: the exact version to search for CVEs, confirmation that anonymous access works, and a way in to start with. CI/CD servers do not belong on the public internet.
Status partly fixed. The root page now returns 403, but at retest some information disclosure remained.
7. Internal API docs and a CORS time bomb (Medium)
Development and production instances of an internal key-value storage service both returned a full endpoint listing at their root path with no authentication: storage, retrieval, batch operations and container management. Both had wildcard CORS and sat directly on a web server instead of behind the WAF.
Endpoint documentation is an attacker's roadmap. And Access-Control-Allow-Origin: * on an internal API means its safety depends on authentication being perfect. The first bypass or leaked token turns into a cross-origin exploit.
Status fixed. Both instances now return 401.
8. A portal called "Internal" on the public internet (Medium)
A subdomain titled "Internal", with the description "Internal company portal", loaded fully with no login wall. It included a chatbot widget with an API key embedded in the page HTML, pointing at the production API. Anyone could lift the key from the page source and talk to that API directly.
If it has "internal" in the title, it should not resolve on the public internet. Put it behind a VPN or SSO gateway.
Status not fixed at retest. The portal was still public and the key was still in the source.
9. Fifteen non-production environments, all public (Medium)
Enumeration found 15+ development, release, sandbox and internal-facing environments, all with public IPs and live responses:
- Development and release builds of the main marketplace app, complete with UI
- Development and release APIs (some returning 401, which confirms the routes exist)
- Sandbox instances, some still serving default web-server pages
- An internal observability tool behind a third-party OAuth login
- An internal logging dashboard
Non-production is where security is weakest: debug endpoints on, verbose errors, test accounts with weak passwords, half-finished auth middleware. Every public subdomain is a door, and development doors tend to have the weakest locks.
Status not fixed at retest. At least four of these were still public.
The retest
About two weeks after the first report, I retested all nine findings.
| Finding | Original | Retest |
|---|---|---|
| Bucket with customer API keys | Listing and download open | 403 on every request |
| Installer bucket | Listing and download open | Listing blocked, individual files still reachable |
| Competitive intelligence | Downloadable | Resolved with the bucket fix |
| Sitemap emails | 618+ decoded | Reduced to 2 |
| OAuth token manager | Open registration | Signup disabled, CORS wildcard and config still exposed |
| CI/CD server | Public, with version headers | Root blocked, some disclosure remains |
| Internal portal | Public, with embedded key | Unchanged |
| Internal API docs | Full listing at root | 401, authentication required |
| Non-production environments | 15+ public | 4+ still public |
Four of nine were fully fixed. Three were partly fixed. Two had not changed.
The most damaging findings, the third-party credentials and the personal-email exposure, got priority, and the vendor deserves credit for that. The rest of the list is ordinary hygiene work, and I've reported it as such. I'll update this post when it's closed.
What I took away
- Public buckets are still the cheapest high-impact finding. This time the bucket held no backups or dumps. It held the output of a security tool.
- Audit every prefix, not just the obvious one. The strategy documents were sitting next to the keys.
- The installer is the architecture diagram. Every variable name, default and dependency is in the script.
- Wildcard CORS on internal services is a time bomb. It does nothing until another bug shows up, and then it makes that bug worse.
- SEO features leak PII. Nobody reviews a sitemap until someone parses it.
- Non-production is the forgotten attack surface. If it resolves in DNS, someone will find it. Restrict dev, release and sandbox to a VPN or IP allowlist by default.
- Tools that log the secrets they find need the same protection as the secrets. The scanner found every key and then handed them all to the internet.
If you defend a system like this, enumerate your own footprint the way I did. You will probably find something.
Tools: subfinder and assetfinder for enumeration, dnsx for resolution, httpx for probing, a template-based scanner for first-pass checks, then manual review with an HTTP client and browser DevTools.
Want a review of your own externally exposed surface? Reach me at hello@jmadhan.me.
MORE WRITE-UPS