# CVE-2026-27590: caddy RCE — Fixed in 2.11.1

🐹 Go

Not in CISA KEV

# CVE-2026-27590 — caddy

CVE-2026-27590 is a Improper Input Validation vulnerability in github.com/caddyserver/caddy/v2. A fix is available for github.com/caddyserver/caddy/v2 — see the affected versions and patch details below.

Caddy: Unicode case-folding length expansion causes incorrect split\_path index (SCRIPT\_NAME/PATH\_INFO confusion) in FastCGI transport

Also known as[GHSA-5r3v-vc8m-m96g](/vulnerability/GHSA-5r3v-vc8m-m96g)GO-2026-4536

Published

Feb 24, 2026

Updated

Oct 28, 2026

Affected

1 pkg

Patched

1 / 1

Exploits

None indexed

Exploitation data as of Nov 8, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

## Exploitation Status

### Proof-of-concept exploit code exists

-   CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
-   CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
-   A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-27590.

## EPSS Exploitation Probability

[via FIRST.org ↗](https://www.first.org/epss/)

0.6%probability of exploitation in next 30 days

Lower Risk0.00%

Lower risk than most CVEs50th percentile — riskier than 50% of all scored CVEsHighest risk

0.00%0.45%0.91%1.36%0.3%0.6%May 26Sep 26Nov 26

Probability of exploitation in the next 30 days, from [FIRST.org EPSS](https://www.first.org/epss/).

## Real-World Exposure

1 pkg affected

🐹`github.com/caddyserver/caddy/v2`

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

## Description

#### Summary

Caddy's FastCGI path splitting logic computes the split index on a lowercased copy of the request path and then uses that byte index to slice the original path. This is unsafe for Unicode because `strings.ToLower()` can change UTF-8 byte length for some characters. As a result, Caddy can derive an incorrect `SCRIPT_NAME`/`SCRIPT_FILENAME` and `PATH_INFO`, potentially causing a request that contains `.php` to execute a different on-disk file than intended (path confusion). In setups where an attacker can control file contents (e.g., upload features), this can lead to unintended PHP execution of non-.php files (potential RCE depending on deployment).

#### Details

The issue is in `github.com/caddyserver/caddy/modules/caddyhttp/fastcgi.Trasnport.splitPos()` (and the subsequent slicing in `buildEnv()`):

```
lowerPath := strings.ToLower(path)
idx := strings.Index(lowerPath, strings.ToLower(split))
return idx + len(split)
```

The returned index is computed in the byte space of lowerPath, but `buildEnv()` applies it to the original path:

-   `docURI = path[:splitPos]`
-   `pathInfo = path[splitPos:]`
-   `scriptName = strings.TrimSuffix(path, fc.pathInfo)`
-   `scriptFilename = caddyhttp.SanitizedPathJoin(fc.documentRoot, fc.scriptName)`

This assumes `lowerPath` and `path` have identical byte lengths and identical byte offsets, which is not true for some Unicode case mappings. Certain characters expand when lowercased (UTF-8 byte length increases), shifting the computed index. This creates a mismatch where `.php` is found in the lowercased string at an offset that does not correspond to the same position in the original string, causing the split point to land later/earlier than intended.

#### PoC

Create a small Go program that reproduces Caddy's `splitPos()` behavior (compute the `.php` split point on a lowercased path, then use that byte index on the original path):

1.  Save this as `poc.go`:

```
package main

import (
	"fmt"
	"strings"
)

func splitPos(path string, split string) int {
	lowerPath := strings.ToLower(path)
	idx := strings.Index(lowerPath, strings.ToLower(split))
	if idx < 0 {
		return -1
	}
	return idx + len(split)
}

func main() {
	// U+023A: Ⱥ (UTF-8: C8 BA). Lowercase is ⱥ (UTF-8: E2 B1 A5), longer in bytes.
	path := "/ȺȺȺȺshell.php.txt.php"
	split := ".php"

	pos := splitPos(path, split)

	fmt.Printf("orig bytes=%d\n", len(path))
	fmt.Printf("lower bytes=%d\n", len(strings.ToLower(path)))
	fmt.Printf("splitPos=%d\n", pos)

	fmt.Printf("orig[:pos]=%q\n", path[:pos])
	fmt.Printf("orig[pos:]=%q\n", path[pos:])

	// Expected split: right after the first ".php" in the original string
	want := strings.Index(path, split) + len(split)
	fmt.Printf("expected splitPos=%d\n", want)
	fmt.Printf("expected orig[:]=%q\n", path[:want])
}
```

1.  Run it:

```
go run poc.go
```

Output on my side:

```
orig bytes=26
lower bytes=30
splitPos=22
orig[:pos]="/ȺȺȺȺshell.php.txt"
orig[pos:]=".php"
expected splitPos=18
expected orig[:]="/ȺȺȺȺshell.php"
```

Expected split is right after the first `.php` (`/ȺȺȺȺshell.php`). Instead, the computed split lands later and cuts the original path after `shell.php.txt`, leaving `.php` as the remainder.

#### Impact

Security boundary bypass/path confusion in script resolution. In typical deployments, `.php` extension boundaries are relied on to decide what is executed by PHP. This bug can cause Caddy/FPM to execute a different file than intended by confusing `SCRIPT_NAME`/`SCRIPT_FILENAME`. If an attacker can place attacker-controlled content into a file that can be resolved as `SCRIPT_FILENAME` (common in web apps with uploads or writable directories), this can lead to unintended PHP execution of non-.php files and potentially remote code execution. Severity depends on deployment and presence of attacker-controlled file writes, but the primitive itself is remotely triggerable via crafted URLs.

This vulnerability was initially reported to FrankenPHP ([https://github.com/php/frankenphp/security/advisories/GHSA-g966-83w7-6w38](https://github.com/php/frankenphp/security/advisories/GHSA-g966-83w7-6w38)) by @AbdrrahimDahmani. The affected code has been copied/adapted from Caddy, which, according to research, is also affected.

The patch is a port of the FrankenPHP patch.

## Affected Packages

1 total 1 fixed

Ecosystem

Package

Vulnerable range

Fix

🐹Go

`github.com/caddyserver/caddy/v2` 

all versions

2.11.1`go get github.com/caddyserver/caddy/v2@v2.11.1`

## Affected Products

1 product · 1 configurations

Application

`caddy`caddyserver

< 2.11.1

range

## Detection & mitigation playbook

Open-source dependency

1.  ### Detect
    
    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/caddyserver/caddy/v2, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
    
2.  ### Fix
    
    Update github.com/caddyserver/caddy/v2 to 2.11.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-27590 is resolved across your whole dependency graph.
    
3.  ### Workarounds
    
    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
    

## Frequently Asked Questions

\### Summary Caddy's FastCGI path splitting logic computes the split index on a lowercased copy of the request path and then uses that byte index to slice the original path. This is unsafe for Unicode because \`strings.ToLower()\` can change UTF-8 byte length for some characters. As a result, Caddy can derive an incorrect \`SCRIPT\_NAME\`/\`SCRIPT\_FILENAME\` and \`PATH\_INFO\`, potentially causing a request that contains \`.php\` to execute a different on-disk file than intended (path confusion). In setups where an attacker can control file contents (e.g., upload features), this can lead to unintended PHP

O3 Security · Impact-Aware SCA

### Is CVE-2026-27590 in your dependencies?

Find it across Go, including transitive dependencies.

[Scan my dependencies](/book-demo) [How O3 SCA works](/impact-aware-sca)