What If CI/CD Pipelines Had Built-in Security and Observability with eBPF?
Observing and Securing GitHub Actions with eBPF
GitHub Actions often feels like a black box—things run, but you don’t really know what’s happening under the hood.
There’s little observability, no easy way to debug slow parts of the pipeline, and no clear insights into potential security risks.
It might seem like that’s just how it is—but it doesn’t have to be.
To put things into perspective, the study On the rise and fall of CI services in GitHub, which analyzed 201,000 repositories, found that GitHub Actions—despite being introduced only in 2019—became one of the most, if not the most, popular CI services in their dataset within just 18 months.
And while GitHub provides some tooling for observability, like Workflow Run Insights, and security features like Code Scanning and Secret Scanning (paid), it doesn’t really go much further than that.
I don’t know about you, but having zero observability and no runtime security enforcement in a CI/CD pipelines like GitHub Actions feels pretty scary to me.
Just consider a recent event from last week, when the tj-actions/changed-files action was compromised. More than 23,000 repositories were affected—which is a pretty large number.
This exploit simply printed the secrets into the GitHub Actions logs, which was quite problematic for public repositories.
Exploits obviously extend beyond this—exfiltrating credentials or data to a remote machine or even compromising self-hosted runners.
As seen above, many exploits originate from community-maintained GitHub Actions steps. While well-maintained steps may respond quickly to security incidents, the damage is often already done.
This is where the usage of tools like Harden Runner by Step Security or Jibril by Garnet becomes incredibly important.
In short, both tools are easy to install and integrate into your GitHub Actions pipeline, offering the ability to detect and prevent malicious operations—if any are detected, of course.
While this is impressive and technically complex under the hood, a full-fledged solution might not always be necessary. Maybe you just want:
identify bottlenecks and optimizing workflow execution time.
To restrict egress traffic to specific external endpoints.
To develop your own custom security observability solution.
All of these use cases can be implemented with eBPF. Since GitHub Actions run on ephemeral Azure VMs, anything you can achieve with eBPF on your own hosts can also be applied within a pipeline.
For this post, I’ll focus on the simplest eBPF example I could think of—it also happens to be one of the first eBPF programs I wrote when I started learning about it.
The setup goes like this:
Write your eBPF program as you normally would, using ebpf-go, Aya, or any other library that suits you best.
Package both the user-space and kernel-space components of your eBPF program into a Docker container.
Start the container at the beginning of your GitHub Actions workflow and stop it at the end.
The second and third step isn’t strictly necessary—you could just run the binary without containerization. However, containerizing your eBPF code simplifies deployment and ensures dependencies are packaged together, making it easier to deploy your solution across different environments.
To view observed kernel events, your eBPF kernel-space program sends data to user-space through perf or ring buffer.
From there it can be either exported to some third-party tool or even post comments on a PR—just like the tools mentioned earlier.
And that's it - It really doesn’t get much more complicated than that.
You can find the complete code on my GitHub Repository.
Before I wrap this up, there’s one more thing I want to mention.
One major downside I encountered while running different eBPF programs in GitHub Actions is that GitHub’s hosted runner images like ubuntu-latest
don’t have BPF-LSM enabled by default.
Even though the kernel version supports it, BPF-LSM requires manual enabling via a GRUB configuration update and a system reboot—something that just isn’t possible within GitHub Actions.
This was a bit frustrating, but ultimately acceptable since runtime enforcement can still be achieved using kprobes or other eBPF-based approaches.
If you're using self-hosted runners, the available eBPF features will depend on your specific environment.
⏪ Did you miss the previous issues? I'm sure you wouldn't, but JUST in case:
I hope you find this resource helpful. Keep an eye out for more updates and developments in eBPF in next week's newsletter.
Until then, keep 🐝-ing!
Warm regards, Teodor
I am not an expert on this topic but the same can be with bpftrace also right?
Is there any reason we would choose ebpf over bpftrace since in this example we are just exploring who and which process calls the execve