From d907493b8426150bb6eb7a29b4256631a0fb46b6 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Tue, 30 Jun 2026 00:07:09 +0200 Subject: [PATCH 1/2] articles - validate the path to file to render, do not panic on unknown files --- cmd/web/main.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cmd/web/main.go b/cmd/web/main.go index 63ed37d..7fa6061 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -5,14 +5,17 @@ import ( "context" "database/sql" "encoding/json" + "errors" "flag" "fmt" "html/template" + "io/fs" "log" "log/slog" "net/http" _ "net/http/pprof" "os" + "regexp" "strings" "time" _ "time/tzdata" // help go learn about timezones @@ -65,6 +68,8 @@ var requiredVars = []string{ "SITE_ROOT", } +var articlesRE = regexp.MustCompile("^[a-z0-9]+(_[a-z0-9]+)*$") + func enforceEnvVars(requiredVars []string) { for _, v := range requiredVars { if _, ok := os.LookupEnv(v); !ok { @@ -287,8 +292,21 @@ func main() { r.GET("/articles/:id", func(c *gin.Context) { articleName := c.Param("id") + if !articlesRE.MatchString(articleName) { + c.AbortWithStatus(http.StatusNotFound) + return + } + fname := fmt.Sprintf("client/articles/%s.md", articleName) + if _, err := os.Stat(fname); errors.Is(err, fs.ErrNotExist) { + panic(2) + c.AbortWithStatus(http.StatusNotFound) + return + } else if err != nil { + panic(err) + } + body, err := os.ReadFile((fname)) if err != nil { From d5b20bd2e25d11cdf7b7be7aa7998778288efb87 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Tue, 30 Jun 2026 00:12:02 +0200 Subject: [PATCH 2/2] debug --- cmd/web/main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/web/main.go b/cmd/web/main.go index 7fa6061..a51b45c 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -300,7 +300,6 @@ func main() { fname := fmt.Sprintf("client/articles/%s.md", articleName) if _, err := os.Stat(fname); errors.Is(err, fs.ErrNotExist) { - panic(2) c.AbortWithStatus(http.StatusNotFound) return } else if err != nil {