This issue has been assessed as high severity. Review affected configurations immediately.
Kubernetes admission webhooks are how clusters enforce security policy. OPA Gatekeeper, Kyverno, and custom ValidatingWebhookConfiguration objects intercept API server requests before they’re committed to etcd, checking them against policy rules. They’re the architectural foundation of almost every enterprise Kubernetes security posture. They’re also routinely misconfigured in ways that create complete bypass paths.
This guide covers the principal bypass techniques and the hardening controls that prevent them.
How Admission Webhooks Work
When a resource is submitted to the Kubernetes API server (a Pod, Deployment, namespace, etc.), the API server processes it through the admission control chain. ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks intercept requests matching their defined rules and forward them to an external HTTP endpoint — your policy engine. That engine evaluates the request and returns an allow/deny decision.
The key configuration object is ValidatingWebhookConfiguration:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: gatekeeper-validating-webhook-configuration
webhooks:
- name: validation.gatekeeper.sh
rules:
- apiGroups: ["*"]
apiVersions: ["*"]
operations: ["CREATE", "UPDATE"]
resources: ["pods", "deployments", "replicasets"]
namespaceSelector:
matchExpressions:
- key: admission.gatekeeper.sh/ignore
operator: DoesNotExist
failurePolicy: Fail
clientConfig:
service:
name: gatekeeper-webhook-service
namespace: gatekeeper-system
port: 443
Every field in this configuration is a potential bypass surface.
Bypass Technique 1: Namespace Label Manipulation
The most common misconfiguration is a namespaceSelector that allows labels on namespaces to opt out of webhook evaluation. The Gatekeeper default configuration uses a selector that skips namespaces with the label admission.gatekeeper.sh/ignore. If an attacker has RBAC permissions to label namespaces, they can label a target namespace to exclude it from policy enforcement, then deploy workloads without restriction.
Exploit path:
# Attacker has namespace edit permissions
kubectl label namespace target-namespace admission.gatekeeper.sh/ignore=true
# Now deploy a privileged container — policy engine skips this namespace
kubectl -n target-namespace run bypass \
--image=ubuntu \
--privileged=true \
--overrides='{"spec":{"hostPID":true,"containers":[{"name":"bypass","image":"ubuntu","securityContext":{"privileged":true}}]}}'
Fix: Lock namespace labels. Use RBAC to prevent non-admin users from labelling namespaces. For Gatekeeper, consider using objectSelector or removing the opt-out label mechanism entirely in production clusters.
Check for exposure:
# Identify namespaces that bypass webhook evaluation
kubectl get namespaces -l admission.gatekeeper.sh/ignore --no-headers | awk '{print $1}'
Bypass Technique 2: Unscoped Resource Coverage
Webhooks define which API groups, resources, and operations they intercept. A common error is defining coverage for pods but forgetting that pod-creating resources like CronJob, StatefulSet, and custom CRDs also create pods. Policies that only evaluate Pod objects are bypassed by creating a Deployment or Job that spawns the restricted pod.
The more complete resource list for pod policy enforcement:
rules:
- apiGroups: ["", "apps", "batch"]
apiVersions: ["*"]
operations: ["CREATE", "UPDATE"]
resources:
- pods
- deployments
- replicasets
- statefulsets
- daemonsets
- jobs
- cronjobs
- replicationcontrollers
Audit your webhook coverage:
kubectl get validatingwebhookconfigurations -o json \
| jq '.items[].webhooks[] | {name: .name, resources: .rules[].resources}'
If cronjobs or statefulsets are missing from the resource list, those are bypass vectors.
Bypass Technique 3: failurePolicy: Ignore
When the webhook endpoint is unreachable — due to a crash, network partition, or resource starvation — the failurePolicy field determines what happens. Fail blocks the request. Ignore allows it through.
A common compromise path: overwhelm or crash the webhook service pod to force Ignore failurePolicy into effect, then deploy restricted workloads during the window when the policy engine is down.
Audit your failurePolicy:
kubectl get validatingwebhookconfigurations -o json \
| jq '.items[].webhooks[] | {name: .name, failurePolicy: .failurePolicy}'
Any webhook using failurePolicy: Ignore is a bypass risk in a denial-of-service scenario. Policy engines like Gatekeeper and Kyverno should be deployed with Fail in production.
Protect the webhook service from DoS:
# Resource limits for Gatekeeper controller
resources:
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
Set a PodDisruptionBudget for the webhook controller and deploy it across multiple nodes with anti-affinity rules.
Bypass Technique 4: RBAC Access to Webhook Configuration Objects
If an attacker gains access to a ServiceAccount with update or delete permissions on ValidatingWebhookConfiguration objects, they can disable the enforcement layer directly.
# Attacker modifies webhook to allow all
kubectl patch validatingwebhookconfiguration gatekeeper-validating-webhook-configuration \
--type='json' \
-p='[{"op":"replace","path":"/webhooks/0/failurePolicy","value":"Ignore"}]'
# Or delete it entirely
kubectl delete validatingwebhookconfiguration gatekeeper-validating-webhook-configuration
Protect webhook configuration objects with RBAC:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: deny-webhook-mutation
rules:
- apiGroups: ["admissionregistration.k8s.io"]
resources: ["validatingwebhookconfigurations", "mutatingwebhookconfigurations"]
verbs: ["delete", "update", "patch"]
# Leave this empty to deny for non-privileged roles
Audit which principals have write access to these objects:
kubectl auth can-i update validatingwebhookconfigurations \
--as=system:serviceaccount:default:my-serviceaccount
Bypass Technique 5: DryRun Requests
Kubernetes admission webhooks receive a dryRun flag on requests. Webhooks that fail to handle dryRun correctly may pass requests in dryRun mode but reject them otherwise — the inverse of the intended behaviour. Less commonly, some custom webhooks reject dryRun operations without enforcing policy on live operations.
Always check dryRun handling:
kubectl apply --dry-run=server -f privileged-pod.yaml
If a dry-run passes a policy check that a live apply would block, your webhook is correctly enforcing — the dryRun test confirms policy is active. If the inverse is true, audit your webhook code for incorrect dryRun handling.
Audit Your Cluster’s Webhook Posture
# List all admission webhooks
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations
# Check namespace selectors
kubectl get validatingwebhookconfigurations -o json \
| jq '.items[].webhooks[] | {name: .name, namespaceSelector: .namespaceSelector}'
# Verify TLS is enforced
kubectl get validatingwebhookconfigurations -o json \
| jq '.items[].webhooks[].clientConfig'
Key Hardening Checklist
failurePolicy: Failon all production webhooks- No opt-out namespace labels in production without explicit audit trail
- All pod-creating resource types covered (Deployment, StatefulSet, Job, CronJob, DaemonSet)
- RBAC preventing non-admin write access to
validatingwebhookconfigurations - Webhook service deployed with resource limits and PodDisruptionBudget
- Webhook service TLS certificate rotation automated
- Alert on deletion or modification of
ValidatingWebhookConfigurationobjects
Admission webhooks are only effective if they’re correctly scoped, resilient to failure, and protected from modification. Audit each of these surfaces regularly — they’re the first thing a container escape or privilege escalation attempt will try to sidestep.