#60530·istio

WasmPlugin in app namespace targeting Service makes waypoint hang on startup — TrafficExtensionsByName rejects cross-ns

Author: shaikatzzCreated Jun 9, 2026Updated Sep 17, 2026
Labelslifecycle/stale

Question

Is it intended behavior that a WasmPlugin in an application namespace, attached to a Service via targetRefs, gets accepted into a waypoint's LDS but then dropped from the corresponding ECDS response — causing the waypoint to never become ready? Or is the asymmetry between TrafficExtensions and TrafficExtensionsByName an unintended bug?

Setup

  • App service my-app lives in namespace app-ns.
  • Service is bound to a waypoint via istio.io/use-waypoint=waypoint and istio.io/use-waypoint-namespace=gw-ns.
  • Waypoint Gateway waypoint lives in gw-ns.

What I tried

WasmPlugin placed in app-ns (the natural home — it's the app team's resource):

yaml
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: my-plugin
  namespace: app-ns
spec:
  url: oci://...
  failStrategy: FAIL_OPEN
  targetRefs:
  - group: ""
    kind: Service
    name: my-app

What happened

  1. New waypoint pods get an LDS update referencing the WasmPlugin's ECDS resource by name (app-ns.my-plugin).
  2. Envoy issues an ECDS subscription for that name.
  3. istiod responds with an empty ECDS payload — pilot_xds_config_size_bytes_sum{type=".../TypedExtensionConfig"} stays at 0 across many pushes.
  4. istiod logs warn model proxy requested invalid WASM configuration: app-ns/my-plugin on every push.
  5. Envoy never marks the listener ready (waiting for the named ECDS resource), startup probe times out at 30s, waypoint pod is SIGKILL'd, crash-loops indefinitely.

Reproduced with both an OCI image requiring imagePullSecret and a public OCI image with no auth — so it's not credential-related. Confirmed via pilot-discovery request GET /debug/config_dump?proxyID=<waypoint> that EcdsConfigDump for the waypoint is empty even though LDS references the resource.

Workaround

Moving the WasmPlugin into gw-ns (the waypoint's namespace) and using targetRefs: { kind: Gateway, name: waypoint } works — ECDS gets a non-empty payload, the waypoint starts cleanly, and the plugin is correctly applied.

The functional outcome is fine, but operationally this forces app teams to deploy a CRD into a platform/gateway namespace they typically don't own (RBAC, ArgoCD app boundaries, ownership). The natural pattern — "the app team owns the WasmPlugin, scopes it to their Service via targetRefs, ships it inside their app namespace" — silently breaks the waypoint instead of being rejected at admission.

Root cause (suspected)

In pilot/pkg/model/push_context.go, two sister-functions disagree on whether waypoint cross-namespace access is allowed.

TrafficExtensions(proxy) (the LDS-build path) does support cross-namespace for waypoints — explicitly comments on it and iterates ServicesForWaypoint:

go
// TrafficExtensions return the TrafficExtensionWrappers of a proxy.
// For most proxy types, we include only the root namespace and same-namespace objects.
// However, waypoints allow cross-namespace access based on attached Service objects.
func (ps *PushContext) TrafficExtensions(proxy *Proxy) ... {
    if proxy.IsWaypointProxy() {
        servicesInfo := ps.ServicesForWaypoint(WaypointKeyForProxy(proxy))
        for _, si := range servicesInfo {
            ...
            listenerInfo = listenerInfo.WithService(svc)
        }
    }
    ...
}

But TrafficExtensionsByName (the ECDS-by-name lookup path) does not have the same exception:

go
func (ps *PushContext) TrafficExtensionsByName(proxy *Proxy, names []types.NamespacedName) []*TrafficExtensionWrapper {
    for _, n := range names {
        if n.Namespace != proxy.ConfigNamespace && n.Namespace != ps.Mesh.RootNamespace {
            log.Warnf(\"proxy requested invalid TrafficExtension configuration: %v\", n)
            continue   // <-- silently drops cross-namespace plugins for waypoints
        }
        ...
    }
}

So the LDS update advertises the resource, but the ECDS response strips it back out. Envoy is left waiting for a config that will never arrive.

Versions

  • istio: 1.27.2 (pilot/pkg/model/push_context.go:2176, identifier WasmPluginsByName)
  • master: same code path still present (post WasmPluginTrafficExtension rename) at the same file/lines — the rename did not change the namespace check.

Possible fixes

  1. Mirror the waypoint cross-namespace logic from TrafficExtensions(proxy) into TrafficExtensionsByName (or factor the namespace decision into a shared helper).
  2. Or, if cross-namespace from the app namespace is not intended to be supported in this direction, reject it at admission with a clear error rather than silently breaking the waypoint at runtime.

Why this matters

The current behavior pushes the WasmPlugin's lifecycle out of the application namespace, which conflicts with the typical ownership model (app teams own their app namespace, not the platform/gateway namespace). And the failure mode is not "the plugin doesn't apply" — it's "the waypoint crash-loops and traffic stops," which is much more disruptive than a no-op.