Commit ab95b83440

ab95b83440f33e47b7735b71719db154c90e3ff3

parent: ed8f3378b1

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-29 02:15 UTC

httpd: the login return path refuses a leading /\

Ref #297

Layout: unified · split

internal/httpd/flash.go +9 −3
@@ -60,11 +60,17 @@ func (s *Server) reauthNotice(w http.ResponseWriter, notice, path string) bool {
6060
6161const nextCookie = "gitbay_next"
6262
63// localPath reports whether p is a path on this host. Browsers read a
64// leading `/\` like "//", so it is refused too.
65func localPath(p string) bool {
66 return strings.HasPrefix(p, "/") && !strings.HasPrefix(p, "//") && !strings.HasPrefix(p, "/\\")
67}
68
6369// setNext remembers the local path an anonymous visitor asked for, so
6470// the login that follows can return there. Only a GET path is stored:
6571// a POST must not be replayed.
6672func (s *Server) setNext(w http.ResponseWriter, path string) {
67 if !strings.HasPrefix(path, "/") || strings.HasPrefix(path, "//") || len(path) > 300 {
73 if !localPath(path) || len(path) > 300 {
6874 return
6975 }
7076 http.SetCookie(w, &http.Cookie{
@@ -83,7 +89,7 @@ func (s *Server) takeNext(w http.ResponseWriter, r *http.Request) string {
8389 }
8490 http.SetCookie(w, s.clearCookie(nextCookie, http.SameSiteLaxMode))
8591 p, err := url.QueryUnescape(c.Value)
86 if err != nil || !strings.HasPrefix(p, "/") || strings.HasPrefix(p, "//") {
92 if err != nil || !localPath(p) {
8793 return ""
8894 }
8995 return p
@@ -97,7 +103,7 @@ func (s *Server) peekNext(r *http.Request) string {
97103 return ""
98104 }
99105 p, err := url.QueryUnescape(c.Value)
100 if err != nil || !strings.HasPrefix(p, "/") || strings.HasPrefix(p, "//") {
106 if err != nil || !localPath(p) {
101107 return ""
102108 }
103109 return p
internal/httpd/flash_test.go added +19
@@ -0,0 +1,19 @@
1package httpd
2
3import "testing"
4
5func TestLocalPath(t *testing.T) {
6 for p, want := range map[string]bool{
7 "/settings": true,
8 "/a/b?c=d": true,
9 "": false,
10 "settings": false,
11 "//evil.example": false,
12 `/\evil.example`: false,
13 "https://x.test/": false,
14 } {
15 if got := localPath(p); got != want {
16 t.Errorf("localPath(%q) = %v, want %v", p, got, want)
17 }
18 }
19}