Est.

Hardcoded Secrets in SaaS Codebases and Their Real-World Impact

Leaked credentials stay valid for years, turning hardcoded secrets into persistent attack paths.

Features Editor · · 12 min read · Updated
Cover illustration for “Hardcoded Secrets in SaaS Codebases and Their Real-World Impact”
Verified Vulnerabilities & Exploits · August 15, 2026 · 12 min read · 2,658 words

GitGuardian's 2025 numbers tell the story on their own: 28.65 million new hardcoded secrets turned up in public GitHub commits, a 34% jump from the year before and the biggest single-year increase anyone's recorded. This piece is about what happens after that commit gets pushed, not before. A hardcoded secret is a working attack path the second it lands, and treating it as anything less is how teams end up breached.

The 34% jump undersells the actual trend. Leaked secrets on public GitHub have grown 152% since 2021, while the developer population grew only 98% over that same stretch. Secrets are piling up faster than the people who might catch them, which is not a comforting ratio if you're the one on call. Generic secrets, the ones with no fixed prefix, no checksum, nothing a scanner can pattern-match against, now make up 58% of everything detected. I'd bet the real total runs higher than what anyone's counting, honestly.

AI-assisted coding is pouring gas on $a fire that was already lit. Commits made with AI help leak secrets at more than double the rate of human-only commits, 3.2% versus 1.5%. AI-service credentials alone, LLM API keys, embedding service keys, rose 81% year-over-year and crossed 1.27 million detected leaks in 2025. Repos using GitHub Copilot showed a 6.4% leakage rate. These tools suggest code based on patterns they were trained on, and a lot of that training data had real credentials baked right into it; the model picked up the exact habit that leaks a secret and now hands it back to you, politely, in autocomplete.

Mobile apps carry the same risk, just quieter. A scan across more than 156,000 mobile apps turned up over 815,000 leaked hardcoded secrets, keys to cloud storage, payment processors, sitting inside the compiled binary where nobody thinks to look. There's a newer wrinkle too: GitGuardian found 24,008 unique secrets inside MCP (Model Context Protocol) configuration files on public GitHub, and 2,117 of them were still valid. Some of that traces back to the official MCP quickstart docs, which show API keys hardcoded directly in the example config. When the getting-started guide teaches the bad habit, the bad habit spreads fast, and it spreads through no fault of the developer copying it.

The private repository assumption that gets teams into trouble

Private repositories are eight times more likely to contain hardcoded secrets than public ones. Read that twice if you need to, because it cuts against what most engineers assume walking into the job.

The reason is almost embarrassing once you say it out loud: developers treat private repos like a locked drawer in their own house. Nobody's indexing them, nobody's scraping them, so the instinct to sanitize before committing never really kicks in. Thirty-five percent of private repos scanned contained at least one plaintext credential. AWS IAM keys show up in 8% of them, five times the rate in public repos, and hardcoded passwords appear three times as often.

Private access controls stop public indexing. That's the whole job they do, and nothing more. None of it matters once someone gets in through a compromised developer laptop, a stolen access token, or a supply chain intrusion that hands an attacker legitimate repo credentials. At that point, "private" is just a label on the folder, not a control.

The.gitignore fix half of engineering teams reach for doesn't fix much either. Adding a file to.gitignore stops future commits from including it, but does nothing to the commit history that already exists. A secret pushed once, even if it's deleted in the very next commit, lives on in git history until someone goes in and purges it deliberately. Most teams never do.

I don't have a tidy stat to close this section on. The problem here is structural: the control most teams lean on, repo privacy, is built for a threat that isn't the one actually showing up.

How long exposed credentials actually stay valid

GitGuardian flagged a batch of legitimate credentials back in 2022 and checked back in on them years later. Nearly 70% were still valid as of January 2025. As of January 2026, 64% still hadn't been revoked. Say that out loud and it sounds like a typo, but it isn't.

A key committed by a developer who left the company two years ago can still open the door today. That persistence is the whole engine behind this problem, and rotation lags for reasons that are pretty mundane once you've lived through one of these cleanups yourself. Secrets buried in old commits lose out against whatever fire is burning today. Rotating a credential means first mapping out everything that depends on it, and in a codebase with any real age, that map is usually incomplete or just doesn't exist. Teams also fall into the trap of reading silence as safety: no alert fired, so nothing must be happening.

That logic runs backwards, though. The dangerous stretch is the quiet one after the commit, sometimes years long, where the credential just sits there, live and unwatched. Breaches involving stolen or compromised credentials take an average of 292 days to identify and fix, close enough to a full year to make the point on its own. Stolen or compromised credentials show up in 31% of all breaches over the past decade per Verizon's Data Breach Investigations Report, and hardcoded secrets feed straight into that number.

The actual attack path from a leaked credential to a compromised environment

Discovery isn't hard. Public repo indexing, GitHub dorks, scrapers running around the clock, finding exposed secrets at scale takes a script, not a genius. Once an attacker has a candidate, the next move is validation: checking whether the credential still works. For most API key formats, that step is trivial to automate too.

From there, blast radius depends entirely on what got leaked.

An AWS IAM key lets an attacker call sts:GetCallerIdentity to figure out what the key can actually do, then pivot into S3 access, EC2 enumeration, or privilege escalation through whatever IAM policy is attached to it. A database connection string skips all of that; it's direct read and write access to production data, no lateral movement required. A third-party SaaS key, Stripe, Twilio, SendGrid, can pull customer data, push fraudulent transactions, or blast phishing emails from a domain your customers already trust. An OAuth client secret can let an attacker impersonate the application itself, depending on scope, reaching into every connected user account. An LLM provider key lets someone run model calls on your dime, or dig into fine-tuned models and whatever data got fed into prompts along the way.

The real damage rarely stops at one credential. Here's the chain most write-ups skip past: a leaked GitHub token grants repo read access, that repo has a hardcoded AWS key sitting in it, and the key is attached to an overpermissive IAM role. That role can read Secrets Manager or Parameter Store, and those managed secrets include, of course, the production database credentials.

Follow that all the way through, and a single leaked GitHub token turns into full database access, no vulnerability in the application code required. The secret itself was the vulnerability, and the app never had to be wrong about anything.

This is the line between secrets sprawl as a hygiene problem and secrets sprawl as an attack path. The individual credential is rarely the target; it's the first rung on a ladder. A real whitebox pen test maps the whole ladder, following the credential forward through IAM policies, service permissions, and whatever infrastructure it can reach, instead of stopping at "we found a secret" and calling the job done.

What real incidents show about how this plays out in practice

The 2024 U.S. Treasury breach traced back to a leaked API key for BeyondTrust's authentication platform. Attackers walked past layers of security investment because one exposed credential was all they actually needed.

The 2025 Salesloft/Drift-Salesforce breach followed the same shape on a longer fuse. Investigators traced the root cause to a GitHub compromise that happened months before the real damage showed up. Secrets exfiltrated in that window, AWS credentials and Snowflake keys among them, sat unused until attackers came back and used them to pull sensitive customer data through SOQL queries. That gap between compromise and weaponization lines up almost exactly with the 292-day average dwell time from earlier. The damage was set up long before anyone noticed anything wrong.

Then there's the Red Hat internal GitLab breach from October 2025. A group calling itself Crimson Collective pulled 570 GB of data out of 28,000 repositories on an internal GitLab instance, touching roughly 800 organizations worldwide along the way. Private infrastructure, internal repos, same category of exposure as everything else covered here.

Sitecore's CVE-2025-53690 shows the problem reaches past API keys too. A hardcoded ASP.NET machine key, paired with an insecure deserialization flaw, let attackers run unauthenticated remote code execution. Cryptographic material embedded in application config is a hardcoded secret just the same, and it does exactly as much damage as a leaked cloud key would.

Worth going back further, too: Deloitte in 2017, internal VPN credentials sitting openly accessible in a public GitHub repo. This pattern hasn't really changed in nearly a decade, despite how much more awareness exists now than there was then.

Across every one of these, the attacker never needed a novel exploit. The credential was the vulnerability, full stop, and everything downstream was just follow-through.

Why standard detection controls miss more than teams expect

Generic secrets, that 58% chunk from earlier, slip past pattern-based detection constantly because there's no fixed shape to catch. No prefix, no set length, no checksum to check against. GitHub's Push Protection has cut down leaks of specific, well-known credential formats, but it hasn't done much against generic secrets, which happen to be the category growing fastest. The most widely deployed control in the industry is getting outrun by the biggest slice of the problem it's supposed to solve.

There's also a gap between git history scanning and commit-time scanning that trips teams up constantly. Pre-commit hooks and push protection catch what's coming in right now, but they say nothing about what's already sitting in history. A secret committed two years ago and never rotated stays invisible to commit-time tooling, no matter how good that tooling gets.

Detection without rotation is a half-measure, and I've seen teams treat a clean scan report like a finish line. Finding a secret isn't the same as closing the exposure. When the credential's still live and the history hasn't been purged, that finding is just documentation of an open door, nothing more.

Third-party integrations add another blind spot. Secrets passed through CI/CD environment variables, deployment scripts, or infrastructure-as-code configs often sit outside the reach of tools that only scan application code. The MCP configuration issue is a preview of what keeps happening: every time a new pattern shows up, whether it's AI tooling configs or a new framework's quickstart template, detection tooling has to catch up, and there's always a lag before it does.

Scanning is a necessary first layer. Knowing what an attacker could actually do with a finding is a different kind of work entirely, and it's the part most tooling stops short of.

What a penetration test finds that a scanner cannot

Venn diagram: Secret Detection: Scanners vs. Pen Tests. Compares Automated Scanners and Penetration Testing; overlap: Shared Capabilities.

A scanner tells you a secret exists somewhere in your code. A pen test tells you what that secret unlocks, and how far someone could travel from it once they have it in hand.

Whitebox access is what makes that possible. Access to source code means a tester finds secrets buried in config files, environment variable defaults, and initialization scripts, not just the obvious committed strings. Access to cloud configuration means a tester can map exactly what IAM roles a leaked key controls and what services sit within reach of it. Access to infrastructure-as-code, Terraform, CloudFormation, whatever the stack runs on, surfaces secrets baked into resource definitions that no code-only scanner will ever see.

The chained path from earlier, leaked GitHub token to hardcoded AWS key to overpermissive IAM to Secrets Manager to production database, only becomes visible to someone who can follow it across repo, cloud config, and runtime environment all at once. A scanner working from the outside can't do that. It doesn't have the map, and it was never built to draw one.

A finding without a working exploit is really just a guess dressed up in a report template. A rigorous test proves the blast radius by demonstrating access, not estimating it off a CVSS score. Human judgment still matters at the classification step, too: telling a rotated key with zero risk apart from an active key that's live and exploitable right now takes a level of judgment automated tools apply unevenly at best.

This is the approach Trace takes: whitebox access to source code, cloud configs, and internal documentation, every finding backed by a working exploit, OSCP-certified expert review before anything lands in a report. That's the gap between running scans and actually knowing your blast radius. Continuous PR scanning fits the same idea. Catching a secret before it merges costs a lot less than cleaning up one that's been sitting live in a private repo for eight months.

How to respond when a hardcoded secret is found — in code review, in a scan, or in a pen test report

Revoke it immediately. Before scoping the damage, before opening a ticket, before anything else gets touched. An unrevoked credential is a live threat no matter what else is on fire that day, and I've watched teams lose an hour to Slack debates about severity while the key just sits there, working.

Then assume someone else already found it. Treat the credential as compromised from the moment it was committed, not from the moment your team happened to notice it, and pull access logs starting from that first commit date, not from today. Nearly 70% of credentials leaked in 2022 were still valid three years later; the gap between commit and discovery runs in years far more often than anyone's comfortable admitting out loud.

Purge the git history next. Revoking the key doesn't erase it from history, and any future fork, clone, or access grant will still expose the old value sitting there. Git filter-repo is the right tool here, generally preferred over the now-deprecated BFG Repo Cleaner these days. Force-push the cleaned history to every branch, and tell every collaborator to re-clone rather than pull; a pull will just merge the old history right back in.

From there, scope the blast radius. What did the credential actually touch, and is there any evidence it got used? For AWS, dig through CloudTrail logs tied to that IAM key: unusual API calls, new IAM users that shouldn't exist, odd S3 access patterns. For third-party APIs, check whatever access logs the provider makes available, and look for spikes in call volume or data exports nobody on the team can explain.

Fix the root cause, not just this one instance of it. Swap hardcoded values for environment variables, a real secrets manager like AWS Secrets Manager, HashiCorp Vault, or GCP Secret Manager, or a secrets injection step in CI/CD. If a codebase produced one hardcoded secret, the same pattern is almost always sitting somewhere else in it too. It rarely happens just once.

Retest once the fix is in. A pen test finding isn't closed until a tester confirms the attack path no longer works, and retesting belongs in the original engagement scope, not billed separately as an afterthought.

Prevention costs less than cleanup, every single time, no exceptions I've ever seen. Pre-commit hooks and PR scanning catch secrets before they land in a repo, and secrets detection built into CI/CD pipelines catches whatever slips past that first layer. Stack these controls, and incident response stops being a recurring event. It settles into the exception it should have been all along.

Sources

  1. blog.rsisecurity.com
  2. gitguardian.com
  3. cybernews.com
  4. labs.cloudsecurityalliance.org

More in Verified Vulnerabilities & Exploits