On 24 March 2025 the Kubernetes project disclosed five ingress-nginx vulnerabilities, led by CVE-2025-1974 (CVSS 9.8): its validating admission webhook accepted requests from anything on the Pod network without authentication, so anyone there could use configuration-injection bugs to run code in the controller. By default the controller can read every Secret in the cluster, so that often meant cluster takeover. Fixes shipped in v1.12.1 and v1.11.5; the interim mitigation was to turn the admission webhook off and back on after upgrading. In October 2026 there is a further problem: ingress-nginx was retired in March 2026 and gets no more security fixes, so the lasting fix is migration.
- An admission webhook is a network service. If anything on the Pod network can call it, it is part of your attack surface, not just the API server's.
- Four of the five CVEs were configuration injection through Ingress annotations or webhook input; CVE-2025-1974 removed the need for permission to create an Ingress.
- The controller's default access to all Secrets cluster-wide turned code execution in one pod into a cluster-level compromise.
- Fixed in v1.12.1 and v1.11.5; affected were v1.12.0, v1.11.0 to v1.11.4 and everything before v1.11.0.
- The Kubernetes issue reported no known indicators of compromise, and CVE-2025-1974 is not in CISA's KEV catalog; the name IngressNightmare and the exposure figures come from Wiz.
- ingress-nginx has been retired since March 2026, and its repository is archived; plan a move to Gateway API or another maintained controller.
Incident at a glance
- Dates
- to
- Category
- Misconfiguration
- Organizations
- Kubernetes project (ingress-nginx maintainers and SRC)
- Wiz (reporter)
- Impact
- An admission webhook open to the whole Pod network, plus default read access to every Secret, turned configuration-injection bugs in ingress-nginx into a cluster-takeover risk (CVSS 9.8). No exploitation was known at disclosure.
- How it was found
- Reported to the Kubernetes Security Response Committee by researchers from Wiz and fixed in coordinated releases.
Timeline
- The Kubernetes SRC opens public issues for the five CVEs.
- ingress-nginx v1.12.1 and v1.11.5 ship with fixes; the Kubernetes blog publishes mitigation guidance.
- Kubernetes announces the retirement of ingress-nginx, with maintenance until March 2026.
- The last controller release, v1.15.1, is published.
- Maintenance ends and the repository is archived; no further security fixes.
| Setting | Value | Note | Source |
|---|---|---|---|
| CVE-2025-1974 | CVSS 3.1 9.8, AV:N/AC:L/PR:N | Unauthenticated code execution in the controller from the Pod network. | kubernetes/kubernetes issue 131009 |
| Related CVEs | CVE-2025-24514, CVE-2025-1097, CVE-2025-1098 (8.8 each); CVE-2025-24513 (4.8) | Injection through the auth-url, auth-tls-match-cn and mirror annotations; a path traversal in the admission controller. | Kubernetes issues 131005 to 131008 |
| Fixed versions | v1.12.1 and v1.11.5 (released 24 March 2025) | Affected: before v1.11.0, v1.11.0 to v1.11.4, and v1.12.0. | Kubernetes blog; GitHub releases |
| Interim mitigation | Disable the validating admission webhook | Helm: controller.admissionWebhooks.enabled=false; turn it back on after upgrading. | Kubernetes blog |
| Default Secret access | All Secrets cluster-wide | Why code execution in the controller often meant cluster takeover. | Kubernetes blog |
| Project status | Retired: best-effort maintenance ended March 2026 | No further releases or security fixes; repository archived; last controller release v1.15.1 on 19 March 2026. | Kubernetes blog, 11 November 2025; GitHub |
- Run
kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginxin every cluster, and check the image tags of anything that matches, including copies installed under other labels. - For every admission webhook in the cluster (
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations), find the pods behind it and confirm a NetworkPolicy lets only the control plane reach the webhook port. - Check what Secrets each controller's service account can read (
kubectl auth can-i list secrets --all-namespaces --as=system:serviceaccount:<ns>:<sa>), and scope it down where the controller allows. - If you run ingress-nginx anywhere, record a migration plan and date: it has had no security fixes since March 2026.
- Check that annotation validation is on wherever a controller turns annotations into configuration.
- Applies to
- ingress-nginx controller Vulnerable: before v1.11.0, v1.11.0 to v1.11.4, v1.12.0; fixed: v1.11.5, v1.12.1 and later; unsupported since March 2026
- Other Kubernetes controllers with admission webhooks The exposure pattern applies to any webhook reachable from the Pod network
Kubernetes clusters that run ingress-nginx or any controller with an admission webhook, and teams that write controllers which turn user-supplied objects into configuration.
- Part of
- Workflows: Review a design, Respond to a vulnerability. Collection: case studies
Tested fixes: the vulnerable code, the fix, a plausible fix that still fails, and the regression tests that tell them apart.
Learning goals and prerequisites
What you will learn
- Explain how an unauthenticated admission webhook turned configuration-injection bugs into a 9.8 vulnerability.
- Find ingress-nginx and other webhook-bearing controllers in a cluster and judge their exposure.
- Restrict admission webhooks to the control plane and scope controller access to Secrets.
- Plan for a retired component, separating the project's statements from third-party research.
Before you start
Platform and SRE teams who run Kubernetes clusters, and engineers who write controllers or operators with admission webhooks
- Know what an Ingress, an ingress controller and an admission webhook are.
- Know how Kubernetes RBAC grants a service account access to Secrets.
- For the code: Python 3 and
kubectlaccess to a cluster for the inventory script.
Refresh first: Prove network and admission policies enforce the intended boundary
Go to the guided practiceFrom the Pod network to every Secret
- Reach the webhookAny pod, or anything else that can reach the Pod network, connects to the ingress-nginx validating admission webhook, which does not authenticate callers.
- Inject configurationA crafted request carries an Ingress object whose fields inject directives into the nginx configuration the controller builds while validating it.
- Run code in the controllerThe injected configuration leads to arbitrary code execution in the ingress-nginx controller pod.
- Read the SecretsThe controller's service account can read Secrets in every namespace by default, so the attacker can read credentials across the cluster.
ingress-nginx was the Kubernetes project's own ingress controller: it turned Ingress objects into nginx configuration and routed outside traffic to applications. The project's security blog said it was 'deployed in over 40% of Kubernetes clusters'. On 24 March 2025 the maintainers released v1.12.1 and v1.11.5 with fixes for five vulnerabilities, and the Kubernetes Security Response Committee called the situation 'very serious'.
Four of the bugs were about how the controller handled pieces of nginx configuration: 'a specially-crafted Ingress object can cause nginx to misbehave in various ways, including revealing the values of Secrets that are accessible to ingress-nginx.' Normally, exploiting them would need permission to create an Ingress, 'a fairly privileged action'. The fifth, CVE-2025-1974, rated 9.8, removed that requirement: it 'allows anything on the Pod network to exploit configuration injection vulnerabilities via the Validating Admission Controller feature of ingress-nginx.' In the project's words, 'anything on the Pod network has a good chance of taking over your Kubernetes cluster, with no credentials or administrative access required.'
The vulnerabilities were reported by researchers at Wiz, who named them IngressNightmare. The Kubernetes issue for CVE-2025-1974 stated: 'There are no known indicators of compromise that prove this vulnerability has been exploited', and the CVE is not in CISA's KEV catalog.
| When | Event | Source |
|---|---|---|
| 2025-03-23 | The Kubernetes SRC opens issues 131005 to 131009 for CVE-2025-24513, CVE-2025-24514, CVE-2025-1097, CVE-2025-1098 and CVE-2025-1974. | kubernetes/kubernetes issues |
| 2025-03-24 | ingress-nginx v1.12.1 and v1.11.5 are released (just after midnight UTC on 25 March), and the Kubernetes blog explains the risk and the mitigation. | Kubernetes blog; GitHub releases |
| 2025-03-24 | Wiz publishes its research under the name IngressNightmare, with exposure estimates. | Wiz (third-party research) |
| 2025-11-11 | Kubernetes SIG Network and the Security Response Committee announce that ingress-nginx will be retired, with best-effort maintenance until March 2026. | Kubernetes blog |
| 2026-03-19 | The last controller release, v1.15.1, is published. | GitHub releases |
| March 2026 | Maintenance ends; the repository is archived. No further releases, bug fixes or security updates. | Kubernetes blog; GitHub |
A validating admission webhook is an HTTPS service that the Kubernetes API server calls to approve or reject objects before it stores them. ingress-nginx ran one inside the controller pod, on port 8443 in the Helm chart's defaults, to warn users about broken Ingress configurations. The issue for CVE-2025-1974 states the flaw plainly: 'an unauthenticated attacker with access to the pod network can achieve arbitrary code execution in the context of the ingress-nginx controller.' The webhook did not check that its caller was the API server.
Wiz, which found the bugs, describes the rest: when the admission controller processes a request it renders a temporary nginx configuration from the Ingress object and tests it with nginx -t, so injected directives were evaluated during validation. That detail is from Wiz's write-up, not the Kubernetes advisories, which describe only 'configuration injection'. The three injection CVEs named the fields involved: the auth-url, auth-tls-match-cn, and mirror-target and mirror-host annotations. For CVE-2025-24514, the issue notes that the enable-annotation-validation argument, on by default from v1.12.0, prevents it.
This is the injection pattern the injection guide (/domains/appsec/secure-coding/injection-parser-boundaries/) describes, in infrastructure code: user-controlled strings copied into a configuration language that has powerful directives. Treat annotations and snippets as untrusted input to a parser, validate them against a strict grammar, and never pass them through to a configuration file unescaped.
Code execution in a controller pod is bad; what made this cluster-wide was the controller's permissions. Both the blog and the issues repeat the point: 'in the default installation, the controller can access all Secrets cluster-wide.' An ingress controller needs TLS certificates, so its service account was given read access to Secrets in every namespace, and that includes service account tokens, database passwords and cloud credentials.
The Pod network was the other half. The blog warns that 'In many common scenarios, the Pod network is accessible to all workloads in your cloud VPC, or even anyone connected to your corporate network!' Wiz reported that about 43% of cloud environments it analyzed were vulnerable and that it found over 6,500 clusters exposing vulnerable admission controllers to the public internet; those are Wiz's figures, not the project's.
In the OWASP Kubernetes Top 10 (2025 edition) this sits across K06 Overly Exposed Kubernetes Components, K02 Overly Permissive Authorization Configurations and K05 Missing Network Segmentation Controls. The workload security guide (/domains/runtime/containers/kubernetes-workload-security/) covers service account scoping, and the Secrets guide (/domains/identity-crypto/secrets/secrets-in-kubernetes-and-runtime/) what a stolen Secret exposes.
The fix was to upgrade to v1.12.1, v1.11.5 or later. For teams that could not upgrade at once, the blog's interim mitigation was to turn off the Validating Admission Controller: with Helm, reinstall with controller.admissionWebhooks.enabled=false; for manual installs, delete the ValidatingWebhookConfiguration called ingress-nginx-admission and remove --validating-webhook from the controller's arguments. And then: 'remember to turn it back on after you upgrade', because the webhook catches broken Ingress objects before they take effect.
The story did not end with the patch. On 11 November 2025 Kubernetes SIG Network and the Security Response Committee announced the retirement of ingress-nginx: 'Best-effort maintenance will continue until March 2026. Afterward, there will be no further releases, no bugfixes, and no updates to resolve any security vulnerabilities that may be discovered.' Existing deployments keep working and artifacts stay available, and the GitHub repository is now archived. They recommend Gateway API, or another maintained ingress controller, and urge users to begin migrating immediately. A controller that receives no security fixes is a standing exposure; record it in your dependency inventory like any end-of-life component (/domains/appsec/vulnerability-response/dependency-vulnerability-response/).
The blog's check finds pods by the standard label. The script below reads the full pod list instead, so it also catches controllers installed under other labels, and reports each controller image with a verdict. It ran with Python 3.14 on a sample pod list: v1.12.0 and v1.9.6 were flagged as vulnerable, v1.11.5 as fixed but retired. Image tags can lie about what is inside; when in doubt, check the digest against the project's release notes.
"""List ingress-nginx controllers and flag versions without the March 2025 fixes.
Usage: kubectl get pods -A -o json | python3 find_ingress_nginx.py
Fixed: v1.11.5 and v1.12.1 (CVE-2025-1974 and four related CVEs). The project
was retired in March 2026, so every version is now unsupported: plan a migration.
"""
import json
import re
import sys
FIXED = {(1, 11): (1, 11, 5), (1, 12): (1, 12, 1)}
def verdict(version):
if version is None:
return "unknown version: check the image"
fixed = FIXED.get(version[:2])
if version < (1, 11, 0) or (fixed and version < fixed):
return "VULNERABLE to CVE-2025-1974: upgrade or disable the admission webhook"
return "has the March 2025 fixes; project retired, plan a migration"
for pod in json.load(sys.stdin)["items"]:
for container in pod["spec"]["containers"]:
image = container["image"]
if "ingress-nginx/controller" not in image:
continue
match = re.search(r":v(\d+)\.(\d+)\.(\d+)", image)
version = tuple(map(int, match.groups())) if match else None
meta = pod["metadata"]
print(f"{meta['namespace']}/{meta['name']}: {image.split('@')[0]}: {verdict(version)}")A webhook should accept connections only from the API server. The NetworkPolicy below selects the controller pods by the Helm chart's labels, keeps ports 80 and 443 open for the traffic the controller serves, and allows the webhook port, 8443 by default, only from an IP range. Which range depends on the cluster: on self-managed clusters it is the control-plane nodes; on managed services the control plane is usually outside the cluster network, so check your provider's documentation for the source addresses of webhook calls. The policy parses as valid YAML but was not applied to a live cluster for this page; test it with a positive and a negative probe as the network policy guide (/domains/runtime/containers/container-network-admission/) describes. It needs a network plugin that enforces NetworkPolicy.
# Allow client traffic to the controller, and the admission webhook port only
# from the API server. Replace the CIDR with the address range your control
# plane uses to call webhooks; on managed clusters, check your provider's docs.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/component: controller
policyTypes: [Ingress]
ingress:
- ports: # the traffic the controller exists to serve
- {port: 80, protocol: TCP}
- {port: 443, protocol: TCP}
- from:
- ipBlock: {cidr: 10.0.0.0/28} # placeholder: your control plane's source range
ports:
- {port: 8443, protocol: TCP} # validating admission webhook (chart default)| Control | Who | SSDF practice or standard | Guide |
|---|---|---|---|
| Authenticate or network-restrict admission webhooks so only the API server can call them | Controller authors and operators | SSDF PW.9.1; OWASP Kubernetes Top 10 2025 K06 | /domains/runtime/containers/container-network-admission/ |
| Validate annotations against a strict grammar; never copy them into configuration unescaped | Controller authors | SSDF PW.5.1, PW.8.2 | /domains/appsec/secure-coding/injection-parser-boundaries/ |
| Least-privilege RBAC: no cluster-wide Secret access by default | Controller authors and operators | SSDF PW.9.1; K02 | /domains/runtime/containers/kubernetes-workload-security/ |
| Default-deny NetworkPolicy, so the Pod network is not one flat trust zone | Operators | K05 | /domains/runtime/containers/container-network-admission/ |
| Inventory cluster add-ons with versions, and patch them on the same clock as applications | Operators | SSDF RV.1.1, RV.2.2 | /domains/appsec/vulnerability-response/dependency-vulnerability-response/ |
| Replace end-of-life components before they need an emergency fix | Operators | SSDF PW.4.4 | /domains/runtime/containers/managed-kubernetes-hardening/ |
| Root-cause the class: every place that renders user input into configuration | Controller authors | SSDF RV.3.3 | /domains/appsec/secure-coding/injection-parser-boundaries/ |
- Every admission webhook is a network service inside your cluster. Restrict who can call it.
- Configuration languages are code. User input that becomes nginx, Envoy or any other configuration needs the same care as SQL.
- Scope controller permissions: read access to all Secrets turns any controller bug into a cluster compromise.
- A flat Pod network makes every pod an attacker's starting point. Default-deny between namespaces.
- Track the lifecycle of add-ons, not just their versions: ingress-nginx is now retired and will not be fixed again.
The Kubernetes advisories do not describe the exploitation technique beyond 'configuration injection' through the admission controller; the nginx -t mechanism comes from Wiz's write-up. The figures of about 43% of cloud environments and more than 6,500 exposed clusters are Wiz's estimates from its own data, and the over-40% deployment share is the Kubernetes blog's statement. No exploitation was known when the issues were published, and CVE-2025-1974 is not in CISA's KEV catalog as of its 8 October 2026 version; that is not proof it was never exploited. What happens to vulnerabilities found in ingress-nginx after March 2026 is set only by the retirement announcement: they will not be fixed by the project.
Guided practice
It is October 2026. You run four Kubernetes clusters. Decide each one's exposure and the next step. Treat the facts as true.
Supplied scenario material
C1: ingress-nginx v1.12.0; admission webhook enabled; no NetworkPolicies in any namespace; controller service account can list Secrets cluster-wide.
C2: ingress-nginx v1.11.5; admission webhook enabled; NetworkPolicy allows port 8443 only from the control-plane range; RBAC unchanged from the default.
C3: ingress-nginx v1.12.1 installed under a custom name, so the standard label selector finds nothing; a security review marked the cluster 'no ingress-nginx'.
C4: Gateway API with a maintained implementation; no ingress-nginx; a third-party operator in the cluster runs its own validating webhook on port 9443, reachable from all pods.1. Fix C1
What is C1's exposure and what do you do first?
Expected result and reasoning
Critical: a vulnerable version, an open webhook, a flat Pod network and cluster-wide Secret access. Disable the admission webhook now, upgrade, rotate Secrets the controller could read if compromise cannot be ruled out, add a webhook NetworkPolicy, and plan migration off ingress-nginx.
2. Judge C2
C2 has the fix and a NetworkPolicy. Is it done?
Expected result and reasoning
It is patched for the March 2025 CVEs, but ingress-nginx has been unsupported since March 2026, so future flaws will not be fixed. Keep the policy, reduce the controller's Secret access, and schedule the migration.
3. Correct C3
What went wrong in C3's review?
Expected result and reasoning
The check relied on a label. Search pod images, not labels, as the script on this page does; then treat C3 like C2.
4. Look past ingress-nginx in C4
Is C4 clear?
Expected result and reasoning
Not of the pattern: the operator's webhook is reachable from every pod. Check whether it authenticates callers, restrict it to the control plane with a NetworkPolicy, and review its RBAC.
The fixture simplifies cluster networking. How the API server reaches webhooks, and which address range a policy must allow, differs between self-managed and managed clusters; verify it before enforcing a policy.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
- Kubernetes blog: Ingress-nginx CVE-2025-1974, what you need to know. 24 March 2025, links revised May 2025.
- kubernetes/kubernetes issue 131009: CVE-2025-1974, ingress-nginx admission controller RCE escalation. 23 March 2025.
- kubernetes/kubernetes issue 131006: CVE-2025-24514, configuration injection via the auth-url annotation. 23 March 2025.
- ingress-nginx release controller-v1.12.1. 25 March 2025 (UTC).
- Kubernetes blog: Ingress NGINX retirement, what you need to know. 11 November 2025.
- Kubernetes documentation: Dynamic admission control
- OWASP Kubernetes Top 10 (2025). 2025 edition.
- Wiz (third-party research, discoverer): IngressNightmare, CVE-2025-1974 in Kubernetes. 24 March 2025.
Reference information
- SDLC phase
- DevelopmentMaintenance