Safeguard
Infrastructure Security

Securing Infrastructure as Code in GitOps workflows

GitOps auto-applies every merged Terraform and Kubernetes change within minutes. Here's how CVE-2022-24348 and CVE-2025-30066 show why PR-time IaC checks are non-negotiable.

Vikram Iyer
Cloud Security Engineer
7 min read

GitOps promises that everything about your infrastructure lives in a Git repository, and that a controller continuously converges the live environment to match it. That promise is also the risk: one merged pull request can rewrite IAM policies, open a security group to 0.0.0.0/0, or roll a compromised container image out across every cluster a controller manages. In February 2022, CVE-2022-24348 showed how a crafted Helm chart could let Argo CD read files outside its intended application path, turning the reconciliation engine itself into an attack surface. Terraform plans and Kubernetes manifests no longer sit behind a change-advisory board; they merge and auto-apply within minutes, sometimes seconds. Securing infrastructure as code in a GitOps workflow means pushing misconfiguration, secrets, and supply-chain checks into the pull request itself, before Argo CD, Flux, or a Terraform Cloud run ever touches production.

What makes IaC security different in a GitOps model?

In GitOps, the security boundary moves from the runtime environment to the Git repository and the controller's sync loop, because git merge is the deployment event. A traditional change goes through a ticket, a change-advisory board, and a scheduled maintenance window; a GitOps change goes through a pull request review and then applies automatically, often in under five minutes for tools like Argo CD with a default three-minute sync interval. That speed is the entire point of GitOps, but it also means a reviewer who approves a 400-line Terraform diff at 5 p.m. on a Friday has effectively authorized whatever that diff does in production, with no separate gate afterward. Palo Alto Networks' Unit 42 Cloud Threat Report has repeatedly found that a majority of published Terraform, CloudFormation, and ARM templates contain at least one misconfiguration, and in a GitOps pipeline that misconfigured template isn't a draft sitting in a wiki — it's the live desired state the cluster is actively reconciling toward. The controller has broad credentials (often cluster-admin or an account-wide Terraform Cloud API token) and no concept of "this diff looks suspicious," so the review at PR time is the only checkpoint that matters.

How did CVE-2022-24348 turn a Helm chart into a GitOps attack path?

CVE-2022-24348 let a malicious Helm chart's values.yaml reference a relative path like ../../../etc/secrets, causing Argo CD to render and expose files outside the application's designated directory when it ran helm template. The flaw affected Argo CD versions up to 2.1.6, 2.2.2, and 2.3.0 and was rated CVSS 7.7, because in a multi-tenant Argo CD instance — the norm for platform teams running one controller across dozens of application teams — it let an attacker who could submit a Helm chart in one namespace read Kubernetes Secrets or ConfigMaps belonging to a completely different application. The bug mattered specifically because of the GitOps trust model: teams routinely grant "just open a PR against this chart repo" access without realizing the render step runs with the controller's full privileges, not the submitting team's. HashiCorp's advisory ecosystem has documented similar path and provider-loading issues in Terraform tooling, and the pattern repeats: any GitOps engine that renders untrusted templates (Helm, Kustomize overlays, Terraform modules pulled from a public registry) inherits the blast radius of whatever it's asked to render.

How do secrets end up exposed in Terraform state and Kubernetes manifests?

Secrets leak in GitOps pipelines primarily because Terraform state files and rendered Kubernetes manifests store values in plaintext by default, and both routinely end up somewhere more accessible than the source repo. A terraform apply that creates an RDS instance writes the generated master password directly into terraform.tfstate unless the module explicitly marks the output sensitive and the backend encrypts state at rest; teams using local state or an S3 backend without SSE-KMS have shipped that state file into a build artifact or a public bucket more than once. On the Kubernetes side, a kind: Secret manifest is only base64-encoded, not encrypted, so a values file containing apiKey: c2stbGl2ZS1hYmMxMjM= decodes in one command, and if that manifest is committed straight to the same repo Argo CD or Flux watches, the secret is now readable by anyone with repo clone access — which, in an open internal-source org, can be hundreds of engineers. GitGuardian's 2023 State of Secrets Sprawl report logged over 12.7 million hardcoded secrets exposed on public GitHub in a single year, and IaC files (Terraform variables, Helm values, Kubernetes YAML) were among the fastest-growing categories, precisely because GitOps culture encourages committing configuration that used to live in a ticketing system or a runbook.

What's the real risk when a CI/CD action in the pipeline itself gets compromised?

The risk is that a compromised GitHub Action or CI step runs with the same repo secrets your GitOps pipeline uses to push manifests, so one poisoned dependency can exfiltrate the credentials that control your infrastructure. In March 2025, the popular tj-actions/changed-files GitHub Action was compromised (tracked as CVE-2025-30066): an attacker modified the action's published tags to run code that dumped CI runner memory, including secrets, into workflow logs, and the action was in use across more than 23,000 public repositories at the time. Any repo using that action inside a workflow that also holds a Terraform Cloud API token, an Argo CD image-updater credential, or a cloud deploy role effectively handed those credentials to the attacker for as long as the poisoned version ran. This is the supply-chain half of GitOps IaC security that's easy to miss: teams harden the Terraform and YAML they write, then pull in third-party GitHub Actions, Terraform providers, and Helm chart dependencies from public registries with far less scrutiny than the first-party code sitting next to them in the same repository.

How should teams catch drift and bad changes before they reconcile into production?

Teams catch these issues by scanning IaC in the pull request, not after the controller has already applied it, because GitOps' fast sync interval leaves almost no window to intervene once a change merges. Practically, that means running policy checks (Open Policy Agent/Gatekeeper, Checkov, or tfsec) as a required GitHub Actions or GitLab CI check that blocks merge on a failing rule, the same way a unit test blocks merge on a failing assertion — not as a nightly scan that reports on infrastructure that's been live for eight hours already. It also means enabling drift detection inside the controller itself: Argo CD's SelfHeal policy and Terraform Cloud's drift detection (generally available since 2023) will flag or revert manual kubectl edit and console changes that diverge from the Git-declared state, closing the loophole where an engineer "just fixes it in prod" and quietly breaks the single-source-of-truth guarantee GitOps depends on. Finally, teams need image and dependency scanning wired into the same PR gate — an IaC file that correctly provisions a deployment is still a finding if the container image it references has a critical, internet-reachable CVE, which is a software composition problem, not a Terraform syntax problem, and requires a different class of tool to catch.

How Safeguard Helps

Safeguard scans Terraform, Helm, and Kubernetes manifests directly in the pull request and layers reachability analysis on top of raw CVE and misconfiguration findings, so a team sees which flagged images and dependencies are actually exposed once that infrastructure is deployed, rather than triaging every CVE with equal urgency. Griffin AI, Safeguard's reasoning engine, correlates a misconfigured security group or overly permissive IAM role in the IaC diff with the workloads it would expose, and explains the actual attack path in plain language instead of leaving reviewers to reconstruct it manually. Safeguard generates and ingests SBOMs for the container images referenced in your manifests, giving GitOps pipelines a live inventory of what's actually running versus what's declared. When Safeguard finds a fixable issue — a pinned Helm chart version with a known CVE, an unencrypted Terraform state backend, a Kubernetes Secret that should be sealed — it opens an auto-fix pull request with the corrected configuration, so the fix lands through the same Git-reviewed path GitOps already trusts, closing the loop between detection and remediation without a manual ticket.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.