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

nic-6443 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 a036f1858 fix(basic-auth): reject an empty consumer password (#13884)
a036f1858 is described below

commit a036f1858b4b84d7f23d408dab33e9618c4c32ec
Author: Mohammad Izzraff Janius 
<[email protected]>
AuthorDate: Thu Aug 27 17:19:38 2026 +0900

    fix(basic-auth): reject an empty consumer password (#13884)
---
 apisix/plugins/basic-auth.lua        |  17 ++-
 docs/en/latest/plugins/basic-auth.md |   2 +-
 docs/zh/latest/plugins/basic-auth.md |   2 +-
 t/admin/plugins.t                    |   4 +-
 t/plugin/basic-auth.t                | 210 ++++++++++++++++++++++++++++++++++-
 5 files changed, 228 insertions(+), 7 deletions(-)

diff --git a/apisix/plugins/basic-auth.lua b/apisix/plugins/basic-auth.lua
index 5d3f46a94..ba22c0ab7 100644
--- a/apisix/plugins/basic-auth.lua
+++ b/apisix/plugins/basic-auth.lua
@@ -43,7 +43,7 @@ local consumer_schema = {
     title = "work with consumer object",
     properties = {
         username = { type = "string" },
-        password = { type = "string" },
+        password = { type = "string", minLength = 1 },
     },
     encrypt_fields = {"password"},
     required = {"username", "password"},
@@ -148,7 +148,20 @@ local function find_consumer(ctx)
         return nil, nil, "Invalid user authorization"
     end
 
-    if cur_consumer.auth_conf.password ~= password then
+    -- the schema rejects an empty password on write and on load, but a secret
+    -- reference ($secret:// or $env://) is resolved after validation and can
+    -- yield "": fail closed so such a consumer never authenticates
+    local expected = cur_consumer.auth_conf.password
+    if expected == "" then
+        err = "empty password configured for consumer: " .. 
cur_consumer.consumer_name
+        if auth_utils.is_running_under_multi_auth(ctx) then
+            return nil, nil, err
+        end
+        core.log.warn(err)
+        return nil, nil, "Invalid user authorization"
+    end
+
+    if expected ~= password then
         return nil, nil, "Invalid user authorization"
     end
 
diff --git a/docs/en/latest/plugins/basic-auth.md 
b/docs/en/latest/plugins/basic-auth.md
index a50e99f20..1e5056f8a 100644
--- a/docs/en/latest/plugins/basic-auth.md
+++ b/docs/en/latest/plugins/basic-auth.md
@@ -48,7 +48,7 @@ For Consumer/Credentials:
 | Name | Type | Required | Default | Valid values | Description |
 |------|------|----------|---------|--------------|-------------|
 | username | string | True | | | Unique basic auth username for a Consumer. |
-| password | string | True | | | Basic auth password for the Consumer. The 
password is encrypted with AES before being stored in etcd. You can also store 
it in an environment variable and reference it using the `env://` prefix, or in 
a secret manager such as HashiCorp Vault's KV secrets engine, and reference it 
using the `secret://` prefix. |
+| password | string | True | | non-empty string | Basic auth password for the 
Consumer. The password is encrypted with AES before being stored in etcd. You 
can also store it in an environment variable and reference it using the 
`env://` prefix, or in a secret manager such as HashiCorp Vault's KV secrets 
engine, and reference it using the `secret://` prefix. |
 
 For Route:
 
diff --git a/docs/zh/latest/plugins/basic-auth.md 
b/docs/zh/latest/plugins/basic-auth.md
index d554b55e2..cc499d926 100644
--- a/docs/zh/latest/plugins/basic-auth.md
+++ b/docs/zh/latest/plugins/basic-auth.md
@@ -48,7 +48,7 @@ import TabItem from '@theme/TabItem';
 | 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
 |------|------|--------|--------|--------|------|
 | username | string | 是 | | | 消费者的唯一基本认证用户名。 |
-| password | string | 是 | | | 消费者的基本认证密码。密码在存储到 etcd 之前会使用 AES 
加密。你也可以将其存储在环境变量中并使用 `env://` 前缀引用,或存储在 HashiCorp Vault 等密钥管理器中并使用 `secret://` 
前缀引用。 |
+| password | string | 是 | | 非空字符串 | 消费者的基本认证密码。密码在存储到 etcd 之前会使用 AES 
加密。你也可以将其存储在环境变量中并使用 `env://` 前缀引用,或存储在 HashiCorp Vault 等密钥管理器中并使用 `secret://` 
前缀引用。 |
 
 Route 端:
 
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index ef70e46d9..b5d96031e 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -264,7 +264,7 @@ plugins:
                 ngx.HTTP_GET,
                 nil,
                 [[
-{"title":"work with consumer 
object","required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string"}},"type":"object"}
+{"title":"work with consumer 
object","required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string","minLength":1}},"type":"object"}
                 ]]
                 )
 
@@ -371,7 +371,7 @@ 
qr/\[\{"name":"multi-auth","priority":2600\},\{"name":"wolf-rbac","priority":255
         }
     }
 --- response_body eval
-qr/\{"encrypt_fields":\["password"\],"properties":\{"password":\{"type":"string"\},"username":\{"type":"string"\}\},"required":\["username","password"\],"title":"work
 with consumer object","type":"object"\}/
+qr/\{"encrypt_fields":\["password"\],"properties":\{"password":\{"minLength":1,"type":"string"\},"username":\{"type":"string"\}\},"required":\["username","password"\],"title":"work
 with consumer object","type":"object"\}/
 
 
 
diff --git a/t/plugin/basic-auth.t b/t/plugin/basic-auth.t
index 2e19a2f6d..8a141859e 100644
--- a/t/plugin/basic-auth.t
+++ b/t/plugin/basic-auth.t
@@ -344,7 +344,7 @@ GET /t
                 ngx.HTTP_GET,
                 nil,
                 [[
-{"title":"work with consumer 
object","required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string"}},"type":"object"}
+{"title":"work with consumer 
object","required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string","minLength":1}},"type":"object"}
                 ]]
                 )
             ngx.status = code
@@ -708,3 +708,211 @@ Authorization: bASiC Zm9vOmJhcg==
 hello world
 --- error_log
 find consumer foo
+
+
+
+=== TEST 31: reject an empty password on the consumer
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/consumers',
+                ngx.HTTP_PUT,
+                [[{
+                    "username": "foo",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "foo",
+                            "password": ""
+                        }
+                    }
+                }]]
+                )
+            ngx.status = code
+            ngx.print(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"invalid plugins configuration: failed to check the configuration 
of plugin basic-auth err: property \"password\" validation failed: string too 
short, expected at least 1, got 0"}
+
+
+
+=== TEST 32: reject an empty password on the credential
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = 
t('/apisix/admin/consumers/foo/credentials/cred_a',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "bar",
+                            "password": ""
+                        }
+                    }
+                }]]
+                )
+            ngx.status = code
+            ngx.print(body)
+        }
+    }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"invalid plugins configuration: failed to check the configuration 
of plugin basic-auth err: property \"password\" validation failed: string too 
short, expected at least 1, got 0"}
+
+
+
+=== TEST 33: store an empty secret into vault
+--- exec
+VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault kv put 
kv/apisix/empty passwd=
+--- response_body
+Success! Data written to: kv/apisix/empty
+
+
+
+=== TEST 34: set basic-auth conf: password uses a secret ref that resolves to 
an empty string
+--- request
+GET /t
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/secrets/vault/test1',
+                ngx.HTTP_PUT,
+                [[{
+                    "uri": "http://127.0.0.1:8200";,
+                    "prefix" : "kv/apisix",
+                    "token" : "root"
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+                return ngx.say(body)
+            end
+
+            code, body = t('/apisix/admin/consumers',
+                ngx.HTTP_PUT,
+                [[{
+                    "username": "foo",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "foo",
+                            "password": "$secret://vault/test1/empty/passwd"
+                        }
+                    }
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+                return ngx.say(body)
+            end
+
+            code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "basic-auth": {}
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 35: verify, empty password on the wire (foo:) is rejected
+--- request
+GET /hello
+--- more_headers
+Authorization: Basic Zm9vOg==
+--- error_code: 401
+
+
+
+=== TEST 36: verify, whitespace-only password on the wire (foo: ) is rejected
+--- request
+GET /hello
+--- more_headers
+Authorization: Basic Zm9vOiA=
+--- error_code: 401
+--- response_body
+{"message":"Invalid user authorization"}
+--- error_log
+empty password configured for consumer: foo
+
+
+
+=== TEST 37: set basic-auth conf: password uses an env ref that resolves to an 
empty string
+--- main_config
+env BASIC_AUTH_EMPTY_PASSWORD=;
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/consumers',
+                ngx.HTTP_PUT,
+                [[{
+                    "username": "foo",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "foo",
+                            "password": "$env://BASIC_AUTH_EMPTY_PASSWORD"
+                        }
+                    }
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 38: verify, empty password on the wire (foo:) is rejected
+--- main_config
+env BASIC_AUTH_EMPTY_PASSWORD=;
+--- request
+GET /hello
+--- more_headers
+Authorization: Basic Zm9vOg==
+--- error_code: 401
+
+
+
+=== TEST 39: verify, whitespace-only password on the wire (foo: ) is rejected
+--- main_config
+env BASIC_AUTH_EMPTY_PASSWORD=;
+--- request
+GET /hello
+--- more_headers
+Authorization: Basic Zm9vOiA=
+--- error_code: 401
+--- response_body
+{"message":"Invalid user authorization"}
+--- error_log
+empty password configured for consumer: foo

Reply via email to