DevOpsUpdated 26 min read6 min read · ELI5

Productionizing with Git: How GitHub Actions Turns Good Habits into Gates

“Production” is not a server you deploy to. It is the path a change has to travel to get there. This is a practical guide to building that path for an AI or data project with Git and GitHub Actions: CI with an eval gate, approvals and OIDC for deploys, scheduled canaries, and the cheap supply-chain controls that the 2025–2026 attacks showed were not optional.

Software is “in production” not because of where it runs but because of the road it travelled to get there, and GitHub Actions lets you build that road so every change has to pass the same checks, by the same rules, every time.

Read it
DevOpsLLMOpsSecurity
@sha256

Thesis

Production is a property of the path code takes, not of the code itself; Git plus GitHub Actions can turn good habits into enforced, auditable gates, and the cheapest of those gates (required checks, least-privilege tokens and pinned dependencies) prevent the most expensive failures.

Key takeaways

  1. A gate is a habit the system enforces. A required status check, a protected environment and a pinned action stop the same mistakes a disciplined engineer avoids, and they keep working when nobody is being disciplined.
  2. The path itself is now a target. In March 2025 a GitHub Action used by more than 23,000 repositories was rewritten to dump CI secrets into build logs. Workflows that pinned it to a full commit SHA were not affected unless they updated during the attack window.
  3. Least privilege limits the damage when a gate fails. Of the 218 repositories that leaked secrets in that incident, the vast majority leaked only the short-lived GITHUB_TOKEN, which is worth little to an attacker when it is scoped to read-only.
  4. AI raises the stakes. DORA’s 2024 report estimated a 7.2% fall in delivery stability for every 25% increase in AI adoption, and its 2025 report, with 90% of respondents using AI, found throughput up but instability still rising.
  5. LLM apps need an eval gate in CI next to the unit tests. A small golden set with a pass-rate threshold catches prompt and model regressions on the pull request, before they reach users.
  6. Most of the protection costs an afternoon. Read-only default tokens, SHA-pinned third-party actions, OIDC instead of stored cloud keys, and a ruleset that requires checks to pass are all free features on GitHub.

At about 16:00 UTC on 14 March 2025, a GitHub Action used by more than 23,000 repositories began printing its users’ secrets into their build logs. None of those repositories had changed a line of code. Someone had rewritten the action’s version tags so that v1 through v45.0.7 all pointed at one malicious commit, and every workflow that referenced the action by tag ran that commit the next time it ran. The teams that were not affected had done one boring thing: they had pinned the action to a full commit SHA, which cannot be moved.

Most AI and data projects start the same way: a notebook, a script, a demo that works on the author’s laptop. The question “is it in production?” is usually answered by pointing at a server. I think that is the wrong place to look. Two copies of the same code can sit on the same server, and one can be production-grade while the other is not. The difference is how the code got there: who reviewed it, which checks it passed, what credentials touched it, and whether anyone could undo it.

My argument is this: production is a property of the path code takes, not of the code itself. Git plus GitHub Actions can turn good engineering habits into enforced, auditable gates, and the cheapest gates (required checks, least-privilege tokens, pinned dependencies) prevent the most expensive failures.

The article explains why this matters more now that AI writes and runs more of our code. It then sets out a five-rung framework for the path to production, and works through each rung with copy-pasteable workflows: CI for a Python LLM app with a small eval gate, a deploy workflow with environments and OIDC, and a scheduled canary. A deep dive on supply-chain security follows, then cost, case examples, objections, implications by role, a plan, and the limits of the evidence.

Context: why this matters now

Three things have changed since “set up CI” was a line item you could defer.

The volume of automated change has exploded. GitHub’s Octoverse 2025 reported 11.5 billion GitHub Actions minutes used in public projects in its 2025 year (September 2024 to August 2025), up 35% from 8.5 billion. Developers merged 518.7 million pull requests, up 29%, and pushed nearly a billion commits. More of those commits now start life in an AI assistant.

Exhibit 1

The pipes are busier than ever: Actions usage and merged pull requests both grew by about a third

GitHub platform activity, Octoverse 2025 (September 2024 to August 2025), and AI adoption among DORA 2025 respondents

11.5BGitHub Actions minutes in public projects, up 35% year on year
518.7Mpull requests merged, up 29%
~1Bcommits pushed, up 25%
90%of DORA 2025 respondents use AI in their work

Actions minutes are CPU minutes in public projects. Octoverse also reports 13.5 billion minutes (up 30%) using the previous year’s method, which included self-hosted usage. The 90% figure is the share of DORA’s 4,867 survey respondents who use AI in their work.

Source: GitHub, Octoverse 2025 (28 Oct 2025); DORA, State of AI-assisted Software Development 2025

AI has changed the balance between speed and stability. DORA, Google Cloud’s long-running research programme on software delivery, has measured this directly. Its 2024 report, drawing on nearly 3,000 respondents that year, found that AI adoption improved the things developers feel: documentation quality, code quality and code review speed. But it estimated that every 25% increase in AI adoption came with a 1.5% fall in delivery throughput and a 7.2% fall in delivery stability. The authors were surprised, and their explanation was blunt: improving the development process does not automatically improve delivery “without proper adherence to the basics of successful software delivery, like small batch sizes and robust testing mechanisms”.

Exhibit 2

In 2024, AI made developers’ work look better while delivery got less stable

Estimated change in each outcome for every 25% increase in AI adoption, DORA 2024 (%)

Documentation quality+7.5%
Code quality+3.4%
Code review speed+3.1%
Delivery throughput−1.5%
Delivery stability−7.2%

Model estimates from a survey, not measured deployments; the report gives 89% uncertainty intervals. The 2025 report found the throughput effect had turned positive, while instability still rose with AI adoption.

Source: DORA, Accelerate State of DevOps Report 2024

View data table
OutcomeEstimated change per 25% more AI adoption
Documentation quality+7.5%
Code quality+3.4%
Code review speed+3.1%
Delivery throughput−1.5%
Delivery stability−7.2%

The 2025 report, State of AI-assisted Software Development, surveyed 4,867 people, 90% of whom use AI at work. This time AI adoption was linked to higher throughput, but “AI adoption does continue to have a negative relationship with software delivery stability”. The Google Cloud announcement named the cause in a sentence I would put on the wall of any AI team: “Without robust control systems, like strong automated testing, mature version control practices, and fast feedback loops, an increase in change volume leads to instability.” Among the seven capabilities DORA found amplify AI’s benefits are strong version control practices and working in small batches. Teams that committed often and used rollback freely got more out of AI.

The delivery path became an attack surface. In eighteen months, three widely used projects were compromised through their CI: tj-actions/changed-files (March 2025), Nx (August 2025) and Aqua Security’s trivy-action (March 2026). GitHub’s 2026 security roadmap, published in March 2026, names all three and states the pattern plainly: “attackers are targeting CI/CD automation itself, not just the software it builds.”

DORA’s benchmarks show what a good path delivers. In 2024 the elite cluster, 19% of respondents, deployed on demand with a lead time under a day, a 5% change fail rate and recovery in under an hour. Compared with low performers, the report puts that at 127 times faster lead time, 182 times more deployments per year, 8 times lower change failure rate and 2,293 times faster recovery from failed deployments.

Exhibit 3

The best teams ship faster and break less: speed and stability move together

Software delivery performance clusters, DORA 2024

EliteHighMediumLow
Change lead timeUnder a day1 day to 1 week1 week to 1 month1 to 6 months
Deployment frequencyOn demandDaily to weeklyWeekly to monthlyMonthly to every 6 months
Change fail rate5%20%10%40%
Failed deployment recoveryUnder an hourUnder a dayUnder a day1 week to 1 month
Share of respondents19%22%35%25%

Cluster analysis of survey responses on the four original DORA metrics; shares have 89% uncertainty intervals (Elite 18–20%). Note that the medium cluster reports a lower change fail rate than the high cluster: DORA calls the faster teams “high”.

Source: DORA, Accelerate State of DevOps Report 2024 (p. 13)

None of those numbers comes from heroics. They come from a path where small changes flow through automated checks, and any failure can be reverted in minutes. That path is what the rest of this article builds.

A framework: the path-to-production ladder

I use a five-rung ladder. Each rung converts a habit into a gate, and each one maps to the DORA metrics it moves. You climb in order, because each rung relies on the one below: you cannot require a check that does not exist, and you cannot promote safely what you cannot gate.

Exhibit 4

Five rungs from “it works on my machine” to production

The path-to-production ladder: each rung turns a habit into an enforced gate

  1. 01PushEverything lives in Git: code, prompts, eval sets, infrastructure, workflows. Small branches, pull requests, nothing edited in a console.
  2. 02CheckEvery pull request runs lint, unit tests and, for LLM apps, a small eval suite. Results are visible on the PR.
  3. 03GateChecks become required. Rulesets block merges that fail, tokens default to read-only, third-party actions are pinned.
  4. 04PromoteBuilds move through environments (staging, then production) with approvals, and authenticate to the cloud with OIDC instead of stored keys.
  5. 05Observe & recoverScheduled canaries watch production; every deploy is recorded; a revert redeploys the last good state in minutes.

Author’s framework.

The Gate rung is highlighted because it is the one most teams skip. Plenty of teams have CI that runs; far fewer have CI that blocks. A check that can be ignored is a suggestion, and suggestions lose to deadlines.

Exhibit 5

Each rung uses specific Actions features and moves specific delivery metrics

Mapping the ladder to GitHub features, DORA metrics and the failure each rung prevents

GitHub featuresDORA metric it movesFailure it prevents
PushBranches, pull requests, CODEOWNERSChange lead time (small batches)Untracked changes nobody can diff or revert
CheckWorkflows on pull_request, matrix, cache, artifactsChange fail rateRegressions found by users instead of tests
GateRulesets, required checks, permissions, SHA pinningChange fail rate; deployment rework rateUnreviewed merges; poisoned dependencies
PromoteEnvironments, required reviewers, OIDC, concurrencyDeployment frequency; lead timeLeaked cloud keys; half-finished deploys
Observe & recoverschedule, workflow_dispatch, deployment historyFailed deployment recovery timeSilent degradation; slow, manual rollback

Author’s framework. DORA metric definitions from dora.dev.

Source: DORA, software delivery performance metrics

Across all five rungs runs one principle: harden the path itself. The workflows, the actions they call and the tokens they hold are production infrastructure. An attacker who can change your pipeline does not need to change your code.

Deep dive 1: Push and Check, CI for an LLM app

Push. Everything that changes behaviour goes in Git. For an LLM application that means more than code: the prompt templates, the model identifier, retrieval parameters, the evaluation set and the workflow files themselves. A prompt edited in a vendor console or a database row is a production change with no diff, no review and no revert. Work on short-lived branches and open a pull request early. DORA has tied small batches to better delivery for years, and its 2025 report found that AI’s positive effects depend on teams working in small batches.

Check. A pull request should trigger a workflow that answers one question quickly: did this change break anything we know how to test? For a Python LLM app, that means three layers. Lint and type checks take seconds. Unit tests, with the model mocked, take a minute. And there is a small eval gate: a golden set of real inputs with known-good properties, run against the real model, which fails the build if the pass rate drops below a threshold.

Here is a CI workflow that does all three. It uses a matrix for two Python versions, the built-in pip cache, a concurrency group that cancels superseded runs on the same pull request, and an artifact so reviewers can read the eval report.

# .github/workflows/ci.yml
name: ci

on:
  pull_request:
  push:
    branches: [main]
  merge_group:            # run again when a merge queue is used

permissions:
  contents: read          # read-only by default; jobs ask for more if needed

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.12", "3.13"]
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
        with:
          python-version: ${{ matrix.python-version }}
          cache: pip
      - run: pip install -r requirements.txt -r requirements-dev.txt
      - run: ruff check .
      - run: pytest -q tests/unit        # model calls mocked here

  eval:
    needs: test
    runs-on: ubuntu-latest
    timeout-minutes: 20
    # Forked PRs get no secrets, so skip the paid eval there.
    if: >-
      github.event_name != 'pull_request' ||
      github.event.pull_request.head.repo.full_name == github.repository
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
        with:
          python-version: "3.13"
          cache: pip
      - run: pip install -r requirements.txt
      - name: Golden-set eval gate
        env:
          LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
          EVAL_MIN_PASS_RATE: "0.90"
        run: python -m evals.run
      - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
        if: always()
        with:
          name: eval-report
          path: eval-report.json
          retention-days: 14

The SHAs are the commits behind those release tags in October 2026. Do not copy SHAs from a blog post into production, this one included. Resolve them yourself from each action’s own repository, as GitHub’s guidance says, and let Dependabot keep them current (Deep dive 4).

The eval gate itself can be very small. This one reads a JSONL golden set where each case lists phrases the answer must and must not contain, calls the app, writes a report and fails the job if the pass rate falls below the threshold.

# evals/run.py: fail the build if the golden-set pass rate drops
import json
import os
import sys
from pathlib import Path

from app.pipeline import answer  # your app's entry point

MIN_PASS = float(os.environ.get("EVAL_MIN_PASS_RATE", "0.90"))


def passes(case: dict, output: str) -> bool:
    text = output.lower()
    has_all = all(s.lower() in text for s in case.get("must_include", []))
    has_bad = any(s.lower() in text for s in case.get("must_not_include", []))
    return has_all and not has_bad


def main() -> int:
    lines = Path("evals/golden.jsonl").read_text(encoding="utf-8").splitlines()
    cases = [json.loads(line) for line in lines if line.strip()]
    results = []
    for case in cases:
        out = answer(case["input"])
        results.append({"id": case["id"], "pass": passes(case, out), "output": out})
    rate = sum(r["pass"] for r in results) / len(results)
    report = {"pass_rate": rate, "n": len(results), "results": results}
    Path("eval-report.json").write_text(json.dumps(report, indent=2))
    print(f"pass rate {rate:.1%} (min {MIN_PASS:.0%}, n={len(results)})")
    return 0 if rate >= MIN_PASS else 1


if __name__ == "__main__":
    sys.exit(main())

Substring checks are crude on purpose. They are cheap, deterministic and easy to review. You can swap passes() for schema validation, a citation check or an LLM judge later, but the shape of the gate stays the same: fixed cases, a score, a threshold and a non-zero exit code.

A few details make this robust rather than flaky:

  • Pin the model snapshot and set the parameters (temperature, maximum tokens) in code, so the gate measures your change and not the vendor’s.
  • Size the threshold for noise. With 50 cases and a true pass rate of 90%, the standard error is about 4 percentage points, so run-to-run swings of ±8 points are plausible by chance alone. Run the suite several times on main and set the threshold below the low end of what you see.
  • Keep the PR suite small and run the big one on a schedule. Fifty well-chosen cases on every pull request; the full suite nightly (Deep dive 5).
  • Never run paid evals on code from forks. The if: above skips them, and GitHub withholds secrets from fork-triggered pull_request runs anyway.

Matrix, cache, artifacts, concurrency. Four features do most of the work in CI. A matrix fans one job out across versions or platforms. It is useful, but every cell is billed, so test the combinations you actually ship. The cache (here, setup-python’s built-in pip cache) avoids re-downloading dependencies; each repository gets 10 GB of cache storage. Artifacts pass files between jobs and let humans download reports. Concurrency groups stop stale runs from wasting minutes: when you push twice to a pull request, the first run is cancelled.

Deep dive 2: Gate, making checks binding

A workflow that runs is not yet a gate. It becomes one when the repository refuses to merge without it. On GitHub that is a ruleset (or the older branch protection rule) on main with these rules:

  • Require a pull request before merging, with at least one approval. Rulesets allow from 0 to 10 required approvals.
  • Require status checks to pass, naming the jobs (test (3.12), test (3.13), eval). This is the line that turns CI from advice into a gate.
  • Block force pushes and deletions, so history cannot be rewritten under you.
  • Require deployments to succeed in staging before merging, if your flow deploys pull requests.
  • Optionally, require signed commits and linear history.

Rulesets improve on branch protection in ways that matter for teams. Several rulesets can apply to one branch, and the most restrictive rule wins. Anyone with read access can see the active rules. The bypass list is explicit: a named role, team or app, rather than “admins can do anything”.

Add a CODEOWNERS file that names an owner for .github/workflows/. GitHub’s hardening guide recommends this specifically, because a workflow change is a change to what runs with your secrets. Treat it like a change to your IAM policy, not like a change to a README.

Least-privilege tokens. Every workflow run receives a GITHUB_TOKEN, a credential scoped to the repository that expires when the job finishes. What it can do depends on the permissions: you set. GitHub’s guidance is to set the default to read-only for repository contents and grant more only to the jobs that need it. In YAML, a single top-level permissions: contents: read does it. When you specify any permission, every permission you do not mention is set to none.

This sounds like housekeeping until you look at what actually leaked in the tj-actions incident (Deep dive 4). Endor Labs found that the vast majority of leaked secrets were GITHUB_TOKENs. A read-only token that expires within minutes is close to worthless to an attacker. A token with contents: write on a workflow that never needed it is a foothold.

Script injection. The other gate is in how you write run: steps. Never interpolate untrusted text, such as a pull request title, branch name or issue body, directly into a shell script with ${{ }}. Pass it through an environment variable instead, which is GitHub’s “preferred approach”. Nx learned this the hard way, as the case examples show.

Exhibit 6

The cheapest gates prevent the most expensive failures

Path-to-production controls by cost to adopt and severity of the failure they prevent

Author’s framework; placement is a judgement, not a measurement.

The top-left quadrant is the argument of this article in four lines of configuration. None of it needs a platform team or a budget, and every item would have reduced the damage in at least one of the incidents below.

Deep dive 3: Promote, environments, approvals and OIDC

Continuous delivery on GitHub runs through environments. An environment (say staging or production) is a named target with its own secrets, variables and protection rules. A job that declares environment: production cannot start until every rule passes:

  • Required reviewers: up to six people or teams, any one of whom can approve. You can stop the person who triggered the run from approving their own deployment.
  • Wait timer: a delay of 1 to 43,200 minutes (30 days) before the job proceeds.
  • Deployment branches and tags: for example, only main may deploy to production.
  • Environment secrets are released only to jobs that reference the environment, and only after approval.

Check your plan first. On GitHub Free, Pro and Team, required reviewers and wait timers are available only for public repositories. Private repositories need GitHub Enterprise for those two rules.

OIDC instead of stored cloud keys. The classic way to deploy to a cloud is to create an access key, paste it into a GitHub secret and hope nobody prints it. OpenID Connect (OIDC) replaces that. The job asks GitHub for a signed identity token describing exactly what is running: the repository, the branch, the environment. Your cloud provider checks that token against a trust policy and issues a short-lived credential that, in GitHub’s words, is “only valid for a single job, and then automatically expires”. AWS, Azure, Google Cloud and HashiCorp Vault all support it.

Exhibit 7

OIDC removes the long-lived cloud key that a compromised pipeline would steal

Stored cloud secret vs. OIDC federation in a GitHub Actions deploy

Stored access keyOIDC federation
What GitHub storesA long-lived cloud credentialNothing; a trust policy lives in the cloud
LifetimeUntil someone rotates itOne job, then it expires
ScopeWhatever the key’s user can do, from anywhereA role restricted by repo, branch or environment
If a log or runner leaks itUsable elsewhere until revokedExpired or useless outside that job
Rotation workManual, often forgottenNone
Workflow needssecrets.AWS_SECRET_ACCESS_KEYpermissions: id-token: write

Author’s comparison based on GitHub documentation.

Source: GitHub Docs, OpenID Connect; Secure use reference

The trust policy is where OIDC earns its keep. Bind the cloud role to the token’s sub (subject) claim. A job using the production environment presents a subject like repo:acme/llm-app:environment:production, so a workflow on a feature branch, or in a fork, cannot assume the production role. Since 15 July 2026, GitHub issues subject claims for new repositories, renames and transfers that also embed immutable owner and repository IDs. The change exists so that a recycled organisation or repository name cannot mint tokens your cloud still trusts. Check the claim your repository actually emits before writing the policy.

Here is a deploy pipeline split into a reusable workflow, which holds the deploy logic once, and a caller that promotes the same build through staging and production.

# .github/workflows/_deploy.yml (reusable: called, never triggered directly)
name: deploy-env

on:
  workflow_call:
    inputs:
      environment:
        type: string
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    environment: ${{ inputs.environment }}   # protection rules apply here
    permissions:
      contents: read
      id-token: write                        # lets the job request an OIDC token
    steps:
    - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      with:
        persist-credentials: false
    # v6.3.0 (comment moved up only to fit this page's width)
    - uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd
      with:
        role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}  # per-environment variable
        aws-region: ${{ vars.AWS_REGION }}
    - name: Deploy
      env:
        DEPLOY_ENV: ${{ inputs.environment }}   # pass via env, not inline
      run: ./scripts/deploy.sh "$DEPLOY_ENV"
    - name: Smoke test
      env:
        DEPLOY_ENV: ${{ inputs.environment }}
      run: ./scripts/smoke.sh "$DEPLOY_ENV"
# .github/workflows/release.yml
name: release

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: release-main
  cancel-in-progress: false   # never kill a deploy half-way

jobs:
  staging:
    uses: ./.github/workflows/_deploy.yml
    with:
      environment: staging
    permissions:
      contents: read
      id-token: write

  production:
    needs: staging
    uses: ./.github/workflows/_deploy.yml
    with:
      environment: production   # waits here for a required reviewer
    permissions:
      contents: read
      id-token: write

Three design choices are deliberate. The caller grants id-token: write only to the jobs that deploy, because a called workflow cannot receive more than its caller has. The concurrency group does not cancel in progress, because a half-finished deploy is worse than a slightly late one. And the environment name is passed to the shell through env, which keeps the script-injection habit even where the input is trusted.

A long-lived secret may still be the right tool for an API that has no OIDC support, such as many LLM providers. Then scope it to an environment, so it is released only to approved jobs, and give it the smallest spend limit the provider allows.

Deep dive 4: Harden the path, the supply-chain lessons

Every uses: line runs someone else’s code with your token and, often, your secrets. Most of the time that is fine. The tj-actions incident shows what happens when it isn’t.

What happened. CISA’s alert covers every version of tj-actions/changed-files used between 12 March 2025 00:00 UTC and 15 March 12:00 UTC. The National Vulnerability Database records that tags v1 through v45.0.7 were modified to point at a single malicious commit, 0e58ed8. StepSecurity detected it on 14 March when its runtime monitor flagged an unexpected network endpoint. The payload dumped the runner process’s memory and printed secrets, double-encoded in base64, into the workflow log. On a public repository, that log is public. The likely entry point was another compromised action, reviewdog/action-setup@v1 (CVE-2025-30154), which exposed a bot’s personal access token with write access to the tj-actions repository. CVE-2025-30066 scored 8.6 (high), and CISA added it to its Known Exploited Vulnerabilities catalogue. The fix shipped in v46.0.1.

What saved some teams. Wiz’s analysis was unambiguous: users who had pinned the action to a commit hash were not affected, unless they happened to update to the malicious hash during the attack window. A tag is a movable pointer. A full 40-character commit SHA is the content itself.

Exhibit 8

Tens of thousands of repositories were exposed; 218 leaked secrets, mostly short-lived tokens

The tj-actions/changed-files incident, March 2025, from exposure to confirmed leaks (number of repositories)

Using the actionStepSecurity estimate23,000+
Referenced in a workflowdefault branches5,416
Ran in the window24 hours, 14–15 Mar614
Leaked secretsprinted to logs218

Bars use different methods. 23,000 is StepSecurity’s estimate of repositories using the action. The other three come from Endor Labs’ GitHub code search, which covered default branches and direct references only, and a 24-hour window from 14 Mar 15:00 UTC. Endor found that the vast majority of leaked secrets were short-lived GITHUB_TOKENs.

Source: StepSecurity (14 Mar 2025); Endor Labs (19 Mar 2025)

View data table
StageRepositoriesSource
Using the action23,000+StepSecurity
Referencing it in a default-branch workflow5,416Endor Labs
Ran an affected workflow in the window614Endor Labs
Leaked secrets in logs218Endor Labs

The funnel teaches two lessons. First, exposure is set by pinning: a workflow that ran a SHA-pinned version during the window fetched the code it had always fetched. Second, damage is set by privilege. Most of what leaked was the GITHUB_TOKEN, which expires with the job, but Endor Labs also found a few dozen DockerHub, npm and AWS credentials. Those are exactly the long-lived secrets that OIDC and environment scoping remove.

It kept happening. In August 2025, Nx merged a workflow that used the pull_request_target trigger and interpolated the pull request title into a bash step. pull_request_target runs in the context of the base repository, with its token and secrets, even for pull requests from forks. The workflow was reverted on master the next day, but outdated branches still contained it. An attacker pushed a commit that altered the publishing workflow to send the npm token to an external webhook, then opened a pull request with a crafted title against an old branch; the injected command used the workflow’s token to trigger publishing. Malicious Nx versions with a credential-stealing install script reached npm on 26 August. In March 2026, attackers using credentials left over from an earlier, incompletely rotated breach force-pushed 76 of the 77 version tags of aquasecurity/trivy-action and all seven of setup-trivy to malicious commits (CVE-2026-33634). The payload ran before the real security scan, so pipelines looked normal.

GitHub has responded with a series of platform controls, most of them free.

Exhibit 9

Each attack on the path has been followed by a new platform gate

Major CI supply-chain incidents and GitHub’s responses, 2025–2026

  1. 14 MAR 2025tj-actions/changed-files tags rewrittenSecrets dumped into build logs of repositories that referenced the action by tag.
  2. 15 AUG 2025SHA-pinning enforcement in Actions policyAdmins can require full commit SHAs; unpinned workflows fail. Blocklists added.
  3. 24–26 AUG 2025Nx compromised via pull_request_targetPR-title injection steals the npm token; malicious versions published.
  4. 28 OCT 2025Immutable releases generally availableRelease assets and tags cannot be changed or deleted once published.
  5. 8 DEC 2025pull_request_target uses the default branchStale workflows on other branches no longer run for this trigger.
  6. 19 MAR 2026trivy-action: 76 of 77 tags force-pushedCredential theft runs before the real scan.
  7. 15 JUL 2026OIDC subject claims with immutable IDsEnforced for new repositories, renames and transfers.
  8. SEP 2026Lockfiles in preview; execution protections GAactions.lock pins transitive dependencies; rules control who and what can trigger workflows.
  9. 2 NOV 2026pull_request_target off by default (public repos)Default rule moves from evaluate mode to enforcement for affected public repositories.

Dates from the cited advisories and changelogs. Lockfiles had a public-preview label on GitHub’s roadmap from 3 Sep 2026; the GitHub docs describe the feature as public preview.

Source: CISA; Nx; Aqua Security; GitHub Changelog and Blog

Taken together, these give a clear order of operations for any team:

  1. Pin every third-party action to a full commit SHA, with the version in a trailing comment so humans can read it. GitHub’s secure-use guidance says to pin to a commit SHA, and to pin to a tag “only if you trust the creator”, because “a tag can be moved or deleted if a bad actor gains access to the repository”. Organisation admins can now enforce SHA pinning, so an unpinned workflow fails rather than relying on reviewers to notice.
  2. Adopt the lockfile when you can. GitHub’s docs now call a workflow dependency lockfile (.github/workflows/actions.lock, generated by the gh actions-lock extension) “the strongest protection available”, superseding SHA pinning for repositories that adopt it. It also records transitive dependencies, the actions your actions call, which a SHA in your own file cannot reach. As of October 2026 it is in public preview, so trial it on a non-critical repository first.
  3. Keep pins fresh with Dependabot. A pin freezes vulnerabilities as well as attacks. Dependabot’s github-actions ecosystem opens pull requests when actions release new versions, and security updates when an action you use has a published vulnerability. A cooldown lets new versions age for a few days before you adopt them, so attacks that are caught quickly never reach you.
  4. Avoid pull_request_target unless you need it, and never check out or run pull request code under it. Use pull_request, which gives fork runs no secrets and a read-only token. For public repositories, GitHub’s new default rule will disable the trigger on 2 November 2026 unless an event policy explicitly allows it.
  5. Scan the workflows themselves. CodeQL analysis of GitHub Actions workflows has been generally available since April 2025 and flags injection patterns like the Nx one.
# .github/dependabot.yml: keep SHA-pinned actions up to date
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"           # finds workflows in .github/workflows
    schedule:
      interval: "weekly"
    cooldown:
      default-days: 7        # let new releases age before adopting them
    groups:
      actions:
        patterns: ["*"]      # one PR per week, not one per action

Deep dive 5: Observe and recover, schedules, reruns and cost

Scheduled workflows are your cheapest monitor. A schedule trigger runs a workflow on a cron expression, on the latest commit of the default branch. For an LLM app, the most valuable scheduled job is a canary: the full eval suite run against production every night. It catches the problems no pull request can, such as a vendor-side model change, an expired key or a stale retrieval index. Four facts from GitHub’s documentation shape how you write it. The shortest interval is every five minutes. Runs “can be delayed during periods of high loads”, and high-load times include the start of every hour, so pick an odd minute. In a public repository, scheduled workflows are disabled automatically after 60 days without repository activity. And since March 2026 you can give a schedule an IANA time zone instead of working in UTC.

# .github/workflows/nightly-canary.yml
name: nightly-canary

on:
  schedule:
    - cron: "17 2 * * *"          # 02:17, away from the top of the hour
      timezone: "Asia/Manila"
  workflow_dispatch:              # run on demand after a vendor incident

permissions:
  contents: read

concurrency:
  group: nightly-canary
  cancel-in-progress: false

jobs:
  canary:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    permissions:
      contents: read
      issues: write               # only to open an issue on failure
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
        with:
          python-version: "3.13"
          cache: pip
      - run: pip install -r requirements.txt
      - name: Full eval suite against production
        env:
          LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
          TARGET_URL: ${{ vars.PROD_URL }}
          EVAL_MIN_PASS_RATE: "0.90"
        run: python -m evals.run
      - name: Open an issue if the canary fails
        if: failure()
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          run_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
          gh issue create \
            --title "Nightly canary failed $(date -u +%F)" \
            --body "Run: $run_url"

Recovery is a revert. If every production change went through the path, then undoing one is the same path in reverse: revert the commit, let the required checks pass, and let release.yml promote it. Every deployment to an environment is recorded with its commit and time, which also gives you most of what you need to compute DORA’s throughput metrics without buying a tool. DORA’s 2025 report found that teams that used their version control system’s rollback features more often got more of AI’s benefit to team performance. The authors read it as a safety net that lets teams experiment without fear.

What it costs (October 2026). For most AI and data projects, the honest answer is very little. GitHub’s billing documentation says standard GitHub-hosted runners are free for public repositories, and self-hosted runners are free. Private repositories get a monthly allowance (2,000 minutes on GitHub Free, 3,000 on Pro and Team, 50,000 on Enterprise Cloud) and then pay per minute. GitHub cut hosted-runner prices by up to 39% on 1 January 2026. It had also announced a $0.002-per-minute platform charge on self-hosted runner usage from 1 March 2026, then postponed it “to take time to re-evaluate our approach”. GitHub’s billing documentation still listed self-hosted runners as free when I checked on 14 October 2026. One newer cost line to watch: since 1 June 2026, Copilot code review consumes Actions minutes in private repositories.

Exhibit 10

A macOS minute costs about ten times a Linux minute, and a matrix multiplies whichever you choose

Per-minute price of standard GitHub-hosted runners beyond the included allowance, private repositories (US$, October 2026)

Linux 1-core$0.002
Linux 2-core arm64$0.005
Linux 2-core x64$0.006
Windows 2-core$0.010
macOS$0.062

Public repositories and self-hosted runners are free. Included minutes per month: GitHub Free 2,000; Pro 3,000; Team 3,000; Enterprise Cloud 50,000. Larger runners are always billed.

Source: GitHub Docs, GitHub Actions billing (accessed 14 Oct 2026)

View data table
RunnerUS$ per minute
Linux 1-core (x64)0.002
Linux 2-core (arm64)0.005
Linux 2-core (x64)0.006
Windows 2-core0.010
macOS 3- or 4-core0.062

The arithmetic is reassuring. A 10-minute CI run on a two-cell Linux matrix uses 20 minutes, so a private repository on GitHub Free covers about 100 such runs a month before paying. Past the allowance, the same run costs about $0.12. The cost to watch in an LLM project is not the runner but the API calls your eval gate makes. Keep the PR suite small, pin a cheaper model for smoke checks where that is valid, and set a hard spend limit on the CI key.

Case examples

This website (a small path, fully gated). The site you are reading is deployed by a single GitHub Actions workflow. It is a small example, but it shows the ladder at the scale most side projects and many internal tools actually run at.

Reviewing the file for this article showed the trade-offs this article describes. The first version pinned only the third-party action and still referenced the first-party actions/checkout and actions/deploy-pages by major tag, which GitHub’s guidance accepts only for creators you trust; I have since pinned both to commit SHAs. The pinned withastro/action SHA resolves to v4.2.1, and the action’s repository has since published v5 and v6 tags: that is the cost of pinning without an update bot such as Dependabot to propose bumps. And the schedule is written in UTC with a comment, where the March 2026 time-zone support would now let it say timezone: "Asia/Manila" directly. None of these was urgent for a static site. All of them were cheap to fix, which is the point.

tj-actions/changed-files (pinning as the whole defence). Most organisations that used the action did nothing wrong in the usual sense. They referenced a popular, maintained action by version tag, as countless tutorials do. The ones protected were the ones whose workflows did not trust the tag. Those who were exposed had to work out which runs used the action during the window, decode their logs and rotate everything that might have appeared there.

Nx (a gate that was reverted but not removed). The vulnerable workflow was live on master for less than a day. It stayed exploitable because old branches still carried it, and pull_request_target at the time could run a workflow from a non-default branch. GitHub changed that behaviour from 8 December 2025. Nx’s remediation is a checklist in itself: it moved npm publishing to trusted publishing, which removes the long-lived token; required maintainer approval for workflows triggered by outside contributors; enabled CodeQL; and rebased old branches to remove the workflow.

trivy-action (incomplete rotation). The March 2026 attack reused credentials from a breach disclosed on 1 March, after a rotation that NVD’s record describes as “not atomic”. Recovery is a gate too. If you rotate secrets one at a time, the attacker keeps the ones you have not reached yet. OIDC shrinks the set of things to rotate in the first place.

Counterpoints

Implications by stakeholder

Exhibit 11

Everyone touches the path, so each role owns part of the gate

What each stakeholder should do differently

Hiring managersEngineering leadsPractitionersSecurity & risk
MindsetA repo’s workflows show how a candidate shipsThe pipeline is production infrastructureIf it isn’t in Git, it didn’t happenCI is part of the attack surface
Ask forA walk-through of a real workflow file and its permissionsRequired checks and a ruleset on every main branchAn eval gate for every prompt or model changeSHA-pinning enforcement and OIDC for cloud access
Invest inTake-home tasks that include CI, not just codeReusable workflows so teams don’t copy-paste YAMLA golden set that grows with every incidentLockfile trials and workflow scanning
Red flag“We deploy from my laptop”Admins bypassing every rulePrompts edited in a consoleLong-lived cloud keys in repo secrets

Author’s framework.

For hiring managers and recruiters, the workflow folder is one of the most honest artefacts in a portfolio. It shows whether a candidate thinks about permissions, reproducibility and failure, and it cannot be faked in an interview. For engineering leads, the main move is organisational: put rulesets and SHA-pinning enforcement at the organisation level so new repositories start gated, rather than relying on each team to remember. For security teams, the 2025–2026 incidents mean treating .github/workflows like infrastructure code: owned, reviewed, scanned and monitored.

What to do next: a gate-first plan

Start with the top-left quadrant of Exhibit 6. The order below puts the cheapest, highest-leverage gates first, so a team that stops after the first step is already far safer.

Exhibit 12

Most of the protection lands in the first afternoon

Recommended sequence for an AI or data project moving from prototype to production

  1. AFTERNOON 1Close the cheap gapsSet top-level permissions to contents: read. Pin every third-party action to a full SHA with a version comment. Add CODEOWNERS for .github/workflows. Replace any pull_request_target you don’t need with pull_request.
  2. WEEK 1Make CI bindingCI on every pull request: lint, unit tests, a 30–50 case eval gate. Add a ruleset on main requiring a PR, one approval and the named checks. Block force pushes. Add dependabot.yml for github-actions with a cooldown.
  3. MONTH 1Promote safelyCreate staging and production environments. Move cloud access to OIDC with subject-bound roles and delete stored keys. Put deploy logic in a reusable workflow; require a reviewer for production where your plan allows.
  4. QUARTER 1Observe and hardenNightly canary with the full eval suite and an issue on failure. Compute deployment frequency, lead time and change fail rate from deployment history. Trial the actions.lock lockfile; enable SHA-pinning enforcement at organisation level.

Author’s framework.

You will know it is working when three things are true. A failing eval blocks a merge at least once, and the team agrees it should have. A deploy to production happens on a normal afternoon without anyone holding their breath. And when the next compromised action is announced, the first question, “are we exposed?”, takes minutes to answer because every reference is pinned and every credential is short-lived.

Limitations and methodology

  • Survey evidence. DORA’s findings come from surveys (nearly 3,000 respondents in 2024 and 4,867 in 2025) and statistical models, not from measured deployment logs. The AI effects in Exhibit 2 are estimates with uncertainty intervals, and DORA’s samples skew towards teams already interested in delivery practice.
  • Incident counts. The tj-actions figures come from different methods. StepSecurity’s 23,000 counts repositories using the action; Endor Labs’ counts cover default branches and direct references only. Exhibit 8 mixes the two, and the note says so. Confirmed leaks may undercount private repositories, whose logs researchers could not see.
  • Moving targets. GitHub features and prices change often. Lockfiles were in public preview, the self-hosted runner charge was postponed rather than cancelled, and the pull_request_target default enforcement was scheduled for 2 November 2026 at the time of writing (14 October 2026). Check the linked changelogs before relying on any of them.
  • Code samples. The workflows are illustrative and assume a repository layout (app/, evals/, scripts/) that you will need to adapt. Commit SHAs were resolved from each action’s repository in October 2026; resolve your own. Action input names can change between major versions.
  • Frameworks and judgement. The ladder (Exhibits 4–5), the cost–severity matrix (Exhibit 6) and the plan (Exhibit 12) are my synthesis, not empirical findings. The field note describes this site’s own workflow file as committed; the observations about it are from reading that file.

Closing

Nobody can see “production” by looking at a server. What you can inspect is the path: which checks a change had to pass, who had to approve it, which credentials it could reach, and how fast it could be undone. Git records the path, and GitHub Actions enforces it. The most important parts of that enforcement cost almost nothing: a permissions: line, a 40-character hash and a required check.

Good engineers have always done these things by habit. The point of a gate is that it still works on the day nobody does.

In short

  1. Good habits (testing, reviewing, being careful with passwords) only protect you when someone remembers them. A gate is a habit the computer enforces for you, even on a busy Friday.
  2. The road itself can be attacked. In March 2025, a popular building block used by more than 23,000 projects was secretly swapped for a version that leaked passwords. Projects that had locked it to an exact fingerprint were not affected.
  3. The most useful protections are nearly free: give automated jobs read-only access by default, lock outside tools to an exact version, use short-lived logins instead of stored keys, and make the tests required before anything can be merged.

Think about how food reaches a supermarket shelf. Nobody trusts a sandwich because of the shelf it sits on. You trust it because of the road it took: the kitchen was inspected, the temperature was logged, the label was checked, and if something goes wrong the batch can be traced and recalled. Software is the same. “Production” is not a place. It is the road a change has to travel, and the checkpoints it must pass on the way.

Most AI and data projects begin as a demo that works on one person’s laptop. Getting from there to something a business can rely on is mostly not about writing better code. It is about building the road: making sure every change gets checked, reviewed and recorded the same way, every time.

The argument of the full article, in plain words: production is about the path code takes, not the code itself. Git and GitHub Actions let you turn good habits into automatic checkpoints, and the cheapest checkpoints prevent the most expensive disasters.

Jargon decoder

In plain words
GitA system that records every change to a project, who made it and why, so any change can be compared or undone
Pull request (PR)A proposed change that others can review and test before it joins the main version
GitHub ActionsRobots on GitHub that run jobs automatically: test this, build that, deploy it
WorkflowA recipe file telling those robots what to do and when
CI (continuous integration)Automatically testing every proposed change
GateA check the system enforces: if it fails, the change cannot go through
EvalA test for AI answers: a fixed set of questions with known good answers, and a score
Token / secretA password-like key that lets a robot do things, such as publish or deploy
OIDCA way for a job to get a one-time pass from the cloud instead of carrying a permanent key
Pinning to a SHALocking an outside tool to the exact fingerprint of one version, so nobody can swap it

Why this matters more now

Three things have changed.

There is far more automated change. GitHub reported 11.5 billion minutes of these robot jobs in public projects in its 2025 year, up 35%. People merged 518.7 million pull requests.

AI writes more of the code, and that makes things wobblier. DORA, a long-running research programme on how software teams deliver, found in 2024 that using more AI made developers’ work look better (better documentation, better code, faster reviews), but delivery got less stable: an estimated 7.2% drop in stability for every 25% increase in AI use. In 2025, with 90% of people surveyed using AI, delivery got faster, but it was still getting less stable. DORA’s explanation was simple: without strong automated testing, good version control and fast feedback, more change just means more breakage.

Exhibit 1

AI made the work look better, but delivery got less stable

Estimated change for every 25% more AI use, DORA 2024 survey (%)

Documentation+7.5%
Code quality+3.4%
Stability−7.2%

These are estimates from a survey, not measurements of real deployments.

Source: DORA, Accelerate State of DevOps Report 2024

View data table
OutcomeEstimated change
Documentation quality+7.5%
Code quality+3.4%
Delivery stability−7.2%

The road itself is now being attacked. In eighteen months, attackers broke into three popular projects through their automation. GitHub put it plainly: attackers are targeting the automation itself, not just the software it builds.

The ladder: five steps from laptop to production

Here is the framework from the full article. Each step turns a good habit into something the system enforces.

Exhibit 2

Five steps from “works on my laptop” to production

Each step turns a habit into a checkpoint

  1. 01PushPut everything in Git, including the AI prompts and test questions.
  2. 02CheckEvery proposed change is tested automatically.
  3. 03GateFailing tests block the change. Robots get read-only access by default.
  4. 04PromoteChanges go to a test copy first, then to the real thing after approval.
  5. 05Watch & undoA nightly job checks the live system; any change can be reversed quickly.

Author’s framework.

Push. If a change isn’t recorded in Git, nobody can see it, review it or undo it. For an AI app, that includes the prompt (the instructions you give the model) and the list of test questions, not just the code. DORA found that teams who make small, frequent changes and use “undo” freely get more out of AI.

Check. Every pull request triggers the robots. They check the code’s style, run normal tests, and, for AI apps, run an eval: maybe 50 real questions with known good answers. If the AI’s score drops below, say, 90%, the check fails. AI answers vary a little each time, so you measure how much the score naturally wobbles and set the bar below that wobble. Otherwise the check fails at random and people learn to ignore it.

Gate. This is the step most teams skip. Having tests that run is not enough. They become a gate only when GitHub is told to refuse any change that fails them. A test you can ignore is a suggestion, and suggestions lose to deadlines.

Promote. Changes go to a test copy of the system (“staging”) first, then to the real one (“production”), where a named person can be required to approve. To talk to a cloud provider, the job uses OIDC: it asks for a one-time pass that only works for that one job and then expires. There’s no permanent key sitting around to be stolen.

Watch and undo. A scheduled job, like an alarm clock, runs the full set of AI test questions against the live system every night and opens a ticket if the score drops. And because every change went through Git, undoing one is just the same road in reverse.

The attack that proved the point

On 14 March 2025, a popular GitHub Actions building block called tj-actions/changed-files, used by more than 23,000 projects, was quietly swapped for a bad version. The attacker moved its version labels (like “v45”) to point at malicious code. That code copied secrets out of the robot’s memory and printed them into the job’s logs, which anyone can read on a public project. Nobody using it had changed anything. Their robots simply fetched the bad version the next time they ran.

Researchers later measured the damage. 218 projects actually leaked secrets, and most of what leaked was a short-lived robot key that expires when the job finishes.

Exhibit 3

Thousands were exposed; a few hundred leaked secrets

The March 2025 tj-actions incident (number of projects)

Using the tool23,000+
Leaked secrets218

The two counts were measured differently: 23,000+ is an estimate of projects using the tool; 218 is projects found to have leaked secrets.

Source: StepSecurity (14 Mar 2025); Endor Labs (19 Mar 2025)

View data table
StageProjects
Using the tool23,000+
Leaked secrets218

Two cheap habits made the difference:

  1. Pinning. A version label like “v45” is a sticky note that anyone with access can move. A SHA is a fingerprint of the exact contents, and it can’t be moved. Projects that pinned the tool to a SHA kept running the safe version.
  2. Least privilege. A robot key that can only read and expires within minutes is nearly worthless to a thief. A permanent key that can publish software or reach the cloud is not.

It happened again in other forms. In August 2025, the Nx project was hacked through a risky robot setting (pull_request_target) combined with a pull request whose title was crafted to run as a command. In March 2026, attackers moved 76 of the 77 version labels of a security-scanning tool called trivy-action. Each time, GitHub added a new safeguard: organisations can now require pinning (August 2025), releases can be locked so they can never change (October 2025), and a “lockfile” that records the exact version of every tool, including the tools your tools use, is in preview as of October 2026.

What it costs, and what to do first

For most projects, very little. Robot minutes are free for public projects. Private projects get a free monthly allowance (2,000 minutes on GitHub’s free plan) and then pay by the minute: about $0.006 a minute for a standard Linux machine, and around ten times that for a Mac. In an AI project, the bigger cost is usually the AI calls your tests make, so keep the per-change test set small and run the big one overnight.

If you do only one afternoon of work, do this:

  • Set the robots’ default access to read-only.
  • Pin every outside tool to a SHA, with the version written in a comment so humans can read it.
  • Name an owner who must review any change to the workflow files.
  • Make the tests required before anything can be merged.
  • Then, in the following weeks: add a small eval for AI answers, switch cloud logins to OIDC, add a staging step, and set up a nightly check.

Won’t all these gates slow you down? DORA’s data says the opposite: the best teams deploy whenever they want and break things least often, because their checks are automatic and their changes are small. What slows teams down is manual checks and big, risky batches.

If you remember one thing

You can’t see “production” by looking at a server. You can only see the road a change travelled: what checked it, who approved it, what keys it could touch, and how quickly it could be undone. Git records that road, and GitHub Actions enforces it. The most important checkpoints cost almost nothing: one line setting read-only access, one 40-character fingerprint, one required check.

Good engineers do these things out of habit. A gate keeps working on the day nobody remembers to.

Sources

  1. CISA — Supply Chain Compromise of Third-Party GitHub Action, CVE-2025-30066 (18 Mar 2025, revised 26 Mar 2025)
  2. NIST NVD — CVE-2025-30066, tj-actions/changed-files (published 15 Mar 2025)
  3. StepSecurity — Harden-Runner detection: tj-actions/changed-files action is compromised (14 Mar 2025)
  4. Wiz — GitHub Action tj-actions/changed-files supply chain attack: everything you need to know (15 Mar 2025)
  5. Endor Labs — Blast radius of the tj-actions/changed-files supply chain attack (19 Mar 2025)
  6. Nx — Security advisory GHSA-cxm3-wv7p-598c, malicious versions of Nx (27 Aug 2025)
  7. Aqua Security — Trivy supply chain attack: what you need to know (22 Mar 2026)
  8. NIST NVD — CVE-2026-33634, Trivy and trivy-action tag compromise (published 23 Mar 2026)
  9. GitHub Changelog — Actions policy now supports blocking and SHA pinning actions (15 Aug 2025)
  10. GitHub Changelog — Immutable releases are now generally available (28 Oct 2025)
  11. GitHub Changelog — Actions pull_request_target and environment branch protections changes (7 Nov 2025)
  12. GitHub Blog — What’s coming to our GitHub Actions 2026 security roadmap (26 Mar 2026)
  13. GitHub Changelog — Immutable subject claims for GitHub Actions OIDC tokens (23 Apr 2026)
  14. GitHub Changelog — Workflow execution protections in GitHub Actions generally available (17 Sep 2026)
  15. GitHub Docs — Workflow dependency lockfiles, public preview (accessed 14 Oct 2026)
  16. GitHub Docs — Secure use reference for GitHub Actions (accessed 14 Oct 2026)
  17. GitHub Docs — OpenID Connect in GitHub Actions (accessed 14 Oct 2026)
  18. GitHub Docs — Deployments and environments (accessed 14 Oct 2026)
  19. GitHub Docs — Events that trigger workflows: schedule (accessed 14 Oct 2026)
  20. GitHub Changelog — GitHub Actions: Late March 2026 updates (timezones for schedules; 19 Mar 2026)
  21. GitHub Docs — GitHub Actions billing: included minutes and per-minute rates (accessed 14 Oct 2026)
  22. GitHub Changelog — Update to GitHub Actions pricing (16 Dec 2025, with postponement note)
  23. GitHub Docs — Actions limits (accessed 14 Oct 2026)
  24. DORA / Google Cloud — Accelerate State of DevOps Report 2024 (Oct 2024)
  25. DORA / Google Cloud — State of AI-assisted Software Development 2025 (Sep 2025)
  26. Google Cloud — Announcing the 2025 DORA Report (Sep 2025)
  27. DORA — Software delivery performance metrics (guide)
  28. GitHub — Octoverse 2025 (28 Oct 2025)

Marc Castro

Forward-deployed AI engineer. I ship RAG, agent and LLMOps systems into enterprise production — currently Lead AI Engineer at Intelligent AI Solutions (UK).