Cyber Security  

CodeQL 2.26.3: Testing GitHub Actions Workflow Security Detection

Introduction

GitHub Actions has become an important part of modern software delivery. Build, test, deployment, release, infrastructure, and automation tasks can all be controlled through workflow files stored directly in a repository.

That flexibility is useful, but it also creates a security concern.

A workflow is code.

If an attacker can influence a workflow's inputs, expressions, environment variables, cache behavior, or checkout process, the workflow may become a path to execute unintended commands with permissions available to the job.

This is where CodeQL becomes particularly valuable.

CodeQL 2.26.3 improves several GitHub Actions security queries and introduces additional modeling for JavaScript and TypeScript applications. The release includes improvements around untrusted checkout detection, cache poisoning, environment-variable injection, workflow triggers, and output clobbering.

For development teams, the interesting question is not simply whether CodeQL finds vulnerabilities.

It is whether the updated analysis can help identify unsafe GitHub Actions patterns before they become a real CI/CD security problem.

Why GitHub Actions Workflows Need Security Analysis

A typical workflow might look harmless:

name: Build

on:
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Build
        run: dotnet build

      - name: Test
        run: dotnet test

But GitHub Actions workflows can also:

  • Read repository contents

  • Access environment variables

  • Use tokens

  • Download dependencies

  • Upload artifacts

  • Access caches

  • Run scripts

  • Trigger deployments

  • Interact with cloud services

  • Use third-party actions

The security question therefore becomes:

Who controls the input?
        |
        v
What does the workflow do with it?
        |
        v
What permissions does the job have?

That is the foundation of workflow security.

What CodeQL Does

CodeQL analyzes source code and represents it as a queryable model.

Instead of checking only individual lines, it can reason about relationships such as:

Untrusted Input
      |
      v
Workflow Expression
      |
      v
Privileged Step
      |
      v
Potential Security Risk

This is particularly useful for GitHub Actions because the dangerous behavior may involve multiple parts of a workflow.

For example:

run: echo "${{ github.event.pull_request.title }}"

The risk is not simply that the workflow contains a string.

The important question is whether attacker-controlled data can reach a command execution context.

CodeQL 2.26.3 and GitHub Actions

CodeQL 2.26.3 includes several GitHub Actions analysis improvements.

Among them:

  • Better recognition of untrusted data from github.event.merge_group

  • Improved output-clobbering detection

  • Better cache-poisoning analysis

  • Improved untrusted-checkout alerts

  • More accurate environment-variable injection analysis

  • Better classification of workflow triggers

  • Improvements to how cache permissions are considered

These changes matter because CI/CD security problems are often dependent on context.

A query that understands the trigger, source of data, and permissions can produce more useful results than a simple pattern search.

Understanding Untrusted Data

One of the most important concepts in GitHub Actions security is trust level.

Not every event has the same security characteristics.

For example:

push
pull_request
merge_group
workflow_dispatch
schedule

can have different sources of input and different security implications.

A workflow should therefore ask:

Can an untrusted actor influence this value?

before passing that value into a sensitive operation.

Example of Dangerous Command Construction

Consider:

- name: Print title
  run: echo "${{ github.event.pull_request.title }}"

The problem is that the pull request title is external input.

A safer approach is to pass the value through an environment variable:

- name: Print title
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: echo "$PR_TITLE"

This does not automatically make every workflow safe, but it avoids directly embedding untrusted data into shell source.

The broader principle is:

Untrusted Data
      |
      v
Safe Data Boundary
      |
      v
Command

rather than:

Untrusted Data
      |
      v
Shell Source

Why Context Matters

Suppose the same input is used in two workflows.

Workflow A:

permissions:
  contents: read

Workflow B:

permissions:
  contents: write

The potential impact is different.

This is why CodeQL's GitHub Actions queries attempt to reason about the context in which a workflow operates.

CodeQL 2.26.3 specifically improves environment-variable injection analysis by requiring the untrusted source and privileged context to originate from the same trigger event.

That kind of contextual analysis can reduce misleading results.

Untrusted Checkout

Checkout behavior is another important security area.

A workflow may check out code associated with an external contribution and then execute scripts from that repository.

Conceptually:

External Contribution
        |
        v
Checkout Code
        |
        v
Run Repository Script
        |
        v
Potential Code Execution

This becomes particularly important when the workflow has elevated permissions or access to sensitive resources.

CodeQL 2.26.3 improves the actions/untrusted-checkout queries and changes the reported path so that alerts begin at expressions controlling the untrusted checkout.

That makes the alert more directly connected to the risky configuration.

Why Self-Hosted Runners Need Extra Attention

A self-hosted runner executes workflow jobs on infrastructure controlled by the organization.

That can provide access to:

Internal Network
Private Tools
Cached Credentials
Local Files
Cloud Configuration

Therefore, allowing untrusted code to execute on a self-hosted runner can have a much larger impact than executing it on an isolated managed runner.

However, CodeQL 2.26.3 removed the codeql.actions.security.SelfHostedQuery module because runner labels cannot reliably distinguish self-hosted runners from managed runners. Custom CodeQL queries depending on that module need to be updated.

This is an important example of why teams maintaining custom queries should pay attention to CodeQL breaking changes.

Cache Poisoning

GitHub Actions caching is useful for improving build performance.

A .NET workflow might cache:

NuGet packages
Build dependencies
Tool downloads

But caches can become a security concern when low-trust workflows can influence content that a higher-trust workflow later consumes.

Conceptually:

Low-Trust Workflow
       |
       v
Write Cache
       |
       v
Privileged Workflow
       |
       v
Restore Cache
       |
       v
Execute Cached Content

This creates a potential trust-boundary problem.

CodeQL 2.26.3 improves several cache-poisoning queries and now accounts for read-only cache access on low-trust triggers operating in the default-branch scope. Results are retained for situations where GitHub permits writing to the relevant cache scope.

A .NET Example

Consider a .NET workflow:

name: Build

on:
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest

    permissions:
      contents: read

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'

      - name: Restore
        run: dotnet restore

      - name: Build
        run: dotnet build --no-restore

      - name: Test
        run: dotnet test --no-build

This is relatively straightforward.

Now imagine a much more complicated workflow:

Pull Request
    |
    +--> Checkout
    |
    +--> Restore Cache
    |
    +--> Execute Script
    |
    +--> Generate Artifact
    |
    +--> Upload Artifact
    |
    +--> Deploy

Each additional capability increases the number of security relationships that need to be considered.

This is where static analysis becomes valuable.

Workflow Permissions Matter

One of the simplest improvements developers can make is limiting permissions.

Instead of relying on broad defaults:

permissions:
  contents: write

use the minimum required:

permissions:
  contents: read

If a particular job needs additional access, grant it at the narrowest appropriate scope.

For example:

jobs:
  build:
    permissions:
      contents: read

and separately:

jobs:
  release:
    permissions:
      contents: write

This creates a smaller blast radius.

The principle is:

Minimum Permission
        +
Minimum Scope
        =
Smaller Attack Surface

Environment Variable Injection

Environment variables are another area that deserves attention.

Consider:

- name: Run command
  env:
    USER_INPUT: ${{ github.event.issue.title }}
  run: |
    echo "$USER_INPUT"

Passing untrusted data through an environment variable is generally easier to reason about than inserting it directly into shell syntax.

But developers should still be careful about how that value is subsequently used.

For example:

eval "$USER_INPUT"

turns the value back into executable shell code.

The safer principle is:

Input
 |
 v
Data
 |
 v
Explicit Command

rather than:

Input
 |
 v
Dynamic Command

Output Clobbering

GitHub Actions workflows can communicate values between steps and jobs.

That makes output handling another potential security boundary.

Imagine:

Untrusted Input
      |
      v
Step Output
      |
      v
Later Step
      |
      v
Sensitive Operation

If output is interpreted incorrectly, data can potentially influence subsequent workflow behavior.

CodeQL 2.26.3 improves the actions/output-clobbering/high query.

The update also reduces false positives for simple jq path filters where output remains JSON-encoded and fixes a performance problem related to unescaped regular-expression input.

This illustrates an important point about security analysis:

Accuracy matters as much as detection.

If a tool reports too many irrelevant findings, developers may eventually stop trusting the results.

Trigger Classification Matters

Workflow triggers define when automation can execute.

For example:

on:
  push:
  pull_request:
  schedule:

These triggers have different characteristics.

CodeQL 2.26.3 improves how GitHub Actions queries classify the schedule event when determining whether a workflow can be externally triggered.

This is useful because security analysis needs to understand not only what a workflow does, but also how it can start.

Testing a Workflow With CodeQL

A repository can enable CodeQL analysis through GitHub's code-scanning workflow.

A simplified workflow might look like:

name: CodeQL

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
  schedule:
    - cron: '30 2 * * 1'

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest

    permissions:
      security-events: write
      packages: read
      actions: read
      contents: read

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      # CodeQL initialization and analysis
      # should be configured according to
      # the repository's supported languages.

The exact CodeQL workflow depends on the languages and setup selected for the repository.

The important point is that CodeQL should be treated as part of the security pipeline rather than as a one-time scan.

Testing GitHub Actions Security

A good testing strategy should deliberately include risky patterns.

For example:

Test 1
Untrusted PR input

Test 2
Workflow expression in shell

Test 3
Untrusted checkout

Test 4
Cache write/read relationship

Test 5
Environment variable injection

Test 6
Workflow output handling

Test 7
Self-hosted runner usage

Test 8
Privileged permissions

The goal is not to intentionally create production vulnerabilities.

Instead, create isolated test workflows or use known safe reproductions in a dedicated security-testing repository.

Then verify whether the expected CodeQL alerts appear.

Build a Security Test Repository

A dedicated repository can be useful for security tooling evaluation.

For example:

codeql-workflow-tests/
│
├── vulnerable/
│   ├── untrusted-checkout.yml
│   ├── cache-test.yml
│   ├── env-injection.yml
│   └── output-test.yml
│
├── safe/
│   ├── safe-checkout.yml
│   ├── safe-cache.yml
│   └── safe-input.yml
│
└── README.md

The test suite can then track:

Expected Alert
Actual Alert
False Positive
Missed Detection

This provides a repeatable way to evaluate security-analysis updates.

Testing False Positives

Detection accuracy is important.

Suppose a workflow contains:

- name: Filter JSON
  run: |
    jq '.users[] | .name' input.json

If CodeQL reports an output-clobbering issue, the team should understand why.

CodeQL 2.26.3 specifically improves this area so that simple jq path filters whose output remains JSON-encoded are no longer reported by the high-severity output-clobbering query.

That is the type of refinement that can make security tooling more practical.

Custom CodeQL Queries

Some organizations maintain custom CodeQL queries.

This can be useful when a company has internal security rules that are not covered by standard queries.

For example:

Internal deployment action
        |
        v
Must require environment
        |
        v
Must use approved permissions

A custom query could identify workflows that violate that standard.

However, custom queries create maintenance responsibilities.

When CodeQL changes APIs or libraries, custom queries may need updates.

CodeQL 2.26.3's removal of:

codeql.actions.security.SelfHostedQuery

is a concrete example of a breaking change that can affect custom query code.

Understanding CodeQL Coverage

CodeQL 2.26.3's default suite contains 497 security queries covering 170 CWEs, while the extended suite adds another 131 queries covering 32 additional CWEs.

These numbers are useful for understanding the scope of the analysis, but they should not be interpreted as:

497 queries = complete security coverage

No static-analysis tool can detect every security problem.

There will always be risks involving:

  • Business logic

  • Incorrect authorization design

  • Operational configuration

  • Secrets management

  • Infrastructure

  • Human processes

  • Unknown vulnerabilities

CodeQL is one layer of a broader security program.

Common Mistakes

Mistake 1: Giving Every Workflow Write Access

Use the smallest permission set possible.

Mistake 2: Running Untrusted Code on Sensitive Runners

Be especially careful with self-hosted infrastructure.

Mistake 3: Embedding External Input Directly Into Shell Commands

Treat external values as data.

Mistake 4: Ignoring Cache Trust Boundaries

Understand who can write and who can later consume cached data.

Mistake 5: Assuming Passing CodeQL Means the Workflow Is Secure

Static analysis has limitations.

Mistake 6: Ignoring CodeQL Breaking Changes

Custom queries may need maintenance when CodeQL libraries change.

Mistake 7: Disabling Alerts Without Understanding Them

First determine whether an alert is a true positive, false positive, or acceptable risk.

Troubleshooting CodeQL Alerts

ProblemWhat to Check
New GitHub Actions alert appearsReview the workflow trigger and data flow
Alert seems incorrectInspect the source, sink, and security context
Too many alertsCheck query suite and workflow patterns
Custom query stops compilingReview CodeQL breaking changes
Results change after upgradeCompare query and library changes
Workflow scan takes longerCheck query complexity and repository size
Alert path is difficult to understandReview the updated query message and source expression
Security check does not runVerify workflow triggers and permissions

Best Practices

Use Least-Privilege Permissions

Start with:

permissions:
  contents: read

and add permissions only when necessary.

Treat Workflow Files as Security-Sensitive Code

Review .github/workflows changes just as carefully as application code.

Separate Trusted and Untrusted Workflows

Do not assume every workflow input has the same trust level.

Avoid Dynamic Shell Construction

Keep external values separate from executable command syntax.

Be Careful With Caches

Understand who writes cache entries and who later consumes them.

Review Self-Hosted Runner Usage

Do not execute untrusted workloads on sensitive infrastructure without appropriate isolation.

Keep CodeQL Updated Carefully

New versions can improve detection, reduce false positives, and introduce breaking changes.

Maintain Custom Queries

If your organization uses custom CodeQL libraries, test them against new CodeQL releases.

Combine CodeQL With Other Controls

Use:

CodeQL
+
Dependency Scanning
+
Secret Scanning
+
Least Privilege
+
Pull Request Review
+
Runtime Security

rather than relying on one mechanism.

Advantages

Better GitHub Actions Detection

CodeQL 2.26.3 improves several workflow-security queries, including cache poisoning, untrusted checkout, environment-variable injection, and output clobbering.

Better Contextual Analysis

The queries consider more information about triggers, trust levels, and workflow behavior.

Reduced Noise

Several updates are specifically aimed at improving accuracy and reducing false positives.

Stronger JavaScript and TypeScript Modeling

The release adds modeling for Vue Composition API helpers and improves flow tracking in several JavaScript and TypeScript scenarios.

Useful for CI/CD Security

Workflow security can be analyzed alongside application code.

Disadvantages and Limitations

Not Complete Security Coverage

CodeQL cannot detect every vulnerability.

Query Updates Can Change Results

An upgrade may produce new findings or remove previous findings.

Custom Queries Can Break

CodeQL 2.26.3 includes a breaking change involving the removed SelfHostedQuery module.

Analysis Adds CI Complexity

Security analysis requires workflow configuration and maintenance.

False Positives Still Exist

Even improved queries require human investigation.

Workflow Security Requires Operational Controls

Code analysis alone cannot compensate for excessive permissions or poorly isolated infrastructure.

A Practical Security Workflow

A strong GitHub Actions security process can look like this:

Pull Request
      |
      v
Build
      |
      v
Unit Tests
      |
      v
CodeQL
      |
      +---- Security Alert
      |          |
      |          v
      |       Investigate
      |
      v
Dependency Checks
      |
      v
Secret Scanning
      |
      v
Human Review
      |
      v
Protected Branch

For workflow changes specifically:

Workflow Modification
        |
        v
Review Permissions
        |
        v
Review Triggers
        |
        v
Review Untrusted Inputs
        |
        v
Review Shell Commands
        |
        v
Review Cache Usage
        |
        v
Run CodeQL
        |
        v
Approve

This gives security analysis a clear place in the development lifecycle.

A Useful Security Checklist

Before merging a workflow change, ask:

[ ] Does the workflow use minimum permissions?

[ ] Can an external contributor influence any command?

[ ] Is untrusted input passed into shell code?

[ ] Does the workflow check out untrusted code?

[ ] Could an untrusted job write data consumed later?

[ ] Is caching configured safely?

[ ] Does the workflow use a self-hosted runner?

[ ] Are sensitive secrets available to the job?

[ ] Are workflow outputs handled safely?

[ ] Did CodeQL report anything?

[ ] Has every security alert been reviewed?

[ ] Is the workflow still required to pass before merge?

This checklist is simple, but it catches many common workflow-security mistakes.

Conclusion

CodeQL 2.26.3 is a useful update for teams that rely heavily on GitHub Actions because several of its changes focus directly on workflow-security analysis. The improvements around untrusted checkouts, cache poisoning, environment-variable injection, output clobbering, and trigger classification show why GitHub Actions should be treated as security-sensitive code rather than simple automation configuration. For .NET developers, the practical approach is to include CodeQL alongside builds, tests, dependency checks, and human review, while keeping workflow permissions as small as possible. It is also important to remember that CodeQL is an analysis tool, not a complete security solution. The best results come when teams use its findings to understand trust boundaries, investigate risky data flows, and continuously improve how their CI/CD workflows are designed.