In June 2023, researchers at Aqua Security's Team Nautilus published a dataset analysis that should have unsettled every engineering leader relying on open-source dependencies: after sampling 1.25 million GitHub repositories, they found that 2.95% — nearly 37,000 repos — were vulnerable to a technique called repojacking, including repositories tied to Google and Lyft. Extrapolated across GitHub's full base of more than 300 million repositories, the researchers estimated millions of projects could be exposed. Repojacking exploits a quiet mechanic of GitHub's renaming feature: when a user or org renames an account, the old namespace becomes available for anyone to claim, and every project still pointing at the old name — in a go.mod, a package.json, an install script, a CI pipeline — gets silently redirected to whatever the attacker uploads there. This piece breaks down how the attack works, what Aqua's research revealed, why GitHub's existing safeguard falls short, and how Safeguard closes the gap.
What Is a Repojacking Attack?
A repojacking attack is the takeover of an abandoned GitHub username or organization name so that old references to repositories under that name resolve to attacker-controlled code instead. It works because GitHub automatically redirects github.com/old-owner/repo to github.com/new-owner/repo after a rename — a convenience feature meant to prevent broken links. The catch is that "old-owner" is just a username, and usernames on GitHub can be re-registered by anyone once the original account renames itself or is deleted. If an attacker registers that freed username and creates a repository with the matching name, every downstream project, build script, Dockerfile, or dependency manifest that still references the original path gets pointed at the attacker's repo instead of a 404. Because the compromise happens entirely outside the victim's own codebase, it can persist for years without detection — the source string never changes, only what it resolves to.
How Did Aqua Security Uncover the Scale of Repojacking?
Aqua Security uncovered the scale by systematically diffing a sample of GitHub repositories against their historical rename records and checking whether the resulting "vulnerable" usernames were still available to register. Their June 2023 report, "GitHub Dataset Research Reveals Millions Potentially Vulnerable to RepoJacking," found 36,983 exploitable repositories in a 1.25 million-repo sample — a 2.95% hit rate — and specifically called out Lyft as a concrete casualty. Aqua's team found an installation script inside a Lyft repository that fetched a ZIP archive from a second, unrelated repository whose owner had since renamed their account. Because that second repository's old namespace was unclaimed, Aqua (acting as researchers, not attackers) could have registered it and had their payload silently pulled into any environment running Lyft's install script. Google repositories showed the same pattern: internal or third-party references to renamed accounts that nobody had cleaned up.
Why Doesn't GitHub's Namespace Retirement Protection Stop It?
GitHub's protection doesn't stop it because the "Popular repository namespace retirement" feature only applies to repositories that had more than 100 clones in the week before the account was renamed — everything below that threshold is fair game for re-registration. That 100-clone bar sounds reasonable until you consider transitive dependencies: a niche utility library with 40 clones a week can still sit deep in the build chain of a widely used package, meaning the "unpopular" repo GitHub declines to protect is exactly the one attackers target. Even where the protection does apply, it has been repeatedly bypassed. Checkmarx's Supply Chain Security team reported two separate bypasses in 2022, independent researcher Joren Vrancken found a third the same year by deleting and restoring accounts to reset the retirement flag, and in September 2023 Checkmarx disclosed a fourth logic flaw — a race condition during account deletion and recreation — that left more than 4,000 packages across Go, PHP, Swift, and GitHub Actions exposed before GitHub patched it. Four distinct bypasses in roughly eighteen months is not a one-off bug; it's a structural weakness in how namespace ownership is verified.
Has Repojacking Been Exploited in the Wild?
Yes — this is not a theoretical research exercise. Checkmarx documented real-world exploitation in which an attacker used the technique to hijack open-source packages that collectively represented millions of downloads, poisoning at least two of three compromised projects via repojacking rather than credential theft or typosquatting. Because the attack path runs through infrastructure the victim doesn't control — someone else's renamed GitHub account — traditional code review and dependency pinning by version number don't catch it; the package name and version stay identical while the underlying source flips to malicious. Checkmarx's own disclosure timeline notes a six-month responsible-disclosure process with GitHub before the fourth bypass was fixed, meaning the exposure window for the 4,000+ affected packages ran well into 2023 before remediation landed.
What Made Google and Lyft's Exposure Possible?
Their exposure came down to indirect, unaudited references rather than any flaw in Google's or Lyft's own source code. In Lyft's case, an install script treated a GitHub repository path as a static, trusted source of a ZIP archive without pinning to a commit hash or verifying integrity after download — a pattern common across CI scripts, Dockerfiles, and Terraform modules that fetch "convenience" assets straight from raw.githubusercontent.com or a release URL. Neither Google nor Lyft had renamed the vulnerable accounts themselves; the risk originated one or two hops downstream, in a dependency's dependency, which is precisely why these references tend to go unnoticed. Nobody owns the responsibility of periodically re-checking whether every GitHub path baked into a build system still points to an account the original developer controls.
How Safeguard Helps
The pattern across Aqua's and Checkmarx's research is consistent: repojacking hides in references nobody is actively watching — install scripts, GitHub Actions, transitive Go modules, container base image sources — and it survives because verifying namespace ownership isn't a one-time check, it's an ongoing one. Safeguard is built to close exactly that gap.
Safeguard continuously scans your software supply chain — source repositories, CI/CD pipelines, container images, and dependency manifests — for direct references to GitHub usernames and organizations, then cross-checks each one against current account status. When a referenced namespace has been renamed, deleted, or transferred, Safeguard flags it immediately instead of waiting for an incident, and distinguishes between references that fall under GitHub's 100-clone retirement threshold and those that don't, since the latter are silently exploitable today. For dependencies pulled by URL rather than a package registry — the exact pattern behind the Lyft finding — Safeguard verifies that install scripts and CI steps pin to immutable references (commit SHAs or content-addressed hashes) rather than mutable paths, and surfaces every place in your pipeline where that discipline is missing.
Because repojacking exploits trust in identity rather than in code, Safeguard also maintains provenance records that tie each dependency back to a verified maintainer identity at the time it was first pulled into your build, so a namespace takeover shows up as a provenance mismatch before the malicious payload ever executes. Combined with policy enforcement that blocks builds from resolving unpinned third-party GitHub references, this turns repojacking from an invisible, persistent risk into a finding your team sees and remediates during code review — not after a Checkmarx or Aqua-style disclosure tells you it already happened.
If your organization ships software that depends on GitHub-hosted code — which is to say, nearly every organization — the question isn't whether a renamed namespace exists somewhere in your dependency graph. Aqua's numbers say it almost certainly does. The question is whether you find out from your own tooling or from the next headline.