On April 1, 2021, a Codecov customer noticed that the SHA-256 hash on the bash uploader script did not match the published value. The script had quietly been sending every CI environment variable to an attacker-controlled server for two months. AWS keys, API tokens, and GitHub credentials from 29,000 organizations (Codecov's full customer base at the time) were captured. Rotation fixed the leaked credentials. It did not fix the five surfaces that made the leak structurally inevitable.
API tokens stored as CI/CD environment variables are not secret in practice. Five structural surfaces each create a persistent copy of the value that rotation cannot retroactively invalidate. Codecov in 2021, CircleCI in 2023, and PyTorch in 2022 proved the same pattern in three consecutive years.
Build Logs Are Not Private Storage: They Are Searchable Public Archives
CI/CD platforms write environment variable values into build logs through three common failure modes. First: echo $API_KEY left in from debugging sessions. Second: npm install --verbose, which prints registry URLs containing authentication tokens from .npmrc in plain text. Third: curl -H "Authorization: Bearer $TOKEN" --verbose, which displays request headers including the token value.
GitHub Actions masks registered secrets in log output, but only for exact string matches. Base64-encoded variants, URL-encoded forms, and values split across multiple echo calls bypass the mask. GitHub secret scanning does not analyze workflow run logs or build artifacts; it analyzes only repository file content.
A token written to a log at 14:32 is searchable in GitHub's log UI. Any user with read access to the repository can view it, including outside collaborators. The platform retains logs for 90 days by default on public repositories.
pull_request_target Runs Your Secrets Against Code You Did Not Write
The pull_request_target trigger executes in the base repository context with full secret access while processing code from the pull request's head (fork) branch. The standard pull_request runs in the fork context with no secret access. The pull_request_target runs in the base context with repository secrets and a write-permissive GITHUB_TOKEN.
Orca Security found approximately 50 exploitable repositories out of 5,000 public repositories analyzed using this trigger. The canonical attack: a fork submits a PR that modifies a shell script the workflow executes. That script exfiltrates ACTIONS_RUNTIME_TOKEN or named secrets via curl to an attacker-controlled endpoint.
The tj-actions/changed-files supply chain attack in December 2024 exploited this pattern at scale. GitHub partially mitigated it in 2025 by requiring workflows to come from the default branch. Repositories where the vulnerable workflow already lives on the default branch receive no protection.
The Artifact Cache Stores Credentials Alongside Build Outputs
actions/checkout with persist-credentials: true (the default) writes GITHUB_TOKEN as a git credential helper into .git/config inside the working directory. When a build uploads an artifact containing the workspace, it includes .git/config with a live token. The artifact is downloadable by any user with repository access for the default 90-day retention period.
The ArtiPACKED research by Palo Alto Networks Unit 42 (2023) found this pattern in repositories from major organizations. Affected repositories include Google (firebase-js-sdk), Microsoft (TypeScript-repos-automation), Red Hat (quay-clair), and AWS (opensearch-security). The ACTIONS_RUNTIME_TOKEN is an undocumented JWT with approximately 6-hour expiry. It was extracted from leaked artifacts and used to replace artifacts with malicious payloads mid-run.
pip credential stores and .npmrc files with authentication tokens are cached by actions/cache unless explicitly excluded from the cache key path.
Codecov 2021, CircleCI 2023, PyTorch 2022: The Pattern That Repeated Three Times
Three independent supply chain incidents between 2021 and 2023 all exploited the same structural fact. CI/CD environment variables flow into third-party integrations, script executors, and dependency installers as plain text. The fourth surface is third-party integrations that receive environment variables during build: the vector in all three incidents below.
Codecov (January 31 to April 1, 2021): attackers modified the bash uploader script after extracting credentials from a Docker image build error. The modified script sent all CI environment variables (including AWS_*, GITHUB_TOKEN, and CI_BUILD_TOKEN) to a remote server. 23,000 organizations were confirmed affected: those with active uploads during the 74-day window.
CircleCI (December 21, 2022 to January 4, 2023): malware on an engineer's laptop stole a 2FA-authenticated session cookie. The attacker exfiltrated customer environment variables and encryption keys from running processes. The 14-day breach window between the December 21 exfiltration and the January 4 breach announcement left secrets valid throughout.
PyTorch (December 25-30, 2022): a dependency confusion attack placed the malicious package torchtriton on PyPI. The binary exfiltrated .ssh directory contents and files under 99,999 bytes from the build environment. There were 2,717 total downloads, with 2,500 on December 26 alone.
All three incidents shared the same remediation: rotate every secret. None modified the architecture that made secrets structurally accessible to the compromised component.
Token Enumeration: What Attackers Search for in Public Build Logs
Provider token prefixes are stable, well-documented, and searchable through GitHub's code search API and public CI log archives. Any secret that appeared in a public build log, even briefly, is discoverable by pattern matching long after rotation.
GitHub secret scanning detects 200+ token formats from 100+ providers. Covered patterns include ghp_ (GitHub PAT), AKIA (AWS IAM), sk_live_ (Stripe live), and xoxb- (Slack bot). The feature does not scan workflow run logs, only repository file content, creating a systematic blind spot for the most common exposure vector.
GitGuardian's 2026 State of Secrets Sprawl report recorded 28.65 million secrets exposed in public repositories. 70% of secrets confirmed in 2022 were still active in 2026. The attacker workflow: scan public CI log files for known prefixes, validate via the provider's API, exfiltrate silently. Stolen tokens generate normal API calls, not failed authentication events.
Rotation Closes One Copy. OIDC Eliminates the Need for Copies.
OIDC workload identity federation removes long-lived API tokens from the CI/CD environment entirely. The cloud provider mints a short-lived, job-scoped credential on demand. AWS, Azure, and GCP all trust OIDC tokens from GitHub Actions. Each exchanges them for scoped credentials; the default TTL is 1 hour for AWS STS.
Jobs must include permissions: id-token: write. Without it, the OIDC token is not minted even when the workflow has the correct action configured.
No static credential is stored, logged, or cached. OIDC tokens are not environment variables; they are requested at runtime and expire before log retention begins. The Codecov attacker could not have exfiltrated a token that was never set. The CircleCI attacker would have found nothing to steal.
Three complementary controls reduce the remaining surface. Set permissions: read-all at the workflow level and grant write access only at the step that needs it. Never check out PR code in a workflow that has secret access. Pin third-party actions to the full commit SHA, not a tag.
What MAGO Intel Detects in API Security Assessments
Secret pattern detection in public build artifacts and repository content is a standard component of API security assessments. The MAGO Intel tool (intel.mago.team) identifies token patterns across public CI/CD logs and exposed configuration files in build artifacts. Repository commit history is also scanned as part of external attack surface enumeration.
Scans cover GitHub PAT (ghp_), AWS IAM (AKIA), Stripe live keys (sk_live_), Slack tokens (xoxb-), and npm tokens. Findings are triaged by token validity (live API call to the provider endpoint) and permission scope before reporting.
The recurring breach pattern (Codecov, CircleCI, PyTorch, and dozens of smaller incidents) is not a secret management failure. It is an architecture failure. Environment variables were designed to pass configuration between processes on a single machine. They were not designed to be the security boundary for credentials shared across third-party scripts, fork pull requests, and artifact caches. OIDC federation, strict workflow permissions, and commit-SHA action pinning address the architecture. Rotation addresses the symptom.
Top comments (0)