Skip to content

fix(env): fix os.getenv prefix collision bug in init_by_lua phase - #13147

Closed
currenjin wants to merge 2 commits into
apache:masterfrom
currenjin:master
Closed

fix(env): fix os.getenv prefix collision bug in init_by_lua phase#13147
currenjin wants to merge 2 commits into
apache:masterfrom
currenjin:master

Conversation

@currenjin

Copy link
Copy Markdown

Problem

In OpenResty's init_by_lua phase, os.getenv() uses nginx's internal environment mechanism which performs prefix-based matching when env NAME=VALUE directives share a common prefix. This causes os.getenv() to return the shorter-named variable's value when the longer-named one is requested.

Example: with both KUBERNETES_CLIENT_TOKEN and KUBERNETES_CLIENT_TOKEN_FILE defined as env NAME=VALUE; directives, calling os.getenv("KUBERNETES_CLIENT_TOKEN_FILE") returns KUBERNETES_CLIENT_TOKEN's value when the shorter name appears first in the config.

Fixes #13055

Root cause

OpenResty's os.getenv in init_by_lua does not perform exact-match lookup when env NAME=VALUE; directives have a common prefix — the shorter name is matched first. In contrast, reading ffi.C.environ directly with Lua string operations (as _M.init() already does) produces a correctly keyed table.

Fix

  1. apisix/core/env.lua: After _M.init() populates apisix_env_vars from ffi.C.environ, override os.getenv with a wrapper that does exact table lookup. Falls back to the original os.getenv for variables set dynamically after startup (e.g., via core.os.setenv).

  2. apisix/init.lua: Move core.env.init() to be the first call in http_init(), ensuring the override is applied before any other code in init_by_lua can call os.getenv.

Tests

  • TEST 10: Verifies os.getenv returns correct values when shorter-prefix variable is declared first in nginx config.
  • TEST 11: Verifies os.getenv called inside init_by_lua (via extra_init_by_lua) returns correct values for prefix-colliding variables.

Risk / Compatibility

  • os.getenv is monkey-patched globally after _M.init(). Existing callers continue to work — the semantics are identical for non-colliding variable names.
  • Variables set dynamically via core.os.setenv fall back to the original os.getenv, so those are unaffected.
  • The ordering change in http_init is safe: core.env.init() has no dependencies on resolver or id initialization.

In OpenResty's init_by_lua phase, os.getenv() uses nginx's internal
environment mechanism which performs prefix-based matching when env
NAME=VALUE directives share a common prefix. This causes os.getenv()
to return the shorter-named variable's value when the longer-named one
is requested (e.g., KUBERNETES_CLIENT_TOKEN_FILE returns the value of
KUBERNETES_CLIENT_TOKEN).

The fix overrides os.getenv in _M.init() with a wrapper that uses
exact table lookup from apisix_env_vars (built correctly from
ffi.C.environ). Also moves core.env.init() to be the first call in
http_init() to ensure the override is applied before any other code
in init_by_lua can call os.getenv.

Fixes apache#13055
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. bug Something isn't working labels Apr 2, 2026
@Baoyuantop

Copy link
Copy Markdown
Contributor

Hi @currenjin, please fix the lint error

@currenjin

Copy link
Copy Markdown
Author

Fixed the lint error by adding -- luacheck: ignore to suppress the read-only field warning on the os.getenv override.

@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown

This pull request has been marked as stale due to 60 days of inactivity. It will be closed in 4 weeks if no further activity occurs. If you think that's incorrect or this pull request should instead be reviewed, please simply write any comment. Even if closed, you can still revive the PR at any time or discuss it on the dev@apisix.apache.org list. Thank you for your contributions.

@github-actions github-actions Bot added the stale label Jun 6, 2026
@nic-6443

Copy link
Copy Markdown
Member

I looked into the underlying issue (#13055) and I don't think this approach can work, unfortunately. The collision happens in init_by_lua, where lua-resty-core has replaced os.getenv with a shim backed by ngx_http_lua_ffi_get_conf_env — and at that point nginx hasn't applied env directives to the real environ yet (that happens in worker init), so the ffi.C.environ snapshot this PR builds doesn't contain env NAME=VALUE directive vars at all. The lookup misses and falls back to the captured os.getenv, which is the buggy shim itself — I verified this by simulating the override on openresty 1.29.2.4, and it matches the CI failures here.

The real bug is in lua-nginx-module's ngx_http_lua_ffi_get_conf_env: it matches entries with ngx_strncmp(name, var[i].data, var[i].len) without checking that the queried name ends at var[i].len, so the first prefix-colliding entry wins. That needs an exact-length check upstream (plus a runtime patch until it's released); an APISIX-side Lua fix can detect the wrong entry but can't reach the correct one, since the FFI only returns the first match.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Environment variable with common prefix returns incorrect value in init_by_lua

3 participants