From b628b22615be2144d4365d9bf46a7a7b45903bfa Mon Sep 17 00:00:00 2001 From: John Lybeck Date: Wed, 24 Jun 2026 11:26:24 +0000 Subject: [PATCH 1/5] EAI-7043: fix UUID routing by not setting x-ai-eg-model when backend is present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When x-ai-eg-backend is set (from body or as a direct header), x-ai-eg-model is intentionally skipped. This prevents model-name fallback rules in other HTTPRoutes from matching — Envoy Gateway maintains per-HTTPRoute ordering so an older route's model-name rule fires before a newer route's UUID rule when both have equal specificity. Without x-ai-eg-model, only the UUID rule and catch-all rules remain; the UUID rule (2 conditions) beats catch-all (0 conditions) by specificity regardless of creation order. --- .../envoy-extension-policy-model-header.yaml | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/sources/envoy-gateway-config/templates/envoy-extension-policy-model-header.yaml b/sources/envoy-gateway-config/templates/envoy-extension-policy-model-header.yaml index eb4400b9..fc792c15 100644 --- a/sources/envoy-gateway-config/templates/envoy-extension-policy-model-header.yaml +++ b/sources/envoy-gateway-config/templates/envoy-extension-policy-model-header.yaml @@ -1,15 +1,16 @@ {{- if .Values.aiGateway.enabled }} -# Lua filter that sets x-ai-eg-model from the request body before ext_authz runs, on the -# ai-gateway (where all AI traffic terminates and is authorized). +# Lua filter that extracts routing headers from the request body before ext_authz runs, +# on the ai-gateway (where all AI traffic terminates and is authorized). # -# The AI Gateway's ext_proc reads the body and sets x-ai-eg-model, but it is inserted via -# xdsTranslator post-hooks which run after filterOrder is applied. So ext_authz (cluster-auth) -# would run without x-ai-eg-model and fall through to a catch-all route with no auth -# annotations, allowing the request regardless of which model is targeted (EAI-6805). +# When "backend" (AIM UUID) is present in the body or already set as a request header, +# only x-ai-eg-backend is set and the filter returns early. x-ai-eg-model is intentionally +# NOT set in this case so that model-name fallback rules in other HTTPRoutes cannot match — +# the UUID rule wins by specificity over catch-all rules without any cross-route ordering +# dependency (EAI-7043). # -# This Lua filter runs early (ordered before ext_authz via the ai-gateway EnvoyProxy -# filterOrder), reads the model field from the JSON body, and sets x-ai-eg-model so -# cluster-auth can match the correct per-model route and enforce cluster-auth/allowed-group. +# When "backend" is absent, x-ai-eg-model is set from the body for standard +# OpenAI-compatible clients so cluster-auth can match the correct per-model route and +# enforce cluster-auth/allowed-group (EAI-6805). apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyExtensionPolicy metadata: @@ -28,7 +29,17 @@ spec: if body == nil then return end - local model = tostring(body):match('"model"%s*:%s*"([^"]+)"') + local body_str = tostring(body) + local backend = body_str:match('"backend"%s*:%s*"([^"]+)"') + if backend ~= nil then + request_handle:headers():replace("x-ai-eg-backend", backend) + return + end + local existing_backend = request_handle:headers():get("x-ai-eg-backend") + if existing_backend ~= nil and existing_backend ~= "" then + return + end + local model = body_str:match('"model"%s*:%s*"([^"]+)"') if model ~= nil then request_handle:headers():replace("x-ai-eg-model", model) end From a31bc974bd26984bd10c23d63ef035f5fa65f426 Mon Sep 17 00:00:00 2001 From: John Lybeck Date: Wed, 24 Jun 2026 11:39:01 +0000 Subject: [PATCH 2/5] EAI-7043: preserve model attribution in access logs via x-ai-eg-model-log header --- .../templates/ai-gateway-proxy-config.yaml | 2 +- .../envoy-extension-policy-model-header.yaml | 23 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/sources/envoy-gateway-config/templates/ai-gateway-proxy-config.yaml b/sources/envoy-gateway-config/templates/ai-gateway-proxy-config.yaml index ec99311a..56b666d4 100644 --- a/sources/envoy-gateway-config/templates/ai-gateway-proxy-config.yaml +++ b/sources/envoy-gateway-config/templates/ai-gateway-proxy-config.yaml @@ -44,7 +44,7 @@ spec: authority: "%REQ(:AUTHORITY)%" backendHost: "%UPSTREAM_HOST%" backendCluster: "%UPSTREAM_CLUSTER%" - model: "%REQ(x-ai-eg-model)%" + model: "%REQ(x-ai-eg-model-log)%" llm_input_token: "%DYNAMIC_METADATA(io.envoy.ai_gateway:llm_input_token)%" llm_output_token: "%DYNAMIC_METADATA(io.envoy.ai_gateway:llm_output_token)%" llm_total_token: "%DYNAMIC_METADATA(io.envoy.ai_gateway:llm_total_token)%" diff --git a/sources/envoy-gateway-config/templates/envoy-extension-policy-model-header.yaml b/sources/envoy-gateway-config/templates/envoy-extension-policy-model-header.yaml index fc792c15..5c9b3ed8 100644 --- a/sources/envoy-gateway-config/templates/envoy-extension-policy-model-header.yaml +++ b/sources/envoy-gateway-config/templates/envoy-extension-policy-model-header.yaml @@ -1,14 +1,17 @@ {{- if .Values.aiGateway.enabled }} -# Lua filter that extracts routing headers from the request body before ext_authz runs, -# on the ai-gateway (where all AI traffic terminates and is authorized). +# Lua filter that extracts routing and logging headers from the request body before +# ext_authz runs, on the ai-gateway (where all AI traffic terminates and is authorized). +# +# x-ai-eg-model-log is always set from the body's "model" field (when present) so the +# access log can attribute requests to a model regardless of routing path. # # When "backend" (AIM UUID) is present in the body or already set as a request header, -# only x-ai-eg-backend is set and the filter returns early. x-ai-eg-model is intentionally -# NOT set in this case so that model-name fallback rules in other HTTPRoutes cannot match — -# the UUID rule wins by specificity over catch-all rules without any cross-route ordering -# dependency (EAI-7043). +# only x-ai-eg-backend is set for routing and the filter returns early. x-ai-eg-model is +# intentionally NOT set in this case so that model-name fallback rules in other HTTPRoutes +# cannot match — the UUID rule wins by specificity over catch-all rules without any +# cross-route ordering dependency (EAI-7043). # -# When "backend" is absent, x-ai-eg-model is set from the body for standard +# When "backend" is absent, x-ai-eg-model is also set from the body for standard # OpenAI-compatible clients so cluster-auth can match the correct per-model route and # enforce cluster-auth/allowed-group (EAI-6805). apiVersion: gateway.envoyproxy.io/v1alpha1 @@ -31,17 +34,21 @@ spec: end local body_str = tostring(body) local backend = body_str:match('"backend"%s*:%s*"([^"]+)"') + local model = body_str:match('"model"%s*:%s*"([^"]+)"') if backend ~= nil then request_handle:headers():replace("x-ai-eg-backend", backend) + if model ~= nil then + request_handle:headers():replace("x-ai-eg-model-log", model) + end return end local existing_backend = request_handle:headers():get("x-ai-eg-backend") if existing_backend ~= nil and existing_backend ~= "" then return end - local model = body_str:match('"model"%s*:%s*"([^"]+)"') if model ~= nil then request_handle:headers():replace("x-ai-eg-model", model) + request_handle:headers():replace("x-ai-eg-model-log", model) end end {{- end }} From c6f0f738ecb723985d1f26b0cb513c73aa8b7a2d Mon Sep 17 00:00:00 2001 From: John Lybeck Date: Wed, 24 Jun 2026 13:01:46 +0000 Subject: [PATCH 3/5] EAI-7043: switch to 3-condition UUID rule, Lua sets both backend and model headers --- .../templates/ai-gateway-proxy-config.yaml | 2 +- .../envoy-extension-policy-model-header.yaml | 29 ++++--------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/sources/envoy-gateway-config/templates/ai-gateway-proxy-config.yaml b/sources/envoy-gateway-config/templates/ai-gateway-proxy-config.yaml index 56b666d4..ec99311a 100644 --- a/sources/envoy-gateway-config/templates/ai-gateway-proxy-config.yaml +++ b/sources/envoy-gateway-config/templates/ai-gateway-proxy-config.yaml @@ -44,7 +44,7 @@ spec: authority: "%REQ(:AUTHORITY)%" backendHost: "%UPSTREAM_HOST%" backendCluster: "%UPSTREAM_CLUSTER%" - model: "%REQ(x-ai-eg-model-log)%" + model: "%REQ(x-ai-eg-model)%" llm_input_token: "%DYNAMIC_METADATA(io.envoy.ai_gateway:llm_input_token)%" llm_output_token: "%DYNAMIC_METADATA(io.envoy.ai_gateway:llm_output_token)%" llm_total_token: "%DYNAMIC_METADATA(io.envoy.ai_gateway:llm_total_token)%" diff --git a/sources/envoy-gateway-config/templates/envoy-extension-policy-model-header.yaml b/sources/envoy-gateway-config/templates/envoy-extension-policy-model-header.yaml index 5c9b3ed8..02844207 100644 --- a/sources/envoy-gateway-config/templates/envoy-extension-policy-model-header.yaml +++ b/sources/envoy-gateway-config/templates/envoy-extension-policy-model-header.yaml @@ -1,19 +1,11 @@ {{- if .Values.aiGateway.enabled }} -# Lua filter that extracts routing and logging headers from the request body before -# ext_authz runs, on the ai-gateway (where all AI traffic terminates and is authorized). +# Lua filter that extracts routing headers from the request body before ext_authz runs, +# on the ai-gateway (where all AI traffic terminates and is authorized). # -# x-ai-eg-model-log is always set from the body's "model" field (when present) so the -# access log can attribute requests to a model regardless of routing path. -# -# When "backend" (AIM UUID) is present in the body or already set as a request header, -# only x-ai-eg-backend is set for routing and the filter returns early. x-ai-eg-model is -# intentionally NOT set in this case so that model-name fallback rules in other HTTPRoutes -# cannot match — the UUID rule wins by specificity over catch-all rules without any -# cross-route ordering dependency (EAI-7043). -# -# When "backend" is absent, x-ai-eg-model is also set from the body for standard -# OpenAI-compatible clients so cluster-auth can match the correct per-model route and -# enforce cluster-auth/allowed-group (EAI-6805). +# Both x-ai-eg-backend and x-ai-eg-model are set from the request body so that the +# 3-condition UUID rule (Host + x-ai-eg-backend + x-ai-eg-model) wins by specificity +# over any catch-all rules in other HTTPRoutes, regardless of HTTPRoute creation order. +# The model-name fallback rule has been removed — clients must supply a backend UUID. apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyExtensionPolicy metadata: @@ -37,18 +29,9 @@ spec: local model = body_str:match('"model"%s*:%s*"([^"]+)"') if backend ~= nil then request_handle:headers():replace("x-ai-eg-backend", backend) - if model ~= nil then - request_handle:headers():replace("x-ai-eg-model-log", model) - end - return - end - local existing_backend = request_handle:headers():get("x-ai-eg-backend") - if existing_backend ~= nil and existing_backend ~= "" then - return end if model ~= nil then request_handle:headers():replace("x-ai-eg-model", model) - request_handle:headers():replace("x-ai-eg-model-log", model) end end {{- end }} From 810bda7e8808edae919bdb3e42dc0254abfb1e7f Mon Sep 17 00:00:00 2001 From: John Lybeck Date: Wed, 24 Jun 2026 14:30:44 +0000 Subject: [PATCH 4/5] bump cluster-auth to 0.6.0-rc9 --- sources/cluster-auth/0.5.9/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/cluster-auth/0.5.9/values.yaml b/sources/cluster-auth/0.5.9/values.yaml index db9164c8..59b1115f 100644 --- a/sources/cluster-auth/0.5.9/values.yaml +++ b/sources/cluster-auth/0.5.9/values.yaml @@ -3,7 +3,7 @@ replicaCount: 1 image: repository: ghcr.io/silogen/cluster-auth pullPolicy: Always - tag: "0.6.0-rc8" + tag: "0.6.0-rc9" imagePullSecrets: [] nameOverride: "" From 80bea9c0e08fd34f417e5974e06fbaac018c2019 Mon Sep 17 00:00:00 2001 From: John Lybeck Date: Thu, 25 Jun 2026 04:29:34 +0000 Subject: [PATCH 5/5] bump cluster-auth to 0.6.0-rc10 --- sources/cluster-auth/0.5.9/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/cluster-auth/0.5.9/values.yaml b/sources/cluster-auth/0.5.9/values.yaml index 59b1115f..36d70e7a 100644 --- a/sources/cluster-auth/0.5.9/values.yaml +++ b/sources/cluster-auth/0.5.9/values.yaml @@ -3,7 +3,7 @@ replicaCount: 1 image: repository: ghcr.io/silogen/cluster-auth pullPolicy: Always - tag: "0.6.0-rc9" + tag: "0.6.0-rc10" imagePullSecrets: [] nameOverride: ""