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 b5b50fa71 fix(oas-validator): verify the TLS certificate of the 
spec_url host (#14146)
b5b50fa71 is described below

commit b5b50fa71fd4fc9e7ef6d3bd3eba793f603a4dd1
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Fri Nov 6 09:54:48 2026 +0800

    fix(oas-validator): verify the TLS certificate of the spec_url host (#14146)
---
 apisix/plugins/oas-validator.lua        |   9 +-
 docs/en/latest/plugins/oas-validator.md |   3 +-
 docs/zh/latest/plugins/oas-validator.md |   3 +-
 t/plugin/oas-validator3.t               | 147 ++++++++++++++++++++++++++++++++
 4 files changed, 154 insertions(+), 8 deletions(-)

diff --git a/apisix/plugins/oas-validator.lua b/apisix/plugins/oas-validator.lua
index dd3ec5ecd..5b20efebe 100644
--- a/apisix/plugins/oas-validator.lua
+++ b/apisix/plugins/oas-validator.lua
@@ -62,7 +62,7 @@ local schema = {
         ssl_verify = {
             description = "whether to verify SSL certificate when fetching 
spec_url",
             type = "boolean",
-            default = false,
+            default = true,
         },
         timeout = {
             description = "HTTP request timeout in milliseconds for fetching 
spec_url",
@@ -156,7 +156,7 @@ local function fetch_and_compile(conf)
 
     local params = {
         method = "GET",
-        ssl_verify = conf.ssl_verify or false,
+        ssl_verify = conf.ssl_verify,
     }
     if conf.spec_url_request_headers then
         params.headers = conf.spec_url_request_headers
@@ -198,6 +198,8 @@ function _M.check_schema(conf, schema_type)
         return false, err
     end
 
+    core.utils.check_tls_bool({"ssl_verify"}, conf, plugin_name)
+
     if conf.spec and not secret.is_secret_ref(conf.spec) then
         local _, decode_err = core.json.decode(conf.spec)
         if decode_err then
@@ -223,8 +225,7 @@ local function get_validator(conf)
     end
 
     local lrucache = get_spec_url_lrucache()
-    local ssl_verify = conf.ssl_verify or false
-    local cache_key = conf.spec_url .. "#ssl_verify=" .. tostring(ssl_verify)
+    local cache_key = conf.spec_url .. "#ssl_verify=" .. 
tostring(conf.ssl_verify)
     if conf.spec_url_request_headers then
         local sorted_keys = {}
         for k in pairs(conf.spec_url_request_headers) do
diff --git a/docs/en/latest/plugins/oas-validator.md 
b/docs/en/latest/plugins/oas-validator.md
index 9cfe9dd5a..bc2ede3ea 100644
--- a/docs/en/latest/plugins/oas-validator.md
+++ b/docs/en/latest/plugins/oas-validator.md
@@ -47,7 +47,7 @@ The OpenAPI spec can be provided as an inline JSON string or 
fetched from a remo
 | spec | string | No* | | | Inline OpenAPI 3.x specification in JSON format. 
Required if `spec_url` is not set. |
 | spec_url | string | No* | | `^https?://` | URL to fetch the OpenAPI 
specification from. Required if `spec` is not set. |
 | spec_url_request_headers | object | No | | | Custom HTTP request headers 
sent when fetching `spec_url`. Useful for authenticated specification 
endpoints. |
-| ssl_verify | boolean | No | false | | Whether to verify the TLS certificate 
when fetching `spec_url`. |
+| ssl_verify | boolean | No | true | | Whether to verify the TLS certificate 
when fetching `spec_url`. Disabling it lets an on-path attacker serve a 
permissive specification and receive any credential configured in 
`spec_url_request_headers`. |
 | timeout | integer | No | 10000 | [1000, 60000] | HTTP request timeout in 
milliseconds for fetching `spec_url`. |
 | verbose_errors | boolean | No | false | | When `true`, include detailed 
validation error messages in the response body. |
 | skip_request_body_validation | boolean | No | false | | Skip validation of 
the request body. |
@@ -147,7 +147,6 @@ curl "http://127.0.0.1:9180/apisix/admin/routes"; -X PUT \
     "plugins": {
       "oas-validator": {
         "spec_url": "https://petstore3.swagger.io/api/v3/openapi.json";,
-        "ssl_verify": false,
         "verbose_errors": true
       }
     },
diff --git a/docs/zh/latest/plugins/oas-validator.md 
b/docs/zh/latest/plugins/oas-validator.md
index 4a05f3985..860ad306a 100644
--- a/docs/zh/latest/plugins/oas-validator.md
+++ b/docs/zh/latest/plugins/oas-validator.md
@@ -46,7 +46,7 @@ OpenAPI 规范可以以内联 JSON 字符串的形式提供,也可以从远程
 | spec | string | 否* | | | 内联 OpenAPI 3.x 规范(JSON 格式)。未设置 `spec_url` 时必填。 |
 | spec_url | string | 否* | | `^https?://` | 获取 OpenAPI 规范的 URL。未设置 `spec` 时必填。 
|
 | spec_url_request_headers | object | 否 | | | 获取 `spec_url` 时附带的自定义 HTTP 
请求头,适用于需要鉴权的规范接口。 |
-| ssl_verify | boolean | 否 | false | | 获取 `spec_url` 时是否校验 TLS 证书。 |
+| ssl_verify | boolean | 否 | true | | 获取 `spec_url` 时是否校验 TLS 
证书。关闭后,中间人可以返回宽松的规范,并拿到 `spec_url_request_headers` 中配置的凭证。 |
 | timeout | integer | 否 | 10000 | [1000, 60000] | 获取 `spec_url` 的 HTTP 
请求超时时间(毫秒)。 |
 | verbose_errors | boolean | 否 | false | | 为 `true` 时,在响应体中返回详细的校验错误信息。 |
 | skip_request_body_validation | boolean | 否 | false | | 跳过请求体校验。 |
@@ -146,7 +146,6 @@ curl "http://127.0.0.1:9180/apisix/admin/routes"; -X PUT \
     "plugins": {
       "oas-validator": {
         "spec_url": "https://petstore3.swagger.io/api/v3/openapi.json";,
-        "ssl_verify": false,
         "verbose_errors": true
       }
     },
diff --git a/t/plugin/oas-validator3.t b/t/plugin/oas-validator3.t
index 8f457e4e1..d17ba6a14 100644
--- a/t/plugin/oas-validator3.t
+++ b/t/plugin/oas-validator3.t
@@ -84,6 +84,21 @@ add_block_preprocessor(sub {
             }
         }
     }
+
+    # serves a certificate that is not in the trust store
+    server {
+        listen 1972 ssl;
+        ssl_certificate     cert/test2.crt;
+        ssl_certificate_key cert/test2.key;
+        location /spec.json {
+            content_by_lua_block {
+                local file = io.open("t/spec/spec.json", "r")
+                local content = file:read("*a")
+                file:close()
+                ngx.print(content)
+            }
+        }
+    }
 _EOC_
 
     $block->set_value("http_config", $http_config);
@@ -1163,3 +1178,135 @@ live: 51
 --- response_body
 fetches: 2
 --- timeout: 15
+
+
+
+=== TEST 31: ssl_verify defaults to true
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.oas-validator")
+            local conf = {spec_url = "https://127.0.0.1:1972/spec.json"}
+            local ok, err = plugin.check_schema(conf)
+            if not ok then
+                ngx.say(err)
+                return
+            end
+            ngx.say("ssl_verify: ", conf.ssl_verify)
+        }
+    }
+--- response_body
+ssl_verify: true
+
+
+
+=== TEST 32: opting out of ssl_verify is logged
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.oas-validator")
+            local ok, err = plugin.check_schema({
+                spec_url = "https://127.0.0.1:1972/spec.json";,
+                ssl_verify = false,
+            })
+            if not ok then
+                ngx.say(err)
+                return
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+Keeping ssl_verify disabled in oas-validator configuration is a security risk
+
+
+
+=== TEST 33: create route with an https spec_url served by an untrusted 
certificate
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin")
+            local code, body = t.test('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "uri": "/*",
+                    "plugins": {
+                        "oas-validator": {
+                            "spec_url": "https://127.0.0.1:1972/spec.json";
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                            "127.0.0.1:1970": 1
+                        }
+                    }
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 34: the spec fetch fails because the certificate is not trusted
+--- request
+GET /api/v3/pet/1
+--- error_code: 500
+--- response_body_like: failed to parse openapi spec
+--- error_log
+failed to fetch spec from URL
+
+
+
+=== TEST 35: ssl_verify false fetches the same spec_url
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin")
+            local code, body = t.test('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "uri": "/*",
+                    "plugins": {
+                        "oas-validator": {
+                            "spec_url": "https://127.0.0.1:1972/spec.json";,
+                            "ssl_verify": false
+                        }
+                    },
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                            "127.0.0.1:1970": 1
+                        }
+                    }
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 36: request validation works with ssl_verify disabled
+--- request
+POST /api/v3/pet
+{"id": 10, "name": "doggie", "category": {"id": 1, "name": "Dogs"}, 
"photoUrls": ["string"], "tags": [{"id": 1, "name": "tag1"}], "status": 
"available"}
+--- more_headers
+Content-Type: application/json
+--- no_error_log
+[error]

Reply via email to