public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Carl George <carlwgeorge@gmail.com>
To: git-commits@fedoraproject.org
Subject: [rpms/caddy] epel10.2: Port to new golang packaging guidelines
Date: Wed, 24 Jun 2026 15:32:05 GMT	[thread overview]
Message-ID: <178231512589.1.5124066313581423591.rpms-caddy-1603919fd45c@fedoraproject.org> (raw)

            A new commit has been pushed.

            Repo   : rpms/caddy
            Branch : epel10.2
            Commit : 1603919fd45cbc752394021a99e387a65098f612
            Author : Carl George <carlwgeorge@gmail.com>
            Date   : 2026-06-22T22:19:50-05:00
            Stats  : +1521/-271 in 14 file(s)
            URL    : https://src.fedoraproject.org/rpms/caddy/c/1603919fd45cbc752394021a99e387a65098f612?branch=epel10.2

            Log:
            Port to new golang packaging guidelines

- Backport upstream fix for CVE-2026-27585
- Backport upstream fix for CVE-2026-27586
- Backport upstream fix for CVE-2026-27587
- Backport upstream fix for CVE-2026-27588
- Backport upstream fix for CVE-2026-27589
- Backport upstream fix for CVE-2026-27590
- Backport upstream fix for CVE-2026-30851
- Backport upstream fix for CVE-2026-30852
- Update vendored github.com/quic-go/quic-go to v0.57.0 for CVE-2025-64702
- Update vendored golang.org/x/crypto to v0.52.0 for CVE-2025-47913, CVE-2026-39828, CVE-2026-39829, and CVE-2026-39830
- Update vendored github.com/smallstep/certificates to v0.30.0 for CVE-2025-44005 and CVE-2026-40097
- Update vendored github.com/go-chi/chi/v5 to v5.2.5 for CVE-2025-69725
- Update vendored github.com/yuin/goldmark/renderer/html to v1.7.17 for CVE-2026-5160

---
diff --git a/.gitignore b/.gitignore
index 7b6357f..7b4269b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-caddy-*.tar.gz
+/caddy-*.tar.gz
+/caddy-*-vendor.tar.bz2

diff --git a/0001-Disable-commands-that-can-alter-the-binary.patch b/0001-Disable-commands-that-can-alter-the-binary.patch
index 73a53c8..8211105 100644
--- a/0001-Disable-commands-that-can-alter-the-binary.patch
+++ b/0001-Disable-commands-that-can-alter-the-binary.patch
@@ -91,6 +91,3 @@ index 085a9d78..f6d01fa3 100644
  	}
  
  	if !reflect.DeepEqual(expectedCommandNames, commandNames) {
--- 
-2.50.1
-

diff --git a/0002-fileserver-Replace-with-in-file-matcher-paths.patch b/0002-fileserver-Replace-with-in-file-matcher-paths.patch
new file mode 100644
index 0000000..0e0f4f6
--- /dev/null
+++ b/0002-fileserver-Replace-with-in-file-matcher-paths.patch
@@ -0,0 +1,47 @@
+From 50e117440c1a0ab07d313bdf074e1c68c168823f Mon Sep 17 00:00:00 2001
+From: Matthew Holt <mholt@users.noreply.github.com>
+Date: Thu, 19 Feb 2026 13:17:19 -0700
+Subject: [PATCH] fileserver: Replace \ with \\ in file matcher paths
+
+Fixes: CVE-2026-27585
+
+(cherry picked from commit a2825c5dd952769f139a16448dc1ca1be61b6058)
+---
+ modules/caddyhttp/fileserver/matcher.go                     | 1 +
+ modules/caddyhttp/fileserver/matcher_test.go                | 6 ++++++
+ "modules/caddyhttp/fileserver/testdata/foodir/secr\\et.txt" | 0
+ 3 files changed, 7 insertions(+)
+ create mode 100644 "modules/caddyhttp/fileserver/testdata/foodir/secr\\et.txt"
+
+diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go
+index fbcd36e0..9fe2774c 100644
+--- a/modules/caddyhttp/fileserver/matcher.go
++++ b/modules/caddyhttp/fileserver/matcher.go
+@@ -720,6 +720,7 @@ var globSafeRepl = strings.NewReplacer(
+ 	"*", "\\*",
+ 	"[", "\\[",
+ 	"?", "\\?",
++	"\\", "\\\\",
+ )
+ 
+ const (
+diff --git a/modules/caddyhttp/fileserver/matcher_test.go b/modules/caddyhttp/fileserver/matcher_test.go
+index f0ec4b39..36483fcd 100644
+--- a/modules/caddyhttp/fileserver/matcher_test.go
++++ b/modules/caddyhttp/fileserver/matcher_test.go
+@@ -115,6 +115,12 @@ func TestFileMatcher(t *testing.T) {
+ 			expectedType: "file",
+ 			matched:      !isWindows,
+ 		},
++		{
++			path:         "/foodir/secr%5Cet.txt",
++			expectedPath: "/foodir/secr\\et.txt",
++			expectedType: "file",
++			matched:      true,
++		},
+ 	} {
+ 		m := &MatchFile{
+ 			fsmap:    &filesystems.FileSystemMap{},
+diff --git "a/modules/caddyhttp/fileserver/testdata/foodir/secr\\et.txt" "b/modules/caddyhttp/fileserver/testdata/foodir/secr\\et.txt"
+new file mode 100644
+index 00000000..e69de29b

diff --git a/0003-caddytls-Return-errors-instead-of-nil-in-client-auth-provisioning-7464.patch b/0003-caddytls-Return-errors-instead-of-nil-in-client-auth-provisioning-7464.patch
new file mode 100644
index 0000000..06e7850
--- /dev/null
+++ b/0003-caddytls-Return-errors-instead-of-nil-in-client-auth-provisioning-7464.patch
@@ -0,0 +1,43 @@
+From 73a6287031f5ea6847f222aa9641ff5b72ba4200 Mon Sep 17 00:00:00 2001
+From: moscowchill <72578879+moscowchill@users.noreply.github.com>
+Date: Thu, 12 Feb 2026 23:42:54 +0800
+Subject: [PATCH] caddytls: Return errors instead of nil in client auth
+ provisioning (#7464)
+
+Two error returns in ClientAuthentication.provision() were
+returning nil instead of the actual error, silently swallowing
+failures when converting PEM files to DER and when provisioning
+the CA pool. This could cause mTLS client authentication to
+silently fall back to the system trust store, accepting any
+client certificate signed by a public CA instead of restricting
+to the configured trust anchors.
+
+Fixes: CVE-2026-27586
+
+(cherry picked from commit d42d39b4bc237c628f9a95363b28044cb7a7fe72)
+---
+ modules/caddytls/connpolicy.go | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go
+index 724271a8..a6aedcc2 100644
+--- a/modules/caddytls/connpolicy.go
++++ b/modules/caddytls/connpolicy.go
+@@ -794,7 +794,7 @@ func (clientauth *ClientAuthentication) provision(ctx caddy.Context) error {
+ 		for _, fpath := range clientauth.TrustedCACertPEMFiles {
+ 			ders, err := convertPEMFilesToDER(fpath)
+ 			if err != nil {
+-				return nil
++				return err
+ 			}
+ 			clientauth.TrustedCACerts = append(clientauth.TrustedCACerts, ders...)
+ 		}
+@@ -807,7 +807,7 @@ func (clientauth *ClientAuthentication) provision(ctx caddy.Context) error {
+ 		}
+ 		err := caPool.Provision(ctx)
+ 		if err != nil {
+-			return nil
++			return err
+ 		}
+ 		clientauth.ca = caPool
+ 	}

diff --git a/0004-caddyhttp-Lowercase-comparison-when-matching-with-escape-sequence.patch b/0004-caddyhttp-Lowercase-comparison-when-matching-with-escape-sequence.patch
new file mode 100644
index 0000000..214a31d
--- /dev/null
+++ b/0004-caddyhttp-Lowercase-comparison-when-matching-with-escape-sequence.patch
@@ -0,0 +1,64 @@
+From 41df2955b0f3222cc01a1f5d0b4dbc08ec39cedb Mon Sep 17 00:00:00 2001
+From: Matthew Holt <mholt@users.noreply.github.com>
+Date: Mon, 9 Feb 2026 09:43:07 -0700
+Subject: [PATCH] caddyhttp: Lowercase comparison when matching with escape
+ sequence
+
+Necessary as otherwise the early-bail in `until =
+strings.IndexByte(remaining, nextCh) ... if until == -1` can cause a
+case-insensitive mismatch
+
+Fixes: CVE-2026-27587
+Co-authored-by: Asim Viladi Oglu Manizada <manizada@users.noreply.github.com>
+
+(cherry picked from commit bd374ca9d72e296c9361aee76924b6540f22f0c0)
+(cherry picked from commit a1081194bfae4e0d8c227ec44aecb95eded55d1e)
+---
+ modules/caddyhttp/matchers.go      |  5 +++--
+ modules/caddyhttp/matchers_test.go | 10 ++++++++++
+ 2 files changed, 13 insertions(+), 2 deletions(-)
+
+diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go
+index 22976cfb..3e6c2932 100644
+--- a/modules/caddyhttp/matchers.go
++++ b/modules/caddyhttp/matchers.go
+@@ -533,6 +533,7 @@ func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
+ }
+ 
+ func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) bool {
++	escapedPath = strings.ToLower(escapedPath)
+ 	// We would just compare the pattern against r.URL.Path,
+ 	// but the pattern contains %, indicating that we should
+ 	// compare at least some part of the path in raw/escaped
+@@ -632,8 +633,8 @@ func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) b
+ 	// we can now treat rawpath globs (%*) as regular globs (*)
+ 	matchPath = strings.ReplaceAll(matchPath, "%*", "*")
+ 
+-	// ignore error here because we can't handle it anyway=
+-	matches, _ := path.Match(matchPath, sb.String())
++	// ignore error here because we can't handle it anyway
++	matches, _ := path.Match(matchPath, strings.ToLower(sb.String()))
+ 	return matches
+ }
+ 
+diff --git a/modules/caddyhttp/matchers_test.go b/modules/caddyhttp/matchers_test.go
+index 068207e8..c002a3e4 100644
+--- a/modules/caddyhttp/matchers_test.go
++++ b/modules/caddyhttp/matchers_test.go
+@@ -412,6 +412,16 @@ func TestPathMatcher(t *testing.T) {
+ 			input:  "/foo%2fbar/baz",
+ 			expect: true,
+ 		},
++		{
++			match:  MatchPath{"/admin%2fpanel"},
++			input:  "/ADMIN%2fpanel",
++			expect: true,
++		},
++		{
++			match:  MatchPath{"/admin%2fpa*el"},
++			input:  "/ADMIN%2fPaAzZLm123NEL",
++			expect: true,
++		},
+ 	} {
+ 		err := tc.match.Provision(caddy.Context{})
+ 		if err == nil && tc.provisionErr {

diff --git a/0005-caddyhttp-Use-case-insensitive-comparison-for-large-Host-lists.patch b/0005-caddyhttp-Use-case-insensitive-comparison-for-large-Host-lists.patch
new file mode 100644
index 0000000..9697690
--- /dev/null
+++ b/0005-caddyhttp-Use-case-insensitive-comparison-for-large-Host-lists.patch
@@ -0,0 +1,63 @@
+From c8f27e75fe97672dffafccab6d7b2b26f02ba9de Mon Sep 17 00:00:00 2001
+From: Matthew Holt <mholt@users.noreply.github.com>
+Date: Mon, 9 Feb 2026 14:18:55 -0700
+Subject: [PATCH] caddyhttp: Use case-insensitive comparison for large Host
+ lists
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Normalize exact hosts at provisioning and reqHost in the fast path so case-different Host variants can’t bypass host-gated routes.
+
+Fixes: CVE-2026-27588
+Co-authored-by: Asim Viladi Oglu Manizada <manizada@users.noreply.github.com>
+
+(cherry picked from commit 1f43e8566b4c1d66a00a138188f0defc5adc6d75)
+(cherry picked from commit eec32a0bb5a11651c6a7b04ce82dc50610f2b27e)
+---
+ modules/caddyhttp/matchers.go | 15 ++++++++++-----
+ 1 file changed, 10 insertions(+), 5 deletions(-)
+
+diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go
+index 3e6c2932..27e5c5ae 100644
+--- a/modules/caddyhttp/matchers.go
++++ b/modules/caddyhttp/matchers.go
+@@ -262,13 +262,17 @@ func (m MatchHost) Provision(_ caddy.Context) error {
+ 		if err != nil {
+ 			return fmt.Errorf("converting hostname '%s' to ASCII: %v", host, err)
+ 		}
+-		if asciiHost != host {
+-			m[i] = asciiHost
+-		}
+ 		normalizedHost := strings.ToLower(asciiHost)
+ 		if firstI, ok := seen[normalizedHost]; ok {
+ 			return fmt.Errorf("host at index %d is repeated at index %d: %s", firstI, i, host)
+ 		}
++		// Normalize exact hosts for standardized comparison in large-list fastpath later on.
++		// Keep wildcards/placeholders untouched.
++		if m.fuzzy(asciiHost) {
++			m[i] = asciiHost
++		} else {
++			m[i] = normalizedHost
++		}
+ 		seen[normalizedHost] = i
+ 	}
+ 
+@@ -312,14 +316,15 @@ func (m MatchHost) MatchWithError(r *http.Request) (bool, error) {
+ 	}
+ 
+ 	if m.large() {
++		reqHostLower := strings.ToLower(reqHost)
+ 		// fast path: locate exact match using binary search (about 100-1000x faster for large lists)
+ 		pos := sort.Search(len(m), func(i int) bool {
+ 			if m.fuzzy(m[i]) {
+ 				return false
+ 			}
+-			return m[i] >= reqHost
++			return m[i] >= reqHostLower
+ 		})
+-		if pos < len(m) && m[pos] == reqHost {
++		if pos < len(m) && m[pos] == reqHostLower {
+ 			return true, nil
+ 		}
+ 	}

diff --git a/0006-admin-Reject-requests-with-Sec-Fetch-Mode-headers.patch b/0006-admin-Reject-requests-with-Sec-Fetch-Mode-headers.patch
new file mode 100644
index 0000000..8b89580
--- /dev/null
+++ b/0006-admin-Reject-requests-with-Sec-Fetch-Mode-headers.patch
@@ -0,0 +1,60 @@
+From 61f8e68a4abad32958d2adf03ba733de23073c43 Mon Sep 17 00:00:00 2001
+From: Matthew Holt <mholt@users.noreply.github.com>
+Date: Thu, 5 Feb 2026 09:39:11 -0700
+Subject: [PATCH] admin: Reject requests with Sec-Fetch-Mode headers
+
+And buggy Origin: null headers.
+
+Resolves a low-risk security report by @1seal.
+
+Fixes: CVE-2026-27589
+
+(cherry picked from commit 42ca010e9d8da35e91edd69e724d3736678b5620)
+---
+ admin.go | 27 ++++++++++++++++++++++++++-
+ 1 file changed, 26 insertions(+), 1 deletion(-)
+
+diff --git a/admin.go b/admin.go
+index e6895c39..7dbfd478 100644
+--- a/admin.go
++++ b/admin.go
+@@ -807,13 +807,38 @@ func (h adminHandler) serveHTTP(w http.ResponseWriter, r *http.Request) {
+ 		}
+ 	}
+ 
++	// common mitigations in browser contexts
+ 	if strings.Contains(r.Header.Get("Upgrade"), "websocket") {
+ 		// I've never been able demonstrate a vulnerability myself, but apparently
+ 		// WebSocket connections originating from browsers aren't subject to CORS
+ 		// restrictions, so we'll just be on the safe side
+-		h.handleError(w, r, fmt.Errorf("websocket connections aren't allowed"))
++		h.handleError(w, r, APIError{
++			HTTPStatus: http.StatusBadRequest,
++			Err:        errors.New("websocket connections aren't allowed"),
++			Message:    "WebSocket connections aren't allowed.",
++		})
+ 		return
+ 	}
++	if strings.Contains(r.Header.Get("Sec-Fetch-Mode"), "no-cors") {
++		// turns out web pages can just disable the same-origin policy (!???!?)
++		// but at least browsers let us know that's the case, holy heck
++		h.handleError(w, r, APIError{
++			HTTPStatus: http.StatusBadRequest,
++			Err:        errors.New("client attempted to make request by disabling same-origin policy using no-cors mode"),
++			Message:    "Disabling same-origin restrictions is not allowed.",
++		})
++		return
++	}
++	if r.Header.Get("Origin") == "null" {
++		// bug in Firefox in certain cross-origin situations (yikes?)
++		// (not strictly a security vuln on its own, but it's red flaggy,
++		// since it seems to manifest in cross-origin contexts)
++		h.handleError(w, r, APIError{
++			HTTPStatus: http.StatusBadRequest,
++			Err:        errors.New("invalid origin 'null'"),
++			Message:    "Buggy browser is sending null Origin header.",
++		})
++	}
+ 
+ 	if h.enforceHost {
+ 		// DNS rebinding mitigation

diff --git a/0007-fix-FastCGI-split-SCRIPT_NAME-PATH_INFO-confusion.patch b/0007-fix-FastCGI-split-SCRIPT_NAME-PATH_INFO-confusion.patch
new file mode 100644
index 0000000..6c342c9
--- /dev/null
+++ b/0007-fix-FastCGI-split-SCRIPT_NAME-PATH_INFO-confusion.patch
@@ -0,0 +1,417 @@
+From c840bfaf4a5f0c16b2a5880a3b95f32d3e83aeba Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= <kevin@dunglas.fr>
+Date: Tue, 10 Feb 2026 19:52:36 +0100
+Subject: [PATCH] fix: FastCGI split SCRIPT_NAME/PATH_INFO confusion
+
+Fixes: CVE-2026-27590
+
+(cherry picked from commit 7c28c0c07ac70a8960a166c7126150a408ba7464)
+---
+ .../caddyhttp/reverseproxy/fastcgi/fastcgi.go |  90 ++++++-
+ .../reverseproxy/fastcgi/fastcgi_test.go      | 246 ++++++++++++++++++
+ 2 files changed, 332 insertions(+), 4 deletions(-)
+ create mode 100644 modules/caddyhttp/reverseproxy/fastcgi/fastcgi_test.go
+
+diff --git a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
+index d451dd38..51d4ebc2 100644
+--- a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
++++ b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
+@@ -16,6 +16,7 @@ package fastcgi
+ 
+ import (
+ 	"crypto/tls"
++	"errors"
+ 	"fmt"
+ 	"net"
+ 	"net/http"
+@@ -23,9 +24,12 @@ import (
+ 	"strconv"
+ 	"strings"
+ 	"time"
++	"unicode/utf8"
+ 
+ 	"go.uber.org/zap"
+ 	"go.uber.org/zap/zapcore"
++	"golang.org/x/text/language"
++	"golang.org/x/text/search"
+ 
+ 	"github.com/caddyserver/caddy/v2"
+ 	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
+@@ -33,7 +37,11 @@ import (
+ 	"github.com/caddyserver/caddy/v2/modules/caddytls"
+ )
+ 
+-var noopLogger = zap.NewNop()
++var (
++	ErrInvalidSplitPath = errors.New("split path contains non-ASCII characters")
++
++	noopLogger = zap.NewNop()
++)
+ 
+ func init() {
+ 	caddy.RegisterModule(Transport{})
+@@ -50,6 +58,9 @@ type Transport struct {
+ 	// actual resource (CGI script) name, and the second piece will be set to
+ 	// PATH_INFO for the CGI script to use.
+ 	//
++	// Split paths can only contain ASCII characters.
++	// Comparison is case-insensitive.
++	//
+ 	// Future enhancements should be careful to avoid CVE-2019-11043,
+ 	// which can be mitigated with use of a try_files-like behavior
+ 	// that 404s if the fastcgi path info is not found.
+@@ -109,6 +120,28 @@ func (t *Transport) Provision(ctx caddy.Context) error {
+ 		t.DialTimeout = caddy.Duration(3 * time.Second)
+ 	}
+ 
++	var b strings.Builder
++
++	for i, split := range t.SplitPath {
++		b.Grow(len(split))
++
++		for j := 0; j < len(split); j++ {
++			c := split[j]
++			if c >= utf8.RuneSelf {
++				return ErrInvalidSplitPath
++			}
++
++			if 'A' <= c && c <= 'Z' {
++				b.WriteByte(c + 'a' - 'A')
++			} else {
++				b.WriteByte(c)
++			}
++		}
++
++		t.SplitPath[i] = b.String()
++		b.Reset()
++	}
++
+ 	return nil
+ }
+ 
+@@ -371,8 +404,15 @@ func (t Transport) buildEnv(r *http.Request) (envVars, error) {
+ 	return env, nil
+ }
+ 
++var splitSearchNonASCII = search.New(language.Und, search.IgnoreCase)
++
+ // splitPos returns the index where path should
+ // be split based on t.SplitPath.
++//
++// example: if splitPath is [".php"]
++// "/path/to/script.php/some/path": ("/path/to/script.php", "/some/path")
++//
++// Adapted from FrankenPHP's code (copyright 2026 Kévin Dunglas, MIT license)
+ func (t Transport) splitPos(path string) int {
+ 	// TODO: from v1...
+ 	// if httpserver.CaseSensitivePath {
+@@ -382,12 +422,54 @@ func (t Transport) splitPos(path string) int {
+ 		return 0
+ 	}
+ 
+-	lowerPath := strings.ToLower(path)
++	pathLen := len(path)
++
++	// We are sure that split strings are all ASCII-only and lower-case because of validation and normalization in Provision().
+ 	for _, split := range t.SplitPath {
+-		if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
+-			return idx + len(split)
++		splitLen := len(split)
++
++		for i := 0; i < pathLen; i++ {
++			if path[i] >= utf8.RuneSelf {
++				if _, end := splitSearchNonASCII.IndexString(path, split); end > -1 {
++					return end
++				}
++
++				break
++			}
++
++			if i+splitLen > pathLen {
++				continue
++			}
++
++			match := true
++			for j := 0; j < splitLen; j++ {
++				c := path[i+j]
++
++				if c >= utf8.RuneSelf {
++					if _, end := splitSearchNonASCII.IndexString(path, split); end > -1 {
++						return end
++					}
++
++					break
++				}
++
++				if 'A' <= c && c <= 'Z' {
++					c += 'a' - 'A'
++				}
++
++				if c != split[j] {
++					match = false
++
++					break
++				}
++			}
++
++			if match {
++				return i + splitLen
++			}
+ 		}
+ 	}
++
+ 	return -1
+ }
+ 
+diff --git a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi_test.go b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi_test.go
+new file mode 100644
+index 00000000..7097ff79
+--- /dev/null
++++ b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi_test.go
+@@ -0,0 +1,246 @@
++package fastcgi
++
++import (
++	"strings"
++	"testing"
++
++	"github.com/stretchr/testify/assert"
++	"github.com/stretchr/testify/require"
++
++	"github.com/caddyserver/caddy/v2"
++)
++
++func TestProvisionSplitPath(t *testing.T) {
++	tests := []struct {
++		name          string
++		splitPath     []string
++		wantErr       error
++		wantSplitPath []string
++	}{
++		{
++			name:          "valid lowercase split path",
++			splitPath:     []string{".php"},
++			wantErr:       nil,
++			wantSplitPath: []string{".php"},
++		},
++		{
++			name:          "valid uppercase split path normalized",
++			splitPath:     []string{".PHP"},
++			wantErr:       nil,
++			wantSplitPath: []string{".php"},
++		},
++		{
++			name:          "valid mixed case split path normalized",
++			splitPath:     []string{".PhP", ".PHTML"},
++			wantErr:       nil,
++			wantSplitPath: []string{".php", ".phtml"},
++		},
++		{
++			name:          "empty split path",
++			splitPath:     []string{},
++			wantErr:       nil,
++			wantSplitPath: []string{},
++		},
++		{
++			name:      "non-ASCII character in split path rejected",
++			splitPath: []string{".php", ".Ⱥphp"},
++			wantErr:   ErrInvalidSplitPath,
++		},
++		{
++			name:      "unicode character in split path rejected",
++			splitPath: []string{".phpⱥ"},
++			wantErr:   ErrInvalidSplitPath,
++		},
++	}
++
++	for _, tt := range tests {
++		t.Run(tt.name, func(t *testing.T) {
++			tr := Transport{SplitPath: tt.splitPath}
++			err := tr.Provision(caddy.Context{})
++
++			if tt.wantErr != nil {
++				require.ErrorIs(t, err, tt.wantErr)
++
++				return
++			}
++
++			require.NoError(t, err)
++			assert.Equal(t, tt.wantSplitPath, tr.SplitPath)
++		})
++	}
++}
++
++func TestSplitPos(t *testing.T) {
++	tests := []struct {
++		name      string
++		path      string
++		splitPath []string
++		wantPos   int
++	}{
++		{
++			name:      "simple php extension",
++			path:      "/path/to/script.php",
++			splitPath: []string{".php"},
++			wantPos:   19,
++		},
++		{
++			name:      "php extension with path info",
++			path:      "/path/to/script.php/some/path",
++			splitPath: []string{".php"},
++			wantPos:   19,
++		},
++		{
++			name:      "case insensitive match",
++			path:      "/path/to/script.PHP",
++			splitPath: []string{".php"},
++			wantPos:   19,
++		},
++		{
++			name:      "mixed case match",
++			path:      "/path/to/script.PhP/info",
++			splitPath: []string{".php"},
++			wantPos:   19,
++		},
++		{
++			name:      "no match",
++			path:      "/path/to/script.txt",
++			splitPath: []string{".php"},
++			wantPos:   -1,
++		},
++		{
++			name:      "empty split path",
++			path:      "/path/to/script.php",
++			splitPath: []string{},
++			wantPos:   0,
++		},
++		{
++			name:      "multiple split paths first match",
++			path:      "/path/to/script.php",
++			splitPath: []string{".php", ".phtml"},
++			wantPos:   19,
++		},
++		{
++			name:      "multiple split paths second match",
++			path:      "/path/to/script.phtml",
++			splitPath: []string{".php", ".phtml"},
++			wantPos:   21,
++		},
++		// Unicode case-folding tests (security fix for GHSA-g966-83w7-6w38)
++		// U+023A (Ⱥ) lowercases to U+2C65 (ⱥ), which has different UTF-8 byte length
++		// Ⱥ: 2 bytes (C8 BA), ⱥ: 3 bytes (E2 B1 A5)
++		{
++			name:      "unicode path with case-folding length expansion",
++			path:      "/ȺȺȺȺshell.php",
++			splitPath: []string{".php"},
++			wantPos:   18, // correct position in original string
++		},
++		{
++			name:      "unicode path with extension after expansion chars",
++			path:      "/ȺȺȺȺshell.php/path/info",
++			splitPath: []string{".php"},
++			wantPos:   18,
++		},
++		{
++			name:      "unicode in filename with multiple php occurrences",
++			path:      "/ȺȺȺȺshell.php.txt.php",
++			splitPath: []string{".php"},
++			wantPos:   18, // should match first .php, not be confused by byte offset shift
++		},
++		{
++			name:      "unicode case insensitive extension",
++			path:      "/ȺȺȺȺshell.PHP",
++			splitPath: []string{".php"},
++			wantPos:   18,
++		},
++		{
++			name:      "unicode in middle of path",
++			path:      "/path/Ⱥtest/script.php",
++			splitPath: []string{".php"},
++			wantPos:   23, // Ⱥ is 2 bytes, so path is 23 bytes total, .php ends at byte 23
++		},
++		{
++			name:      "unicode only in directory not filename",
++			path:      "/Ⱥ/script.php",
++			splitPath: []string{".php"},
++			wantPos:   14,
++		},
++		// Additional Unicode characters that expand when lowercased
++		// U+0130 (İ - Turkish capital I with dot) lowercases to U+0069 + U+0307
++		{
++			name:      "turkish capital I with dot",
++			path:      "/İtest.php",
++			splitPath: []string{".php"},
++			wantPos:   11,
++		},
++		// Ensure standard ASCII still works correctly
++		{
++			name:      "ascii only path with case variation",
++			path:      "/PATH/TO/SCRIPT.PHP/INFO",
++			splitPath: []string{".php"},
++			wantPos:   19,
++		},
++		{
++			name:      "path at root",
++			path:      "/index.php",
++			splitPath: []string{".php"},
++			wantPos:   10,
++		},
++		{
++			name:      "extension in middle of filename",
++			path:      "/test.php.bak",
++			splitPath: []string{".php"},
++			wantPos:   9,
++		},
++	}
++
++	for _, tt := range tests {
++		t.Run(tt.name, func(t *testing.T) {
++			gotPos := Transport{SplitPath: tt.splitPath}.splitPos(tt.path)
++			assert.Equal(t, tt.wantPos, gotPos, "splitPos(%q, %v)", tt.path, tt.splitPath)
++
++			// Verify that the split produces valid substrings
++			if gotPos > 0 && gotPos <= len(tt.path) {
++				scriptName := tt.path[:gotPos]
++				pathInfo := tt.path[gotPos:]
++
++				// The script name should end with one of the split extensions (case-insensitive)
++				hasValidEnding := false
++				for _, split := range tt.splitPath {
++					if strings.HasSuffix(strings.ToLower(scriptName), split) {
++						hasValidEnding = true
++						break
++					}
++				}
++				assert.True(t, hasValidEnding, "script name %q should end with one of %v", scriptName, tt.splitPath)
++
++				// Original path should be reconstructable
++				assert.Equal(t, tt.path, scriptName+pathInfo, "path should be reconstructable from split parts")
++			}
++		})
++	}
++}
++
++// TestSplitPosUnicodeSecurityRegression specifically tests the vulnerability
++// described in GHSA-g966-83w7-6w38 where Unicode case-folding caused
++// incorrect SCRIPT_NAME/PATH_INFO splitting
++func TestSplitPosUnicodeSecurityRegression(t *testing.T) {
++	// U+023A: Ⱥ (UTF-8: C8 BA). Lowercase is ⱥ (UTF-8: E2 B1 A5), longer in bytes.
++	path := "/ȺȺȺȺshell.php.txt.php"
++	split := []string{".php"}
++
++	pos := Transport{SplitPath: split}.splitPos(path)
++
++	// The vulnerable code would return 22 (computed on lowercased string)
++	// The correct code should return 18 (position in original string)
++	expectedPos := strings.Index(path, ".php") + len(".php")
++	assert.Equal(t, expectedPos, pos, "split position should match first .php in original string")
++	assert.Equal(t, 18, pos, "split position should be 18, not 22")
++
++	if pos > 0 && pos <= len(path) {
++		scriptName := path[:pos]
++		pathInfo := path[pos:]
++
++		assert.Equal(t, "/ȺȺȺȺshell.php", scriptName, "script name should be the path up to first .php")
++		assert.Equal(t, ".txt.php", pathInfo, "path info should be the remainder after first .php")
++	}
++}

diff --git a/0008-forward_auth-copy_headers-does-not-strip-client-supplied-identity-headers-7545.patch b/0008-forward_auth-copy_headers-does-not-strip-client-supplied-identity-headers-7545.patch
new file mode 100644
index 0000000..6ccc093
--- /dev/null
+++ b/0008-forward_auth-copy_headers-does-not-strip-client-supplied-identity-headers-7545.patch
@@ -0,0 +1,630 @@
+From 24dd7026ec1a6fec7ffe447f969e1d2dd11d5193 Mon Sep 17 00:00:00 2001
+From: newklei <105632119+NucleiAv@users.noreply.github.com>
+Date: Tue, 3 Mar 2026 23:30:49 -0500
+Subject: [PATCH] forward_auth: `copy_headers` does not strip client-supplied
+ identity headers (#7545)
+
+When using copy_headers in a forward_auth block, client-supplied headers with
+the same names were not being removed before being forwarded to the backend.
+
+This happens because PR #6608 added a MatchNot guard that skips the Set
+operation when the auth service does not return a given header. That guard
+prevents setting headers to empty strings, which is the correct behavior,
+but it also means a client can send X-User-Id: admin in their request and
+if the auth service validates the token without returning X-User-Id, Caddy
+skips the Set and the client value passes through unchanged to the backend.
+
+The fix adds an unconditional delete route for each copy_headers entry,
+placed just before the existing conditional set route. The delete always runs
+regardless of what the auth service returns. The conditional set still only
+runs when the auth service provides that header.
+
+The end result is:
+  - Client-supplied headers are always removed
+  - When the auth service returns the header, the backend gets that value
+  - When the auth service does not return the header, the backend sees nothing
+
+Existing behavior is unchanged for any deployment where the auth service
+returns all of the configured copy_headers entries.
+
+Fixes: GHSA-7r4p-vjf4-gxv4
+Fixes: CVE-2026-30851
+
+(cherry picked from commit 2dbcdefbbee68e7b4a31ac66361a0f4e3bcd2eea)
+---
+ .../forward_auth_authelia.caddyfiletest       |  50 ++++-
+ ...ward_auth_copy_headers_strip.caddyfiletest | 146 +++++++++++++
+ .../forward_auth_rename_headers.caddyfiletest |  62 +++++-
+ caddytest/integration/forwardauth_test.go     | 206 ++++++++++++++++++
+ .../reverseproxy/forwardauth/caddyfile.go     |  18 ++
+ 5 files changed, 480 insertions(+), 2 deletions(-)
+ create mode 100644 caddytest/integration/caddyfile_adapt/forward_auth_copy_headers_strip.caddyfiletest
+ create mode 100644 caddytest/integration/forwardauth_test.go
+
+diff --git a/caddytest/integration/caddyfile_adapt/forward_auth_authelia.caddyfiletest b/caddytest/integration/caddyfile_adapt/forward_auth_authelia.caddyfiletest
+index 240bdc62..831d7d2f 100644
+--- a/caddytest/integration/caddyfile_adapt/forward_auth_authelia.caddyfiletest
++++ b/caddytest/integration/caddyfile_adapt/forward_auth_authelia.caddyfiletest
+@@ -46,6 +46,18 @@ app.example.com {
+ 																		}
+ 																	]
+ 																},
++																{
++																	"handle": [
++																		{
++																			"handler": "headers",
++																			"request": {
++																				"delete": [
++																					"Remote-Email"
++																				]
++																			}
++																		}
++																	]
++																},
+ 																{
+ 																	"handle": [
+ 																		{
+@@ -73,6 +85,18 @@ app.example.com {
+ 																		}
+ 																	]
+ 																},
++																{
++																	"handle": [
++																		{
++																			"handler": "headers",
++																			"request": {
++																				"delete": [
++																					"Remote-Groups"
++																				]
++																			}
++																		}
++																	]
++																},
+ 																{
+ 																	"handle": [
+ 																		{
+@@ -100,6 +124,18 @@ app.example.com {
+ 																		}
+ 																	]
+ 																},
++																{
++																	"handle": [
++																		{
++																			"handler": "headers",
++																			"request": {
++																				"delete": [
++																					"Remote-Name"
++																				]
++																			}
++																		}
++																	]
++																},
+ 																{
+ 																	"handle": [
+ 																		{
+@@ -127,6 +163,18 @@ app.example.com {
+ 																		}
+ 																	]
+ 																},
++																{
++																	"handle": [
++																		{
++																			"handler": "headers",
++																			"request": {
++																				"delete": [
++																					"Remote-User"
++																				]
++																			}
++																		}
++																	]
++																},
+ 																{
+ 																	"handle": [
+ 																		{
+@@ -200,4 +248,4 @@ app.example.com {
+ 			}
+ 		}
+ 	}
+-}
+\ No newline at end of file
++}
+diff --git a/caddytest/integration/caddyfile_adapt/forward_auth_copy_headers_strip.caddyfiletest b/caddytest/integration/caddyfile_adapt/forward_auth_copy_headers_strip.caddyfiletest
+new file mode 100644
+index 00000000..887bef0a
+--- /dev/null
++++ b/caddytest/integration/caddyfile_adapt/forward_auth_copy_headers_strip.caddyfiletest
+@@ -0,0 +1,146 @@
++:8080
++
++forward_auth 127.0.0.1:9091 {
++	uri /
++	copy_headers X-User-Id X-User-Role
++}
++----------
++{
++	"apps": {
++		"http": {
++			"servers": {
++				"srv0": {
++					"listen": [
++						":8080"
++					],
++					"routes": [
++						{
++							"handle": [
++								{
++									"handle_response": [
++										{
++											"match": {
++												"status_code": [
++													2
++												]
++											},
++											"routes": [
++												{
++													"handle": [
++														{
++															"handler": "vars"
++														}
++													]
++												},
++												{
++													"handle": [
++														{
++															"handler": "headers",
++															"request": {
++																"delete": [
++																	"X-User-Id"
++																]
++															}
++														}
++													]
++												},
++												{
++													"handle": [
++														{
++															"handler": "headers",
++															"request": {
++																"set": {
++																	"X-User-Id": [
++																		"{http.reverse_proxy.header.X-User-Id}"
++																	]
++																}
++															}
++														}
++													],
++													"match": [
++														{
++															"not": [
++																{
++																	"vars": {
++																		"{http.reverse_proxy.header.X-User-Id}": [
++																			""
++																		]
++																	}
++																}
++															]
++														}
++													]
++												},
++												{
++													"handle": [
++														{
++															"handler": "headers",
++															"request": {
++																"delete": [
++																	"X-User-Role"
++																]
++															}
++														}
++													]
++												},
++												{
++													"handle": [
++														{
++															"handler": "headers",
++															"request": {
++																"set": {
++																	"X-User-Role": [
++																		"{http.reverse_proxy.header.X-User-Role}"
++																	]
++																}
++															}
++														}
++													],
++													"match": [
++														{
++															"not": [
++																{
++																	"vars": {
++																		"{http.reverse_proxy.header.X-User-Role}": [
++																			""
++																		]
++																	}
++																}
++															]
++														}
++													]
++												}
++											]
++										}
++									],
++									"handler": "reverse_proxy",
++									"headers": {
++										"request": {
++											"set": {
++												"X-Forwarded-Method": [
++													"{http.request.method}"
++												],
++												"X-Forwarded-Uri": [
++													"{http.request.uri}"
++												]
++											}
++										}
++									},
++									"rewrite": {
++										"method": "GET",
++										"uri": "/"
++									},
++									"upstreams": [
++										{
++											"dial": "127.0.0.1:9091"
++										}
++									]
++								}
++							]
++						}
++					]
++				}
++			}
++		}
++	}
++}
+\ No newline at end of file
+diff --git a/caddytest/integration/caddyfile_adapt/forward_auth_rename_headers.caddyfiletest b/caddytest/integration/caddyfile_adapt/forward_auth_rename_headers.caddyfiletest
+index c2be2ed4..5d61e5ff 100644
+--- a/caddytest/integration/caddyfile_adapt/forward_auth_rename_headers.caddyfiletest
++++ b/caddytest/integration/caddyfile_adapt/forward_auth_rename_headers.caddyfiletest
+@@ -35,6 +35,18 @@ forward_auth localhost:9000 {
+ 														}
+ 													]
+ 												},
++												{
++													"handle": [
++														{
++															"handler": "headers",
++															"request": {
++																"delete": [
++																	"1"
++																]
++															}
++														}
++													]
++												},
+ 												{
+ 													"handle": [
+ 														{
+@@ -62,6 +74,18 @@ forward_auth localhost:9000 {
+ 														}
+ 													]
+ 												},
++												{
++													"handle": [
++														{
++															"handler": "headers",
++															"request": {
++																"delete": [
++																	"B"
++																]
++															}
++														}
++													]
++												},
+ 												{
+ 													"handle": [
+ 														{
+@@ -89,6 +113,18 @@ forward_auth localhost:9000 {
+ 														}
+ 													]
+ 												},
++												{
++													"handle": [
++														{
++															"handler": "headers",
++															"request": {
++																"delete": [
++																	"3"
++																]
++															}
++														}
++													]
++												},
+ 												{
+ 													"handle": [
+ 														{
+@@ -116,6 +152,18 @@ forward_auth localhost:9000 {
+ 														}
+ 													]
+ 												},
++												{
++													"handle": [
++														{
++															"handler": "headers",
++															"request": {
++																"delete": [
++																	"D"
++																]
++															}
++														}
++													]
++												},
+ 												{
+ 													"handle": [
+ 														{
+@@ -143,6 +191,18 @@ forward_auth localhost:9000 {
+ 														}
+ 													]
+ 												},
++												{
++													"handle": [
++														{
++															"handler": "headers",
++															"request": {
++																"delete": [
++																	"5"
++																]
++															}
++														}
++													]
++												},
+ 												{
+ 													"handle": [
+ 														{
+@@ -203,4 +263,4 @@ forward_auth localhost:9000 {
+ 			}
+ 		}
+ 	}
+-}
+\ No newline at end of file
++}
+diff --git a/caddytest/integration/forwardauth_test.go b/caddytest/integration/forwardauth_test.go
+new file mode 100644
+index 00000000..d0ecc2be
+--- /dev/null
++++ b/caddytest/integration/forwardauth_test.go
+@@ -0,0 +1,206 @@
++// Copyright 2015 Matthew Holt and The Caddy Authors
++//
++// Licensed under the Apache License, Version 2.0 (the "License");
++// you may not use this file except in compliance with the License.
++// You may obtain a copy of the License at
++//
++//     http://www.apache.org/licenses/LICENSE-2.0
++//
++// Unless required by applicable law or agreed to in writing, software
++// distributed under the License is distributed on an "AS IS" BASIS,
++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++// See the License for the specific language governing permissions and
++// limitations under the License.
++
++package integration
++
++import (
++	"fmt"
++	"net/http"
++	"net/http/httptest"
++	"strings"
++	"sync"
++	"testing"
++
++	"github.com/caddyserver/caddy/v2/caddytest"
++)
++
++// TestForwardAuthCopyHeadersStripsClientHeaders is a regression test for the
++// header injection vulnerability in forward_auth copy_headers.
++//
++// When the auth service returns 200 OK without one of the copy_headers headers,
++// the MatchNot guard skips the Set operation. Before this fix, the original
++// client-supplied header survived unchanged into the backend request, allowing
++// privilege escalation with only a valid (non-privileged) bearer token. After
++// the fix, an unconditional delete route runs first, so the backend always
++// sees an absent header rather than the attacker-supplied value.
++func TestForwardAuthCopyHeadersStripsClientHeaders(t *testing.T) {
++	// Mock auth service: accepts any Bearer token, returns 200 OK with NO
++	// identity headers. This is the stateless JWT validator pattern that
++	// triggers the vulnerability.
++	authSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
++		if strings.HasPrefix(r.Header.Get("Authorization"), "Bearer ") {
++			w.WriteHeader(http.StatusOK)
++			return
++		}
++		w.WriteHeader(http.StatusUnauthorized)
++	}))
++	defer authSrv.Close()
++
++	// Mock backend: records the identity headers it receives. A real application
++	// would use X-User-Id / X-User-Role to make authorization decisions.
++	type received struct{ userID, userRole string }
++	var (
++		mu   sync.Mutex
++		last received
++	)
++	backendSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
++		mu.Lock()
++		last = received{
++			userID:   r.Header.Get("X-User-Id"),
++			userRole: r.Header.Get("X-User-Role"),
++		}
++		mu.Unlock()
++		w.WriteHeader(http.StatusOK)
++		fmt.Fprint(w, "ok")
++	}))
++	defer backendSrv.Close()
++
++	authAddr := strings.TrimPrefix(authSrv.URL, "http://")
++	backendAddr := strings.TrimPrefix(backendSrv.URL, "http://")
++
++	tester := caddytest.NewTester(t)
++	tester.InitServer(fmt.Sprintf(`
++	{
++		skip_install_trust
++		admin localhost:2999
++		http_port 9080
++		https_port 9443
++		grace_period 1ns
++	}
++	http://localhost:9080 {
++		forward_auth %s {
++			uri /
++			copy_headers X-User-Id X-User-Role
++		}
++		reverse_proxy %s
++	}
++	`, authAddr, backendAddr), "caddyfile")
++
++	// Case 1: no token. Auth must still reject the request even when the client
++	// includes identity headers. This confirms the auth check is not bypassed.
++	req, _ := http.NewRequest(http.MethodGet, "http://localhost:9080/", nil)
++	req.Header.Set("X-User-Id", "injected")
++	req.Header.Set("X-User-Role", "injected")
++	resp := tester.AssertResponseCode(req, http.StatusUnauthorized)
++	resp.Body.Close()
++
++	// Case 2: valid token, no injected headers. The backend should see absent
++	// identity headers (the auth service never returns them).
++	req, _ = http.NewRequest(http.MethodGet, "http://localhost:9080/", nil)
++	req.Header.Set("Authorization", "Bearer token123")
++	tester.AssertResponse(req, http.StatusOK, "ok")
++	mu.Lock()
++	gotID, gotRole := last.userID, last.userRole
++	mu.Unlock()
++	if gotID != "" {
++		t.Errorf("baseline: X-User-Id should be absent, got %q", gotID)
++	}
++	if gotRole != "" {
++		t.Errorf("baseline: X-User-Role should be absent, got %q", gotRole)
++	}
++
++	// Case 3 (the security regression): valid token plus forged identity headers.
++	// The fix must strip those values so the backend never sees them.
++	req, _ = http.NewRequest(http.MethodGet, "http://localhost:9080/", nil)
++	req.Header.Set("Authorization", "Bearer token123")
++	req.Header.Set("X-User-Id", "admin")        // forged
++	req.Header.Set("X-User-Role", "superadmin") // forged
++	tester.AssertResponse(req, http.StatusOK, "ok")
++	mu.Lock()
++	gotID, gotRole = last.userID, last.userRole
++	mu.Unlock()
++	if gotID != "" {
++		t.Errorf("injection: X-User-Id must be stripped, got %q", gotID)
++	}
++	if gotRole != "" {
++		t.Errorf("injection: X-User-Role must be stripped, got %q", gotRole)
++	}
++}
++
++// TestForwardAuthCopyHeadersAuthResponseWins verifies that when the auth
++// service does include a copy_headers header in its response, that value
++// is forwarded to the backend and takes precedence over any client-supplied
++// value for the same header.
++func TestForwardAuthCopyHeadersAuthResponseWins(t *testing.T) {
++	const wantUserID = "service-user-42"
++	const wantUserRole = "editor"
++
++	// Auth service: accepts bearer token and sets identity headers.
++	authSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
++		if strings.HasPrefix(r.Header.Get("Authorization"), "Bearer ") {
++			w.Header().Set("X-User-Id", wantUserID)
++			w.Header().Set("X-User-Role", wantUserRole)
++			w.WriteHeader(http.StatusOK)
++			return
++		}
++		w.WriteHeader(http.StatusUnauthorized)
++	}))
++	defer authSrv.Close()
++
++	type received struct{ userID, userRole string }
++	var (
++		mu   sync.Mutex
++		last received
++	)
++	backendSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
++		mu.Lock()
++		last = received{
++			userID:   r.Header.Get("X-User-Id"),
++			userRole: r.Header.Get("X-User-Role"),
++		}
++		mu.Unlock()
++		w.WriteHeader(http.StatusOK)
++		fmt.Fprint(w, "ok")
++	}))
++	defer backendSrv.Close()
++
++	authAddr := strings.TrimPrefix(authSrv.URL, "http://")
++	backendAddr := strings.TrimPrefix(backendSrv.URL, "http://")
++
++	tester := caddytest.NewTester(t)
++	tester.InitServer(fmt.Sprintf(`
++	{
++		skip_install_trust
++		admin localhost:2999
++		http_port 9080
++		https_port 9443
++		grace_period 1ns
++	}
++	http://localhost:9080 {
++		forward_auth %s {
++			uri /
++			copy_headers X-User-Id X-User-Role
++		}
++		reverse_proxy %s
++	}
++	`, authAddr, backendAddr), "caddyfile")
++
++	// The client sends forged headers; the auth service overrides them with
++	// its own values. The backend must receive the auth service values.
++	req, _ := http.NewRequest(http.MethodGet, "http://localhost:9080/", nil)
++	req.Header.Set("Authorization", "Bearer token123")
++	req.Header.Set("X-User-Id", "forged-id")   // must be overwritten
++	req.Header.Set("X-User-Role", "forged-role") // must be overwritten
++	tester.AssertResponse(req, http.StatusOK, "ok")
++
++	mu.Lock()
++	gotID, gotRole := last.userID, last.userRole
++	mu.Unlock()
++	if gotID != wantUserID {
++		t.Errorf("X-User-Id: want %q, got %q", wantUserID, gotID)
++	}
++	if gotRole != wantUserRole {
++		t.Errorf("X-User-Role: want %q, got %q", wantUserRole, gotRole)
++	}
++}
+diff --git a/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go b/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go
+index 347f6dfb..5d4393ac 100644
+--- a/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go
++++ b/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go
+@@ -208,6 +208,24 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
+ 	for _, from := range sortedHeadersToCopy {
+ 		to := http.CanonicalHeaderKey(headersToCopy[from])
+ 		placeholderName := "http.reverse_proxy.header." + http.CanonicalHeaderKey(from)
++
++		// Always delete the client-supplied header before conditionally setting
++		// it from the auth response. Without this, a client that pre-supplies a
++		// header listed in copy_headers can inject arbitrary values when the auth
++		// service does not return that header: the MatchNot guard below would
++		// skip the Set entirely, leaving the original client-controlled value
++		// intact and forwarding it to the backend.
++		copyHeaderRoutes = append(copyHeaderRoutes, caddyhttp.Route{
++			HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
++				&headers.Handler{
++					Request: &headers.HeaderOps{
++						Delete: []string{to},
++					},
++				},
++				"handler", "headers", nil,
++			)},
++		})
++
+ 		handler := &headers.Handler{
+ 			Request: &headers.HeaderOps{
+ 				Set: http.Header{

diff --git a/0009-Only-apply-repl.ReplaceAll-on-values-from-literal-variable-names.patch b/0009-Only-apply-repl.ReplaceAll-on-values-from-literal-variable-names.patch
new file mode 100644
index 0000000..8cc75a3
--- /dev/null
+++ b/0009-Only-apply-repl.ReplaceAll-on-values-from-literal-variable-names.patch
@@ -0,0 +1,93 @@
+From 6a0c6e84b17754eb92cfb200a58dca8df228f212 Mon Sep 17 00:00:00 2001
+From: "Sam.An" <56215891+sammiee5311@users.noreply.github.com>
+Date: Thu, 5 Mar 2026 01:08:39 +0900
+Subject: [PATCH] Only apply repl.ReplaceAll() on values from literal variable
+ names
+
+Only apply repl.ReplaceAll() on values from literal variable names
+(e.g. map outputs), not on values resolved from placeholder keys
+(e.g. {http.request.header.*}). The placeholder path already resolves
+the value via repl.Get(), so a second expansion allows user-controlled
+input containing {env.*} or {file.*} to be evaluated, leaking
+environment variables and file contents.
+
+Add regression test to verify placeholder-sourced values are not
+re-expanded.
+
+Fixes: CVE-2026-30852
+
+(cherry picked from commit 7e83775e3adea8b8da72fea3b159207bd71000dd)
+---
+ modules/caddyhttp/matchers_test.go | 11 ++++++++++-
+ modules/caddyhttp/vars.go          | 11 ++++++++++-
+ 2 files changed, 20 insertions(+), 2 deletions(-)
+
+diff --git a/modules/caddyhttp/matchers_test.go b/modules/caddyhttp/matchers_test.go
+index c002a3e4..e91cb44e 100644
+--- a/modules/caddyhttp/matchers_test.go
++++ b/modules/caddyhttp/matchers_test.go
+@@ -967,6 +967,7 @@ func TestVarREMatcher(t *testing.T) {
+ 		desc       string
+ 		match      MatchVarsRE
+ 		input      VarsMiddleware
++		headers    http.Header
+ 		expect     bool
+ 		expectRepl map[string]string
+ 	}{
+@@ -1001,6 +1002,14 @@ func TestVarREMatcher(t *testing.T) {
+ 			input:  VarsMiddleware{"Var1": "var1Value"},
+ 			expect: true,
+ 		},
++		{
++			desc:       "placeholder key value containing braces is not double-expanded",
++			match:      MatchVarsRE{"{http.request.header.X-Input}": &MatchRegexp{Pattern: ".+", Name: "val"}},
++			input:      VarsMiddleware{},
++			headers:    http.Header{"X-Input": []string{"{env.HOME}"}},
++			expect:     true,
++			expectRepl: map[string]string{"val.0": "{env.HOME}"},
++		},
+ 	} {
+ 		tc := tc // capture range value
+ 		t.Run(tc.desc, func(t *testing.T) {
+@@ -1018,7 +1027,7 @@ func TestVarREMatcher(t *testing.T) {
+ 			}
+ 
+ 			// set up the fake request and its Replacer
+-			req := &http.Request{URL: new(url.URL), Method: http.MethodGet}
++			req := &http.Request{URL: new(url.URL), Method: http.MethodGet, Header: tc.headers}
+ 			repl := caddy.NewReplacer()
+ 			ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
+ 			ctx = context.WithValue(ctx, VarsCtxKey, make(map[string]any))
+diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go
+index d01f4a43..f19ca16f 100644
+--- a/modules/caddyhttp/vars.go
++++ b/modules/caddyhttp/vars.go
+@@ -312,10 +312,12 @@ func (m MatchVarsRE) MatchWithError(r *http.Request) (bool, error) {
+ 	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
+ 	for key, val := range m {
+ 		var varValue any
++		var fromPlaceholder bool
+ 		if strings.HasPrefix(key, "{") &&
+ 			strings.HasSuffix(key, "}") &&
+ 			strings.Count(key, "{") == 1 {
+ 			varValue, _ = repl.Get(strings.Trim(key, "{}"))
++			fromPlaceholder = true
+ 		} else {
+ 			varValue = vars[key]
+ 		}
+@@ -334,7 +336,14 @@ func (m MatchVarsRE) MatchWithError(r *http.Request) (bool, error) {
+ 			varStr = fmt.Sprintf("%v", vv)
+ 		}
+ 
+-		valExpanded := repl.ReplaceAll(varStr, "")
++		// Only expand placeholders in values from literal variable names
++		// (e.g. map outputs). Values resolved from placeholder keys are
++		// already final and must not be re-expanded, as that would allow
++		// user input like {env.SECRET} to be evaluated.
++		valExpanded := varStr
++		if !fromPlaceholder {
++			valExpanded = repl.ReplaceAll(varStr, "")
++		}
+ 		if match := val.Match(valExpanded, repl); match {
+ 			return match, nil
+ 		}

diff --git a/caddy.spec b/caddy.spec
index cc55301..7229f9e 100644
--- a/caddy.spec
+++ b/caddy.spec
@@ -1,24 +1,18 @@
-%global goipath         github.com/caddyserver/caddy
+%bcond check 1
 
-%if %{defined el8}
-%global gotest() go test -short -compiler gc -ldflags "${LDFLAGS:-}" %{?**};
-%else
+%global gomodulesmode GO111MODULE=on
 %global gotestflags %{gocompilerflags} -short
-%endif
 
 Name:           caddy
 Version:        2.10.2
 Release:        %autorelease
+ExclusiveArch:  %{golang_arches_future}
 Summary:        Web server with automatic HTTPS
+License:        Apache-2.0 AND BSD-2-Clause AND BSD-2-Clause-Views AND BSD-3-Clause AND CC0-1.0 AND ISC AND MIT AND MPL-2.0
 URL:            https://caddyserver.com
-
-# main source code is Apache-2.0
-# see comments above bundled provides for a breakdown of the rest
-License:        Apache-2.0 AND BSD-1-Clause AND BSD-2-Clause AND BSD-2-Clause-Views AND BSD-3-Clause AND CC0-1.0 AND ISC AND MIT AND MPL-2.0
-
-Source0:        https://%{goipath}/archive/v%{version}/caddy-%{version}.tar.gz
-Source1:        caddy-%{version}-vendor.tar.gz
-Source2:        create-vendor-tarball.sh
+Source0:        https://github.com/caddyserver/caddy/archive/v%{version}/caddy-%{version}.tar.gz
+Source1:        caddy-%{version}-vendor.tar.bz2
+Source2:        go-vendor-tools.toml
 
 # based on reference files upstream
 # https://github.com/caddyserver/dist
@@ -30,14 +24,28 @@ Source30:       poweredby-white.png
 Source31:       poweredby-black.png
 
 # downstream only patch to disable commands that can alter the binary
-Patch1:         0001-Disable-commands-that-can-alter-the-binary.patch
+Patch:          0001-Disable-commands-that-can-alter-the-binary.patch
+
+# CVE-2026-27585
+Patch:          0002-fileserver-Replace-with-in-file-matcher-paths.patch
+# CVE-2026-27586
+Patch:          0003-caddytls-Return-errors-instead-of-nil-in-client-auth-provisioning-7464.patch
+# CVE-2026-27587
+Patch:          0004-caddyhttp-Lowercase-comparison-when-matching-with-escape-sequence.patch
+# CVE-2026-27588
+Patch:          0005-caddyhttp-Use-case-insensitive-comparison-for-large-Host-lists.patch
+# CVE-2026-27589
+Patch:          0006-admin-Reject-requests-with-Sec-Fetch-Mode-headers.patch
+# CVE-2026-27590
+Patch:          0007-fix-FastCGI-split-SCRIPT_NAME-PATH_INFO-confusion.patch
+# CVE-2026-30851
+Patch:          0008-forward_auth-copy_headers-does-not-strip-client-supplied-identity-headers-7545.patch
+# CVE-2026-30852
+Patch:          0009-Only-apply-repl.ReplaceAll-on-values-from-literal-variable-names.patch
 
-%if %{defined el8}
-ExclusiveArch:  %{golang_arches}
-%else
 BuildRequires:  go-rpm-macros
-ExclusiveArch:  %{golang_arches_future}
-%endif
+BuildRequires:  go-vendor-tools
+BuildRequires:  askalono-cli
 
 BuildRequires:  systemd-rpm-macros
 %{?systemd_requires}
@@ -46,190 +54,6 @@ BuildRequires:  systemd-rpm-macros
 Requires:       system-logos-httpd
 Provides:       webserver
 
-# https://github.com/caddyserver/caddy/commit/05acc5131ed5c80acbd28ed8d907b166cd15b72c
-BuildRequires:  golang >= 1.25
-
-# Apache-2.0:
-Provides:       bundled(golang(cel.dev/expr)) = 0.24.0
-Provides:       bundled(golang(cloud.google.com/go/auth)) = 0.16.2
-Provides:       bundled(golang(cloud.google.com/go/auth/oauth2adapt)) = 0.2.8
-Provides:       bundled(golang(cloud.google.com/go/compute/metadata)) = 0.7.0
-Provides:       bundled(golang(github.com/Masterminds/goutils)) = 1.1.1
-Provides:       bundled(golang(github.com/caddyserver/certmagic)) = 0.24.0
-Provides:       bundled(golang(github.com/coreos/go-oidc/v3)) = 3.14.1
-Provides:       bundled(golang(github.com/dgraph-io/badger)) = 1.6.2
-Provides:       bundled(golang(github.com/dgraph-io/badger/v2)) = 2.2007.4
-Provides:       bundled(golang(github.com/go-logr/logr)) = 1.4.3
-Provides:       bundled(golang(github.com/go-logr/stdr)) = 1.2.2
-Provides:       bundled(golang(github.com/google/cel-go)) = 0.26.0
-Provides:       bundled(golang(github.com/google/certificate-transparency-go)) = 74a5dd3
-Provides:       bundled(golang(github.com/google/go-tpm)) = 0.9.5
-Provides:       bundled(golang(github.com/google/go-tspi)) = 0.3.0
-Provides:       bundled(golang(github.com/google/s2a-go)) = 0.1.9
-Provides:       bundled(golang(github.com/googleapis/enterprise-certificate-proxy)) = 0.3.6
-Provides:       bundled(golang(github.com/inconshreveable/mousetrap)) = 1.1.0
-Provides:       bundled(golang(github.com/kylelemons/godebug)) = 1.1.0
-Provides:       bundled(golang(github.com/pires/go-proxyproto)) = 0.8.1
-Provides:       bundled(golang(github.com/prometheus/client_model)) = 0.6.2
-Provides:       bundled(golang(github.com/prometheus/common)) = 0.65.0
-Provides:       bundled(golang(github.com/prometheus/procfs)) = 0.16.1
-Provides:       bundled(golang(github.com/smallstep/go-attestation)) = 2306d5b
-Provides:       bundled(golang(github.com/smallstep/linkedca)) = 0.23.0
-Provides:       bundled(golang(github.com/smallstep/nosql)) = 0.7.0
-Provides:       bundled(golang(github.com/smallstep/truststore)) = 0.13.0
-Provides:       bundled(golang(github.com/spf13/cobra)) = 1.9.1
-Provides:       bundled(golang(go.opentelemetry.io/auto/sdk)) = 1.1.0
-Provides:       bundled(golang(go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp)) = 0.61.0
-Provides:       bundled(golang(go.opentelemetry.io/contrib/propagators/autoprop)) = 0.62.0
-Provides:       bundled(golang(go.opentelemetry.io/contrib/propagators/aws)) = 1.37.0
-Provides:       bundled(golang(go.opentelemetry.io/contrib/propagators/b3)) = 1.37.0
-Provides:       bundled(golang(go.opentelemetry.io/contrib/propagators/jaeger)) = 1.37.0
-Provides:       bundled(golang(go.opentelemetry.io/contrib/propagators/ot)) = 1.37.0
-Provides:       bundled(golang(go.opentelemetry.io/otel)) = 1.37.0
-Provides:       bundled(golang(go.opentelemetry.io/otel/exporters/otlp/otlptrace)) = 1.37.0
-Provides:       bundled(golang(go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc)) = 1.37.0
-Provides:       bundled(golang(go.opentelemetry.io/otel/metric)) = 1.37.0
-Provides:       bundled(golang(go.opentelemetry.io/otel/sdk)) = 1.37.0
-Provides:       bundled(golang(go.opentelemetry.io/otel/trace)) = 1.37.0
-Provides:       bundled(golang(go.opentelemetry.io/proto/otlp)) = 1.7.0
-Provides:       bundled(golang(go.uber.org/mock)) = 0.5.2
-Provides:       bundled(golang(google.golang.org/genproto/googleapis/api)) = 513f239
-Provides:       bundled(golang(google.golang.org/genproto/googleapis/rpc)) = 513f239
-Provides:       bundled(golang(google.golang.org/grpc)) = 1.73.0
-Provides:       bundled(golang(google.golang.org/grpc/cmd/protoc-gen-go-grpc)) = 1.5.1
-
-# BSD-2-Clause:
-Provides:       bundled(golang(github.com/pkg/errors)) = 0.9.1
-Provides:       bundled(golang(github.com/russross/blackfriday/v2)) = 2.1.0
-
-# BSD-3-Clause:
-Provides:       bundled(golang(dario.cat/mergo)) = 1.0.1
-Provides:       bundled(golang(github.com/antlr4-go/antlr/v4)) = 4.13.0
-Provides:       bundled(golang(github.com/cloudflare/circl)) = 1.6.1
-Provides:       bundled(golang(github.com/golang/protobuf)) = 1.5.4
-Provides:       bundled(golang(github.com/golang/snappy)) = 0.0.4
-Provides:       bundled(golang(github.com/google/uuid)) = 1.6.0
-Provides:       bundled(golang(github.com/grpc-ecosystem/grpc-gateway/v2)) = 2.27.1
-Provides:       bundled(golang(github.com/manifoldco/promptui)) = 0.9.0
-Provides:       bundled(golang(github.com/miekg/dns)) = 1.1.63
-Provides:       bundled(golang(github.com/munnerz/goautoneg)) = a7dc8b6
-Provides:       bundled(golang(github.com/pbnjay/memory)) = 7b4eea6
-Provides:       bundled(golang(github.com/pmezard/go-difflib)) = 1.0.0
-Provides:       bundled(golang(github.com/spf13/pflag)) = 1.0.7
-Provides:       bundled(golang(github.com/tailscale/tscert)) = d3f8340
-Provides:       bundled(golang(golang.org/x/crypto)) = 0.40.0
-Provides:       bundled(golang(golang.org/x/crypto/x509roots/fallback)) = 49bf5b8
-Provides:       bundled(golang(golang.org/x/exp)) = 7e4ce0a
-Provides:       bundled(golang(golang.org/x/mod)) = 0.25.0
-Provides:       bundled(golang(golang.org/x/net)) = 0.42.0
-Provides:       bundled(golang(golang.org/x/oauth2)) = 0.30.0
-Provides:       bundled(golang(golang.org/x/sync)) = 0.16.0
-Provides:       bundled(golang(golang.org/x/sys)) = 0.34.0
-Provides:       bundled(golang(golang.org/x/term)) = 0.33.0
-Provides:       bundled(golang(golang.org/x/text)) = 0.27.0
-Provides:       bundled(golang(golang.org/x/time)) = 0.12.0
-Provides:       bundled(golang(golang.org/x/tools)) = 0.34.0
-Provides:       bundled(golang(google.golang.org/api)) = 0.240.0
-Provides:       bundled(golang(google.golang.org/protobuf)) = 1.36.6
-
-# CC0-1.0:
-Provides:       bundled(golang(github.com/zeebo/blake3)) = 0.2.4
-
-# ISC:
-Provides:       bundled(golang(github.com/davecgh/go-spew)) = 1.1.1
-
-# MIT:
-Provides:       bundled(golang(github.com/BurntSushi/toml)) = 1.5.0
-Provides:       bundled(golang(github.com/KimMachineGun/automemlimit)) = 0.7.4
-Provides:       bundled(golang(github.com/Masterminds/semver/v3)) = 3.3.0
-Provides:       bundled(golang(github.com/Masterminds/sprig/v3)) = 3.3.0
-Provides:       bundled(golang(github.com/Microsoft/go-winio)) = 0.6.0
-Provides:       bundled(golang(github.com/alecthomas/chroma/v2)) = 2.20.0
-Provides:       bundled(golang(github.com/aryann/difflib)) = ff5ff6d
-Provides:       bundled(golang(github.com/beorn7/perks)) = 1.0.1
-Provides:       bundled(golang(github.com/caddyserver/zerossl)) = 0.1.3
-Provides:       bundled(golang(github.com/ccoveille/go-safecast)) = 1.6.1
-Provides:       bundled(golang(github.com/cenkalti/backoff/v5)) = 5.0.2
-Provides:       bundled(golang(github.com/cespare/xxhash)) = 1.1.0
-Provides:       bundled(golang(github.com/cespare/xxhash/v2)) = 2.3.0
-Provides:       bundled(golang(github.com/chzyer/readline)) = 1.5.1
-Provides:       bundled(golang(github.com/cpuguy83/go-md2man/v2)) = 2.0.7
-Provides:       bundled(golang(github.com/dgryski/go-farm)) = a6ae236
-Provides:       bundled(golang(github.com/dlclark/regexp2)) = 1.11.5
-Provides:       bundled(golang(github.com/dustin/go-humanize)) = 1.0.1
-Provides:       bundled(golang(github.com/felixge/httpsnoop)) = 1.0.4
-Provides:       bundled(golang(github.com/francoispqt/gojay)) = 1.2.13
-Provides:       bundled(golang(github.com/fxamacker/cbor/v2)) = 2.8.0
-Provides:       bundled(golang(github.com/go-chi/chi/v5)) = 5.2.2
-Provides:       bundled(golang(github.com/huandu/xstrings)) = 1.5.0
-Provides:       bundled(golang(github.com/jackc/pgpassfile)) = 1.0.0
-Provides:       bundled(golang(github.com/jackc/pgservicefile)) = 091c0ba
-Provides:       bundled(golang(github.com/jackc/pgx/v5)) = 5.6.0
-Provides:       bundled(golang(github.com/jackc/puddle/v2)) = 2.2.1
-Provides:       bundled(golang(github.com/klauspost/cpuid/v2)) = 2.3.0
-Provides:       bundled(golang(github.com/libdns/libdns)) = 1.1.0
-Provides:       bundled(golang(github.com/mattn/go-colorable)) = 0.1.13
-Provides:       bundled(golang(github.com/mattn/go-isatty)) = 0.0.20
-Provides:       bundled(golang(github.com/mgutz/ansi)) = d51e80e
-Provides:       bundled(golang(github.com/mitchellh/copystructure)) = 1.2.0
-Provides:       bundled(golang(github.com/mitchellh/go-ps)) = 1.0.0
-Provides:       bundled(golang(github.com/mitchellh/reflectwalk)) = 1.0.2
-Provides:       bundled(golang(github.com/quic-go/qpack)) = 0.5.1
-Provides:       bundled(golang(github.com/quic-go/quic-go)) = 0.54.0
-Provides:       bundled(golang(github.com/rs/xid)) = 1.6.0
-Provides:       bundled(golang(github.com/shopspring/decimal)) = 1.4.0
-Provides:       bundled(golang(github.com/shurcooL/sanitized_anchor_name)) = 1.0.0
-Provides:       bundled(golang(github.com/sirupsen/logrus)) = 1.9.3
-Provides:       bundled(golang(github.com/slackhq/nebula)) = 1.9.5
-Provides:       bundled(golang(github.com/smallstep/pkcs7)) = 0.2.1
-Provides:       bundled(golang(github.com/spf13/cast)) = 1.7.0
-Provides:       bundled(golang(github.com/stoewer/go-strcase)) = 1.2.0
-Provides:       bundled(golang(github.com/stretchr/testify)) = 1.10.0
-Provides:       bundled(golang(github.com/urfave/cli)) = 1.22.17
-Provides:       bundled(golang(github.com/x448/float16)) = 0.8.4
-Provides:       bundled(golang(github.com/yuin/goldmark)) = 1.7.13
-Provides:       bundled(golang(github.com/yuin/goldmark-highlighting/v2)) = 37449ab
-Provides:       bundled(golang(go.etcd.io/bbolt)) = 1.3.10
-Provides:       bundled(golang(go.uber.org/automaxprocs)) = 1.6.0
-Provides:       bundled(golang(go.uber.org/multierr)) = 1.11.0
-Provides:       bundled(golang(go.uber.org/zap)) = 1.27.0
-Provides:       bundled(golang(go.uber.org/zap/exp)) = 0.3.0
-Provides:       bundled(golang(gopkg.in/natefinch/lumberjack.v2)) = 2.2.1
-
-# MPL-2.0:
-Provides:       bundled(golang(github.com/go-sql-driver/mysql)) = 1.8.1
-
-# Apache-2.0 AND BSD-2-Clause:
-Provides:       bundled(golang(go.step.sm/crypto)) = 0.67.0
-Provides:       bundled(golang(github.com/smallstep/cli-utils)) = 0.12.1
-
-# Apache-2.0 AND BSD-3-Clause:
-Provides:       bundled(golang(github.com/go-jose/go-jose/v3)) = 3.0.4
-Provides:       bundled(golang(github.com/go-jose/go-jose/v4)) = 4.0.5
-Provides:       bundled(golang(github.com/googleapis/gax-go/v2)) = 2.14.2
-Provides:       bundled(golang(github.com/mholt/acmez/v3)) = 3.1.2
-Provides:       bundled(golang(github.com/smallstep/certificates)) = 0.28.4
-
-# Apache-2.0 AND MIT:
-Provides:       bundled(golang(github.com/dgraph-io/ristretto)) = 0.2.0
-Provides:       bundled(golang(gopkg.in/yaml.v3)) = 3.0.1
-
-# BSD-1-Clause AND BSD-3-Clause:
-Provides:       bundled(golang(filippo.io/edwards25519)) = 1.1.0
-
-# BSD-2-Clause-Views AND BSD-3-Clause:
-Provides:       bundled(golang(howett.net/plist)) = 1.0.0
-
-# BSD-3-Clause AND MIT:
-Provides:       bundled(golang(github.com/smallstep/scep)) = 8cf1ca4
-
-# CC0-1.0 AND MIT:
-Provides:       bundled(golang(github.com/AndreasBriese/bbloom)) = 46b345b
-
-# Apache-2.0 AND BSD-3-Clause AND MIT:
-Provides:       bundled(golang(github.com/klauspost/compress)) = 1.18.0
-Provides:       bundled(golang(github.com/prometheus/client_golang)) = 1.23.0
-
 
 %description
 Caddy is an extensible server platform that uses TLS by default.
@@ -237,24 +61,21 @@ Caddy is an extensible server platform that uses TLS by default.
 
 %prep
 %autosetup -p 1 -a 1
-mkdir -p src/$(dirname %{goipath})
-ln -s $PWD src/%{goipath}
 
 
 %build
-%if %{defined el8}
-export GO111MODULE=off
-%endif
-export GOPATH=$PWD
-export LDFLAGS="-X %{goipath}.CustomVersion=v%{version}"
-%gobuild -o bin/caddy %{goipath}/cmd/caddy
+export GO_LDFLAGS="-X github.com/caddyserver/caddy/v2.CustomVersion=v%{version}"
+%gobuild -o bin/caddy ./cmd/caddy
 
 
 %install
+# licenses
+%go_vendor_license_install -c %{S:2}
+
 # command
 install -D -p -m 0755 -t %{buildroot}%{_bindir} bin/caddy
 
-# man pages
+# man page
 ./bin/caddy manpage --directory %{buildroot}%{_mandir}/man8
 
 # config
@@ -297,10 +118,13 @@ install -d -m 0755 %{buildroot}%{fish_completions_dir}
 # ensure that the version was embedded correctly
 [[ "$(./bin/caddy version)" == "v%{version}" ]] || exit 1
 
-# run the upstream tests
-export GOPATH=$PWD
-cd src/%{goipath}
-%gotest ./...
+# license validation
+%go_vendor_license_check -c %{S:2}
+
+# upstream tests
+%if %{with check}
+%gocheck2
+%endif
 
 
 %pre
@@ -358,9 +182,7 @@ if [ $1 -eq 0 ]; then
 fi
 
 
-%files
-%license LICENSE
-%doc README.md AUTHORS
+%files -f %{go_vendor_license_filelist}
 %{_bindir}/caddy
 %{_mandir}/man8/caddy*.8*
 %{_datadir}/caddy
@@ -371,13 +193,6 @@ fi
 %config(noreplace) %{_sysconfdir}/caddy/Caddyfile
 %dir %{_sysconfdir}/caddy/Caddyfile.d
 %attr(0750,caddy,caddy) %dir %{_sharedstatedir}/caddy
-%if %{defined el8}
-# this is normally owned by filesystem
-%dir %{_datadir}/zsh
-%dir %{_datadir}/zsh/site-functions
-%dir %{_datadir}/fish
-%dir %{_datadir}/fish/vendor_completions.d
-%endif
 %{bash_completions_dir}/caddy
 %{zsh_completions_dir}/_caddy
 %{fish_completions_dir}/caddy.fish

diff --git a/create-vendor-tarball.sh b/create-vendor-tarball.sh
deleted file mode 100755
index 87bace1..0000000
--- a/create-vendor-tarball.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/bash
-
-tag=$1
-
-if [[ -z $tag ]]; then
-    echo "This script requires the tag as an argument."
-    exit 1
-fi
-
-set -euo pipefail
-
-PKG="caddy"
-REPO="https://github.com/caddyserver/$PKG"
-
-# transform tag into version
-case $tag in
-    *beta*)
-        # v2.0.0-beta.1 -> 2.0.0~beta1
-        temp=${tag#v}
-        version=${temp/-beta./~beta}
-        ;;
-    *rc*)
-        # v2.0.0-rc.1 -> 2.0.0~rc1
-        temp=${tag#v}
-        version=${temp/-rc./~rc}
-        ;;
-    *)
-        # v2.0.0 -> 2.0.0
-        version=${tag#v}
-        ;;
-esac
-
-echo "Using tag: $tag"
-echo "Using version: $version"
-
-git -c advice.detachedHead=false clone --branch $tag --depth 1 $REPO.git $PKG-$version
-pushd $PKG-$version
-GOPROXY='https://proxy.golang.org,direct' go mod vendor
-popd
-tar -C $PKG-$version -czf $PKG-$version-vendor.tar.gz vendor

diff --git a/go-vendor-tools.toml b/go-vendor-tools.toml
new file mode 100644
index 0000000..7cf1636
--- /dev/null
+++ b/go-vendor-tools.toml
@@ -0,0 +1,60 @@
+[archive]
+pre_commands = [
+    # this patch adds some import paths that vendoring must account for
+    ["bash", "-c", "patch -p 1 -i $GVT_CWD/0007-fix-FastCGI-split-SCRIPT_NAME-PATH_INFO-confusion.patch"],
+]
+
+[archive.dependency_overrides]
+# CVE-2025-64702
+"github.com/quic-go/quic-go" = "v0.57.0"
+
+# CVE-2025-47913, CVE-2026-39828, CVE-2026-39829, and CVE-2026-39830
+"golang.org/x/crypto" = "v0.52.0"
+
+# CVE-2025-44005 and CVE-2026-40097
+"github.com/smallstep/certificates" = "v0.30.0"
+
+# CVE-2025-69725
+# v5.2.4 fixes the CVE, but v5.2.5 is needed to work with other overrides
+"github.com/go-chi/chi/v5" = "v5.2.5"
+
+# CVE-2026-5160
+"github.com/yuin/goldmark/renderer/html" = "v1.7.17"
+
+[licensing]
+detector = "askalono"
+
+[[licensing.licenses]]
+path = "vendor/github.com/AndreasBriese/bbloom/LICENSE"
+sha256sum = "6dd1cec8fe966990fbcdd00ca64e05604c66bd1fa1573d862dceaf18cde869e4"
+expression = "CC0-1.0 AND MIT"
+
+[[licensing.licenses]]
+path = "vendor/github.com/cloudflare/circl/LICENSE"
+sha256sum = "b0da1764fe4d13d610b695536fc3f2ebc362482d053a1cd258e36975bc6b97a7"
+expression = "BSD-3-Clause"
+
+[[licensing.licenses]]
+path = "vendor/github.com/dgraph-io/ristretto/z/LICENSE"
+sha256sum = "349fec2711443e0da8f36e388931fdfe2f874fdd4df307b9a379d01bb9764dc8"
+expression = "Apache-2.0 AND MIT"
+
+[[licensing.licenses]]
+path = "vendor/github.com/miekg/dns/COPYRIGHT"
+sha256sum = "66550c0ad5ca7ec1e08683e5f872cc45c741f311eee3b8ee484206ecbf9c740d"
+expression = "BSD-3-Clause"
+
+[[licensing.licenses]]
+path = "vendor/github.com/shopspring/decimal/LICENSE"
+sha256sum = "b92ba0f6ee02f2309628bfdadb123668a17c016e475ba477b857d33470d9d625"
+expression = "MIT"
+
+[[licensing.licenses]]
+path = "vendor/gopkg.in/yaml.v3/LICENSE"
+sha256sum = "d18f6323b71b0b768bb5e9616e36da390fbd39369a81807cca352de4e4e6aa0b"
+expression = "Apache-2.0 AND MIT"
+
+[[licensing.licenses]]
+path = "vendor/howett.net/plist/LICENSE"
+sha256sum = "20d9b7d9372369484ecadae229d3e3efee8029b259e11c9a1b07d2e0a3c82e39"
+expression = "BSD-2-Clause-Views AND BSD-3-Clause"

diff --git a/sources b/sources
index 83d8cbc..1d64240 100644
--- a/sources
+++ b/sources
@@ -1,2 +1,2 @@
 SHA512 (caddy-2.10.2.tar.gz) = 986b11e26cdaa4fbe554cf7b6bb333404fc33190945ef995122518a3fe2fe582a4cf4d2a8ab463e045857650e9deb88123f8d86a93dbdc465635755b00356205
-SHA512 (caddy-2.10.2-vendor.tar.gz) = 8b4b63f5b8ba4b29ec01da55820214f4c63a1c545756815e26d73bdcc3b3ac5aada863ef32ab6db898b90a9501c0ed6e03ed44e53612cdb8d872ab89480d466d
+SHA512 (caddy-2.10.2-vendor.tar.bz2) = d2f64abee70503b70268c1f62497c9368517ea1a7ff073977974ebee0968cd21649fe01bf691bef1e3b0a60f5596af217ebbb50d4263831bb5b7cbf8fa46d6ed

                 reply	other threads:[~2026-06-24 15:32 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178231512589.1.5124066313581423591.rpms-caddy-1603919fd45c@fedoraproject.org \
    --to=carlwgeorge@gmail.com \
    --cc=git-commits@fedoraproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox