Safeguard
Cloud Security•

Block risky Ingress annotations with a ValidatingAdmissionPolicy

Two 2026 ingress-nginx advisories turn Ingress annotations into code execution. A built-in CEL policy can block them in minutes, with no webhook to run.

Safeguard Security Team
5 min read

In March 2026 the Kubernetes Security Response Committee published two ingress-nginx advisories with the same shape. CVE-2026-3288 (9 March) lets the rewrite-target annotation inject configuration into nginx, and CVE-2026-4342 (19 March) does the same through a combination of annotations. Both are rated 8.8 and both can lead to code execution in the controller and disclosure of Secrets it can read. For CVE-2026-3288 the advisory names a stopgap before you upgrade: use admission control to block the annotation.

If you have never written an admission policy, this is a good first one, because the built-in ValidatingAdmissionPolicy needs no webhook server, no certificates and no extra pod.

What's affected

ingress-nginx, in these ranges per the advisories:

  • CVE-2026-3288: versions below 1.13.8, below 1.14.4 and below 1.15.0.
  • CVE-2026-4342: versions below 1.13.9, below 1.14.5 and below 1.15.1.

Upgrading to 1.13.9, 1.14.5 or 1.15.1 or later covers both. The advisories note that in the default installation the controller can read Secrets cluster-wide, which is why code execution in that pod is serious. If ingress-nginx is not installed, you are not affected. Both advisories were reported by outside researchers, Kai Aizen for CVE-2026-3288 and wooseokdotkim for CVE-2026-4342.

How to check if you're exposed

The advisory's own check for whether ingress-nginx runs in your cluster:

kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx

Then read the image tag of the controller:

kubectl get pods -A -l app.kubernetes.io/name=ingress-nginx \
  -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}: {.spec.containers[*].image}{"\n"}{end}'

Find existing Ingresses that already use the annotation:

kubectl get ingress -A -o json | jq -r '
  .items[]
  | select(.metadata.annotations // {} | has("nginx.ingress.kubernetes.io/rewrite-target"))
  | "\(.metadata.namespace)/\(.metadata.name)"'

The advisories give one detection hint: suspicious data inside rules.http.paths.path of an Ingress could indicate an attempt. Dump the paths and read them with your own eyes.

What to do now

  1. Upgrade ingress-nginx to a fixed version using the project's upgrade guide linked from the advisory.
  2. While you plan the upgrade, add this policy. It follows the structure in the Kubernetes docs (stable since v1.30):
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: block-risky-ingress-annotations.example.com
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: ["networking.k8s.io"]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["ingresses"]
  validations:
    - expression: "!has(object.metadata.annotations) || !('nginx.ingress.kubernetes.io/rewrite-target' in object.metadata.annotations)"
      message: "The rewrite-target annotation is blocked cluster-wide until ingress-nginx is upgraded."
  1. Bind it in audit mode first. The docs list Deny, Warn and Audit as validation actions, and Warn and Audit can be combined:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: block-risky-ingress-annotations-binding.example.com
spec:
  policyName: block-risky-ingress-annotations.example.com
  validationActions: [Warn, Audit]
  1. Read the audit annotations for a day, fix the Ingresses that trip the rule, then change the binding to [Deny].
  2. Test the deny path without creating anything. A server-side dry run goes through admission and returns the policy message:
kubectl apply --dry-run=server -f ingress.yaml
  1. Remove the policy once every controller is on a fixed version, or keep it and widen it to the other annotations you decide nobody should set.

The Kubernetes docs warn that admission policies are part of the control plane and should be deployed with great caution. A broken Fail policy on a core resource blocks real changes, which is why steps 3 and 4 come before Deny.

How this could have been avoided

  • Admission control on annotations. The advisory lists it as the mitigation. A policy that rejects dangerous annotation keys turns a tenant's Ingress write into a rejected request.
  • Fewer people allowed to create Ingress objects. The CVSS vector for both issues requires low privileges, meaning any identity that can write an Ingress. Restrict that verb with RBAC and give application teams a reviewed path.
  • Limit what the controller can read. The advisories point out that the default install reads Secrets cluster-wide. Review the controller's RBAC and narrow it to the namespaces it serves.
  • Egress limits on the controller pod. If code does run there, a default-deny egress policy limits where stolen data can go.
  • A patch routine for cluster add-ons. Ingress controllers sit on the edge. Track them like your base images, with a version inventory and an owner.

Why it matters

Annotations look like metadata, but for an ingress controller they are input that becomes configuration. Any controller that renders text config from user-controlled fields has this risk. The Kubernetes CVE feed lists a long run of ingress-nginx entries for 2025 and 2026, and the pattern is consistent.

An in-process admission policy is cheap to add and easy to remove. Treat it as a standing guardrail for the next annotation bug, not as a one-off patch.

Try it yourself

On a test cluster, apply the policy with the Warn and Audit binding, then submit an Ingress that sets the rewrite-target annotation as a server-side dry run (step 5). Confirm you get a warning and not an error. Switch the binding to Deny and repeat. Then see how the same idea looks as a production gate.

Click through it at your own pace, or open it full screen.

Quick check

Why bind the policy with Warn and Audit before Deny?

It shows which existing and planned Ingresses would be rejected, so you can fix them before the policy starts blocking real changes.

What does the advisory for CVE-2026-3288 say you can do before upgrading?

Use admission control to block the use of the rewrite-target annotation.

Sources

Never miss an update

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

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.