Brand logo
HomeCloud ComputingSecuritySoftware DevelopmentAI ApplicationsStartupsGeneral
  1. Home
  2. /
  3. security
  4. /
  5. gitee-credential-exposure-7-25-million-repositories

Credential Exposure in the Wild: A Security Playbook Built on 26,400 Verified-Live Secrets Across Public Gitee Repositories

October 28, 2026 · 7 min read · James Hartley, IT Insights — ItInsightsWorld Security desk

Secrets seldom surface during a code review; they surface inside an incident report. A large-scale measurement study now gives defenders a baseline instead of a guess: scanning 7,256,531 public repositories on Gitee — China's largest home-grown Git hosting platform — ended with 26,400 unique verified-live credentials, among them an active organization-admin credential for a major enterprise AI platform and thousands of live cloud provider access keys. The underlying work was contributed as a guest post by independent security researcher Ben Zimmermann, produced with the Kingfisher team at MongoDB. What follows reframes it as checks, misconceptions and actions for security and platform teams.

Scope in one line: TruffleHog and Kingfisher were run against 7.25 Million public Gitee repositories, and every candidate was authenticated against the issuing vendor's API before it was counted.

Why this platform behaves differently from the ones you already monitor

Gitee launched in 2013 under OSChina and was chosen by China's Ministry of Industry and Information Technology as the national open-source code hosting platform. The defensively interesting part is not the mandate but the tooling gaps. Its public API is severely constrained: there is no paginated projects endpoint, and the on-site code-search feature appears to have been retired years ago. A 2021 attempt to study credential exposure on Gitee concluded that its APIs were too limited to scale the research. Visibility is thin for researchers, for the operator itself, and for anyone else.

As with any large Git platform, that blind spot sits on an attractive exposure surface:

  • Because it is Git-based, secrets buried deep in commit history persist.
  • Millions of public repositories carry continuous commit volume, so the target set keeps refreshing.
  • There is no public equivalent of the platform-vendor secret-scanning partnerships that have become standard on GitHub.

Enumerating a repository universe with almost no official API

Enumeration required combining many surfaces: the only official REST endpoint that returned useful data at scale, the platform's Explore feed, per-user repository and stargazer pages on the website, and the internal Elasticsearch widget behind the on-site search UI.

Step 1 — bootstrap a username corpus

That endpoint was GET /api/v5/search/users?q=, queried with three-character prefix queries ( aaa , aab , ..., zzz ). Results were merged with usernames from the public Explore feed ( /explore/all?order=starred|popular|updated ), then expanded through each user's following list and the stargazers of popular repositories. The seed corpus reached 1,704,759 usernames.

Step 2 — scrape per-user pages on the frontend

Two per-user pages yield repositories: gitee.com/{user}/projects and gitee.com/{user}/stared_projects. Page 1 of /projects returns full HTML, while later pages are AJAX-paginated ( X-Requested-With: XMLHttpRequest ). The stargazer-list pages used during username bootstrap paginate the same way and also require a CSRF token extracted from the page-1 HTML.

Step 3 — drive the internal search widget

A large fraction of the platform sits outside the social graph, so the same endpoint that backs the search UI was queried directly: so.gitee.com/v1/search/widget/wong1slagnlmzwvsu5ya , accepting a query ID, a q parameter, and pagination via from + size :

Unformatted
Code Snippet  # elastic_scrape_repos.py
API_URL = "https://so.gitee.com/v1/search/widget/wong1slagnlmzwvsu5ya"
QUERY_ID = "1048"
PAGE_SIZE = 20
MAX_RESULTS = 200
async def fetch_page(session, username, offset=0, language=None):
params = {
"query": QUERY_ID,
"q": username,
"from": str(offset),
"size": str(PAGE_SIZE),
"sort_by_f": "",
}
if language:
params["编程语言"] = language  # "programming language"
async with session.get(
API_URL,
params=params,
headers={**HEADERS, "userid": str(uuid.uuid4())},
) as resp:
resp.raise_for_status()
return await resp.json()

Each query returned at most 20 results per page under a hard cap of 200 results total ( from + size <= MAX_RESULTS ), regardless of how many matches actually existed. For specific usernames that was usually fine; for common usernames and trigram prefix queries the cap silently truncated results.

Step 4 — escape the cap by splitting buckets

The widget's response includes Elasticsearch aggregations that bucket results by language ( 编程语言 ) and license ( 许可证 ). Where a per-username query was capped, it was re-issued once per language bucket that the first response had already returned, paginating inside each bucket and stopping as soon as a page came back shorter than PAGE_SIZE. For trigram prefix queries used to surface repositories not associated with any discovered user, the same idea extended into a BFS over a query tree: capped nodes split first by language, then by license, then by deeper prefix until each branch fit within 200 results. After deduplicating repository IDs across all sources, the working set was 7,256,531 unique public Gitee repositories.

Wiring the scan pipeline: one script, two scanners, serverless compute

Cloning and scanning 7.25 million repositories was handled in a single Python script using Modal , a serverless compute platform popular for AI workloads but well suited to most parallel jobs. TruffleHog and Kingfisher ran concurrently against every repository for increased coverage; each surface verified credentials the other missed, so running both caught more exposed credentials than either alone.

Figure 1. Dual-scanner pipeline architecture.

The Docker image

A multi-stage build pulls both scanner binaries from their upstream releases into a single Python base, plus git and OpenSSH for cloning:

FROM trufflesecurity/trufflehog:latest AS trufflehog
FROM ghcr.io/mongodb/kingfisher:latest AS kingfisher
FROM python:3.11-slim
COPY --from=trufflehog /usr/bin/trufflehog /usr/local/bin/trufflehog
COPY --from=kingfisher /usr/local/bin/kingfisher /usr/local/bin/kingfisher
RUN apt-get update && apt-get install -y git openssh-client && rm -rf /var/lib/apt/lists/*

The per-repo scan function

One Modal function bare-clones each repository over SSH and runs both scanners concurrently as subprocesses against the same on-disk clone — TruffleHog with --only-verified , Kingfisher with --only-valid — writing hits to a shared findings volume:

@app.function(
image=image,
volumes={"/findings": volume},
timeout=300,
cpu=(0.125, 2.0),
memory=(256, 4096),
max_containers=350,
)
def scan(repo_url: str):
repo_name = repo_url.rstrip("/").split("/")[-1].removesuffix(".git")
clone_path = f"/tmp/{repo_name}.git"
ssh_url = repo_url.replace("https://gitee.com/", "git@gitee.com:")
subprocess.run(
["git", "clone", "--bare", "--quiet", ssh_url, clone_path],
capture_output=True, check=True,
)
th_proc = subprocess.Popen([
"trufflehog", "git", "--bare", f"file://{clone_path}",
"--json", "--only-verified", "--allow-verification-overlap",
], stdout=subprocess.PIPE, text=True)
kf_proc = subprocess.Popen([
"kingfisher", "scan", clone_path,
"--format", "jsonl", "--only-valid",
], stdout=subprocess.PIPE, text=True)
th_output = th_proc.communicate()[0].strip()
kf_output = kf_proc.communicate()[0].strip()
# ... write hits to the shared findings volume

Dispatch, cost and wall time

The corpus was split into 50 batches of ~150K repos, then run sequentially at a steady 350 concurrent containers; smaller batches were safer at this scale and easier to resume after interruptions, while larger batches were unstable and hit clone-side blocks. Total wall time was roughly one month of continuous execution. Per-repo clone times were a major contributor: Gitee's servers sit in China, and network distance across the Pacific plus SSH protocol overhead made each clone materially slower than an equivalent GitHub clone. Total cost was $3,097.64, fully absorbed by the $5,000 compute-credit sponsorship from Modal that supported this research. The same Modal + dual-scanner architecture works against most Git hosts with little more than a clone-URL substitution; a smaller-scale version powered a prior investigation of Algolia DocSearch admin keys .

Reading the results without over-reading them

From those public repositories the pipeline surfaced 26,400 unique, verified-live credentials after deduplicating by token value. Both scanners authenticate each candidate against its vendor's API at scan time (TruffleHog's --only-verified and Kingfisher's --only-valid modes), discarding anything that pattern-matches but fails to authenticate, and the 26,400 figure is deduplicated by raw secret value, so duplicates across repositories or scanners count once.

ScannerUnique Findings
TruffleHog12,737
Kingfisher14,664
Caught by both1,001
Total unique credentials26,400

Either scanner alone would have caught only about half of these credentials; the two tools' detector coverage was largely complementary. TruffleHog dominated Alibaba Cloud key detection (9,428 vs. 993), while Kingfisher dominated DeepSeek (2,940 vs. 230), OpenAI (1,198 vs. 150), HuggingFace (154 vs. 1), and Chinese-AI services like Zhipu (1,489 vs. 0) that TruffleHog has no detector for.

By credential type

The single most common leaked credential type was Alibaba Cloud AccessKey pairs: roughly 1 in every 766 Gitee repositories contained a verified-live Alibaba Cloud credential. A long tail of additional vendors — LangSmith, OpenWeather, Algolia, PostHog, OpenRouter, MySQL, Stripe, Slack, Twitter, and others — accounts for the rest.

Figure 2. Credentials by vendor.

By commit date

The vast majority of verified-live credentials were committed in the last two years, with 2025 alone accounting for 12,242 unique credentials, nearly half the entire dataset.

Figure 3. Unique credentials committed by year.

By committer email domain

Most credentials were committed from personal mail services (qq.com, 163.com, gmail.com), making per-company attribution impossible at scale. The corporate-domain numbers are therefore a floor, and even that floor spans major Chinese and Western technology companies — the breadth includes Huawei, ByteDance, Google, Microsoft, Oracle, and others — alongside smaller footprints from Apple, Meta, Anthropic, plus Chinese commercial and academic domains.

Figure 5. Unique credentials by committer email domain.

Benchmark: Gitee against GitLab and Bitbucket

The closest prior data points come from TruffleSecurity's scan of 5.6 million public GitLab repositories , and a companion Bitbucket scan referenced in that post.

PlatformRepos scannedVerified-live credentialsPer 1K repos
Bitbucket~2,600,0006,2122.39
GitLab~5,600,00017,4303.11
Gitee7,256,53126,4003.64

Gitee tops both Western platforms on raw count and on per-repository density. One methodology note before you quote these numbers: the TruffleSecurity scans used TruffleHog alone, while this one used TruffleHog and Kingfisher together. TruffleHog's detector library skews toward Western vendors, and Gitee has heavy Chinese-vendor activity (Alibaba Cloud, DeepSeek, Zhipu, and others) that Kingfisher's coverage helps to surface; stripping the findings to TruffleHog-only puts Gitee at 1.76 per 1,000 repos.

Disclosure: what was done, and what was not

Vendors with the highest exposure volume in this scan were emailed a coordinated report containing the full list of verified-live credentials associated with their service, along with source-repository metadata to help them attribute affected customers. Per-developer notification — contacting the committer of each leaked credential individually — was not pursued at this scale, because vendor-side revocation is the more reliable path and does not require any action from the user who leaked the credential. At the time of writing, revocation status for the disclosed credentials has not yet been confirmed.

Common misconceptions

  • “Verified-live means an intrusion was confirmed.” It means the token authenticated against its vendor API at scan time. Nothing in the dataset evidences exploitation, and revocation status was still unconfirmed.
  • “One good scanner is enough.” TruffleHog returned 12,737 findings and Kingfisher 14,664, but only 1,001 were caught by both — either alone would have missed roughly half.
  • “Deleting the file removes the secret.” Because it is Git-based, secrets buried deep in commit history persist; the credential has to be revoked at the issuer, not just removed from a working tree.
  • “This is a problem belonging to one region.” Committer metadata spans Huawei, ByteDance, Google, Microsoft and Oracle as well as Chinese commercial and academic domains, so this is not a purely-Chinese-developer phenomenon.
  • “You need a rich public API to measure exposure.” One REST endpoint, frontend pagination and an internal Elasticsearch widget were enough to assemble 7.25M repository IDs.

Action checklist for security and platform teams

  1. Scan history, not HEAD. Treat every hosted Git repository as a credential store and search the full commit graph; anything already pushed to a public host should be assumed discoverable.
  2. Run at least two detectors and require verification. Pattern matching alone produces noise; validate candidates against the issuing vendor's API, the way TruffleHog's --only-verified and Kingfisher's --only-valid do.
  3. Prioritise cloud provider key pairs first. Alibaba Cloud AccessKey pairs dominated the dataset, and the headline sample included an active organization-admin credential for a major enterprise AI platform plus thousands of live cloud provider access keys.
  4. Close the platform gap yourself where the host provides nothing. Gitee has no public equivalent of the vendor secret-scanning partnerships standard on GitHub, so bring an independent scanner and a pre-commit gate.
  5. Focus triage on recent work. The bulk of live credentials were committed in the last two years, and 2025 alone produced 12,242 of them.
  6. Prefer issuer-side revocation over chasing committers, and record disclosure routes in advance: Western companies typically have bug bounty programs or accessible security channels, while most Chinese companies do not, leaving leaked credentials without a clear remediation path.
  7. Treat measurement as affordable. The full scan cost $3,097.64 inside $5,000 of sponsored compute, and the pipeline was portable across Git hosts — periodic internal scanning is cheaper than a single incident.

FAQ

How much credential exposure did the Gitee scan actually measure?

26,400 unique verified-live credentials across 7,256,531 public repositories — the 7.25 Million repository figure that frames the study — deduplicated by raw secret value after both TruffleHog and Kingfisher authenticated candidates against vendor APIs.

Why pair two scanners instead of one?

Detector coverage was complementary. TruffleHog led on Alibaba Cloud (9,428 vs. 993), while Kingfisher led on DeepSeek (2,940 vs. 230), OpenAI (1,198 vs. 150), HuggingFace (154 vs. 1) and Zhipu (1,489 vs. 0), where TruffleHog has no detector at all.

Is Gitee genuinely worse than GitLab or Bitbucket?

On published density, yes: 3.64 per 1K repositories against 3.11 for GitLab (~5,600,000 repos, 17,430 credentials) and 2.39 for Bitbucket (~2,600,000 repos, 6,212 credentials). Normalise the tooling by using TruffleHog only, however, and Gitee reads 1.76 per 1,000 repos — compare like-for-like before drawing conclusions.

Were the leaked keys revoked?

Not confirmed. Reports went to the highest-volume vendors with repository metadata for customer attribution, but revocation status for the disclosed credentials had not been confirmed at the time of writing.

Can this method be reused on our own Git hosts?

Yes. The Modal + dual-scanner architecture runs against most Git hosts with little more than a clone-URL substitution, cost roughly $3,000 end to end, and was previously used at smaller scale in an investigation of Algolia DocSearch admin keys.

The takeaway

Gitee is now a measurable credential-exposure surface rather than an unknown: higher per-repository density than comparable published scans of GitLab and Bitbucket, a committer population that spans every major vendor's workforce, and an asymmetric remediation landscape where disclosure often has nowhere to land. None of that requires exotic tooling to detect — two open-source scanners, one Python script and about a month of compute were sufficient. Teams that run the same checks against their own repositories, on their own schedule, will always learn the number before an attacker does. Questions on this analysis or on hardening Git-side secret detection for your environment are welcome at the IT Insights security desk, contact@itinsights.com .

Brand logo

IT Insights

Cloud & Infrastructure Practice Media for Enterprise Technology Decision-Makers

Cloud ComputingSecuritySoftware DevelopmentAI ApplicationsStartupsGeneralAI Data News Global

© 2026 IT Insights.

Cookie Notice

We use cookies to improve your experience and analyze website traffic. Read our Privacy Policy to learn more.