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