1"use strict";(self.webpackChunkcilium_io=self.webpackChunkcilium_io||[]).push([[3355],{2920:function(e,t,n){n.r(t),n.d(t,{Head:function(){return m},default:function(){return h}});var a=n(8453),s=n(6540),o=n(6452);function l(e){const t=Object.assign({span:"span",p:"p",a:"a",h2:"h2",table:"table",thead:"thead",tr:"tr",th:"th",tbody:"tbody",td:"td",strong:"strong",em:"em",h3:"h3",ul:"ul",li:"li",hr:"hr"},(0,a.RP)(),e.components),{BlogAuthor:n}=t;return n||function(e,t){throw new Error("Expected "+(t?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("BlogAuthor",!0),s.createElement(s.Fragment,null,s.createElement("style",null,"\n .supply-chain-post pre { font-size: 0.82em; line-height: 1.45; }\n"),"\n",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<span\n class="gatsby-resp-image-wrapper"\n style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1008px; "\n >\n <a\n class="gatsby-resp-image-link"\n href="/static/642efe4dd98d622058c8103226f4e9dc/8fd38/cover.png"\n style="display: block"\n target="_blank"\n rel="noopener"\n >\n <span\n class="gatsby-resp-image-background-image"\n style="padding-bottom: 56.34920634920635%; position: relative; bottom: 0; left: 0; background-image: url(\'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAC8UlEQVR42k2T+08UVxTH96f+0tif+i80aiWpr2hIQFtsIgqS2GADNjECJYRWHpGHRjEgUauGNOoPxkd9YDC2tEVDU1DULhgFEXRhwV2Wx4K7LMLOsrvMsrM7uzPz6cxg0p4f7j03555vPveccy1JRWN6rAP/1EtUVSMcDiPLsu6rSJKEpmkYZmyqvhjnaEzmUlsvuY332H2rn/t9LqZ8ghm3OL3DnC/ZxtXSIlzj41i7u3k1MMDA4CBvbDaC4ZApqGoKSUUx/QstXew9cg3H7CJfn77PF1XNVHWO6ZdULKLk505rK10Pbfh8szgcDiYmJhgZGUGMREgkEvpFaHkboz2g6nQR7CNOnO5Zcutusr6+jSdzMWr6/ChyAgv/M+PJyWQS5QOJIfRiqRe3NkllS5LVP4Up74syODONMO9lXUETN6yjnHoT4PAzn5mgC2r0PO9j1jf/QVRFURXsdjvCgp+CR4Vk/5NFtf0OGZfe8Xl9EOu7EMvRJbIrLrLz+gtODMUYD+kQmrpCWHa4lpydWfz9VwexuIwoxfiusJjSH8r4/mIxn/28lrQ/1nLwt2E+KhHongkh6o15NOSlfTyKOP07AY8Vo32m4LG6k2zesJH0tHQqyyp4bH1OzYkG9nybx4H8YrKu5vBJ08c0D0yS0uDnwZjIQkRC8HQRWlok+L6HgO/xf4LVR+vYtCaFnC8zWL95E1vSM8g7UMT2zF0UFv1IY+c5Vp39lGFhEs9inGl3P3PBMMLELwi+HrNUsl53Y7QsrqkZmu+1UVZeSUbadlK3ppK6bQfNv/5J8aEKvsrM4u3UPD1uG44ZG8KygjB2Ab/3qUmUMIXUlQ4ahLn5BykoKefKzbtcvn6bb/LzWJOygdejLgRR4kFHJ07PAmEZ3g/V60S9ZqpkjJNui/rTXXNBluMrZ0v7w25Ky2vZt7+AmuON3Ghppf5ME/ZRJ3EdIRoL4O2vJBwVkZMSkdiymZhUVDwBEfdCSI/F9clY+VH/AkVLy9cDMamhAAAAAElFTkSuQmCC\'); background-size: cover; display: block;"\n ></span>\n <picture>\n <source\n srcset="/static/642efe4dd98d622058c8103226f4e9dc/2ff5b/cover.webp 252w,\n/static/642efe4dd98d622058c8103226f4e9dc/4d583/cover.webp 504w,\n/static/642efe4dd98d622058c8103226f4e9dc/905a7/cover.webp 1008w,\n/static/642efe4dd98d622058c8103226f4e9dc/bb9f8/cover.webp 1512w,\n/static/642efe4dd98d622058c8103226f4e9dc/83a93/cover.webp 2016w,\n/static/642efe4dd98d622058c8103226f4e9dc/f7a32/cover.webp 2200w"\n sizes="(max-width: 1008px) 100vw, 1008px"\n type="image/webp"\n />\n <source\n srcset="/static/642efe4dd98d622058c8103226f4e9dc/019e0/cover.png 252w,\n/static/642efe4dd98d622058c8103226f4e9dc/0dcb2/cover.png 504w,\n/static/642efe4dd98d622058c8103226f4e9dc/832a9/cover.png 1008w,\n/static/642efe4dd98d622058c8103226f4e9dc/19357/cover.png 1512w,\n/static/642efe4dd98d622058c8103226f4e9dc/29ed2/cover.png 2016w,\n/static/642efe4dd98d622058c8103226f4e9dc/8fd38/cover.png 2200w"\n sizes="(max-width: 1008px) 100vw, 1008px"\n type="image/png"\n />\n <img\n class="gatsby-resp-image-image"\n src="/static/642efe4dd98d622058c8103226f4e9dc/832a9/cover.png"\n alt="cover"\n title=""\n loading="lazy"\n decoding="async"\n style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"\n />\n </picture>\n </a>\n </span>'}}),"\n",s.createElement("div",{className:"supply-chain-post"},s.createElement(t.p,null,"The last twelve months have been rough on the open source supply chain. ",s.createElement(t.a,{href:"https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan"}
1,"Axios was compromised on npm")," and shipped a remote access trojan inside otherwise normal-looking releases. ",s.createElement(t.a,{href:"https://futuresearch.ai/blog/litellm-pypi-supply-chain-attack/"},"LiteLLM's PyPI package was hijacked")," to exfiltrate environment variables. ",s.createElement(t.a,{href:"https://rosesecurity.dev/2026/03/20/typosquatting-trivy.html"},"Typosquatted forks of Trivy")," were published to catch people who fat-finger ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">go install</code>'}}),". And the canonical example, the ",s.createElement(t.a,{href:"https://en.wikipedia.org/wiki/2020_United_States_federal_government_data_breach"},"2020 SolarWinds breach"),", is still the cautionary tale we keep coming back to: attackers got into the build system and pushed malware through normal Orion updates to roughly 18,000 organizations, including U.S. federal agencies, NATO, and Microsoft. The malware sat dormant for months. The breach went undetected for the better part of a year."),s.createElement(t.p,null,"Cilium runs in the kernel-level networking path of millions of Kubernetes pods. If our supply chain were compromised, the blast radius would not be small. Hardening the project against that scenario is something we work on continuously, and we wanted to write down what we actually do, in detail. Most of what follows isn't Cilium-specific: any open source project running CI/CD on GitHub Actions can apply these patterns. We've also called out where we still fall short, in case any of it makes a useful starting point for someone else."),s.createElement(t.h2,null,"TL;DR"),s.createElement(t.p,null,"If you don't have time to read the whole thing, here's what Cilium does to harden its supply chain today, organized by which layer of the pipeline each control lives at:"),s.createElement(t.table,null,s.createElement(t.thead,null,s.createElement(t.tr,null,s.createElement(t.th,null,"Layer"),s.createElement(t.th,null,"Control"),s.createElement(t.th,null,"What it does"))),s.createElement(t.tbody,null,s.createElement(t.tr,null,s.createElement(t.td,null,s.createElement(t.strong,null,"Who triggers builds")),s.createElement(t.td,null,s.createElement(t.a,{href:"#workflow-trigger-restrictions-with-ariane"},"Trigger control via Ariane")),s.createElement(t.td,null,"Only verified org members can fire CI workflows from PR comments, against an explicit allow-list of workflows.")),s.createElement(t.tr,null,s.createElement(t.td,null,s.createElement(t.strong,null,"What code CI executes")),s.createElement(t.td,null,s.createElement(t.a,{href:"#separating-trusted-and-untrusted-code-in-ci"},"Two-phase checkouts for ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">pull_request_target</code>'}}))),s.createElement(t.td,null,"Trusted code (composite actions, scripts, signing logic) is loaded from the base branch; the PR head is only used as Docker build context, never executed as a script.")),s.createElement(t.tr,null,s.createElement(t.td,null,s.createElement(t.strong,null,"Who reviews CI changes")),s.createElement(t.td,null,s.createElement(t.a,{href:"#codeowners-as-a-review-gate"},"CODEOWNERS gates")),s.createElement(t.td,null,"Anything under ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">.github/</code>'}})," requires review from the security-focused CI team, and ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">auto-approve.yaml</code>'}})," requires a maintainer.")),s.createElement(t.tr,null,s.createElement(t.td,null,s.createElement(t.strong,null,"What dependencies CI pulls in")),s.createElement(t.td,null,s.createElement(t.a,{href:"#pinning-github-actions-by-sha-digest"},"SHA-pinned actions and images")),s.createElement(t.td,null,"Every ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">uses:</code>'}})," references a 40-character commit SHA; container images are pinned by ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">@sha256:</code>'}})," digest. ",s.createElement(t.a,{href:"#automated-updates-with-a-trust-boundary"},"Renovate")," keeps the pins fresh and waits 5 days before picking up new releases.")),s.createElement(t.tr,null,s.createElement(t.td,null,s.createElement(t.strong,null,"What Go modules ship in the binary")),s.createElement(t.td,null,s.createElement(t.a,{href:"#go-module-vendoring"},"Vendored Go dependencies")),s.createElement(t.td,null,"Everything is checked into ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">vendor/</code>'}})," and reviewed by the ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">@cilium/vendor</code>'}})," team, so a typosquatted or hijacked module shows up as a diff at review time.")),s.createElement(t.tr,null,s.createElement(t.td,null,s.createElement(t.strong,null,"What workflows are even allowed to look like")),s.createElement(t.td,null,s.createElement(t.a,{href:"#catching-mistakes-with-static-analysis"},"Static analysis on workflows")),s.createElement(t.td,null,"CodeQL enforces explicit ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">permissions:</code>'}}
1)," on every workflow, actionlint catches unsafe patterns, and both flag GitHub Actions expression injection in ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">run:</code>'}})," blocks.")),s.createElement(t.tr,null,s.createElement(t.td,null,s.createElement(t.strong,null,"What credentials are reachable")),s.createElement(t.td,null,s.createElement(t.a,{href:"#ci-vs-production-credential-isolation"},"CI vs. production credential isolation")),s.createElement(t.td,null,"CI credentials can only push to ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">*-ci</code>'}})," development tags; production registry credentials sit behind a protected ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">release</code>'}})," environment that requires maintainer approval.")),s.createElement(t.tr,null,s.createElement(t.td,null,s.createElement(t.strong,null,"What consumers can verify")),s.createElement(t.td,null,s.createElement(t.a,{href:"#signing-and-attesting-what-we-ship"},"Signed releases")),s.createElement(t.td,null,"Every release image and Helm chart is signed with ",s.createElement(t.a,{href:"https://github.com/sigstore/cosign"},"Sigstore Cosign")," using keyless OIDC, with SBOM attestations attached.")),s.createElement(t.tr,null,s.createElement(t.td,null,s.createElement(t.strong,null,"Where we still fall short")),s.createElement(t.td,null,s.createElement(t.a,{href:"#what-were-still-working-on"},"Gaps we're still closing")),s.createElement(t.td,null,"No SLSA provenance yet, no PR-time dependency review, no ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">govulncheck</code>'}})," in CI, and a handful of internal ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">@main</code>'}})," references that need to move to a dedicated composite-actions repo.")))),s.createElement(t.p,null,"The rest of the post walks through each row in more depth, including the design decisions behind them and the things we deliberately chose ",s.createElement(t.em,null,"not")," to do (like forking every third-party action into our own org)."),s.createElement(t.h2,null,"Controlling who runs what"),s.createElement(t.p,null,"The first question in any CI supply chain story is: who can trigger a build, and what code does it execute? Plenty of CI compromises start right here, by tricking the system into running attacker-controlled code with elevated privileges."),s.createElement("span",{id:"workflow-trigger-restrictions-with-ariane"}),s.createElement(t.h3,null,"Workflow trigger restrictions with Ariane"),s.createElement(t.p,null,s.createElement(t.a,{href:"https://github.com/cilium/ariane"},"Ariane")," is a GitHub bot we wrote in-house to dispatch CI workflows from PR comments. When a maintainer types ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">/test</code>'}})," or ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">/ci-eks</code>'}})," on a pull request, Ariane checks that the commenter belongs to the ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">organization-members</code>'}})," team, figures out which workflows to fire (including dependencies, like tests that need a fresh image build first), and dispatches them via ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">workflow_dispatch</code>'}}),"."),s.createElement(t.p,null,"The interesting bit is the allow-list. Only verified org members can trigger workflows, and the set of workflows that can be triggered is enumerated by hand in the config:"),s.createElement(t.p,null,s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/ariane-config.yaml"},s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">.github/ariane-config.yaml</code>'}}))),s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">allowed-teams</span><span class="token punctuation">:</span>\n <span class="token punctuation">-</span> organization<span class="token punctuation">-</span>members\n\n<span class="token key atrule">triggers</span><span class="token punctuation">:</span>\n <span class="token key atrule">/test\\s*</span><span class="token punctuation">:</span>\n <span class="token key atrule">workflows</span><span class="token punctuation">:</span>
1\n <span class="token punctuation">-</span> conformance<span class="token punctuation">-</span>aws<span class="token punctuation">-</span>cni.yaml\n <span class="token punctuation">-</span> conformance<span class="token punctuation">-</span>clustermesh.yaml\n <span class="token punctuation">-</span> conformance<span class="token punctuation">-</span>eks.yaml\n <span class="token comment"># ...and so on</span>\n <span class="token key atrule">depends-on</span><span class="token punctuation">:</span>\n <span class="token punctuation">-</span> /build<span class="token punctuation">-</span>images<span class="token punctuation">-</span>dependency\n <span class="token key atrule">/ci-aks</span><span class="token punctuation">:</span>\n <span class="token key atrule">workflows</span><span class="token punctuation">:</span>\n <span class="token punctuation">-</span> conformance<span class="token punctuation">-</span>aks.yaml\n <span class="token key atrule">depends-on</span><span class="token punctuation">:</span>\n <span class="token punctuation">-</span> /build<span class="token punctuation">-</span>images<span class="token punctuation">-</span>dependency</code></pre></div>'}}),s.createElement(t.p,null,"A random external commenter typing ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">/test</code>'}})," in a PR is ignored. They can't kick off our expensive cloud-provider conformance suites or burn through our CI minutes."),s.createElement("span",{id:"separating-trusted-and-untrusted-code-in-ci"}),s.createElement(t.h3,null,"Separating trusted and untrusted code in CI"),s.createElement(t.p,null,"When somebody opens a PR we need to build their code, but we obviously can't trust it. This is the classic ",s.createElement(t.a,{href:"https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/"},s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">pull_request_target</code>'}})," problem"),". We avoid ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">pull_request_target</code>'}})," where we can, but a handful of workflows still need it, and we wrap those in mitigating controls."),s.createElement(t.p,null,"The image build workflow is the canonical example. It splits the checkout in two:"),s.createElement(t.p,null,s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/workflows/build-images-ci.yaml"},s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">.github/workflows/build-images-ci.yaml</code>'}}))),s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Checkout base or default branch (trusted)\n <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd <span class="token comment"># v6.0.2</span>\n <span class="token key atrule">with</span><span class="token punctuation">:</span>\n <span class="token key atrule">ref</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> github.base_ref <span class="token punctuation">|</span><span class="token punctuation">|</span> github.event.repository.default_branch <span class="token punctuation">}</span><span class="token punctuation">}</span>\n <span class="token key atrule">persist-credentials</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>\n\n<span class="token comment"># ...trusted setup steps run here, including loading composite actions...</span>\n\n<span class="token comment"># Warning: since this is a privileged workflow, subsequent workflow job</span>\n<span class="token comment">
1# steps must take care not to execute untrusted code.</span>\n<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Checkout pull request branch (NOT TRUSTED)\n <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd <span class="token comment"># v6.0.2</span>\n <span class="token key atrule">with</span><span class="token punctuation">:</span>\n <span class="token key atrule">persist-credentials</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>\n <span class="token key atrule">ref</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> steps.tag.outputs.sha <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre></div>'}}),s.createElement(t.p,null,"The first checkout grabs the ",s.createElement(t.em,null,"base branch")," (code that's already been reviewed and merged) so we can load our composite actions, scripts, and the Cosign signing logic from a known-good source. Only after that does the workflow check out the PR head, and that checkout is used purely as build context for ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">docker build</code>'}}),". Nothing from the PR branch is ever executed as a script."),s.createElement(t.p,null,'We get security reports about this pattern fairly regularly. Automated scanners and well-meaning researchers see "',s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">pull_request_target</code>'}})," plus a second checkout\" and flag it as a vulnerability. In the general case they're right too. In ours, the workflow is intentionally designed so the pattern is safe:"),s.createElement(t.ul,null,"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"No ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">run:</code>'}})," steps execute scripts from the untrusted checkout.")," Every shell block after the second checkout is written inline in the workflow YAML (disk usage checks, file copies, digest output). Nothing is sourced from the PR branch."),"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"No composite actions are loaded from the untrusted checkout either.")," All composite actions (",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">set-runtime-image</code>'}}),", ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">cosign</code>'}}),", ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">set-env-variables</code>'}}),") come from the trusted base-branch checkout or from the saved ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">../cilium-base-branch/</code>'}})," directory. We're also working on moving these composite actions into a dedicated repository so we don't have to check out source to run them at all."),"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"Docker BuildKit does execute the untrusted Dockerfile"),", and that's the whole point of building a CI image from a PR. BuildKit runs in isolation: no GitHub Actions environment variables, no repo secrets, no access to the runner's Docker credential store. The build args we pass contain no secrets, just the runtime image reference and the operator variant name."),"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"Untrusted data flows into exactly one trusted action.")," The ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">runtime-image*.txt</code>'}})," file from the PR is fed into the trusted ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">set-runtime-image</code>'}})," action, which checks the image reference starts with ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">quay.io/cilium/</code>'}})," and strips newlines so an attacker can't smuggle in a ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">GITHUB_ENV</code>'}})," injection. There's no way to repoint the build to anything outside the Cilium namespace."),"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"Only CI credentials are in scope.")," The Docker login uses ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">QUAY_USERNAME_CI</code>'}})," / ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">QUAY_PASSWORD_CI</code>'}}
1),", which can only push to the ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">-ci</code>'}})," development registry. Production credentials aren't on the runner at all."),"\n"),s.createElement(t.p,null,"The worst-case outcome of a compromised PR build is a malicious CI image landing in the development registry, which is the same blast radius any CI system that builds contributor code carries. We do appreciate every report and read each one carefully, but this pattern is intentional."),s.createElement("span",{id:"codeowners-as-a-review-gate"}),s.createElement(t.h3,null,"CODEOWNERS as a review gate"),s.createElement(t.p,null,"We lean on ",s.createElement(t.a,{href:"https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners"},"CODEOWNERS")," pretty heavily so that changes always land in front of the people with the most context. For CI configuration that means anything under ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">.github/</code>'}})," is owned by ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">@cilium/github-sec</code>'}})," (our security-focused CI team) plus ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">@cilium/ci-structure</code>'}}),", and the ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">auto-approve.yaml</code>'}})," workflow is owned by ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">@cilium/cilium-maintainers</code>'}}),":"),s.createElement(t.p,null,s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/CODEOWNERS"},s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">CODEOWNERS</code>'}}))),s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">/.github/ @cilium/github-sec @cilium/ci-structure\n/.github/ariane-config.yaml @cilium/github-sec @cilium/ci-structure\n/.github/renovate.json5 @cilium/github-sec @cilium/ci-structure\n/.github/workflows/ @cilium/github-sec @cilium/ci-structure\n/.github/workflows/auto-approve.yaml @cilium/cilium-maintainers</code></pre></div>'}}),s.createElement(t.p,null,"Nobody can change the CI pipeline without an explicit review from the team responsible for keeping it safe."),s.createElement(t.h2,null,"Locking down dependencies"),s.createElement(t.p,null,"Once you control who triggers builds, the next question is what code those builds pull in. A pinned workflow that fetches a compromised dependency is still a compromised workflow."),s.createElement("span",{id:"pinning-github-actions-by-sha-digest"}),s.createElement(t.h3,null,"Pinning GitHub Actions by SHA digest"),s.createElement(t.p,null,"The single highest-leverage thing any project can do here is stop trusting mutable tags."),s.createElement(t.p,null,"Every ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">uses:</code>'}})," directive in our workflow files references actions by full 40-character commit SHA, with the human-readable version stuck on the end as a comment:"),s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">-</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd <span class="token comment"># v6.0.2</span></code></pre></div>'}}),s.createElement(t.p,null,"If somebody compromises the ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">v6</code>'}})," tag on ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">actions/checkout</code>'}})," and force-pushes malicious code, our workflows won't pull it. They're pinned to a specific commit. Same story for every third-party action we use: ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">docker/build-push-action</code>'}}),", ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">sigstore/cosign-installer</code>'}}),", ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">golangci/golangci-lint-action</code>'}}),", dozens more. We pin container images used directly in workflow steps the same way, by ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">@sha256:</code>'}})," digest, so even the tools we run inside CI are content-addressed."),s.createElement(t.p,null,"Pinning has one annoying blind spot, which is transitive dependencies. When we pin ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">actions/checkout@de0fac2e...</code>'}})," we know exactly which code runs for that action. But if ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">actions/checkout</code>'}})," itself references another action by tag (",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">uses: some-org/some-helper@v1</code>'}}),"), that resolution happens at runtime and is invisible to us. An attacker who pops the nested dependency can still reach our pipeline."),s.createElement(t.p,null,"A fix is on the way: workflow-level dependency locking was announced in GitHub's ",s.createElement(t.a,{href:"https://github.blog/news-insights/product-news/whats-coming-to-our-github-actions-2026-security-roadmap/"},"2026 Actions security roadmap"),". It would add a ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">dependencies:</code>'}})," section to workflow YAML that locks all direct ",s.createElement(t.em,null,"and transitive")," action dependencies by commit SHA, similar to what ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">go.mod</code>'}})," + ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">go.sum</code>'}})," do for Go. We'll adopt it as soon as it ships."),s.createElement("span",{id:"automated-updates-with-a-trust-boundary"}),s.createElement(t.h3,null,"Automated updates with a trust boundary"),s.createElement(t.p,null,"Maintaining SHA pins by hand would be miserable, so we don't. Our ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/renovate.json5"},"Renovate configuration")," extends the ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">helpers:pinGitHubActionDigests</code>'}})," preset and sets ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">pinDigests: true</code>'}})," globally. When a new action version drops, Renovate opens a PR bumping the SHA. We stay current without ever falling back to a mutable ref."),s.createElement(t.p,null,"Renovate runs as a ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/workflows/renovate.yaml"},"self-hosted bot")," on an hourly schedule, using a dedicated GitHub App with fine-grained permissions instead of a personal access token. ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">vulnerabilityAlerts</code>'}})," is on, so known CVEs in the dependency tree turn into PRs straight away."),s.createElement(t.p,null,"We ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/pull/45491"},"recently added")," a Renovate cooldown so we don't pick up brand-new releases the moment they appear. Given the current pace of supply chain attacks, those few days are usually the window in which a compromised package gets noticed and yanked:"),s.createElement(t.p,null,s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/renovate.json5"},s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">.github/renovate.json5</code>'}}))),s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="json5"><pre class="language-json5"><code class="language-json5"><span class="token punctuation">{</span>\n <span class="token comment">// Dependency cooldown: skip versions published less than 5 days ago</span>\n <span class="token property">"matchUpdateTypes"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"major"</span><span class="token punctuation">,</span> <span class="token string">"minor"</span><span class="token punctuation">,</span> <span class="token string">"patch"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>\n <span class="token property">"minimumReleaseAge"</span><span class="token operator">:</span> <span class="token string">"5 days"</span>\n<span class="token punctuation">}</span><span class="token punctuation">,</span>\n<span class="token punctuation">{</span>\n <span class="token property">
1"matchPackageNames"</span><span class="token operator">:</span> <span class="token punctuation">[</span>\n <span class="token string">"actions/{/,}**"</span><span class="token punctuation">,</span> <span class="token comment">// GitHub\'s official actions</span>\n <span class="token string">"docker/{/,}**"</span><span class="token punctuation">,</span> <span class="token comment">// Official Docker actions</span>\n <span class="token string">"cilium/{/,}**"</span><span class="token punctuation">,</span> <span class="token comment">// Our own ecosystem</span>\n <span class="token string">"k8s.io/{/,}**"</span><span class="token punctuation">,</span> <span class="token comment">// Kubernetes official</span>\n <span class="token string">"sigs.k8s.io/{/,}**"</span><span class="token punctuation">,</span> <span class="token comment">// Kubernetes SIGs</span>\n <span class="token string">"golang.org/x/{/,}**"</span><span class="token punctuation">,</span> <span class="token comment">// Go experimental</span>\n <span class="token string">"github.com/golang/{/,}**"</span><span class="token punctuation">,</span> <span class="token comment">// Go official org</span>\n <span class="token string">"github.com/prometheus/{/,}**"</span><span class="token punctuation">,</span>\n <span class="token string">"github.com/hashicorp/{/,}**"</span><span class="token punctuation">,</span>\n <span class="token string">"go.etcd.io/etcd/{/,}**"</span><span class="token punctuation">,</span>\n <span class="token comment">// ...trimmed</span>\n <span class="token punctuation">]</span><span class="token punctuation">,</span>\n <span class="token property">"automerge"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>\n <span class="token property">"automergeType"</span><span class="token operator">:</span> <span class="token string">"pr"</span><span class="token punctuation">,</span>\n <span class="token property">"groupName"</span><span class="token operator">:</span> <span class="token string">"auto-merge-trusted-deps"</span><span class="token punctuation">,</span>\n <span class="token property">"reviewers"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"ciliumbot"</span><span class="token punctuation">]</span>\n<span class="token punctuation">}</span></code></pre></div>'}}),s.createElement(t.p,null,"Updates from this allow-list auto-merge after CI passes. Everything else needs a human review."),s.createElement(t.p,null,"The ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/workflows/auto-approve.yaml"},"auto-approve workflow")," adds another belt-and-suspenders check: it verifies that the PR was created by ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">cilium-renovate[bot]</code>'}})," ",s.createElement(t.em,null,"and")," that the review request was actually triggered by the bot itself, not by a human pretending to be it:"),s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">if</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span>\n github.event.pull_request.user.login == \'cilium<span class="token punctuation">-</span>renovate<span class="token punctuation">[</span>bot<span class="token punctuation">]</span>\' <span class="token important">&&</span>\n (github.triggering_actor == \'cilium<span class="token punctuation">-</span>renovate<span class="token punctuation">[</span>bot<span class="token punctuation">]</span>\' <span class="token punctuation">|</span><span class="token punctuation">|</span>\n github.triggering_actor == \'auto<span class="token punctuation">-</span>committer<span class="token punctuation">[</span>bot<span class="token punctuation">]</span>\')\n <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre></div>'}}),s.createElement(t.p,null,"If those conditions don't hold, no auto-approval happens."),s.createElement("span",{id:"go-module-vendoring"}),s.createElement(t.h3,null,"Go module vendoring"),s.createElement(t.p,null,"All Go dependencies are vendored and committed to the repo. CI ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/workflows/lint-go.yaml"},"verifies there's no drift")," between ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">go.mod</code>'}}),", ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">go.sum</code>'}}),", and ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">vendor/</code>'}}),". Builds are reproducible and don't talk to external module proxies at build time, so a tampered module on a proxy never reaches us. We also run license checks (",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">go run ./tools/licensecheck</code>'}}),") to keep dependencies with unwanted licenses out of the tree."),s.createElement(t.h3,null,"Would forking actions into our own org be even safer?"),s.createElement(t.p,null,"In theory, yes. If we forked every third-party action into ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">cilium/</code>'}})," and pinned to our own fork's SHA, an upstream compromise wouldn't reach us at all. Some high-security projects do exactly this."),s.createElement(t.p,null,"We've decided against it, mostly because the operational cost is real and the security win is smaller than it first looks:"),s.createElement(t.ul,null,"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"Maintenance burden.")," We use dozens of third-party actions. Keeping forks in sync with upstream security patches becomes a part-time job, and a stale fork with unpatched vulnerabilities is itself a security problem."),"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"Missed improvements.")," Upstream actions regularly fix bugs and ship security features. Forks add friction to picking those up."),"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"Renovate complexity.")," Our update pipeline would have to track upstream releases, open PRs against each fork, and then update the consuming workflows. The chain doubles in length."),"\n"),s.createElement(t.p,null,"SHA pinning gives us the immutability guarantee that actually matters: a specific commit is a specific commit, regardless of which org hosts it. Combined with Renovate proposing updates as new versions come out, we get the security benefit without the operational tax. If a major action provider got repeatedly compromised, forking the high-risk ones is a reasonable escalation, but we haven't been pushed to that point."),s.createElement(t.h3,null,"The same tradeoff applies to Go dependencies"),s.createElement(t.p,null,'The "should we fork it?" question applies just as much to our Go dependency tree. Cilium pulls in hundreds of Go modules: Kubernetes client libraries, gRPC, etcd, Prometheus, the works. Forking and maintaining all of them isn\'t realistic.'),s.createElement(t.p,null,"Go is in a slightly better starting position than npm or PyPI because import paths explicitly include the source (",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">github.com/stretchr/testify</code>'}}),"), which kills off the ",s.createElement(t.a,{href:"https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610"},"Dependency Confusion")," attack class entirely. Typosquatting is still a real threat, though. ",s.createElement(t.a,{href:"https://michenriksen.com/archive/blog/finding-evil-go-packages/"},"Michael Henriksen's research")," found typosquatted Go packages in the wild, including a fork of ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">urfave/cli</code>'}})," registered as ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">utfave</code>'}})," (one transposed letter) that phoned home with hostname, OS, and architecture. Swapping that callback for a reverse shell would have been a one-line change."),s.createElement(t.p,null,"And typosquatting isn't the worst case. SolarWinds showed that a legitimate, widely-trusted vendor can have its build pipeline compromised and then push malware through normal updates. Same can happen to any Go module: an attacker who gets into a maintainer's account publishes a malicious release, the proxy caches it, and anyone running ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">go get</code>'}})," pulls it in. That's why we vendor: it moves the trust decision from build time, where it's invisible, to review time, where a human can see the diff."),s.createElement(t.p,null,"Vendoring is the main defense here. A typosquatted import path shows up as a diff in ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">vendor/</code>'}})," during code review instead of silently resolving from a module proxy. It doesn't catch the typo at the moment it's introduced (it relies on a reviewer noticing the unfamiliar path in the PR), but combined with CODEOWNERS gating it has held up well so far."),s.createElement(t.p,null,"We're also deliberate about which depen
1dencies we take on. The Renovate config has an explicit list of disabled dependencies that we manage by hand, either because they need coordinated updates (like ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">sigs.k8s.io/gateway-api</code>'}})," alongside conformance tests), because we maintain a fork with project-specific patches (like ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">github.com/cilium/dns</code>'}}),"), or because the dependency is one we develop ourselves and want to bump deliberately (like ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">github.com/cilium/ebpf</code>'}}),", which isn't a fork but a standalone Go library maintained under the Cilium org). Changes to ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">vendor/</code>'}})," are reviewed by the dedicated ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/CODEOWNERS"},s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">@cilium/vendor</code>'}}))," team via the same CODEOWNERS mechanism above."),s.createElement(t.p,null,"There's a Go proverb worth quoting here: ",s.createElement(t.a,{href:"https://go-proverbs.github.io/"},'"A little copying is better than a little dependency."')," We take that one seriously beyond style. We ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/pull/45078"},"periodically audit our third-party libraries")," and actively shrink the tree. If a dependency exists only to provide a small utility function, we replace it with a few lines copied inline. Every dependency you remove is one that can never be compromised, the vendor tree gets smaller, and reviewing future dependency changes gets easier. The benefits compound."),s.createElement("span",{id:"catching-mistakes-with-static-analysis"}),s.createElement(t.h2,null,"Catching mistakes with static analysis"),s.createElement(t.p,null,"Even with the right policies in place, mistakes happen. A well-meaning contributor can add a workflow without ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">permissions:</code>'}}),", or use ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">ubuntu-latest</code>'}})," instead of a pinned runner. We use static analysis to catch this stuff before review."),s.createElement(t.p,null,"Where workflows need write access (release signing, OIDC for Cosign), they declare only the specific scope they need, like ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">id-token: write</code>'}})," or ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">contents: write</code>'}}),". Where they don't, they declare ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">permissions: read-all</code>'}})," or ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">permissions: {}</code>'}})," to opt out of the broader defaults. We don't rely on memory for this, though. ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/workflows/codeql.yaml"},"CodeQL runs on every push and PR")," with the ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">actions/missing-workflow-permissions</code>'}})," rule turned on, and the workflow fails any modified workflow file that doesn't set permissions explicitly."),s.createElement(t.p,null,"On top of that, ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/workflows/lint-workflows.yaml"},"actionlint")," statically checks every workflow file for syntax errors, unsafe patterns, and misconfigurations. The same lint pipeline also enforces project conventions: every job and step has a ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">name</code>'}}),", no job uses the floating ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">ubuntu-latest</code>'}})," runner tag (we pin to ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">ubuntu-24.04</code>'}}),"), and there's no trailing whitespace in workflow files."),s.createElement(t.p,null,"One vulnerability class is worth singling out: ",s.createElement(t.strong,null,"GitHub Actions expression injection"),". The ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">${{ }}</code>'}})," syntax in workflow YAML is a text substitution that happens before bash sees the line at all. If an attacker controls the value being substituted (a PR title, a branch name), they can inject arbitrary shell commands via ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">;</code>'}}),", ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">$(...)</code>'}}),", or backticks. Bash has no idea where the value came from. The fix is to assign the value to an environment variable first and reference it as ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">"$MY_VAR"</code>'}})," in the ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">run:</code>'}})," block, so bash treats it as a single variable regardless of contents. The GitHub security team reported this to us a while back, and we fixed every instance. It's a subtle bug that's easy to introduce and hard to spot in review, which is exactly why static analysis matters: both ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/workflows/lint-workflows.yaml"},"actionlint")," and ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/workflows/codeql.yaml"},"CodeQL")," flag ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">${{ }}</code>'}})," usage in ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">run:</code>'}})," blocks where untrusted input flows in."),s.createElement(t.h2,null,"Protecting credentials"),s.createElement(t.p,null,"We assume any individual layer can fail. If a CI workflow ever does get compromised, the question becomes: what can the attacker actually reach? The answer should be: nothing that matters."),s.createElement(t.h3,null,"Strong defaults"),s.createElement(t.p,null,"By default our ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">GITHUB_TOKEN</code>'}}),"s are scoped to ",s.createElement(t.a,{href:"https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#setting-the-permissions-of-the-github_token-for-your-repository"},"minimal read permissions")," on ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">contents</code>'}})," and ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">packages</code>'}}),". Workflows that need anything more have to opt in explicitly, so a workflow that forgets to declare permissions doesn't end up with broad org-wide write access."),s.createElement("span",{id:"ci-vs-production-credential-isolation"}),s.createElement(t.h3,null,"CI vs. production credential isolation"),s.createElement(t.p,null,"We keep two distinct set
1s of registry credentials behind separate GitHub ",s.createElement(t.a,{href:"https://docs.github.com/en/actions/deployment/targeting-different-environments/managing-environments-for-deployment"},"protected environments"),":"),s.createElement(t.ul,null,"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"CI credentials")," can push to our development image registry (",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">quay.io/cilium/*-ci</code>'}}),") and are available to CI builds. Even if a CI workflow is compromised somehow, these credentials cannot push to production image tags."),"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"Production credentials")," sit behind the ",s.createElement(t.a,{href:"https://docs.github.com/en/actions/deployment/targeting-different-environments/managing-environments-for-deployment"},s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">release</code>'}})," environment"),", which requires an explicit maintainer approval before a workflow run can touch them. No fork, no feature branch, and no CI build can reach those secrets. Only tag-triggered release builds that a maintainer has approved can."),"\n"),s.createElement(t.p,null,"Worst-case, in a CI compromise, the attacker can publish a malicious ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">-ci</code>'}})," image. They cannot publish to ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">quay.io/cilium/cilium:v1.x.x</code>'}})," or ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">docker.io/cilium/cilium:v1.x.x</code>'}}),". The credentials simply aren't on the runner."),s.createElement(t.p,null,"Every ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">actions/checkout</code>'}})," call also sets ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">persist-credentials: false</code>'}}),", so the ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">GITHUB_TOKEN</code>'}})," never ends up in the runner's git config where a later step could grab it."),s.createElement("span",{id:"signing-and-attesting-what-we-ship"}),s.createElement(t.h2,null,"Signing and attesting what we ship"),s.createElement(t.p,null,"The previous sections are about preventing bad things from getting into the pipeline. This one is about letting consumers verify what comes out of it."),s.createElement(t.p,null,"Every container image we release (",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">cilium</code>'}}),", ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">operator-*</code>'}}),", ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">hubble-relay</code>'}}),", ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">clustermesh-apiserver</code>'}}),") is signed with ",s.createElement(t.a,{href:"https://github.com/sigstore/cosign"},"Sigstore Cosign")," using keyless OIDC. There are no long-lived signing keys for anyone to steal."),s.createElement(t.p,null,"A reusable composite action handles the signing pipeline:"),s.createElement(t.p,null,s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/actions/cosign/action.yaml"},s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">.github/actions/cosign/action.yaml</code>'}}))),s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Install Cosign\n <span class="token key atrule">uses</span><span class="token punctuation">:</span> sigstore/cosign<span class="token punctuation">-</span>installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 <span class="token comment"># v4.1.1</span>\n\n<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Generate SBOM\n <span class="token key atrule">uses</span><span class="token punctuation">:</span> anchore/sbom<span class="token punctuation">-</span>action@e22c389904149dbc22b58101806040fa8d37a610 <span class="token comment"># v0.24.0</span>\n <span class="token key atrule">with</span><span class="token punctuation">:</span>\n <span class="token key atrule">artifact-name</span><span class="token punctuation">:</span> sbom_$<span class="token punctuation">{</span><span class="token punctuation">{</span>
1 inputs.sbom_name <span class="token punctuation">}</span><span class="token punctuation">}</span>.spdx.json\n <span class="token key atrule">output-file</span><span class="token punctuation">:</span> ./sbom_$<span class="token punctuation">{</span><span class="token punctuation">{</span> inputs.sbom_name <span class="token punctuation">}</span><span class="token punctuation">}</span>.spdx.json\n <span class="token key atrule">image</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> inputs.image_tag <span class="token punctuation">}</span><span class="token punctuation">}</span>\n\n<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Sign Container Image\n <span class="token key atrule">shell</span><span class="token punctuation">:</span> bash\n <span class="token key atrule">run</span><span class="token punctuation">:</span> cosign sign <span class="token punctuation">-</span>y "$<span class="token punctuation">{</span><span class="token punctuation">{</span> inputs.image <span class="token punctuation">}</span><span class="token punctuation">}</span>"\n\n<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Attach SBOM Attestation\n <span class="token key atrule">shell</span><span class="token punctuation">:</span> bash\n <span class="token key atrule">run</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">\n cosign attest -y \\\n --predicate "./sbom_${{ inputs.sbom_name }}.spdx.json" \\\n --type spdxjson \\\n "${{ inputs.image }}"</span></code></pre></div>'}}),s.createElement(t.p,null,"This runs for every release image build and for our Helm chart OCI artifacts. Verification instructions are in the ",s.createElement(t.a,{href:"https://docs.cilium.io/en/stable/configuration/verify-image-signatures/#verify-signed-container-images"},"Cilium docs"),"."),s.createElement(t.p,null,"Release builds also run inside ",s.createElement(t.a,{href:"https://docs.github.com/en/actions/deployment/targeting-different-environments/managing-environments-for-deployment"},"protected environments")," (",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">release</code>'}}),", ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">release-tool</code>'}}),", ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">release-helm</code>'}}),") so production registry credentials are gated behind environment protection rules. You can't trigger a release build from a fork or a feature branch."),s.createElement(t.h2,null,"The Cilium security team"),s.createElement(t.p,null,"If you've ever reported a security issue to the project (via ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/security/advisories"},"GitHub security advisories")," or ",s.createElement(t.a,{href:"mailto:[email protected]"},"[email protected]"),"), you've already interacted with ",s.createElement(t.a,{href:"https://github.com/cilium/community/blob/main/roles/Security-Team.md"},"Cilium's Security Team"),". Beyond triaging vulnerability reports, the team also runs the operational side of supply chain security:"),s.createElement(t.ul,null,"\n",s.createElement(t.li,null,"Auditing and rotating credentials and permissions across the GitHub organization."),"\n",s.createElement(t.li,null,"When necessary, carrying out incident investigation and audits."),"\n",s.createElement(t.li,null,"Monitoring for patterns in our security issues and industry developments in order to propose mitigations and controls in areas where our security posture is weak."),"\n"),s.createElement(t.h2,null,"Additional layers"),s.createElement(t.p,null,"A few smaller things worth mentioning:"),s.createElement(t.ul,null,"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"Tag immutability.")," Once a GitHub release is published, the tags and assets attached to it can't be modified. The setting lives in the repository's ",s.createElement(t.em,null,"Settings â Releases")," page."),"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"DCO sign-off enforcement.")," Every commit must carry a ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">
1Signed-off-by</code>'}})," line. Our ",s.createElement(t.a,{href:"https://github.com/cilium/cilium/blob/main/.github/maintainers-little-helper.yaml"},"maintainers-little-helper")," config blocks merges with a ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">dont-merge/needs-sign-off</code>'}})," label until a sign-off is present."),"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"Third-party security audits.")," We've been audited by ",s.createElement(t.a,{href:"https://adalogics.com"},"ADA Logics"),", and we maintain a published ",s.createElement(t.a,{href:"https://docs.cilium.io/en/latest/security/threat-model/"},"threat model"),"."),"\n"),s.createElement("span",{id:"what-were-still-working-on"}),s.createElement(t.h2,null,"What we're still working on"),s.createElement(t.p,null,"We audited our ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">.github/</code>'}})," directory against current best practices (OpenSSF Scorecard, SLSA, StepSecurity recommendations) and turned up a number of real gaps. The bigger ones:"),s.createElement(t.ul,null,"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"No SLSA provenance.")," Every ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">docker/build-push-action</code>'}})," call sets ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">provenance: false</code>'}}),". We sign images with Cosign, but we don't generate SLSA build provenance attestations. Consumers can verify ",s.createElement(t.em,null,"who")," signed an image, but not ",s.createElement(t.em,null,"how")," it was built. Adopting ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">slsa-framework/slsa-github-generator</code>'}})," (or at minimum enabling BuildKit-native provenance) is on the list."),"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"No dependency review at PR time.")," We rely on Renovate's ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">vulnerabilityAlerts</code>'}})," to flag known-vulnerable dependencies, but that's reactive. Wiring in ",s.createElement(t.a,{href:"https://github.com/actions/dependency-review-action"},"actions/dependency-review-action")," would catch malicious or vulnerable new dependencies ",s.createElement(t.em,null,"before")," they merge."),"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"No ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">govulncheck</code>'}})," in CI.")," We fuzz and we lint, but we don't yet run Go's official vulnerability scanner, which checks whether our code actually calls vulnerable functions rather than just whether a vulnerable package shows up in ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">go.sum</code>'}}),"."),"\n",s.createElement(t.li,null,s.createElement(t.strong,null,"68 internal ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">@main</code>'}})," references.")," A bunch of conformance and scale-test workflows reference ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">cilium/cilium/.github/actions/set-commit-status@main</code>'}}),", which is a mutable branch ref. It's lower risk than a third-party tag, but inconsistent with our SHA-pinning policy. The plan is to move all of our composite actions out of cilium/cilium into a dedicated repository, which removes the need for ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">@main</code>'}})," here."),"\n"),s.createElement(t.p,null,"A few smaller items in the same audit:"),s.createElement(t.ul,null,"\n",s.createElement(t.li,null,"No ",s.createElement(t.a,{href:"https://securityscorecards.dev/"},"OpenSSF Scorecard")," workflow for continuous supply chain health monitoring."),"\n",s.createElement(t.li,null,"Our ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">SECURITY-INSIGHTS.yml</code>'}})," expired in January 2025 and hasn't been updated. (We actually noticed this while writing this post.)"),"\n",s.createElement(t.li,null,"No ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">go mod verify</code>'}})," step to validate vendor directory integrity against ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">go.sum</code>'}})," checksums."),"\n"),s.createElement(t.p,null,"If any of these look like a good first issue and you want to send a PR, we'd take it."),s.createElement(t.hr),s.createElement(t.h2,null,"GitHub's 2026 Actions security roadmap and how it maps to what we do"),s.createElement(t.p,null,"In April 2026, GitHub published their ",s.createElement(t.a,{href:"https://github.blog/news-insights/product-news/whats-coming-to-our-github-actions-2026-security-roadmap/"},"Actions security roadmap")," describing platform-level changes across three layers: ecosystem, attack surface, and infrastru
1cture. Reading it felt like validation of problems we've been working around for years, and a real signal that the platform is finally catching up to what large open source projects need. Here's how it maps to what we do today."),s.createElement(t.h3,null,"Dependency locking: making SHA pinning first-class"),s.createElement(t.p,null,"We pin every action by SHA and lean on Renovate to keep those pins current, but we still have a blind spot for transitive references. GitHub's planned ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">dependencies:</code>'}})," section in workflow YAML would lock all direct ",s.createElement(t.em,null,"and transitive")," dependencies by commit SHA, with hash verification before execution starts. That closes the gap."),s.createElement(t.h3,null,"Policy-driven execution: centralizing what we enforce per-file today"),s.createElement(t.p,null,"We restrict who can trigger workflows (Ariane's allow-list), which events are allowed (per-workflow configuration), and who can approve releases (protected environments). All of that is currently encoded across dozens of YAML files plus a custom bot, and auditing the full picture means reading every file."),s.createElement(t.p,null,"GitHub's planned workflow execution protections, built on rulesets, would let us define those controls centrally at the org level: which actors can trigger workflows, which events are permitted, which repositories the rules apply to. We could prohibit ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">pull_request_target</code>'}})," org-wide except for the workflows where we've intentionally designed a safe two-phase checkout, instead of relying on code review and CODEOWNERS to enforce it."),s.createElement(t.h3,null,"Scoped secrets: closing the implicit inheritance gap"),s.createElement(t.p,null,"CI vs. production credential isolation is one of our strongest controls, but within a given environment, secrets are still scoped pretty broadly: any workflow running in that environment can access them."),s.createElement(t.p,null,"Scoped secrets would let us bind credentials to specific workflow paths, branches, or even individual reusable workflows. A release credential could be restricted not just to the ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">release</code>'}})," environment but to the specific ",s.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">release.yaml</code>'}})," workflow file, so a new workflow added to that environment (by accident or by an attacker) wouldn't inherit the credentials. That's a meaningful step beyond what protected environments alone provide."),s.createElement(t.p,null,"The roadmap also separates secret management from repository write access. Today anyone with write access to a repo can manage its secrets. GitHub plans to move secret management into a dedicated custom role, which lines up with the least-privilege principle we already apply to workflow permissions but can't currently apply to secret administration."),s.createElement(t.h3,null,"Native egress firewall"),s.createElement(t.p,null,"GitHub's planned native egress firewall would restrict outbound network access from GitHub-hosted runners. It runs outside the runner VM at L7, so it's immutable even if an attacker gets root inside the runner. Organizations would define allowed domains, IP ranges, and HTTP methods, and anything else gets blocked."),s.createElement(t.p,null,"For Cilium it's less critical than the rest. Our most security-sensitive workflows (release builds, image signing) already run with credential isolation and least-privilege permissions, which limits what a compromised step could do even with unrestricted network access. Building an accurate egress allow-list for a project that talks to container registries, Go module proxies, cloud APIs, and Sigstore would be a significant chunk of work. Public preview is expected in 6 to 9 months, so we'll evaluate then."),s.createElement(t.h3,null,"Actions Data Stream: making CI observable"),s.createElement(t.p,null,"Our workflows produce logs, but we don't have centralized telemetry for them. If a workflow starts behaving oddly (resolving unexpected dependencies, running longer than usual, making strange network calls), we'd have to notice it manually."),s.createElement(t.p,null,"Actions Data Stream would deliver near real-time execution telemetry to external systems (S3, Azure Event Hub), covering workflow execution details, dependency resolution patterns, and eventually network activity. For an open source project with hundreds of workflow runs per day, that's a blind spot worth closing."),s.createElement(t.h2,null,"The point"),s.createElement(t.p,null,'Supply chain security is mostly the practice of repeatedly asking "what if this thing I trust gets compromised?" and adding a layer that limits the blast radius when it does.'),s.createElement(t.p,null,"We've tried to build defense in depth: access controls so only trusted people can trigger builds, pinned digests so a compromised tag can't reach us, least-privilege permissions so a rogue action can't exfiltrate secrets, credential isolation so CI can never touch production, and signatures so users can verify what they're running."),s.createElement(t.p,null,"None of this makes us invulnerable. But security by obscurity isn't really a thing, and the inverse is also true: the more open source projects share their defenses openly, the higher the collective bar for attackers. We've shown you ours, including the parts that aren't great yet. If you're running CI/CD for an open source project and you've solved something we haven't, open an issue, write your own post, or come tell us on Slack. The open source supply chain is only as strong as its weakest project, and the only way to strengthen it is together."),s.createElement(t.hr),s.createElement(t.p,null,s.createElement(t.em,null,"Relevant resources: ",s.createElement(t.a,{href:"https://securityscorec
1ards.dev/"},"OpenSSF Scorecard")," · ",s.createElement(t.a,{href:"https://slsa.dev/"},"SLSA Framework")," · ",s.createElement(t.a,{href:"https://www.sigstore.dev/"},"Sigstore")," · ",s.createElement(t.a,{href:"https://github.com/step-security/harden-runner"},"StepSecurity Harden Runner")," · ",s.createElement(t.a,{href:"https://docs.github.com/en/actions/security-for-github-actions/security-hardening-for-github-actions"},"GitHub Actions Security Hardening")," · ",s.createElement(t.a,{href:"https://github.blog/news-insights/product-news/whats-coming-to-our-github-actions-2026-security-roadmap/"},"GitHub Actions 2026 Security Roadmap")))),"\n",s.createElement(n,o.A.andreMartinsAndFerozSalam))}var r=function(e){void 0===e&&(e={});const{wrapper:t}=Object.assign({},(0,a.RP)(),e.components);return t?s.createElement(t,e,s.createElement(l,e)):l(e)};var i=n(8125),c=n(5805),u=n(8838),d=n(2744);const p=e=>{const{data:{mdx:t},children:n}=e,{frontmatter:{path:a,title:o,date:l,tags:r,ogSummary:u}}=t;return s.createElement(d.A,{headerWithSearch:!0},s.createElement(i.A,{path:a,content:n,date:l,title:o,tags:r,summary:u}),s.createElement(c.A,{className:"my-10 md:my-20 lg:my-28"}))},m=e=>{var t,n;let{data:{mdx:a,site:o},location:{pathname:l}}=e;const{frontmatter:{title:r,ogImage:i,ogSummary:c,dateIso:d,tags:p,author:m}}=a,{siteUrl:h}=o.siteMetadata,g=`${c.slice(0,133)}...`,y=`${h}${l}`,f=null!=i&&null!==(t=i.childImageSharp)&&void 0!==t&&null!==(n=t.resize)&&void 0!==n&&n.src?`${h}${i.childImageSharp.resize.src}`:null,b={title:r,description:g,image:i||null,slug:l},w={"@context":"https://schema.org","@type":"BlogPosting",headline:r,description:g,url:y,datePublished:d,dateModified:d,author:m?{"@type":"Person",name:m}:{"@type":"Organization",name:"Cilium",url:h},publisher:{"@type":"Organization",name:"Cilium",url:h,logo:{"@type":"ImageObject",url:`${h}/images/social-preview.jpg`}},...f&&{image:{"@type":"ImageObject",url:f,width:1200,height:630}},...(null==p?void 0:p.length)>0&&{keywords:p.join(", ")}};return s.createElement(u.A,{data:b,type:"article",datePublished:d,jsonLd:w})};function h(e){return s.createElement(p,e,s.createElement(r,e))}},6452:function(e,t){t.A={thomasGraf:{header:"Thomas Graf",bio:'Thomas Graf is a Co-Founder of Cilium and the CTO & Co-Founder of <a href="https://isovalent.com/?utm_source=website-cilium&utm_medium=referral&utm_campaign=cilium-enterprise">Isovalent</a>, the company behind Cilium. Before that, Thomas spent 15 years as\n a kernel developer working on the <a href="https://kernel.org">Linux kernel</a> in networking, security and eventually eBPF.'},lizRice:{header:'<a href="https://twitter.com/lizrice">Liz Rice</a>',bio:'Liz is Chief Open Source Officer at <a href="https://isovalent.com/?utm_source=website-cilium&utm_medium=referral&utm_campaign=cilium-enterprise" target="_blank" rel="noopener noreferrer">Isovalent</a>, the company behind Cilium. She is also chair of the CNCF\'s Technical Oversight Committee, and the author of Container Security published by O\'Reilly.'},luanGuimaraes:{header:"Luan Guimarães",bio:"Luan is a Brazilian rock climber, amateur musician, and\n programmer and am enthusiastic about free software communities and other\n open knowledge initiatives. He has been working as a Site Reliability\n Engineer at Wildlife Studios, using and building infrastructure tools on\n top of Kubernetes in order to support millions of users around the world."},joshVanLeeuwen:{header:"Josh Van Leeuwen",bio:"Josh interned at Jetstack during the summer of 2017 before continuing to\n work part time during his final year of study at the University of Bristol.\n During this year, Josh developed a Kubernetes custom controller that\n automates the delegation of RBAC permissions based on time and event\n triggers. This work was later awarded the best Software Development Tool\n Final Year Project. Josh now works full time at Jetstack where if heâs not\n writing more Go, heâs making good food."},howardHao:{header:"Howard Hao",bio:" Howard Hao has been working as a Site Reliability Engineer for five years at\n Ect888.com since graduating from Shanghai Jiao Tong University. His team\n consists of 7 members and has been focusing on the construction of\n container orchestration platform like Kubernetes for one and a half years."},sergeyGeneralov:{header:"Sergey Generalov",bio:"Sergey is a member of the technical staff at Isovalent\n and focuses on helping Cilium users solve challenges related\n to network policies, monitoring, and connectivity troubleshooting\n b
1y building tools like Network Policy Editor, Hubble UI and more."},liWenquan:{header:"Li Wenquan",bio:"Hello everyone, I am Li Wenquan from China. You can call me David. I\n started my Docker journey from 2014 and now work as a project manager of\n enterprise container platform, which is built on Kubernetes and Mesos. I\n got to know Cilium project from Kubecon, it is so interesting and\n promising. I've learned a lot from it, such as BPF, XDP and how to replace\n kube-proxy in a elegant way and I'd love to contribute to it."},alexanderAlemayhu:{header:"Alexander Alemayhu",bio:"Alexander Alemayhu is a software engineer at Isovalent,\n the company behind Cilium. He has been working on eBPF and Linux\n kernel technologies for several years, focusing on networking and observability solutions."},DanielBorkmann:{header:"Daniel Borkmann",bio:"Daniel Borkmann is a Distinguished Software Engineer, Isovalent at Cisco"},ThomasGraf:{header:"Thomas Graf",bio:"Thomas Graf is the CTO & Co-Founder Isovalent and also the Vice President Security Cisco"},JedSalazar:{header:"Jed Salazar",bio:"Jed Salazar is a Senior Solutions Architect, Isovalent"},JedSalazarandJoeStringer:{header:"Jed Salazar and Joe Stringer",bio:"Jed Salazar is a Senior Solutions Architect at Isovalent\n and Joe Stringer is a Principal Engineer, Isovalent at Cisco"},JosephIrving:{header:"Joseph Irving",bio:"Joseph Irving is a Platform Engineer Lead at RVU (Uswitch)"},BillMulligan:{header:"Bill Mulligan",bio:"Bill Mulligan is a Cilium and eBPF Community Pollinator,\n Isovalent at Cisco and a Governing Board Member of the eBPF Foundation."},OndrejBlazek:{header:"Ondrej Blazek",bio:"Ondrej Blazek is an Infrastructure Engineer at Seznam.cz"},LeonardCohnenandMoritzEckert:{header:"Leonard Cohnen and Moritz Eckert",bio:"Leonard Cohnen and Moritz Eckert are team members at Edgeless Systems"},PolArroyo:{header:"Pol Arroyo",bio:"Pol Arroyo is a DevOps Engineer at Hetzner Cloud."},JedSalazarandMartynasPumputis:{header:"Jed Salazar and Martynas Pumputis",bio:"Jed Salazar is a Senior Solutions Architect, Isovalent and Martynas Pumputis is a Principal Software Engineer, Isovalent at Cisco"},ShedrackAkintayo:{header:"Shedrack Akintayo",bio:"Shedrack Akintayo is a Community Manager at\n Isovalent helping build the eBPF and Cilium open source communities"},AmirKheirkhahan:{header:"Amir Kheirkhahan",bio:"Amir Kheirkhahan is a DevOps Specialist at DB Schenker handling design, development,\n deployment and maintenance of wide range of devops toolchain on top of Kubernetes clusters"},PaulArah:{header:"Paul Arah",bio:"Paul Arah is a Community Builder focused on Security at Isovalent (Cisco)"},HimalKumar:{header:"Himal Kumar, Bhaskar Dutta, Arman Pashamokhtari",bio:"Himal Kumar, Bhaskar Dutta, Arman Pashamokhtari are all part of the\n CanopusAI team Real Time Network Observability, powered by eBPF and Agentic AI"},DoniaChaiehloudj:{header:"Donia Chaiehloudj",bio:"Donia Chaiehloudj is a Senior Software Engineer and Community Oriented at Isovalent.\n She has been working on Cilium and eBPF technologies, focusing on networking and security solutions."},KatieMeinders:{header:"Katie Meinders",bio:"Katie Meinders is a Community Builder at Isovalent where\n she helps grow the Cilium and eBPF communities through storytelling,\n social media, showcasing user success, and building connections across the open source ecosystem."},PeaceSandy:{header:"Peace Sandy",bio:"Peace Sandy is an LFX mentee who contributed to improving Cilium SEO, AEO, and\n AIO during her mentorship period."},NehaAggarwal:{header:"Neha Aggarwal",bio:"Neha Aggarwal is a Principal Engineer at Microsoft."},CharityMbisi:{header:"Charity Mbisi",bio:"Charity Mbisi is an LFX mentee who contributed to improving Cilium's SEO, AEO, and AIO during his mentorship period.\n Professionally, Charity Mbisi is a Software Engineer consulting in the Fin-tech and banking industry, specializing in building cloud native computing solutions and optimized service delivery."},andreMartinsAndFerozSalam:{header:"André Martins and Feroz Salam",bio:"André Martins is a Cilium maintainer and Software Engineer, Isovalent at Cisco.\n Feroz Salam is a member of the Cilium Security Team and a Security Engineer, Isovalent at Cisco."},ChristianHernandez:{header:"Christian Hernandez",bio:"Christian is a well rounded technologist with experience in infrastru
1cture engineering, systems administration, enterprise architecture, tech support, advocacy, and product management. Passionate about OpenSource and containerizing the world one application at a time. He is currently a maintainer of the Argo Project and OpenGitops. Currently, he works as a Technical Marketing Engineer and Tech Lead at Cisco. He focuses on GitOps practices, DevOps, Kubernetes, Network Security, and Containers."},AkilaInduranga:{header:"Akila Induranga",bio:'Akila is a Senior Software Engineer at WSO2, and a maintainer of <a href="https://openchoreo.dev/" target="_blank" rel="noopener noreferrer">OpenChoreo</a>, an open-source internal developer platform for Kubernetes and a CNCF sandbox project.\n He works on the platform\'s observability and networking layers, including the Cilium-based networking module that brings identity-based policy and Hubble observability to OpenChoreo cells.'}}}}]); 2//# sourceMappingURL=component---src-templates-blog-post-jsx-content-file-path-src-posts-2026-05-06-securing-cicd-open-source-lessons-from-cilium-index-md-7e8828a58ef77d2f31b8.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.