In the SANS Emergency Webcast covering the TeamPCP supply chain campaign, my co-author Eric Johnson and I recommended that organizations add the GitHub repository exfiltration technique to their threat hunting playbooks. This post provides the technical depth needed to do exactly that.

The TeamPCP campaign compromised Trivy, Checkmarx KICS, and LiteLLM in March 2026 [1]. Most analysis has focused on the supply chain mechanics: the tag poisoning, the credential stealer, the CanisterWorm npm propagation. But one of the most operationally significant techniques has received comparatively less attention: using GitHub’s own infrastructure as a data exfiltration channel by programmatically creating repositories on the victim’s account and uploading stolen data as release assets via the GitHub REST API.

This technique appears to be novel. While GitHub repositories have been abused for command-and-control and malware distribution before [2], the TeamPCP campaign is the first publicly documented case of an attacker using the GitHub Releases API as a fallback exfiltration channel during a supply chain compromise, turning the victim’s own trusted infrastructure against them. The approach is significant because corporate firewalls, DLP solutions, and network monitoring tools that whitelist api.github.com traffic cannot distinguish this exfiltration from legitimate GitHub API usage.

It is worth noting that this technique is not limited to credential theft. Any data accessible to the compromised CI/CD runner (source code, configuration files, environment variables, internal documentation, database connection strings, or proprietary algorithms) could be exfiltrated through this channel. Throughout this analysis, “exfiltrated data” should be understood broadly, not limited to secrets and credentials.

How the Exfiltration Worked

Step 1: Data Harvesting

Once a victim’s CI/CD pipeline executed the compromised GitHub Action (trivy-action or kics-github-action), a shell script (setup.sh) executed on the GitHub Actions runner. The stealer collected [3][4]:

  • SSH keys, cloud credentials (AWS, GCP, Azure), Kubernetes service account tokens
  • Docker registry credentials, database passwords, TLS private keys
  • .env files, VPN configurations, cryptocurrency wallet files
  • GitHub Actions secrets extracted directly from Runner.Worker process memory by reading /proc/<pid>/mem and searching for the byte pattern {"value":"<secret>","isSecret":true}

That last item warrants emphasis. The stealer did not just scrape environment variables. It read process memory to extract the actual secret values that GitHub Actions normally masks in logs [3].

Step 2: Encryption

The collected data was encrypted using a hybrid scheme [3][4]:

  1. A random 32-byte AES session key was generated per exfiltration event
  2. The harvested data was encrypted with AES-256-CBC using the session key
  3. The session key was then encrypted with the attacker’s RSA-4096 public key using OAEP padding
  4. Both payload.enc (encrypted data) and session.key.enc (encrypted session key) were packaged into tpcp.tar.gz

The cryptographic choices are noteworthy. OAEP padding (IND-CCA2-secure) rather than PKCS#1 v1.5 indicates the author understood modern cryptographic best practices [5]. The encrypted data is unrecoverable without the attacker’s RSA-4096 private key.

What was NOT encrypted: The RSA public key was hardcoded in plaintext in the stealer script itself. This allowed researchers to confirm that the same threat actor was behind both the Trivy and KICS variants by comparing the embedded public keys [4]. The script code, collection paths, and exfiltration logic were all visible in plaintext. Only the harvested data was protected.

Step 3: Dual-Channel Exfiltration

TeamPCP implemented a two-stage fallback system [3][4][6]:

Primary channel: HTTPS POST to attacker-controlled C2 infrastructure

Campaign C2 Domain IP
Trivy scan[.]aquasecurtiy[.]org (typosquat; note the transposed i and t) 45[.]148[.]10[.]212
Trivy (alt) Cloudflare Tunnel via plug-tab-protective-relay[.]trycloudflare[.]com Dynamic
KICS/Checkmarx checkmarx[.]zone 83[.]142[.]209[.]11
LiteLLM models[.]litellm[.]cloud Varied

The encrypted tpcp.tar.gz was POSTed via HTTPS to the C2 domain.

Fallback channel: GitHub REST API

If the primary C2 was unreachable (network restrictions, domain takedown, firewall rules), the stealer fell back to using the victim’s own GitHub token [3][4]:

  1. Authenticate to the GitHub REST API using the captured GITHUB_TOKEN or INPUT_GITHUB_PAT
  2. Create a new public repository on the victim’s GitHub account
  3. Create a GitHub Release with a timestamped tag (data-YYYYMMDDHHMMSS)
  4. Upload tpcp.tar.gz as a Release Asset

This fallback mechanism is the focus of this post and represents a technique that defenders should add to their threat hunting playbooks.

The Repository Names Are Arbitrary

TeamPCP used two repository names across their campaigns [3][4][6]:

  • tpcp-docs: Used in the Trivy campaign (March 19, 2026 onward)
  • docs-tpcp: Used in the KICS/Checkmarx campaign (March 23, 2026 onward)

These names are completely arbitrary. There is nothing special about them. The attacker chose benign-sounding names that include “docs” to blend in with documentation repositories. The variation between campaigns demonstrates that the name is a configurable parameter in the attack tooling.

A third naming pattern appeared on March 22, 2026, when TeamPCP renamed all 44 repositories in Aqua Security’s aquasec-com GitHub organization with a tpcp-docs- prefix [7]. This was a defacement operation, not an exfiltration event, but it demonstrates the naming convention’s flexibility.

For threat hunters, the key implication is this: searching only for these exact repository names is necessary but not sufficient. Any attacker using this technique can choose any repository name. Detection logic must be broader than string matching.

These Were Newly Created Public Repositories

The exfiltration repositories were created programmatically at attack time. They did not exist before the stealer executed. Each victim organization whose CI pipeline triggered the fallback mechanism received a fresh repository created within its own namespace [3].

The repositories were created as public, which was a deliberate design choice. Public visibility allowed the attacker to retrieve the exfiltrated data without needing the victim’s credentials again. The tradeoff was that defenders could also discover these repositories, which is exactly what happened. Microsoft, Wiz, Sysdig, and others published detection guidance identifying these repository names as indicators of compromise [3][4][6].

The Access That Made This Possible

The initial compromise hinged on a single classic Personal Access Token (PAT) bound to the Argon-DevOps-Mgt service account [1][4]. This token had:

  • repo scope: full control of all repositories across both Aqua Security GitHub organizations
  • No IP restriction: usable from any network
  • No expiration enforcement: classic PATs have no org-level lifetime policy
  • No MFA on the service account: service accounts typically do not have MFA enabled

A single classic PAT with repo scope is functionally equivalent to a password with organization-wide write access to every repository. This is the fundamental architectural weakness that the exfiltration technique exploits.

For the fallback exfiltration specifically, the stealer used whatever GitHub token was available in the victim’s CI environment: the GITHUB_TOKEN automatically provisioned by GitHub Actions for each workflow run, or a user-provided INPUT_GITHUB_PAT. Both provided sufficient permissions to create repositories and upload release assets.

Organizational Controls That Could Have Mitigated This

1. Restrict Repository Creation

Under Settings > Member Privileges > Repository creation, organizations can disable member-level repository creation entirely, limiting it to organization owners only [8].

Impact on TeamPCP: The stolen service account PAT would have been unable to create the tpcp-docs repository within the organization namespace. However, it could still create a repository under the user account associated with the PAT (e.g., aqua-bot/tpcp-docs rather than aquasecurity/tpcp-docs). This is an important distinction that many defensive guides overlook. If Aqua had been using a fine-grained PAT scoped to specific repos with only the permissions needed for release automation, the stolen token could not have created aqua-bot/tpcp-docs or any other new repository.

For GitHub Enterprise Cloud customers, Repository Policies provide more granular control, allowing repository creation to be restricted to specific teams or roles rather than the binary owners-vs-members split [9]. This is a significant security engineering consideration: CI/CD service accounts and pipeline tooling typically do not need repository creation permissions. When configuring fine-grained access, the principle of least privilege should explicitly exclude this capability from CI/CD tokens.

Furthermore, as organizations increasingly grant repository creation permissions to AI coding agents and automated workflows, additional compensating controls should be implemented before allowing any automated system this permission. These controls should include approval workflows, audit logging, and scope restrictions that limit which organizations and namespaces the automated system can create repositories in.

2. Block Classic PATs and Enforce Fine-Grained PATs

This is arguably the single highest-impact control available. Organizations can block classic PAT access entirely under Settings > Personal access tokens, forcing all programmatic access through fine-grained PATs [10]. The differences are significant:

Capability Classic PATs Fine-Grained PATs
Repository scoping All repos (no granularity) Specific repos only
Org-level approval Not supported Supported
Lifetime enforcement None Org-configurable maximum
Visibility to admins Limited Full (owners can view and revoke)
Permission granularity Coarse (e.g., repo = everything) Per-permission, per-repo

A fine-grained PAT scoped to a single repository with only “Contents: Write” permission cannot create new repositories, upload release assets to other repos, or access secrets in other repositories. The blast radius of a compromised fine-grained PAT is fundamentally smaller [10].

Limitation: Fine-grained PATs do not yet support all GitHub APIs, notably GitHub Packages and Gists. Organizations with complex CI/CD pipelines may need to maintain classic PAT exceptions, which attackers can exploit [10].

3. Configure IP Allow Lists

GitHub Enterprise Cloud offers IP allow lists at the organization and enterprise level that restrict all API, web UI, and Git access to specified IP ranges [11].

Even with a broadly-scoped stolen PAT, API calls to create repositories or upload data would be rejected if they did not originate from an allowed IP. This is a network-layer control that does not depend on token scope [11].

An important caveat for this specific attack: In the TeamPCP case, the exfiltration likely occurred from GitHub Actions runners that would have been within an IP allow list. The compromised GitHub Action executed on the victim’s own CI infrastructure, meaning the API calls originated from a trusted context. IP allow lists are still a valuable defense-in-depth control (they prevent exfiltration from external attacker infrastructure using stolen tokens) but they would not have blocked the TeamPCP fallback mechanism specifically, since the runner itself was the exfiltration point.

4. Stream Audit Logs With Real-Time Alerting

GitHub audit logs capture repo.create events with the actor, timestamp, IP address, and repository name. With audit log streaming (GA since January 2025), these events can flow to a SIEM in near real-time [12].

A particularly important detection signal is when a service account creates a repository. Service accounts that maintain CI/CD pipelines should not be creating new repositories. This is an anomaly worth investigating regardless of the repository name. The principle of properly scoped permissions on service accounts reinforces this: if a service account has no legitimate need to create repositories, it should not have that permission, and if it does, any repo.create event from that account is inherently suspicious [12].

For organizations that do require automated repository creation (e.g., for project scaffolding or team onboarding), that capability should be implemented with separation of duties: a dedicated service account with only repository creation permissions, gated behind an integration tied to a ticket created in ServiceNow or Jira for change control auditing. This ensures that every new repository creation has a corresponding approved change request.

Additionally, organizational naming conventions can enhance detection. If all repositories in your organization follow a naming standard (e.g., team-project-component), any repository name that deviates from the convention (such as tpcp-docs or docs-tpcp) becomes an anomaly that detection rules can flag automatically.

5. Use GitHub Advanced Security

For completeness, GitHub Advanced Security features including CodeQL static analysis and secret scanning with push protection are relevant to the broader TeamPCP attack chain. CodeQL can detect insecure pull_request_target workflow patterns (the initial access vector that TeamPCP exploited) and push protection can block commits containing exposed tokens [13]. These features are covered in detail in the parent campaign report [1] and address the root cause (token exposure) rather than the exfiltration technique that is the focus of this post.

6. GitHub’s 2026 Actions Security Roadmap

GitHub has announced a 2026 security roadmap for GitHub Actions that directly references the TeamPCP campaign [14]. Three planned features are particularly relevant:

  • Workflow dependency locking pins all action references to immutable commit SHAs, with hash mismatches stopping execution before jobs run. This addresses the tag-rewriting technique that TeamPCP used (public preview in 3-6 months).
  • Policy-driven execution controls use rulesets to restrict which actors can trigger workflows and which execution contexts can access secrets (public preview in 3-6 months).
  • Egress firewall for hosted runners provides Layer 7 network monitoring that could block exfiltration to C2 domains like scan[.]aquasecurtiy[.]org (public preview in 6-9 months).

These controls represent the most substantive platform-level response to the GitHub Actions supply chain attack vector to date. However, the 3-9 month rollout timeline means organizations remain exposed in the interim.

It is worth noting that both workflow dependency locking and GitHub’s own documentation recommend pinning actions to full commit SHAs rather than tags [14][15]. This is the single most impactful immediate action organizations can take while waiting for the platform-level controls to reach general availability.

Beyond Repository Creation: Alternative Exfiltration Permutations

The tpcp-docs technique is one point in a broader design space. An attacker with a stolen GitHub PAT has numerous alternative exfiltration vectors. Understanding these is essential for comprehensive threat hunting, and each vector represents a potential covert channel for exfiltrating any type of data, not just credentials.

Branches in Existing Repositories

If repository creation is blocked but the token has write access to existing repositories, the attacker could push a new branch and commit files containing the encrypted payload. This requires only “Contents: Write” on a specific repo, a narrower scope than creating new repositories.

Properly scoped CI/CD credentials address this directly: a token scoped to push only to specific branches (e.g., main or release/*) via branch protection rules cannot create arbitrary new branches. Organizations should enforce branch naming conventions and alert on branches created by service accounts, since credentials used for a CI/CD pipeline typically should not need to create new branches. The pipeline pushes to existing ones.

When combined with organizational naming conventions for branches, any deviation becomes a detectable anomaly. For example, if your CI/CD branches follow a ci/<pipeline>/<run-id> pattern, a branch named docs/update-2026-03 created by a service account produces a higher fidelity signal for detection.

Secret Gists

If the PAT has gist scope, the attacker could create a secret (unlisted) gist containing encoded exfiltrated data. This is arguably the stealthiest exfiltration vector available on GitHub.

Secret gists are created under the user account, not the organization. There is no organization audit log event for gist creation. Organization admins have no visibility into gists created by their members [16]. The gist is unlisted: not searchable but accessible via direct URL.

Organizations should audit all classic PATs and remove gist scope unless absolutely necessary. The gist scope is often included by default or out of convenience, but it provides a high-stealth exfiltration path with no organizational detection capability.

Release Assets on Existing Repositories

Rather than creating a new repository, the attacker could create a release on an existing repository the token has access to. Each release asset can be up to 2 GiB, with up to 1,000 assets per release, making this the highest-capacity single-API-call exfiltration vector in GitHub [17].

The attacker could create a draft release (not publicly visible) or use a pre-release tag like 0.0.0-dev.1 that is unlikely to attract attention.

Organizations that have adopted SHA-pinning practices face an additional subtlety here. If workflows reference actions by full commit SHA rather than tag, then tag creation itself becomes an anomaly. However, an attacker who is aware of SHA-pinning could instead upload a release asset identified only by its commit SHA, effectively hiding a tree in the forest. In a repository with hundreds of commits and automated releases, an asset attached to an arbitrary SHA blends in with the legitimate release infrastructure. This makes it important to baseline not just tag creation, but all release activity: who creates releases, how often, and what assets are attached.

Draft and pre-release entries appear in the repository’s Releases tab and generate audit log events, but in active repos with automated releases, they may blend in. Detection rules should alert on release creation from unexpected actors, releases on repos that do not normally publish releases, or draft releases created by service accounts.

Wiki Pages

Each GitHub wiki is a separate Git repository accessible via git clone https://github.com/org/repo.wiki.git. Wiki changes do not generate standard audit log events in most configurations, and there is no webhook for wiki pushes via Git [18].

Typically, CI/CD pipeline credentials should not need access to wiki repositories. Organizations can mitigate this vector by ensuring that service account tokens are scoped to exclude wiki access, and by monitoring for unexpected Git operations on *.wiki.git repositories. If wikis are not actively used on a repository, disabling the wiki feature entirely eliminates this vector.

GitHub Actions Artifacts

A modified or injected workflow step could upload exfiltrated data as a workflow artifact using actions/upload-artifact. Artifacts are retained for a configurable period (default 90 days) and appear in the Actions tab, but in repos with heavy CI/CD activity, they may blend in [19].

This vector is mitigated by ensuring that workflow modifications require review (via branch protection and required pull request reviews) and that artifact upload patterns are baselined and monitored.

Commit Messages and Metadata as a Covert Channel

Data can be encoded in commit messages, author names, author email fields, or Git notes. While the per-commit capacity is relatively low (~72 KB for commit messages), this vector functions as a covert channel for any data the attacker wishes to exfiltrate without detection.

An attacker could spread encoded data across hundreds of commits with messages formatted to look like debug output, stack traces, or build logs containing base64 payloads. Commit messages are rarely inspected for encoded data, and automated scanning tools focus on file contents, not commit metadata. The data persists in Git history even if the branch is deleted [20].

This covert channel is particularly insidious because it is bidirectional: an attacker could also use commit messages or branch names to send commands to compromised systems that poll a repository, establishing a full command-and-control channel over GitHub’s trusted infrastructure.

Detection requires pattern matching on commit message content for base64 or hex-encoded blocks, which generates high false-positive rates in practice.

Summary of Exfiltration Vectors

Vector Required Scope Capacity Stealth Least-Privilege Mitigation
New repo + release assets repo 2 GB/asset Medium Restrict repo creation; fine-grained PATs
Branches in existing repos Contents:Write ~100 MB/file Medium Branch protection; scoped branch access
Secret gists gist ~3 GB Very High Remove gist scope unless absolutely necessary
Release assets (existing repo) Contents:Write 2 GB/asset Medium-High Baseline release activity; alert on anomalies
Wiki pages repo ~100 MB/file High Disable unused wikis; exclude wiki from tokens
Actions artifacts Actions:Write Quota-limited Medium Require PR review for workflow changes
Commit metadata (covert channel) Contents:Write ~72 KB/commit Very High Monitor for encoded patterns in commit messages

Threat Hunting Recommendations

Based on this analysis, the following should be added to your organization’s threat hunting playbook:

  1. Search for known exfiltration repository names: tpcp-docs, docs-tpcp, and any repository with the tpcp prefix. This catches the known TeamPCP indicators but is not sufficient on its own.

  2. Alert on repository creation by service accounts: This is an anomaly worth investigating regardless of the repository name. Combine with organizational naming conventions for higher fidelity signal.

  3. Audit release assets on repositories that do not normally publish releases: If a library or internal tool repository suddenly has a release with a binary asset, investigate.

  4. Monitor for timestamped release tags: The data-YYYYMMDDHHMMSS tag pattern is a direct indicator of compromise for TeamPCP’s exfiltration mechanism.

  5. Deprecate all classic PATs. Remove gist scope unless absolutely necessary. Migrate to fine-grained PATs with the narrowest possible scope.

  6. Enable audit log streaming to your SIEM: Create detection rules for the patterns described above, including repo creation, release creation from unexpected actors, and wiki Git pushes.

  7. Search for encrypted blobs in unexpected locations: Release assets, wiki pages, and branch contents containing files named tpcp.tar.gz, payload.enc, or session.key.enc are direct indicators of compromise.

  8. Pin all GitHub Actions to full commit SHAs immediately: Do not wait for GitHub’s dependency locking feature. This is the single most impactful action you can take today [14][15].

Conclusion

The TeamPCP campaign demonstrated that GitHub is not just a target; it is an exfiltration channel. When attackers obtain a token with write access, GitHub’s own API becomes the mechanism for stealing data out of your organization in a way that is encrypted, authenticated, and indistinguishable from legitimate traffic.

The defense is layered: restrict repository creation, enforce fine-grained PATs, implement separation of duties for service accounts, stream audit logs with real-time alerting, and expand your threat hunting to cover the full range of GitHub primitives that can be abused for data exfiltration, including covert channels like commit metadata and secret gists.

The specific repository names tpcp-docs and docs-tpcp are known indicators of compromise, but the technique is generic and the names are arbitrary. Hunt for the pattern, not just the indicator.

The full TeamPCP campaign report, “When the Security Scanner Became the Weapon,” is available from the SANS Technology Institute [1]. A SANS Emergency Webcast replay and ISC diary updates provide additional context on this campaign.

References

[1] K. G. Hartman and E. Johnson, “When the Security Scanner Became the Weapon: Inside the TeamPCP Supply Chain Campaign,” SANS Technology Institute, Mar. 25, 2026. [Online]. Available: https://www.sans.org/white-papers/when-security-scanner-became-weapon

[2] SentinelOne, “Exploiting Repos: 6 Ways Threat Actors Abuse GitHub & Other DevOps Platforms,” SentinelOne Blog, 2025. [Online]. Available: https://www.sentinelone.com/blog/exploiting-repos-6-ways-threat-actors-abuse-github-other-devops-platforms/

[3] Palo Alto Networks Unit 42, “When Security Scanners Become the Weapon: Inside the Trivy Supply Chain Attack,” Palo Alto Networks Blog, Mar. 2026. [Online]. Available: https://www.paloaltonetworks.com/blog/cloud-security/trivy-supply-chain-attack/

[4] Wiz Research, “Trivy Compromised by TeamPCP: A Supply Chain Attack on a Security Tool,” Wiz Blog, Mar. 2026. [Online]. Available: https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack

[5] R. Rivest, A. Shamir, and L. Adleman, “A method for obtaining digital signatures and public-key cryptosystems,” Communications of the ACM, vol. 21, no. 2, pp. 120-126, Feb. 1978.

[6] Sysdig Threat Research Team, “TeamPCP Expands Supply Chain Compromise: Spreads from Trivy to Checkmarx GitHub Actions,” Sysdig Blog, Mar. 2026. [Online]. Available: https://www.sysdig.com/blog/teampcp-expands-supply-chain-compromise-spreads-from-trivy-to-checkmarx-github-actions

[7] SecurityAffairs, “44 Aqua Security Repositories Defaced After Trivy Supply Chain Breach,” SecurityAffairs, Mar. 2026. [Online]. Available: https://securityaffairs.com/189856/hacking/44-aqua-security-repositories-defaced-after-trivy-supply-chain-breach.html

[8] GitHub, “Restricting repository creation in your organization,” GitHub Docs. [Online]. Available: https://docs.github.com/en/organizations/managing-organization-settings/restricting-repository-creation-in-your-organization

[9] GitHub, “Enforcing repository management policies in your enterprise,” GitHub Docs. [Online]. Available: https://docs.github.com/en/enterprise-cloud@latest/admin/enforcing-policies/enforcing-policies-for-your-enterprise/enforcing-repository-management-policies-in-your-enterprise

[10] GitHub, “Setting a personal access token policy for your organization,” GitHub Docs. [Online]. Available: https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization

[11] GitHub, “Managing allowed IP addresses for your organization,” GitHub Docs. [Online]. Available: https://docs.github.com/enterprise-cloud@latest/organizations/keeping-your-organization-secure/managing-allowed-ip-addresses-for-your-organization

[12] GitHub, “Audit log events for your organization,” GitHub Docs. [Online]. Available: https://docs.github.com/en/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/audit-log-events-for-your-organization

[13] GitHub, “About GitHub Advanced Security,” GitHub Docs. [Online]. Available: https://docs.github.com/en/get-started/learning-about-github/about-github-advanced-security

[14] GitHub, “What’s Coming to Our GitHub Actions 2026 Security Roadmap,” GitHub Blog, Mar. 2026. [Online]. Available: https://github.blog/news-insights/product-news/whats-coming-to-our-github-actions-2026-security-roadmap/

[15] GitHub, “Security hardening for GitHub Actions,” GitHub Docs. [Online]. Available: https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions

[16] GitHub, “REST API endpoints for gists,” GitHub Docs. [Online]. Available: https://docs.github.com/en/rest/gists/gists

[17] GitHub, “About releases,” GitHub Docs. [Online]. Available: https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases

[18] GitHub, “About wikis,” GitHub Docs. [Online]. Available: https://docs.github.com/en/communities/documenting-your-project-with-wikis/about-wikis

[19] GitHub, “Storing and sharing data from a workflow,” GitHub Docs. [Online]. Available: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/storing-and-sharing-data-from-a-workflow

[20] S. Chacon and B. Straub, Pro Git, 2nd ed. Apress, 2014.