Repository: envoyproxy/gateway
Pull request #6355 (2025-06-19 03:55:41)

feat: introduce validation levels for lua
**What type of PR is this?**
feat: introduce validation levels for lua

<!--
NOTE: If your PR contains any API changes (changes under `/api`), we recommend you to separate these API changes into
a new PR, and we will review the API part first. It will save you lots of implementation time if the API get accepted.
-->

**What this PR does / why we need it**:
Introduce validation levels so users can disable or have runtime validations on their lua script. Will introduce a compile time validation in a follow up PR.

**Which issue(s) this PR fixes**:
Related discussion in Slack [thread](https://envoyproxy.slack.com/archives/C03E6NHLESV/p1750276621696389?thread_ts=1750261902.415369&cid=C03E6NHLESV)
Related: #6172 #6176 

Release Notes: Yes


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/6355?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 70.70%. Comparing base [(`16747d4`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/16747d4f823dce036568f40ef247f40f021ce0ff?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`4e74f3a`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/4e74f3a5ee2ac213fab63049ba691db5cb825e89?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #6355      +/-   ##
==========================================
- Coverage   70.76%   70.70%   -0.06%     
==========================================
  Files         220      220              
  Lines       37059    37060       +1     
==========================================
- Hits        26225    26205      -20     
- Misses       9299     9317      +18     
- Partials     1535     1538       +3     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/6355?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
Xunzhuo: /retest

Patch:
diff --git a/api/v1alpha1/envoyproxy_types.go b/api/v1alpha1/envoyproxy_types.go
index 482cf2811..d5567d2c5 100644
--- a/api/v1alpha1/envoyproxy_types.go
+++ b/api/v1alpha1/envoyproxy_types.go
@@ -158,12 +158,27 @@ type EnvoyProxySpec struct {
 	// +optional
 	PreserveRouteOrder *bool `json:"preserveRouteOrder,omitempty"`
 
-	// DisableLuaValidation disables the Lua script validation for Lua EnvoyExtensionPolicies
-	// +kubebuilder:default=false
+	// LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies
+	// Default: Strict
 	// +optional
-	DisableLuaValidation *bool `json:"disableLuaValidation,omitempty"`
+	LuaValidation *LuaValidation `json:"luaValidation,omitempty"`
 }
 
+// +kubebuilder:validation:Enum=Strict;Disabled
+type LuaValidation string
+
+const (
+	// LuaValidationStrict is the default level and checks for issues during script execution.
+	// Recommended if your scripts only use the standard Envoy Lua stream handle API.
+	// For supported APIs, see: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter#stream-handle-api
+	LuaValidationStrict LuaValidation = "Strict"
+
+	// LuaValidationDisabled disables all validation of Lua scripts.
+	// Scripts will be accepted and executed without any validation checks.
+	// This is not recommended unless your scripts import libraries that are not supported by Lua runtime validation.
+	LuaValidationDisabled LuaValidation = "Disabled"
+)
+
 // RoutingType defines the type of routing of this Envoy proxy.
 type RoutingType string
 
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index b0e5185a2..0ae6350b2 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -2412,9 +2412,9 @@ func (in *EnvoyProxySpec) DeepCopyInto(out *EnvoyProxySpec) {
 		*out = new(bool)
 		**out = **in
 	}
-	if in.DisableLuaValidation != nil {
-		in, out := &in.DisableLuaValidation, &out.DisableLuaValidation
-		*out = new(bool)
+	if in.LuaValidation != nil {
+		in, out := &in.LuaValidation, &out.LuaValidation
+		*out = new(LuaValidation)
 		**out = **in
 	}
 }
diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml
index c5b6403b5..e4df4ec33 100644
--- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml
+++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml
@@ -270,11 +270,6 @@ spec:
                   the number of cpuset threads on the platform.
                 format: int32
                 type: integer
-              disableLuaValidation:
-                default: false
-                description: DisableLuaValidation disables the Lua script validation
-                  for Lua EnvoyExtensionPolicies
-                type: boolean
               extraArgs:
                 description: |-
                   ExtraArgs defines additional command line options that are provided to Envoy.
@@ -442,6 +437,14 @@ spec:
                       and the log level is the value. If unspecified, defaults to "default: warn".
                     type: object
                 type: object
+              luaValidation:
+                description: |-
+                  LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies
+                  Default: Strict
+                enum:
+                - Strict
+                - Disabled
+                type: string
               mergeGateways:
                 description: |-
                   MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure.
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
index b8f200c01..59d44a753 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
@@ -269,11 +269,6 @@ spec:
                   the number of cpuset threads on the platform.
                 format: int32
                 type: integer
-              disableLuaValidation:
-                default: false
-                description: DisableLuaValidation disables the Lua script validation
-                  for Lua EnvoyExtensionPolicies
-                type: boolean
               extraArgs:
                 description: |-
                   ExtraArgs defines additional command line options that are provided to Envoy.
@@ -441,6 +436,14 @@ spec:
                       and the log level is the value. If unspecified, defaults to "default: warn".
                     type: object
                 type: object
+              luaValidation:
+                description: |-
+                  LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies
+                  Default: Strict
+                enum:
+                - Strict
+                - Disabled
+                type: string
               mergeGateways:
                 description: |-
                   MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure.
diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go
index de83c0d69..5e8899c21 100644
--- a/internal/gatewayapi/envoyextensionpolicy.go
+++ b/internal/gatewayapi/envoyextensionpolicy.go
@@ -452,7 +452,8 @@ func (t *Translator) buildLua(
 	if err != nil {
 		return nil, err
 	}
-	if envoyProxy != nil && envoyProxy.Spec.DisableLuaValidation != nil && *envoyProxy.Spec.DisableLuaValidation {
+	if envoyProxy != nil && envoyProxy.Spec.LuaValidation != nil &&
+		*envoyProxy.Spec.LuaValidation == egv1a1.LuaValidationDisabled {
 		return &ir.Lua{
 			Name: name,
 			Code: luaCode,
diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider.go b/internal/infrastructure/kubernetes/proxy/resource_provider.go
index 87b7a3a4e..61adadaff 100644
--- a/internal/infrastructure/kubernetes/proxy/resource_provider.go
+++ b/internal/infrastructure/kubernetes/proxy/resource_provider.go
@@ -662,14 +662,13 @@ func (r *ResourceRender) getPodSpec(
 
 func (r *ResourceRender) getPodAnnotations(resourceAnnotation map[string]string, pod *egv1a1.KubernetesPodSpec) map[string]string {
 	podAnnotations := map[string]string{}
-	maps.Copy(podAnnotations, resourceAnnotation)
-	maps.Copy(podAnnotations, pod.Annotations)
-
 	if enablePrometheus(r.infra) {
 		podAnnotations["prometheus.io/path"] = "/stats/prometheus" // TODO: make this configurable
 		podAnnotations["prometheus.io/scrape"] = "true"
 		podAnnotations["prometheus.io/port"] = strconv.Itoa(bootstrap.EnvoyStatsPort)
 	}
+	maps.Copy(podAnnotations, resourceAnnotation)
+	maps.Copy(podAnnotations, pod.Annotations)
 
 	if len(podAnnotations) == 0 {
 		podAnnotations = nil
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
index 67f627997..7f810abe0 100644
--- a/release-notes/current.yaml
+++ b/release-notes/current.yaml
@@ -32,8 +32,7 @@ new features: |
   Added support for specifying deployment annotations through the helm chart.
   Added support for customizing the name of the ServiceAccount used by the Proxy.
   Added support for custom backendRefs via extension server using PostClusterModify hook.
-
-
+  Introduce validation strictness levels for Lua scripts in EnvoyExtensionPolicies.
 bug fixes: |
   Handle integer zone annotation values
   Fixed issue where WASM cache init failure caused routes with WASM-less EnvoyExtensionPolicies to have 500 direct responses.
@@ -48,6 +47,8 @@ bug fixes: |
   Fixed issue that default accesslog format not working.
   Fixed validation errors when the rateLimit url for Redis in the EnvoyGateway API includes multiple comma separated hosts.
   Fixes addresses in status of DualStack NodePort Gateways.
+  Fixed issue that not able to override the prometheus annotation in EnvoyProxy CRD.
+
 
 # Enhancements that improve performance.
 performance improvements: |


Test patch:
diff --git a/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml b/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml
index ba0e000f3..7fe4d6837 100644
--- a/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml
@@ -19,7 +19,6 @@ envoyProxyForGatewayClass:
             socket_address:
               address: 127.0.0.1
               port_value: 19000
-    disableLuaValidation: false
     logging:
       level:
         default: warn
diff --git a/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml b/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml
index 090c62945..c79870233 100644
--- a/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml
@@ -6,7 +6,6 @@ envoyProxyForGatewayClass:
     name: example
     namespace: default
   spec:
-    disableLuaValidation: false
     logging:
       level:
         default: warn
diff --git a/internal/gatewayapi/resource/testdata/all-resources.out.yaml b/internal/gatewayapi/resource/testdata/all-resources.out.yaml
index ae5b2eaec..612090f67 100644
--- a/internal/gatewayapi/resource/testdata/all-resources.out.yaml
+++ b/internal/gatewayapi/resource/testdata/all-resources.out.yaml
@@ -169,7 +169,6 @@ envoyProxyForGatewayClass:
     name: example
     namespace: default
   spec:
-    disableLuaValidation: false
     logging:
       level:
         default: warn
diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.in.yaml
index 97bd5af0d..86178c9b5 100644
--- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.in.yaml
+++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.in.yaml
@@ -5,7 +5,7 @@ envoyProxyForGatewayClass:
     namespace: envoy-gateway-system
     name: test
   spec:
-    disableLuaValidation: true
+    luaValidation: Disabled
 gateways:
 - apiVersion: gateway.networking.k8s.io/v1
   kind: Gateway
diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.out.yaml
index c825c8f25..1803ccef5 100644
--- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.out.yaml
+++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.out.yaml
@@ -120,8 +120,8 @@ infraIR:
           name: test
           namespace: envoy-gateway-system
         spec:
-          disableLuaValidation: true
           logging: {}
+          luaValidation: Disabled
         status: {}
       listeners:
       - address: null
diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go
index 2d9ee7b84..def8a1893 100644
--- a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go
+++ b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go
@@ -70,7 +70,7 @@ func (f *fakeKubernetesInfraProvider) GetEnvoyGateway() *egv1a1.EnvoyGateway {
 	return f.EnvoyGateway
 }
 
-func (f *fakeKubernetesInfraProvider) GetOwnerReferenceUID(ctx context.Context, infra *ir.Infra) (map[string]types.UID, error) {
+func (f *fakeKubernetesInfraProvider) GetOwnerReferenceUID(_ context.Context, _ *ir.Infra) (map[string]types.UID, error) {
 	if f.EnvoyGateway.GatewayNamespaceMode() {
 		return map[string]types.UID{
 			gwapiresource.KindGateway: "test-owner-reference-uid-for-gateway",
@@ -469,6 +469,26 @@ func TestDeployment(t *testing.T) {
 				},
 			},
 		},
+		{
+			caseName: "override-prometheus-annotations",
+			infra: newTestInfraWithAnnotations(map[string]string{
+				"prometheus.io/scrape": "true",
+			}),
+			deploy: &egv1a1.KubernetesDeploymentSpec{
+				Pod: &egv1a1.KubernetesPodSpec{
+					Annotations: map[string]string{
+						"prometheus.io/scrape": "false",
+					},
+				},
+			},
+			telemetry: &egv1a1.ProxyTelemetry{
+				Metrics: &egv1a1.ProxyMetrics{
+					Prometheus: &egv1a1.ProxyPrometheusProvider{
+						Disable: false,
+					},
+				},
+			},
+		},
 		{
 			caseName:    "with-concurrency",
 			infra:       newTestInfra(),
@@ -999,6 +1019,24 @@ func TestDaemonSet(t *testing.T) {
 				},
 			},
 		},
+		{
+			caseName: "override-prometheus-annotations",
+			infra:    newTestInfra(),
+			daemonset: &egv1a1.KubernetesDaemonSetSpec{
+				Pod: &egv1a1.KubernetesPodSpec{
+					Annotations: map[string]string{
+						"prometheus.io/scrape": "false",
+					},
+				},
+			},
+			telemetry: &egv1a1.ProxyTelemetry{
+				Metrics: &egv1a1.ProxyMetrics{
+					Prometheus: &egv1a1.ProxyPrometheusProvider{
+						Disable: false,
+					},
+				},
+			},
+		},
 		{
 			caseName:    "with-concurrency",
 			infra:       newTestInfra(),
@@ -1159,9 +1197,6 @@ func TestDaemonSet(t *testing.T) {
 			ds, err := r.DaemonSet()
 			require.NoError(t, err)
 
-			expected, err := loadDaemonSet(tc.caseName)
-			require.NoError(t, err)
-
 			sortEnv := func(env []corev1.EnvVar) {
 				sort.Slice(env, func(i, j int) bool {
 					return env[i].Name > env[j].Name
@@ -1177,8 +1212,12 @@ func TestDaemonSet(t *testing.T) {
 				return
 			}
 
+			expected, err := loadDaemonSet(tc.caseName)
+			require.NoError(t, err)
+
 			sortEnv(ds.Spec.Template.Spec.Containers[0].Env)
 			sortEnv(expected.Spec.Template.Spec.Containers[0].Env)
+
 			assert.Equal(t, expected, ds)
 		})
 	}
@@ -2058,8 +2097,8 @@ func TestGatewayNamespaceModeMultipleResources(t *testing.T) {
 
 func writeTestDataToFile(filename string, resources []any) error {
 	var combinedYAML []byte
-	for i, resource := range resources {
-		resourceYAML, err := yaml.Marshal(resource)
+	for i, res := range resources {
+		resourceYAML, err := yaml.Marshal(res)
 		if err != nil {
 			return err
 		}
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-prometheus-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-prometheus-annotations.yaml
new file mode 100644
index 000000000..099d5a225
--- /dev/null
+++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-prometheus-annotations.yaml
@@ -0,0 +1,386 @@
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  creationTimestamp: null
+  labels:
+    app.kubernetes.io/component: proxy
+    app.kubernetes.io/managed-by: envoy-gateway
+    app.kubernetes.io/name: envoy
+    gateway.envoyproxy.io/owning-gateway-name: default
+    gateway.envoyproxy.io/owning-gateway-namespace: default
+  name: envoy-default-37a8eec1
+  namespace: envoy-gateway-system
+  ownerReferences:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: GatewayClass
+    name: envoy-gateway-class
+    uid: test-owner-reference-uid-for-gatewayclass
+spec:
+  selector:
+    matchLabels:
+      app.kubernetes.io/component: proxy
+      app.kubernetes.io/managed-by: envoy-gateway
+      app.kubernetes.io/name: envoy
+      gateway.envoyproxy.io/owning-gateway-name: default
+      gateway.envoyproxy.io/owning-gateway-namespace: default
+  template:
+    metadata:
+      annotations:
+        prometheus.io/path: /stats/prometheus
+        prometheus.io/port: "19001"
+        prometheus.io/scrape: "false"
+      creationTimestamp: null
+      labels:
+        app.kubernetes.io/component: proxy
+        app.kubernetes.io/managed-by: envoy-gateway
+        app.kubernetes.io/name: envoy
+        gateway.envoyproxy.io/owning-gateway-name: default
+        gateway.envoyproxy.io/owning-gateway-namespace: default
+    spec:
+      automountServiceAccountToken: false
+      containers:
+      - args:
+        - --service-cluster default
+        - --service-node $(ENVOY_POD_NAME)
+        - |
+          --config-yaml admin:
+            access_log:
+            - name: envoy.access_loggers.file
+              typed_config:
+                "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
+                path: /dev/null
+            address:
+              socket_address:
+                address: 127.0.0.1
+                port_value: 19000
+          cluster_manager:
+            local_cluster_name: local_cluster
+          node:
+            locality:
+              zone: $(ENVOY_SERVICE_ZONE)
+          layered_runtime:
+            layers:
+            - name: global_config
+              static_layer:
+                envoy.restart_features.use_eds_cache_for_ads: true
+                re2.max_program_size.error_level: 4294967295
+                re2.max_program_size.warn_level: 1000
+          dynamic_resources:
+            ads_config:
+              api_type: DELTA_GRPC
+              transport_api_version: V3
+              grpc_services:
+              - envoy_grpc:
+                  cluster_name: xds_cluster
+              set_node_on_first_message_only: true
+            lds_config:
+              ads: {}
+              resource_api_version: V3
+            cds_config:
+              ads: {}
+              resource_api_version: V3
+          static_resources:
+            listeners:
+            - name: envoy-gateway-proxy-stats-0.0.0.0-19001
+              address:
+                socket_address:
+                  address: '0.0.0.0'
+                  port_value: 19001
+                  protocol: TCP
+              bypass_overload_manager: true
+              filter_chains:
+              - filters:
+                - name: envoy.filters.network.http_connection_manager
+                  typed_config:
+                    "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+                    stat_prefix: eg-stats-http
+                    normalize_path: true
+                    route_config:
+                      name: local_route
+                      virtual_hosts:
+                      - name: prometheus_stats
+                        domains:
+                        - "*"
+                        routes:
+                        - match:
+                            path: /stats/prometheus
+                            headers:
+                            - name: ":method"
+                              string_match:
+                                exact: GET
+                          route:
+                            cluster: prometheus_stats
+                    http_filters:
+                    - name: envoy.filters.http.router
+                      typed_config:
+                        "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            clusters:
+            - name: prometheus_stats
+              connect_timeout: 0.250s
+              type: STATIC
+              lb_policy: ROUND_ROBIN
+              load_assignment:
+                cluster_name: prometheus_stats
+                endpoints:
+                - lb_endpoints:
+                  - endpoint:
+                      address:
+                        socket_address:
+                          address: 127.0.0.1
+                          port_value: 19000
+            - connect_timeout: 10s
+              lb_policy: ROUND_ROBIN
+              load_assignment:
+                cluster_name: local_cluster
+                endpoints:
+                - lb_endpoints:
+                  - endpoint:
+                      address:
+                        socket_address:
+                          address: 127.0.0.1
+                          port_value: 10080
+                    load_balancing_weight: 1
+                  load_balancing_weight: 1
+                  locality:
+                    zone: $(ENVOY_SERVICE_ZONE)
+              name: local_cluster
+              type: STATIC
+            - connect_timeout: 10s
+              load_assignment:
+                cluster_name: xds_cluster
+                endpoints:
+                - load_balancing_weight: 1
+                  lb_endpoints:
+                  - load_balancing_weight: 1
+                    endpoint:
+                      address:
+                        socket_address:
+                          address: envoy-gateway.envoy-gateway-system.svc.cluster.local
+                          port_value: 18000
+              typed_extension_protocol_options:
+                envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+                  "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions"
+                  explicit_http_config:
+                    http2_protocol_options:
+                      connection_keepalive:
+                        interval: 30s
+                        timeout: 5s
+              name: xds_cluster
+              type: STRICT_DNS
+              transport_socket:
+                name: envoy.transport_sockets.tls
+                typed_config:
+                  "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+                  common_tls_context:
+                    tls_params:
+                      tls_maximum_protocol_version: TLSv1_3
+                    tls_certificate_sds_secret_configs:
+                    - name: xds_certificate
+                      sds_config:
+                        path_config_source:
+                          path: /sds/xds-certificate.json
+                        resource_api_version: V3
+                    validation_context_sds_secret_config:
+                      name: xds_trusted_ca
+                      sds_config:
+                        path_config_source:
+                          path: /sds/xds-trusted-ca.json
+                        resource_api_version: V3
+          overload_manager:
+            refresh_interval: 0.25s
+            resource_monitors:
+            - name: "envoy.resource_monitors.global_downstream_max_connections"
+              typed_config:
+                "@type": type.googleapis.com/envoy.extensions.resource_monitors.downstream_connections.v3.DownstreamConnectionsConfig
+                max_active_downstream_connections: 50000
+        - --log-level warn
+        - --cpuset-threads
+        - --drain-strategy immediate
+        - --component-log-level misc:error
+        - --drain-time-s 60
+        command:
+        - envoy
+        env:
+        - name: ENVOY_POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.namespace
+        - name: ENVOY_POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+        - name: ENVOY_SERVICE_ZONE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.annotations['topology.kubernetes.io/zone']
+        image: docker.io/envoyproxy/envoy:distroless-dev
+        imagePullPolicy: IfNotPresent
+        lifecycle:
+          preStop:
+            httpGet:
+              path: /shutdown/ready
+              port: 19002
+              scheme: HTTP
+        livenessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /ready
+            port: 19003
+            scheme: HTTP
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        name: envoy
+        ports:
+        - containerPort: 19001
+          name: metrics
+          protocol: TCP
+        - containerPort: 19003
+          name: readiness
+          protocol: TCP
+        readinessProbe:
+          failureThreshold: 1
+          httpGet:
+            path: /ready
+            port: 19003
+            scheme: HTTP
+          periodSeconds: 5
+          successThreshold: 1
+          timeoutSeconds: 1
+        resources:
+          requests:
+            cpu: 100m
+            memory: 512Mi
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+          privileged: false
+          runAsGroup: 65532
+          runAsNonRoot: true
+          runAsUser: 65532
+          seccompProfile:
+            type: RuntimeDefault
+        startupProbe:
+          failureThreshold: 30
+          httpGet:
+            path: /ready
+            port: 19003
+            scheme: HTTP
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+        volumeMounts:
+        - mountPath: /certs
+          name: certs
+          readOnly: true
+        - mountPath: /sds
+          name: sds
+      - args:
+        - envoy
+        - shutdown-manager
+        command:
+        - envoy-gateway
+        env:
+        - name: ENVOY_POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.namespace
+        - name: ENVOY_POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+        - name: ENVOY_SERVICE_ZONE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.annotations['topology.kubernetes.io/zone']
+        image: docker.io/envoyproxy/gateway-dev:latest
+        imagePullPolicy: IfNotPresent
+        lifecycle:
+          preStop:
+            exec:
+              command:
+              - envoy-gateway
+              - envoy
+              - shutdown
+        livenessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /healthz
+            port: 19002
+            scheme: HTTP
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        name: shutdown-manager
+        readinessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /healthz
+            port: 19002
+            scheme: HTTP
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        resources:
+          requests:
+            cpu: 10m
+            memory: 32Mi
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+          privileged: false
+          runAsGroup: 65532
+          runAsNonRoot: true
+          runAsUser: 65532
+          seccompProfile:
+            type: RuntimeDefault
+        startupProbe:
+          failureThreshold: 30
+          httpGet:
+            path: /healthz
+            port: 19002
+            scheme: HTTP
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+      dnsPolicy: ClusterFirst
+      restartPolicy: Always
+      schedulerName: default-scheduler
+      serviceAccountName: envoy-default-37a8eec1
+      terminationGracePeriodSeconds: 360
+      volumes:
+      - name: certs
+        secret:
+          defaultMode: 420
+          secretName: envoy
+      - configMap:
+          defaultMode: 420
+          items:
+          - key: xds-trusted-ca.json
+            path: xds-trusted-ca.json
+          - key: xds-certificate.json
+            path: xds-certificate.json
+          name: envoy-default-37a8eec1
+          optional: false
+        name: sds
+  updateStrategy:
+    type: RollingUpdate
+status:
+  currentNumberScheduled: 0
+  desiredNumberScheduled: 0
+  numberMisscheduled: 0
+  numberReady: 0
diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-prometheus-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-prometheus-annotations.yaml
new file mode 100644
index 000000000..7a7fe26c7
--- /dev/null
+++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-prometheus-annotations.yaml
@@ -0,0 +1,386 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  annotations:
+    prometheus.io/scrape: "true"
+  creationTimestamp: null
+  labels:
+    app.kubernetes.io/component: proxy
+    app.kubernetes.io/managed-by: envoy-gateway
+    app.kubernetes.io/name: envoy
+    gateway.envoyproxy.io/owning-gateway-name: default
+    gateway.envoyproxy.io/owning-gateway-namespace: default
+  name: envoy-default-37a8eec1
+  namespace: envoy-gateway-system
+  ownerReferences:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: GatewayClass
+    name: envoy-gateway-class
+    uid: test-owner-reference-uid-for-gatewayclass
+spec:
+  progressDeadlineSeconds: 600
+  revisionHistoryLimit: 10
+  selector:
+    matchLabels:
+      app.kubernetes.io/component: proxy
+      app.kubernetes.io/managed-by: envoy-gateway
+      app.kubernetes.io/name: envoy
+      gateway.envoyproxy.io/owning-gateway-name: default
+      gateway.envoyproxy.io/owning-gateway-namespace: default
+  strategy:
+    type: RollingUpdate
+  template:
+    metadata:
+      annotations:
+        prometheus.io/path: /stats/prometheus
+        prometheus.io/port: "19001"
+        prometheus.io/scrape: "false"
+      creationTimestamp: null
+      labels:
+        app.kubernetes.io/component: proxy
+        app.kubernetes.io/managed-by: envoy-gateway
+        app.kubernetes.io/name: envoy
+        gateway.envoyproxy.io/owning-gateway-name: default
+        gateway.envoyproxy.io/owning-gateway-namespace: default
+    spec:
+      automountServiceAccountToken: false
+      containers:
+      - args:
+        - --service-cluster default
+        - --service-node $(ENVOY_POD_NAME)
+        - |
+          --config-yaml admin:
+            access_log:
+            - name: envoy.access_loggers.file
+              typed_config:
+                "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
+                path: /dev/null
+            address:
+              socket_address:
+                address: 127.0.0.1
+                port_value: 19000
+          cluster_manager:
+            local_cluster_name: local_cluster
+          node:
+            locality:
+              zone: $(ENVOY_SERVICE_ZONE)
+          layered_runtime:
+            layers:
+            - name: global_config
+              static_layer:
+                envoy.restart_features.use_eds_cache_for_ads: true
+                re2.max_program_size.error_level: 4294967295
+                re2.max_program_size.warn_level: 1000
+          dynamic_resources:
+            ads_config:
+              api_type: DELTA_GRPC
+              transport_api_version: V3
+              grpc_services:
+              - envoy_grpc:
+                  cluster_name: xds_cluster
+              set_node_on_first_message_only: true
+            lds_config:
+              ads: {}
+              resource_api_version: V3
+            cds_config:
+              ads: {}
+              resource_api_version: V3
+          static_resources:
+            listeners:
+            - name: envoy-gateway-proxy-stats-0.0.0.0-19001
+              address:
+                socket_address:
+                  address: '0.0.0.0'
+                  port_value: 19001
+                  protocol: TCP
+              bypass_overload_manager: true
+              filter_chains:
+              - filters:
+                - name: envoy.filters.network.http_connection_manager
+                  typed_config:
+                    "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+                    stat_prefix: eg-stats-http
+                    normalize_path: true
+                    route_config:
+                      name: local_route
+                      virtual_hosts:
+                      - name: prometheus_stats
+                        domains:
+                        - "*"
+                        routes:
+                        - match:
+                            path: /stats/prometheus
+                            headers:
+                            - name: ":method"
+                              string_match:
+                                exact: GET
+                          route:
+                            cluster: prometheus_stats
+                    http_filters:
+                    - name: envoy.filters.http.router
+                      typed_config:
+                        "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            clusters:
+            - name: prometheus_stats
+              connect_timeout: 0.250s
+              type: STATIC
+              lb_policy: ROUND_ROBIN
+              load_assignment:
+                cluster_name: prometheus_stats
+                endpoints:
+                - lb_endpoints:
+                  - endpoint:
+                      address:
+                        socket_address:
+                          address: 127.0.0.1
+                          port_value: 19000
+            - connect_timeout: 10s
+              lb_policy: ROUND_ROBIN
+              load_assignment:
+                cluster_name: local_cluster
+                endpoints:
+                - lb_endpoints:
+                  - endpoint:
+                      address:
+                        socket_address:
+                          address: 127.0.0.1
+                          port_value: 10080
+                    load_balancing_weight: 1
+                  load_balancing_weight: 1
+                  locality:
+                    zone: $(ENVOY_SERVICE_ZONE)
+              name: local_cluster
+              type: STATIC
+            - connect_timeout: 10s
+              load_assignment:
+                cluster_name: xds_cluster
+                endpoints:
+                - load_balancing_weight: 1
+                  lb_endpoints:
+                  - load_balancing_weight: 1
+                    endpoint:
+                      address:
+                        socket_address:
+                          address: envoy-gateway.envoy-gateway-system.svc.cluster.local
+                          port_value: 18000
+              typed_extension_protocol_options:
+                envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+                  "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions"
+                  explicit_http_config:
+                    http2_protocol_options:
+                      connection_keepalive:
+                        interval: 30s
+                        timeout: 5s
+              name: xds_cluster
+              type: STRICT_DNS
+              transport_socket:
+                name: envoy.transport_sockets.tls
+                typed_config:
+                  "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+                  common_tls_context:
+                    tls_params:
+                      tls_maximum_protocol_version: TLSv1_3
+                    tls_certificate_sds_secret_configs:
+                    - name: xds_certificate
+                      sds_config:
+                        path_config_source:
+                          path: /sds/xds-certificate.json
+                        resource_api_version: V3
+                    validation_context_sds_secret_config:
+                      name: xds_trusted_ca
+                      sds_config:
+                        path_config_source:
+                          path: /sds/xds-trusted-ca.json
+                        resource_api_version: V3
+          overload_manager:
+            refresh_interval: 0.25s
+            resource_monitors:
+            - name: "envoy.resource_monitors.global_downstream_max_connections"
+              typed_config:
+                "@type": type.googleapis.com/envoy.extensions.resource_monitors.downstream_connections.v3.DownstreamConnectionsConfig
+                max_active_downstream_connections: 50000
+        - --log-level warn
+        - --cpuset-threads
+        - --drain-strategy immediate
+        - --component-log-level misc:error
+        - --drain-time-s 60
+        command:
+        - envoy
+        env:
+        - name: ENVOY_POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.namespace
+        - name: ENVOY_POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+        - name: ENVOY_SERVICE_ZONE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.annotations['topology.kubernetes.io/zone']
+        image: docker.io/envoyproxy/envoy:distroless-dev
+        imagePullPolicy: IfNotPresent
+        lifecycle:
+          preStop:
+            httpGet:
+              path: /shutdown/ready
+              port: 19002
+              scheme: HTTP
+        livenessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /ready
+            port: 19003
+            scheme: HTTP
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        name: envoy
+        ports:
+        - containerPort: 19001
+          name: metrics
+          protocol: TCP
+        - containerPort: 19003
+          name: readiness
+          protocol: TCP
+        readinessProbe:
+          failureThreshold: 1
+          httpGet:
+            path: /ready
+            port: 19003
+            scheme: HTTP
+          periodSeconds: 5
+          successThreshold: 1
+          timeoutSeconds: 1
+        resources:
+          requests:
+            cpu: 100m
+            memory: 512Mi
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+          privileged: false
+          runAsGroup: 65532
+          runAsNonRoot: true
+          runAsUser: 65532
+          seccompProfile:
+            type: RuntimeDefault
+        startupProbe:
+          failureThreshold: 30
+          httpGet:
+            path: /ready
+            port: 19003
+            scheme: HTTP
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+        volumeMounts:
+        - mountPath: /certs
+          name: certs
+          readOnly: true
+        - mountPath: /sds
+          name: sds
+      - args:
+        - envoy
+        - shutdown-manager
+        command:
+        - envoy-gateway
+        env:
+        - name: ENVOY_POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.namespace
+        - name: ENVOY_POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+        - name: ENVOY_SERVICE_ZONE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.annotations['topology.kubernetes.io/zone']
+        image: docker.io/envoyproxy/gateway-dev:latest
+        imagePullPolicy: IfNotPresent
+        lifecycle:
+          preStop:
+            exec:
+              command:
+              - envoy-gateway
+              - envoy
+              - shutdown
+        livenessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /healthz
+            port: 19002
+            scheme: HTTP
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        name: shutdown-manager
+        readinessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /healthz
+            port: 19002
+            scheme: HTTP
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        resources:
+          requests:
+            cpu: 10m
+            memory: 32Mi
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+          privileged: false
+          runAsGroup: 65532
+          runAsNonRoot: true
+          runAsUser: 65532
+          seccompProfile:
+            type: RuntimeDefault
+        startupProbe:
+          failureThreshold: 30
+          httpGet:
+            path: /healthz
+            port: 19002
+            scheme: HTTP
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 1
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+      dnsPolicy: ClusterFirst
+      restartPolicy: Always
+      schedulerName: default-scheduler
+      serviceAccountName: envoy-default-37a8eec1
+      terminationGracePeriodSeconds: 360
+      volumes:
+      - name: certs
+        secret:
+          defaultMode: 420
+          secretName: envoy
+      - configMap:
+          defaultMode: 420
+          items:
+          - key: xds-trusted-ca.json
+            path: xds-trusted-ca.json
+          - key: xds-certificate.json
+            path: xds-certificate.json
+          name: envoy-default-37a8eec1
+          optional: false
+        name: sds
+status: {}
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index 0b2fc9225..95ed18572 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -1635,7 +1635,7 @@ _Appears in:_
 | `backendTLS` | _[BackendTLSConfig](#backendtlsconfig)_ |  false  |  | BackendTLS is the TLS configuration for the Envoy proxy to use when connecting to backends.<br />These settings are applied on backends for which TLS policies are specified. |
 | `ipFamily` | _[IPFamily](#ipfamily)_ |  false  |  | IPFamily specifies the IP family for the EnvoyProxy fleet.<br />This setting only affects the Gateway listener port and does not impact<br />other aspects of the Envoy proxy configuration.<br />If not specified, the system will operate as follows:<br />- It defaults to IPv4 only.<br />- IPv6 and dual-stack environments are not supported in this default configuration.<br />Note: To enable IPv6 or dual-stack functionality, explicit configuration is required. |
 | `preserveRouteOrder` | _boolean_ |  false  |  | PreserveRouteOrder determines if the order of matching for HTTPRoutes is determined by Gateway-API<br />specification (https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule)<br />or preserves the order defined by users in the HTTPRoute's HTTPRouteRule list.<br />Default: False |
-| `disableLuaValidation` | _boolean_ |  false  | false | DisableLuaValidation disables the Lua script validation for Lua EnvoyExtensionPolicies |
+| `luaValidation` | _[LuaValidation](#luavalidation)_ |  false  |  | LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies<br />Default: Strict |
 
 
 #### EnvoyProxyStatus
@@ -3154,6 +3154,21 @@ _Appears in:_
 | `valueRef` | _[LocalObjectReference](#localobjectreference)_ |  false  |  | ValueRef has the source code specified as a local object reference.<br />Only a reference to ConfigMap is supported.<br />The value of key `lua` in the ConfigMap will be used.<br />If the key is not found, the first value in the ConfigMap will be used. |
 
 
+#### LuaValidation
+
+_Underlying type:_ _string_
+
+
+
+_Appears in:_
+- [EnvoyProxySpec](#envoyproxyspec)
+
+| Value | Description |
+| ----- | ----------- |
+| `Strict` | LuaValidationStrict is the default level and checks for issues during script execution.<br />Recommended if your scripts only use the standard Envoy Lua stream handle API.<br />For supported APIs, see: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter#stream-handle-api<br /> | 
+| `Disabled` | LuaValidationDisabled disables all validation of Lua scripts.<br />Scripts will be accepted and executed without any validation checks.<br />This is not recommended unless your scripts import libraries that are not supported by Lua runtime validation.<br /> | 
+
+
 #### LuaValueType
 
 _Underlying type:_ _string_
diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml
index 2e543f636..14f90f39a 100644
--- a/test/helm/gateway-crds-helm/all.out.yaml
+++ b/test/helm/gateway-crds-helm/all.out.yaml
@@ -24078,11 +24078,6 @@ spec:
                   the number of cpuset threads on the platform.
                 format: int32
                 type: integer
-              disableLuaValidation:
-                default: false
-                description: DisableLuaValidation disables the Lua script validation
-                  for Lua EnvoyExtensionPolicies
-                type: boolean
               extraArgs:
                 description: |-
                   ExtraArgs defines additional command line options that are provided to Envoy.
@@ -24250,6 +24245,14 @@ spec:
                       and the log level is the value. If unspecified, defaults to "default: warn".
                     type: object
                 type: object
+              luaValidation:
+                description: |-
+                  LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies
+                  Default: Strict
+                enum:
+                - Strict
+                - Disabled
+                type: string
               mergeGateways:
                 description: |-
                   MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure.
diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
index fb50314e4..089a2574d 100644
--- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
+++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
@@ -6766,11 +6766,6 @@ spec:
                   the number of cpuset threads on the platform.
                 format: int32
                 type: integer
-              disableLuaValidation:
-                default: false
-                description: DisableLuaValidation disables the Lua script validation
-                  for Lua EnvoyExtensionPolicies
-                type: boolean
               extraArgs:
                 description: |-
                   ExtraArgs defines additional command line options that are provided to Envoy.
@@ -6938,6 +6933,14 @@ spec:
                       and the log level is the value. If unspecified, defaults to "default: warn".
                     type: object
                 type: object
+              luaValidation:
+                description: |-
+                  LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies
+                  Default: Strict
+                enum:
+                - Strict
+                - Disabled
+                type: string
               mergeGateways:
                 description: |-
                   MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure.