Skip to content

Commit 3fa4e32

Browse files
Fix CI lint failures (gosec G710) and bump dependencies (#258)
golangci-lint's latest release added gosec rule G710 (open redirect via taint analysis), which broke the "Testing push" workflow on master and on every dependabot branch. Two of the three findings were legitimate: a request path starting with "//" would produce a scheme-relative Location header, redirecting to another host. Fix by collapsing duplicate leading slashes before redirecting, and suppress the remaining intentional case (operator-configured redirect rules may target external URLs by design). Also: - Bump github.com/yuin/goldmark 1.8.2 -> 1.8.4 - Bump golang.org/x/crypto 0.49.0 -> 0.52.0 - Bump github.com/go-chi/chi/v5 5.2.5 -> 5.3.1 - Bump github.com/klauspost/compress 1.18.6 -> 1.19.0 - Bump github.com/go-playground/validator/v10 10.30.2 -> 10.30.3 - Bump actions/checkout v6 -> v7 in all workflows - Migrate gomodguard -> gomodguard_v2 (deprecation warning) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 982094a commit 3fa4e32

9 files changed

Lines changed: 51 additions & 28 deletions

File tree

.github/workflows/releasing.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111
steps:
1212
- name: Checkout
13-
uses: actions/checkout@v6
13+
uses: actions/checkout@v7
1414
with:
1515
fetch-depth: 0
1616
- name: Set up Go

.github/workflows/testing-commit.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
runs-on: ubuntu-latest
99
steps:
1010
- name: Clone repository
11-
uses: actions/checkout@v6
11+
uses: actions/checkout@v7
1212
with:
1313
fetch-depth: 0
1414
- name: Set up Go

.github/workflows/testing-pull-request.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
steps:
1414
- name: Clone repository
15-
uses: actions/checkout@v6
15+
uses: actions/checkout@v7
1616
with:
1717
fetch-depth: 0
1818
- name: Set up Go

.golangci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ linters:
2626
- gocritic
2727
- gocyclo
2828
- goheader
29-
- gomodguard
29+
- gomodguard_v2
3030
- goprintffuncname
3131
- gosec
3232
- gosmopolitan

go.mod

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ module github.com/patrickdappollonio/http-server
33
go 1.25.0
44

55
require (
6-
github.com/go-chi/chi/v5 v5.2.5
7-
github.com/go-playground/validator/v10 v10.30.2
6+
github.com/go-chi/chi/v5 v5.3.1
7+
github.com/go-playground/validator/v10 v10.30.3
88
github.com/golang-jwt/jwt/v5 v5.3.1
9-
github.com/klauspost/compress v1.18.6
9+
github.com/klauspost/compress v1.19.0
1010
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
1111
github.com/spf13/cobra v1.10.2
1212
github.com/spf13/pflag v1.0.10
1313
github.com/spf13/viper v1.21.0
14-
github.com/yuin/goldmark v1.8.2
14+
github.com/yuin/goldmark v1.8.4
1515
go.abhg.dev/goldmark/mermaid v0.6.0
1616
go.uber.org/automaxprocs v1.6.0
1717
)
@@ -30,8 +30,8 @@ require (
3030
github.com/spf13/cast v1.10.0 // indirect
3131
github.com/subosito/gotenv v1.6.0 // indirect
3232
go.yaml.in/yaml/v3 v3.0.4 // indirect
33-
golang.org/x/crypto v0.49.0 // indirect
34-
golang.org/x/sys v0.42.0 // indirect
35-
golang.org/x/text v0.35.0 // indirect
33+
golang.org/x/crypto v0.52.0 // indirect
34+
golang.org/x/sys v0.45.0 // indirect
35+
golang.org/x/text v0.37.0 // indirect
3636
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
3737
)

go.sum

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
1313
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
1414
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
1515
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
16-
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
17-
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
16+
github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8=
17+
github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
1818
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs=
1919
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
2020
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -23,8 +23,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
2323
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
2424
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
2525
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
26-
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
27-
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
26+
github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
27+
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
2828
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
2929
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
3030
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
@@ -39,8 +39,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
3939
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
4040
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
4141
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
42-
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
43-
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
42+
github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
43+
github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
4444
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
4545
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
4646
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -78,20 +78,20 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
7878
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
7979
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
8080
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
81-
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
82-
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
81+
github.com/yuin/goldmark v1.8.4 h1:oat/nd3U6NeQqFEL3xpEJq7d7c86NI+DbSNGAs4xnjA=
82+
github.com/yuin/goldmark v1.8.4/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
8383
go.abhg.dev/goldmark/mermaid v0.6.0 h1:VvkYFWuOjD6cmSBVJpLAtzpVCGM1h0B7/DQ9IzERwzY=
8484
go.abhg.dev/goldmark/mermaid v0.6.0/go.mod h1:uMc+PcnIH2NVL7zjH10Q1wr7hL3+4n4jUMifhyBYB9I=
8585
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
8686
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
8787
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
8888
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
89-
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
90-
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
91-
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
92-
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
93-
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
94-
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
89+
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
90+
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
91+
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
92+
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
93+
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
94+
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
9595
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9696
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
9797
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

internal/middlewares/redir_indexes.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,15 @@ func RedirectIndexes(statusCode int) func(http.Handler) http.Handler {
1616
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1717
for _, index := range indexes {
1818
if strings.HasSuffix(r.URL.Path, index) {
19-
http.Redirect(w, r, strings.TrimSuffix(r.URL.Path, index), statusCode)
19+
target := strings.TrimSuffix(r.URL.Path, index)
20+
21+
// Collapse duplicate leading slashes so the target can't be
22+
// interpreted by browsers as a scheme-relative URL ("//evil.com/").
23+
if strings.HasPrefix(target, "//") {
24+
target = "/" + strings.TrimLeft(target, "/")
25+
}
26+
27+
http.Redirect(w, r, target, statusCode) //nolint:gosec // target is derived from the request path with leading slashes collapsed, so it is always same-origin
2028
return
2129
}
2230
}

internal/redirects/redirect.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (e *Engine) Middleware(logger io.Writer) func(http.Handler) http.Handler {
5050
}
5151

5252
fmt.Fprintf(logger, "REDIR %q -> %q (status: %d)\n", r.URL.RequestURI(), destination, statusCode)
53-
http.Redirect(w, r, destination, statusCode)
53+
http.Redirect(w, r, destination, statusCode) //nolint:gosec // destinations come from the operator-provided redirects file; redirecting to external URLs is an intended feature
5454
})
5555
}
5656
}
@@ -387,6 +387,13 @@ func (rule *RedirectRule) buildDestination(params map[string]string, requestRawQ
387387
destination = destURL.String()
388388
}
389389

390+
// If the rule targets a local path, make sure placeholder expansion can't
391+
// turn it into a scheme-relative URL ("//evil.com") by collapsing any
392+
// duplicate leading slashes introduced by captured values.
393+
if strings.HasPrefix(rule.To, "/") && !strings.HasPrefix(rule.To, "//") && strings.HasPrefix(destination, "//") {
394+
destination = "/" + strings.TrimLeft(destination, "/")
395+
}
396+
390397
return destination
391398
}
392399

internal/server/handlers.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,15 @@ func (s *Server) showOrRender(w http.ResponseWriter, r *http.Request) {
7373
if info.IsDir() {
7474
// Check if the path doesn't ends in a slash, and redirect accordingly
7575
if !strings.HasSuffix(r.URL.Path, "/") {
76-
http.Redirect(w, r, r.URL.Path+"/", http.StatusMovedPermanently)
76+
target := r.URL.Path + "/"
77+
78+
// Collapse duplicate leading slashes so the target can't be
79+
// interpreted by browsers as a scheme-relative URL ("//evil.com/").
80+
if strings.HasPrefix(target, "//") {
81+
target = "/" + strings.TrimLeft(target, "/")
82+
}
83+
84+
http.Redirect(w, r, target, http.StatusMovedPermanently) //nolint:gosec // target is derived from the request path with leading slashes collapsed, so it is always same-origin
7785
return
7886
}
7987

0 commit comments

Comments
 (0)