Update / correction: the original report said the breakage was architecture-independent. That was wrong. After further testing it reproduces only on arm64 / Apple Silicon; amd64 is unaffected. The underlying empty_array bug is also already fixed in lua-cjson 2.1.0.11. The remaining APISIX issue is that the image ships an older lua-cjson (2.1.0.10) in deps/ that shadows the bundled 2.1.0.11. Details below have been corrected accordingly.
Current Behavior
APISIX 3.17.0 ships lua-cjson 2.1.0.10 in /usr/local/apisix/deps/lib/lua/5.1/cjson.so, which shadows OpenResty's bundled 2.1.0.11 in the worker's lua_package_cpath. lua-cjson 2.1.0.10 predates the fix for openresty/lua-cjson#81 / #82 (commit 92cebdc, "compare light userdata with masked address"), so on arm64 / Apple Silicon cjson.empty_array is not recognized by the encoder and serializes to nothing instead of []:
require("cjson").encode({ x = require("cjson").empty_array })
-- arm64 / Apple Silicon, deps cjson 2.1.0.10: {"x":} <-- malformed, value dropped
-- expected (and on amd64, or with bundled 2.1.0.11): {"x":[]}
This is architecture-specific: it manifests on arm64/Apple Silicon only. On amd64 the same 2.1.0.10 encodes empty_array correctly. Any plugin relying on the cjson.empty_array sentinel produces invalid JSON on arm64, with no error raised.
The new oas-validator plugin's dependency lua-resty-openapi-validator = 1.0.5-1 (new in 3.17.0, absent in 3.16.0) declares an unpinned lua-cjson, which is what causes luarocks to install 2.1.0.10 into deps/.
Expected Behavior
cjson.empty_array encodes to [] on all architectures. The image should not ship a deps/ lua-cjson that is older than (and shadows) the OpenResty-bundled one.
Error Logs
No error is raised — the encoder silently drops the sentinel value, yielding malformed JSON.
Steps to Reproduce
On an arm64 / Apple Silicon host (Docker Desktop, arm64 nodes):
-
docker pull apache/apisix:3.17.0-ubuntu
-
Run the bundled OpenResty against the deps/ cjson (what a worker loads):
docker run --rm --entrypoint /usr/local/openresty/bin/resty \
apache/apisix:3.17.0-ubuntu -e \
'package.cpath="/usr/local/apisix/deps/lib/lua/5.1/?.so;"..package.cpath
local c=require"cjson"
print(c._VERSION, c.encode({x=c.empty_array}))'
# arm64: 2.1.0.10 {"x":} (expected: {"x":[]})
# amd64: 2.1.0.10 {"x":[]} (works)
-
Contrast with the bundled cjson (works on both arches):
docker run --rm --entrypoint /usr/local/openresty/bin/resty \
apache/apisix:3.17.0-ubuntu -e \
'package.cpath="/usr/local/openresty/lualib/?.so;"..package.cpath
local c=require"cjson"
print(c._VERSION, c.encode({x=c.empty_array}))'
# => 2.1.0.11 {"x":[]}
-
Confirm the two cjson copies and which rock pulled in the old one:
docker run --rm --entrypoint sh apache/apisix:3.17.0-ubuntu -c \
'find / -name "cjson*.so" 2>/dev/null'
# /usr/local/apisix/deps/lib/lua/5.1/cjson.so (2.1.0.10, luarocks, via lua-resty-openapi-validator)
# /usr/local/openresty/lualib/cjson.so (2.1.0.11, bundled)
Workaround for plugin authors: setmetatable({}, cjson.array_mt) encodes to [] correctly in both versions and on both arches.
Suggested fix
Ensure the deps/ lua-cjson is not older than the bundled one — e.g. pin lua-cjson >= 2.1.0.11 in the APISIX rockspec (so the transitive dep from lua-resty-openapi-validator cannot downgrade it), or otherwise dedupe to the bundled version / prefer it in lua_package_cpath.
Environment
- APISIX version: 3.17.0 (official
apache/apisix:3.17.0-ubuntu, digest sha256:d7c01c5fc829e2f7375f8f7f2b02d29bb3d85f5069c327e61734c5bb8132a823)
- Operating system: Ubuntu image. Reproduces on arm64 / Apple Silicon only; amd64 unaffected (verified on both via the multi-arch image).
- OpenResty / Nginx version: openresty/1.29.2.4
- LuaRocks: lua-cjson 2.1.0.10 installed in
deps/ (via lua-resty-openapi-validator = 1.0.5-1), shadowing bundled 2.1.0.11.
Current Behavior
APISIX 3.17.0 ships lua-cjson 2.1.0.10 in
/usr/local/apisix/deps/lib/lua/5.1/cjson.so, which shadows OpenResty's bundled 2.1.0.11 in the worker'slua_package_cpath. lua-cjson 2.1.0.10 predates the fix for openresty/lua-cjson#81 / #82 (commit92cebdc, "compare light userdata with masked address"), so on arm64 / Apple Siliconcjson.empty_arrayis not recognized by the encoder and serializes to nothing instead of[]:This is architecture-specific: it manifests on arm64/Apple Silicon only. On amd64 the same 2.1.0.10 encodes
empty_arraycorrectly. Any plugin relying on thecjson.empty_arraysentinel produces invalid JSON on arm64, with no error raised.The new
oas-validatorplugin's dependencylua-resty-openapi-validator = 1.0.5-1(new in 3.17.0, absent in 3.16.0) declares an unpinnedlua-cjson, which is what causes luarocks to install 2.1.0.10 intodeps/.Expected Behavior
cjson.empty_arrayencodes to[]on all architectures. The image should not ship adeps/lua-cjson that is older than (and shadows) the OpenResty-bundled one.Error Logs
No error is raised — the encoder silently drops the sentinel value, yielding malformed JSON.
Steps to Reproduce
On an arm64 / Apple Silicon host (Docker Desktop, arm64 nodes):
docker pull apache/apisix:3.17.0-ubuntuRun the bundled OpenResty against the
deps/cjson (what a worker loads):Contrast with the bundled cjson (works on both arches):
Confirm the two cjson copies and which rock pulled in the old one:
Workaround for plugin authors:
setmetatable({}, cjson.array_mt)encodes to[]correctly in both versions and on both arches.Suggested fix
Ensure the
deps/lua-cjson is not older than the bundled one — e.g. pinlua-cjson >= 2.1.0.11in the APISIX rockspec (so the transitive dep fromlua-resty-openapi-validatorcannot downgrade it), or otherwise dedupe to the bundled version / prefer it inlua_package_cpath.Environment
apache/apisix:3.17.0-ubuntu, digestsha256:d7c01c5fc829e2f7375f8f7f2b02d29bb3d85f5069c327e61734c5bb8132a823)deps/(vialua-resty-openapi-validator = 1.0.5-1), shadowing bundled 2.1.0.11.