In March 2025, a single compromised GitHub Action leaked secrets from more than 23,000 repositories. In the same window, security researchers used a well-known workflow-trigger mistake to reach remote code execution in CI pipelines at Microsoft, Google, and Nvidia. These are real GitHub Actions incidents, not hypothetical risks, and neither shows up in a getting-started tutorial. The same patterns keep recurring because the fixes are not obvious from the workflow syntax alone.
This page covers the GitHub Actions incidents worth knowing about and how to prevent them, then maps every other Git and GitHub Actions guide on this site to the specific problem it solves — branching, rebase, hooks, caching, reviews, deployments — so you can jump straight to the one you need instead of reading a generic overview.
What Actually Breaks GitHub Actions in Production
Four failure modes show up again and again: a compromised third-party action leaking secrets, a pull_request_target workflow running attacker code, caches vanishing mid-sprint, and jobs silently hitting a platform limit. None of these appear in a tutorial. All of them have cost real teams real incidents.
A compromised action dumps your secrets into the logs
In March 2025, the widely used tj-actions/changed-files action was compromised (CVE-2025-30066, CVSS 8.6). An attacker repointed every tag from v1 through v45.0.7 to a single malicious commit, 0e58ed8. The injected code read secrets out of the runner’s memory and printed them into the workflow log, which is public on any public repository. More than 23,000 repositories ran the poisoned version before it was caught. The fix shipped as v46.0.1, and CISA advised every affected project to rotate every credential the workflow could touch during the window.
The defense is pinning. A tag is a movable pointer; a commit SHA is not.
# vulnerable — a tag can be repointed
- uses: tj-actions/changed-files@v45
# safe — an immutable reference
- uses: tj-actions/changed-files@a5b3c1d2e4f5...
GitHub’s hardening guide says a full-length commit SHA “is currently the only way to use an action as an immutable release.” Turn on Dependabot so a known-bad version gets flagged, and audit that the SHA belongs to the action’s real repository, not a fork.
pull_request_target runs code from the fork with your token
The pull_request_target trigger runs with your repository’s secrets and a writable GITHUB_TOKEN, but in the base repository’s context, so it can comment on or label a pull request from a fork. The danger is checking out the pull request’s head and then running anything from it:
# dangerous pattern
on: pull_request_target
jobs:
build:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: npm ci && npm run build
That npm ci runs postinstall scripts from an attacker’s branch, with your secrets in scope. Security researchers used exactly this pattern to reach remote code execution in workflows at Microsoft, Google, and Nvidia. The rule from GitHub’s docs: workflows using these triggers “must not explicitly check out untrusted code.” Use plain pull_request for anything that runs a contributor’s code — it has no secrets and a read-only token — and move any privileged follow-up into a separate workflow_run workflow.
Your cache disappears the week you need it
The seven-day eviction plus the 10 GB cap means a cache you depend on can vanish after a quiet week, and a busy monorepo can evict its own caches within a day as new entries push old ones out. The symptom is CI time doubling overnight with no code change and no alert. Check the repository’s cache list when build times jump:
gh cache list --repo owner/name
Scope keys tightly so unrelated changes do not invalidate everything, and design the build so a cold cache is slow but still green. The caching and performance tuning guide covers the mechanics of cache-key invalidation in full.
A job hits a limit and just stops
These limits have no loud error. A job is killed at six hours of execution time. Queued jobs are cancelled after 24 hours waiting for a runner. Matrix builds cap at 256 jobs per run. Concurrent jobs cap at 20 on Free, 40 on Pro, 60 on Team, and 500 on Enterprise, so a large matrix on a Free plan queues behind itself. The GITHUB_TOKEN also gets 1,000 API requests per hour per repository, so a workflow that loops over the API can start returning 403s late in the run.
Set an explicit timeout-minutes well under six hours so a hung job fails fast, keep matrices small, and know your plan’s concurrency number before you fan out. The monitoring and debugging guide covers reading run logs when a job ends without an obvious reason. For the repository side of the same problem, see Git security best practices.
Verdict: pin actions to SHAs, keep untrusted code out of privileged triggers, and assume every cache and every runner is about to disappear. Design for that and GitHub Actions is boringly reliable.
The Git + GitHub Actions Cluster, Mapped to the Problem It Solves
Every topic below has its own dedicated guide with full commands and examples. This section tells you which one to read for the decision in front of you right now — it is a map, not a summary.
Getting started
New to the split between the version control tool and the hosting platform? The difference between Git and GitHub for beginners clears that up in ten minutes. A working setup has three layers: Git tracks history on short-lived branches, GitHub hosts the repository and enforces review rules, and GitHub Actions runs jobs triggered by events like a push or a pull request. Your first CI/CD pipeline and an introduction to automating your workflow both start from that chain.
Branching, rebase, and history
Picking a branching model is a one-time decision with a long tail of consequences. The full breakdown of Git Flow, GitHub Flow, and trunk-based development has the commands and the tradeoffs for each — GitHub Flow is the right default for most teams, trunk-based only once your test suite is fast and trusted, and Git Flow only if you ship versioned software to customers running several versions at once. Pair it with branching and merging strategies and branching and approval best practices for enforcement.
Rebase and merge solve different problems, not competing versions of the same one: rebase your own local commits before the first push, merge branches once other people have pulled them. Advanced Git rebase, stash, and cherry-pick covers the mechanics, squashing commits in a feature branch tidies a messy branch before review, and resolving merge conflicts in Git walks through the markers when the two collide.
For fast local checks that run automatically — linting before a commit, a smoke test before a push — Git hooks with real scripts has working examples of each hook type and how to share them across a team, since hooks live in .git/hooks and are never copied on clone by default.
Building and securing workflows
A workflow is a YAML file with three required pieces — on: for triggers, jobs: for units of work, and runs-on: plus steps: inside each job. The full workflow YAML syntax reference covers every block, and common use cases and examples shows complete working files. Once a single workflow stops being enough, reusable workflows and composite actions covers when to extract shared logic versus duplicate it.
Beyond the production incidents above, managing workflow secrets safely and the full security best practices checklist cover OIDC cloud auth, least-privilege GITHUB_TOKEN scoping, and the rest of the hardening surface this page only summarizes. Git security best practices covers the repository side of the same problem.
Performance, reviews, and testing
Caching and performance tuning covers cache-key design in depth — the eviction behavior above is the symptom, this guide is the fix. For enforcing who can merge what, the CODEOWNERS and review permissions guide and path-based approval rules cover required-reviewer setups, and pull request and issue templates standardizes what contributors submit. Running unit tests in CI covers wiring a test job into branch protection as a required status check — real enforcement lives there, not in the workflow file alone.
Releases and deployment
Git tag and release best practices covers cutting a clean release trigger from a tag. Deployment strategies and environments covers rolling, blue-green, and canary patterns built on environment-scoped jobs with a required reviewer — the cheapest safety net Actions gives you for production. Automating releases covers changelog and versioning automation on top of that. For platform-specific pipelines, see building and publishing Docker images with GitHub Actions and Cloud Run deployment with GitHub Actions.
When something goes wrong
The monitoring and debugging guide covers reading run logs and diagnosing a job that fails without an obvious reason. If you’re prepping for an interview rather than debugging a live pipeline, GitHub Actions interview questions and answers covers the concepts this page assumes you already know.
Frequently Asked Questions
Q: How do I stop a third-party GitHub Action from stealing secrets?
A: Pin it to a full commit SHA, not a tag. A tag can be repointed to malicious code, as the tj-actions/changed-files compromise (CVE-2025-30066) showed in March 2025. Set the GITHUB_TOKEN to read-only, grant write access per job, and use OIDC instead of stored cloud keys. Turn on Dependabot to flag known-bad versions.
Q: Is pull_request_target always dangerous?
A: No — it’s dangerous specifically when you check out and run the pull request’s own head commit. Use it safely for operations that don’t touch the fork’s code (labeling, commenting), and keep anything that builds or tests the fork’s code on the plain pull_request trigger instead, which carries no secrets.
Q: Why did my GitHub Actions build suddenly get slower with no code change?
A: The most common cause is cache eviction. GitHub deletes caches not used in seven days and evicts the oldest once a repository passes 10 GB. Check gh cache list to confirm before assuming it’s a code regression.
Q: Do I need GitHub Actions if I already use Git?
A: Git tracks code history; GitHub Actions automates what happens to that code. You can use Git alone, but you lose automated tests on every pull request, one-click deploys, and required checks that block bad merges. For a solo project it is optional. For a team, that automation is what keeps main releasable.
Q: Is rebase or merge better for a team?
A: Both, at different times. Rebase your own local commits to tidy them before the first push. Merge branches once other people have pulled them. The pull-request merge button is a merge and should stay that way. Never rebase a branch someone else has already based work on.
Quick Summary:
- The
tj-actions/changed-filescompromise (CVE-2025-30066, CVSS 8.6, March 2025) repointed every tag from v1–v45.0.7 to secret-stealing code across 23,000+ repositories — pin third-party actions to a full commit SHA, not a tag. pull_request_targetplus checking out the fork’s head is the pattern researchers used to reach RCE at Microsoft, Google, and Nvidia — use plainpull_requestfor anything that runs a contributor’s code.- GitHub Actions caches are 10 GB per repository and evicted after seven days unused — a build that slows overnight with no code change is usually a lost cache, not a regression.
- Jobs die at 6 hours, queued jobs cancel after 24 hours waiting for a runner, and concurrent-job caps range from 20 (Free) to 500 (Enterprise) — know your plan’s number before you fan out a matrix.
- These GitHub Actions incidents share one root cause — trusting a tag, a trigger, or a limit you never checked — and pinning, trigger discipline, and explicit timeouts prevent all four.
- Every other Git/GitHub Actions decision — branching model, rebase timing, caching, reviews, deployment — has its own dedicated guide linked above; this page is the map, not the manual.