promptdojo_

Three git disasters AI shipped — and what got rotated — step 2 of 8

Disaster 1: Uber, 2016 — AWS credentials in a private repo

Late 2016. Two attackers get into a private GitHub repository used by Uber's engineers. They didn't break GitHub — they logged in with engineer credentials harvested from somewhere else (the password reuse that powers half the breaches you'll ever read about).

Inside that private repo, they found what they were looking for: hardcoded AWS access credentials. Uber engineers had committed a working AWS key straight into source. The attackers used it to reach an Amazon S3 backup datastore and walked off with personal data on roughly 57 million riders and drivers, including about 600,000 US driver's license numbers.

The credential was in version control. That's the part this lesson cares about.

What actually failed

This wasn't one mistake. It was a chain of them, but the chain started with a single Class-1 control failure: a secret was in version control at all. Every other control downstream had to compensate for that fact. None of them did, completely.

Walk the chain:

  1. The AWS key was committed into source. No pre-commit hook scanned for credentials. No reviewer flagged the hardcoded value. The code worked, so it shipped.
  2. The repo was reachable with reused credentials. Once an attacker had an engineer's GitHub login, the private repo wasn't private anymore. "Private on GitHub" is not an access control for the secret inside the repo.
  3. The credential was a broad AWS key, not a scoped one. One leaked key reached a datastore with tens of millions of records. Least privilege would have shrunk the blast radius; it wasn't there.
  4. The credential was not rotated. Once committed, it stayed valid. Git history is forever — even if someone had deleted the line later, the secret was still recoverable from the commit log.

The control chain that would have caught it

Working backward from "attacker downloads 57 million records" to the earliest point a control could have broken the chain:

  • Mandatory rotation on a 30-day cadence. Would have made the committed key stale before it was used. Backstop, not prevention.
  • Least-privilege IAM — a key scoped to exactly what it needs, not broad account access. Would have limited blast radius. Backstop, not prevention.
  • Code review by a human who knew "we don't hardcode creds." Would have caught it IF the reviewer noticed. Reviewers miss these constantly. Weak.
  • .gitignore entries for *.config, credentials, etc. Only helps if the secret is in a separate file the developer thought to exclude. An inline key in source is useless to .gitignore.
  • Pre-commit hook that scans staged content for credential-shaped strings. Would have failed the commit before it left the developer's laptop. This is the earliest mechanical control in the chain.
  • Architectural rule: secrets read from environment variables or a vault at runtime, never present in source. If the code literally cannot work with an inline credential, the developer can't commit one even if they want to.

The last two are the wedge. Everything else is compensating controls.

What got rotated — and what it cost

After the breach, Uber rotated the exposed AWS credentials and secured the S3 data. The expensive part came later, because Uber concealed the breach for over a year — paying the attackers $100,000 through its HackerOne bug-bounty program to delete the data and stay quiet. When the new CEO disclosed it publicly in late 2017, the fallout landed:

  • An expanded FTC consent order in 2018 (the 2016 breach surfaced while Uber was already under FTC investigation for an earlier 2014 breach), with years of mandated security audits.
  • A $148 million settlement with all 50 US state attorneys general.
  • A criminal case against Uber's chief security officer, who was convicted in 2022 of obstruction for the cover-up — the first time a security executive was criminally convicted over breach handling.

Whenever a company runs a real secret-scan against its full git history for the first time, it finds embedded credentials it didn't know about. It always does. GitGuardian's 2025 State of Secrets Sprawl report found that 35% of private repos contain plaintext secrets.

What you should take from this

The cost of the breach to Uber: 8-figure regulatory and legal exposure, an FTC consent order, executive turnover and a criminal conviction, and the brand damage of paying hackers to hide a leak.

The cost of preventing it: a pre-commit hook (gitleaks, trufflehog, GitHub's built-in push protection — all free) and an architectural decision to never read secrets from anywhere but the environment or a vault. Engineering effort: maybe four hours.

The asymmetry is brutal. The cheapest possible control prevents the most expensive possible outcome. Every breach you read about will have this shape.

When Cursor or Claude Code runs git add . in your repo, your brain should reflexively pattern-match to "Uber 2016." Then you should check the diff.