This is an automated email from the ASF dual-hosted git repository.

AlinsRan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 43be2b97e fix(env): resolve os.getenv prefix collision for env 
directives (#13595)
43be2b97e is described below

commit 43be2b97ec582960b93d7ecf592bd6f81e73668a
Author: AlinsRan <[email protected]>
AuthorDate: Mon Jun 29 12:38:54 2026 +0800

    fix(env): resolve os.getenv prefix collision for env directives (#13595)
---
 apisix/core/env.lua                  | 37 +++++++++++++++-
 apisix/discovery/kubernetes/core.lua |  7 ++-
 apisix/init.lua                      | 12 +++++
 t/core/env.t                         | 86 ++++++++++++++++++++++++++++++++++++
 4 files changed, 139 insertions(+), 3 deletions(-)

diff --git a/apisix/core/env.lua b/apisix/core/env.lua
index 6a57a70ed..0ddeee120 100644
--- a/apisix/core/env.lua
+++ b/apisix/core/env.lua
@@ -41,6 +41,26 @@ ffi.cdef [[
 ]]
 
 
+-- Build an exact-keyed table from the process environment (`environ`).
+--
+-- This is intentionally used instead of `os.getenv` to sidestep a bug in
+-- lua-resty-core's `os.getenv` shim that is active before any request is
+-- being served (init / init_worker phases). That shim relies on
+-- `ngx_http_lua_ffi_get_conf_env`, which matches an `env NAME=VALUE;`
+-- directive entry against the queried name with a prefix-only comparison
+-- (`ngx_strncmp(name, var.data, var.len)`) and does not require the queried
+-- name to end at `var.len`. As a result, when two `env` directives share a
+-- common prefix (e.g. `KUBERNETES_CLIENT_TOKEN` and
+-- `KUBERNETES_CLIENT_TOKEN_FILE`), the shorter declared name shadows the
+-- longer one. Reading `environ` directly and keying by the substring before
+-- the first `=` avoids the collision entirely. See apache/apisix#13055.
+--
+-- Note on phases: nginx applies `env NAME=VALUE;` directives to the real
+-- `environ` in `ngx_set_environment`, which runs at worker process start
+-- (before `init_worker_by_lua`). Therefore `init()` must be called again in
+-- the worker init phase so that directive-assigned values are captured; at
+-- the `init_by_lua` phase `environ` only contains variables inherited from
+-- the OS, not the directive-assigned ones.
 function _M.init()
     local e = ffi.C.environ
     if not e then
@@ -61,6 +81,21 @@ function _M.init()
 end
 
 
+-- Look up an environment variable by exact name.
+--
+-- Prefer the snapshot built by `init()` (immune to the prefix-collision bug
+-- described above) and only fall back to `os.getenv` for variables that were
+-- set dynamically after startup (e.g. via `core.os.setenv`).
+function _M.get(name)
+    local val = apisix_env_vars[name]
+    if val ~= nil then
+        return val
+    end
+
+    return os.getenv(name)
+end
+
+
 local function parse_env_uri(env_uri)
     -- Avoid the error caused by has_prefix to cause a crash.
     if type(env_uri) ~= "string" then
@@ -93,7 +128,7 @@ function _M.fetch_by_uri(env_uri)
         return nil, err
     end
 
-    local main_value = apisix_env_vars[opts.key] or os.getenv(opts.key)
+    local main_value = _M.get(opts.key)
     if main_value and opts.sub_key ~= "" then
         local vt, err = json.decode(main_value)
         if not vt then
diff --git a/apisix/discovery/kubernetes/core.lua 
b/apisix/discovery/kubernetes/core.lua
index 1ba74408d..aaf0142a8 100644
--- a/apisix/discovery/kubernetes/core.lua
+++ b/apisix/discovery/kubernetes/core.lua
@@ -26,7 +26,6 @@ local unpack        = unpack
 local string        = string
 local tonumber      = tonumber
 local tostring      = tostring
-local os            = os
 local pcall         = pcall
 local setmetatable  = setmetatable
 
@@ -68,7 +67,11 @@ function _M.read_env(key)
             local last = string.byte(key, #key)
             if last == string.byte('}') then
                 local env = string.sub(key, 3, #key - 1)
-                local value = os.getenv(env)
+                -- Use core.env.get for exact-match lookup, avoiding the
+                -- os.getenv prefix-collision bug in the worker init phase
+                -- (e.g. KUBERNETES_CLIENT_TOKEN vs 
KUBERNETES_CLIENT_TOKEN_FILE).
+                -- See apache/apisix#13055.
+                local value = core.env.get(env)
                 if not value then
                     return nil, "not found environment variable " .. env
                 end
diff --git a/apisix/init.lua b/apisix/init.lua
index 311ba4869..32c9dfa08 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -120,6 +120,12 @@ function _M.http_init_worker()
     -- for testing only
     core.log.info("random test in [1, 10000]: ", math.random(1, 10000))
 
+    -- Re-read the environment in the worker phase: nginx applies
+    -- `env NAME=VALUE;` directives to `environ` at worker start (in
+    -- ngx_set_environment), after the init phase. This rebuild ensures
+    -- directive-assigned values are captured with exact keys. See #13055.
+    core.env.init()
+
     require("apisix.events").init_worker()
 
     core.lrucache.init_worker()
@@ -1261,6 +1267,12 @@ function _M.stream_init_worker()
     -- for testing only
     core.log.info("random stream test in [1, 10000]: ", math.random(1, 10000))
 
+    -- The stream subsystem runs in its own Lua VM, so the env snapshot built 
in
+    -- http_init_worker is not visible here. Rebuild it before any consumer 
(e.g.
+    -- kubernetes discovery's read_env) runs, otherwise core.env.get falls 
back to
+    -- the buggy os.getenv shim. See #13055.
+    core.env.init()
+
     core.lrucache.init_worker()
 
     if core.config.init_worker then
diff --git a/t/core/env.t b/t/core/env.t
index 2e14a4397..4168eae4c 100644
--- a/t/core/env.t
+++ b/t/core/env.t
@@ -179,3 +179,89 @@ env ngx_env=apisix-nice;
 GET /t
 --- response_body
 apisix-nice
+
+
+
+=== TEST 10: env directives sharing a common prefix must not collide (#13055)
+--- main_config
+env KUBERNETES_CLIENT_TOKEN=some-token;
+env KUBERNETES_CLIENT_TOKEN_FILE=/path/to/token;
+--- config
+    location /t {
+        content_by_lua_block {
+            local env = require("apisix.core.env")
+            ngx.say(env.fetch_by_uri("$ENV://KUBERNETES_CLIENT_TOKEN"))
+            ngx.say(env.fetch_by_uri("$ENV://KUBERNETES_CLIENT_TOKEN_FILE"))
+        }
+    }
+--- request
+GET /t
+--- response_body
+some-token
+/path/to/token
+
+
+
+=== TEST 11: longer-named directive declared first must not collide (#13055)
+--- main_config
+env KUBERNETES_CLIENT_TOKEN_FILE=/path/to/token;
+env KUBERNETES_CLIENT_TOKEN=some-token;
+--- config
+    location /t {
+        content_by_lua_block {
+            local env = require("apisix.core.env")
+            ngx.say(env.fetch_by_uri("$ENV://KUBERNETES_CLIENT_TOKEN"))
+            ngx.say(env.fetch_by_uri("$ENV://KUBERNETES_CLIENT_TOKEN_FILE"))
+        }
+    }
+--- request
+GET /t
+--- response_body
+some-token
+/path/to/token
+
+
+
+=== TEST 12: core.env.get resolves prefix-colliding directives exactly (#13055)
+--- main_config
+env COLLIDE_PREFIX=prefix-value;
+env COLLIDE_PREFIX_LONGER=longer-value;
+--- config
+    location /t {
+        content_by_lua_block {
+            local env = require("apisix.core.env")
+            ngx.say(env.get("COLLIDE_PREFIX"))
+            ngx.say(env.get("COLLIDE_PREFIX_LONGER"))
+        }
+    }
+--- request
+GET /t
+--- response_body
+prefix-value
+longer-value
+
+
+
+=== TEST 13: resolve prefix-colliding directives during worker init phase 
(#13055)
+--- main_config
+env INIT_COLLIDE_TOKEN=some-token;
+env INIT_COLLIDE_TOKEN_FILE=/path/to/token;
+--- extra_init_worker_by_lua
+    local env = require("apisix.core.env")
+    package.loaded["test_init_env"] = {
+        token = env.get("INIT_COLLIDE_TOKEN"),
+        token_file = env.get("INIT_COLLIDE_TOKEN_FILE"),
+    }
+--- config
+    location /t {
+        content_by_lua_block {
+            local result = package.loaded["test_init_env"]
+            ngx.say(result.token)
+            ngx.say(result.token_file)
+        }
+    }
+--- request
+GET /t
+--- response_body
+some-token
+/path/to/token

Reply via email to