@@ -19,6 +19,23 @@ import (
1919 "github.com/tikv/client-go/v2/rawkv"
2020)
2121
22+ // isPathWithinRoot returns true if candidate is the same as root or a descendant of it.
23+ func isPathWithinRoot (root , candidate string ) (string , bool ) {
24+ cleanRoot := filepath .Clean (root )
25+ absRoot , err := filepath .Abs (cleanRoot )
26+ if err != nil {
27+ return "" , false
28+ }
29+ absCandidate , err := filepath .Abs (candidate )
30+ if err != nil {
31+ return "" , false
32+ }
33+ if absCandidate == absRoot || strings .HasPrefix (absCandidate , absRoot + string (os .PathSeparator )) {
34+ return absCandidate , true
35+ }
36+ return "" , false
37+ }
38+
2239func main () {
2340 // Read PD addresses from env: TIKV_PD_ADDRS="127.0.0.1:2379|My Cluster 1,127.0.0.1:2381;server-2.com:2379|Cluster 2"
2441 pdAddrsEnv := os .Getenv ("TIKV_PD_ADDRS" )
@@ -78,8 +95,8 @@ func main() {
7895 // In Docker, we will copy the built 'out' directory to 'public'
7996 staticDir := "./public"
8097 mux .HandleFunc ("/" , func (w http.ResponseWriter , r * http.Request ) {
81- // Resolve the static directory to an absolute path
82- staticRoot , err := filepath .Abs (staticDir )
98+ // Resolve the static directory to an absolute, cleaned path
99+ staticRoot , err := filepath .Abs (filepath . Clean ( staticDir ) )
83100 if err != nil {
84101 http .Error (w , "Internal server error" , http .StatusInternalServerError )
85102 return
@@ -99,27 +116,23 @@ func main() {
99116 absPath := filepath .Join (staticRoot , relPath )
100117
101118 // Ensure the resolved path is within staticRoot
102- absPath , err = filepath .Abs (absPath )
103- if err != nil {
104- http .Error (w , "Internal server error" , http .StatusInternalServerError )
105- return
106- }
107- if absPath != staticRoot && ! strings .HasPrefix (absPath , staticRoot + string (os .PathSeparator )) {
119+ resolvedPath , ok := isPathWithinRoot (staticRoot , absPath )
120+ if ! ok {
108121 http .Error (w , "Invalid path" , http .StatusBadRequest )
109122 return
110123 }
111124
112125 // Check if exact file exists (e.g., /favicon.ico, /_next/static/...)
113- if info , err := os .Stat (absPath ); err == nil && ! info .IsDir () {
114- http .ServeFile (w , r , absPath )
126+ if info , err := os .Stat (resolvedPath ); err == nil && ! info .IsDir () {
127+ http .ServeFile (w , r , resolvedPath )
115128 return
116129 }
117130
118131 // For routes like /metrics, try serving metrics.html (Next.js static export)
119132 if r .URL .Path != "/" {
120- htmlPath := absPath + ".html"
121- htmlPathAbs , err := filepath . Abs ( htmlPath )
122- if err != nil || ! strings . HasPrefix ( htmlPathAbs , staticRoot + string ( os . PathSeparator )) {
133+ htmlPath := resolvedPath + ".html"
134+ htmlPathAbs , ok := isPathWithinRoot ( staticRoot , htmlPath )
135+ if ! ok {
123136 http .Error (w , "Invalid path" , http .StatusBadRequest )
124137 return
125138 }
@@ -129,9 +142,9 @@ func main() {
129142 }
130143
131144 // Also check for directory with index.html (e.g., /metrics/index.html)
132- indexPath := filepath .Join (absPath , "index.html" )
133- indexPathAbs , err := filepath . Abs ( indexPath )
134- if err != nil || ! strings . HasPrefix ( indexPathAbs , staticRoot + string ( os . PathSeparator )) {
145+ indexPath := filepath .Join (resolvedPath , "index.html" )
146+ indexPathAbs , ok := isPathWithinRoot ( staticRoot , indexPath )
147+ if ! ok {
135148 http .Error (w , "Invalid path" , http .StatusBadRequest )
136149 return
137150 }
0 commit comments