From af11e843a4e65d6ef030f1817b1adfd200bb98f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lie=20DELHAIE?= Date: Sun, 7 Sep 2025 01:14:19 +0200 Subject: [PATCH] fixing sec issues --- cmd/cli/commands/sync/sync.go | 12 +++++-- cmd/cli/main.go | 2 +- .../tools/prompt/credentials/credentials.go | 2 +- cmd/server/api/api.go | 5 +-- cmd/server/runner.go | 4 ++- cmd/web/server/server.go | 17 +++++++--- jenkinsfile | 5 ++- pkg/data/data.go | 2 +- pkg/remote/client/client.go | 33 ++++++++++++++----- pkg/remote/remote.go | 4 +-- pkg/repository/repository.go | 14 ++++---- pkg/tools/archive/archive.go | 24 +++++++++++--- pkg/tools/hash/hash.go | 1 + 13 files changed, 88 insertions(+), 37 deletions(-) diff --git a/cmd/cli/commands/sync/sync.go b/cmd/cli/commands/sync/sync.go index 75b114b..4e8e250 100644 --- a/cmd/cli/commands/sync/sync.go +++ b/cmd/cli/commands/sync/sync.go @@ -63,9 +63,15 @@ func (p *SyncCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) pg := progressbar.New(-1) destroyPg := func() { - pg.Finish() - pg.Clear() - pg.Close() + if err := pg.Finish(); err != nil { + slog.Error("failed to finish progressbar", "err", err) + } + if err := pg.Clear(); err != nil { + slog.Error("failed to clear progressbar", "err", err) + } + if err := pg.Close(); err != nil { + slog.Error("failed to close progressbar", "err", err) + } } pg.Describe(fmt.Sprintf("[%s] Checking status...", g.Name)) diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 9c71555..95abf8a 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -30,7 +30,7 @@ func main() { } datastorepath := filepath.Join(roaming, "cloudsave", "data") - err = os.MkdirAll(datastorepath, 0740) + err = os.MkdirAll(datastorepath, 0600) if err != nil { panic("cannot make the datastore:" + err.Error()) } diff --git a/cmd/cli/tools/prompt/credentials/credentials.go b/cmd/cli/tools/prompt/credentials/credentials.go index ccc10dd..2e43ac9 100644 --- a/cmd/cli/tools/prompt/credentials/credentials.go +++ b/cmd/cli/tools/prompt/credentials/credentials.go @@ -89,7 +89,7 @@ func save(store map[string]credential) error { Store: store, } - f, err := os.OpenFile(filepath.Join(datastorePath, "credential.json"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0740) + f, err := os.OpenFile(filepath.Join(datastorePath, "credential.json"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { return fmt.Errorf("failed to open datastore: %w", err) } diff --git a/cmd/server/api/api.go b/cmd/server/api/api.go index 9a210b1..fc2655e 100644 --- a/cmd/server/api/api.go +++ b/cmd/server/api/api.go @@ -74,8 +74,9 @@ func NewServer(documentRoot string, srv *data.Service, creds map[string]string, }) }) s.Server = &http.Server{ - Addr: fmt.Sprintf(":%d", port), - Handler: router, + Addr: fmt.Sprintf(":%d", port), + Handler: router, + ReadHeaderTimeout: 2, } return s } diff --git a/cmd/server/runner.go b/cmd/server/runner.go index 3456b66..dbe422d 100644 --- a/cmd/server/runner.go +++ b/cmd/server/runner.go @@ -66,7 +66,9 @@ func run(updateChan <-chan struct{}) { for { <-updateChan if r, ok := repo.(*repository.EagerRepository); ok { - r.Reload() + if err := r.Reload(); err != nil { + fatal("failed to reload data: "+err.Error(), 1) + } } h, err := htpasswd.Open(filepath.Join(documentRoot, ".htpasswd")) if err != nil { diff --git a/cmd/web/server/server.go b/cmd/web/server/server.go index 06672b7..023e58c 100644 --- a/cmd/web/server/server.go +++ b/cmd/web/server/server.go @@ -74,13 +74,19 @@ var ( // NewServer start the http server func NewServer(c config.Configuration) *HTTPServer { dashboardTemplate := template.New("dashboard") - dashboardTemplate.Parse(DashboardHTMLPage) + if _, err := dashboardTemplate.Parse(DashboardHTMLPage); err != nil { + panic("failed to load template 'dashboard': " + err.Error()) + } detailledTemplate := template.New("detailled") - detailledTemplate.Parse(DetailledHTMLPage) + if _, err := detailledTemplate.Parse(DetailledHTMLPage); err != nil { + panic("failed to load template 'detailled': " + err.Error()) + } systemTemplate := template.New("system") - systemTemplate.Parse(SystemHTMLPage) + if _, err := systemTemplate.Parse(SystemHTMLPage); err != nil { + panic("failed to load template 'system': " + err.Error()) + } s := &HTTPServer{ Config: c, @@ -99,8 +105,9 @@ func NewServer(c config.Configuration) *HTTPServer { routerAPI.Get("/system", s.system) }) s.Server = &http.Server{ - Addr: fmt.Sprintf(":%d", c.Server.Port), - Handler: router, + Addr: fmt.Sprintf(":%d", c.Server.Port), + Handler: router, + ReadHeaderTimeout: 2, } return s } diff --git a/jenkinsfile b/jenkinsfile index db04988..fae0c50 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -6,7 +6,10 @@ pipeline { steps { sh ''' go install github.com/securego/gosec/v2/cmd/gosec@v2.22.8 - /var/lib/jenkins/go/bin/gosec ./... + go install honnef.co/go/tools/cmd/staticcheck@v0.6.1 + + /var/lib/jenkins/go/bin/staticcheck ./... + /var/lib/jenkins/go/bin/gosec -exclude="G401,G501,G103" ./... ''' } } diff --git a/pkg/data/data.go b/pkg/data/data.go index 6644418..779d90e 100644 --- a/pkg/data/data.go +++ b/pkg/data/data.go @@ -258,7 +258,7 @@ func (l Service) PullCurrent(id, path string, cli *client.Client) error { } } - if err := os.MkdirAll(path, 0740); err != nil { + if err := os.MkdirAll(path, 0600); err != nil { return fmt.Errorf("failed to create destination directory: %w", err) } diff --git a/pkg/remote/client/client.go b/pkg/remote/client/client.go index 7af433e..abf89b7 100644 --- a/pkg/remote/client/client.go +++ b/pkg/remote/client/client.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "log/slog" "mime/multipart" "net/http" "net/url" @@ -220,7 +221,7 @@ func (c *Client) Pull(gameID, archivePath string) error { req.SetBasicAuth(c.username, c.password) - f, err := os.OpenFile(archivePath+".part", os.O_CREATE|os.O_WRONLY, 0740) + f, err := os.OpenFile(archivePath+".part", os.O_CREATE|os.O_WRONLY, 0600) if err != nil { return fmt.Errorf("failed to open file: %w", err) } @@ -275,20 +276,24 @@ func (c *Client) PullBackup(gameID, uuid, archivePath string) error { req.SetBasicAuth(c.username, c.password) - f, err := os.OpenFile(archivePath+".part", os.O_CREATE|os.O_WRONLY, 0740) + f, err := os.OpenFile(archivePath+".part", os.O_CREATE|os.O_WRONLY, 0600) if err != nil { return fmt.Errorf("failed to open file: %w", err) } res, err := cli.Do(req) if err != nil { - f.Close() + if err := f.Close(); err != nil { + slog.Error("failed to close file", "err", err) + } return fmt.Errorf("cannot connect to remote: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { - f.Close() + if err := f.Close(); err != nil { + slog.Error("failed to close file", "err", err) + } return fmt.Errorf("cannot connect to remote: server return code: %s", res.Status) } @@ -299,10 +304,14 @@ func (c *Client) PullBackup(gameID, uuid, archivePath string) error { defer bar.Close() if _, err := io.Copy(io.MultiWriter(f, bar), res.Body); err != nil { - f.Close() + if err := f.Close(); err != nil { + slog.Error("failed to close file", "err", err) + } return fmt.Errorf("an error occured while copying the file from the remote: %w", err) } - f.Close() + if err := f.Close(); err != nil { + slog.Error("failed to close file", "err", err) + } if err := os.Rename(archivePath+".part", archivePath); err != nil { return fmt.Errorf("failed to move temporary data: %w", err) @@ -431,9 +440,15 @@ func (c *Client) push(u, archivePath string, m repository.Metadata) error { return fmt.Errorf("failed to copy data: %w", err) } - writer.WriteField("name", m.Name) - writer.WriteField("version", strconv.Itoa(m.Version)) - writer.WriteField("date", m.Date.Format(time.RFC3339)) + if err := writer.WriteField("name", m.Name); err != nil { + return err + } + if err := writer.WriteField("version", strconv.Itoa(m.Version)); err != nil { + return err + } + if err := writer.WriteField("date", m.Date.Format(time.RFC3339)); err != nil { + return err + } if err := writer.Close(); err != nil { return err diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index b9ffd6c..8960af8 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -32,7 +32,7 @@ func init() { } datastorepath = filepath.Join(roaming, "cloudsave", "data") - err = os.MkdirAll(datastorepath, 0740) + err = os.MkdirAll(datastorepath, 0600) if err != nil { panic("cannot make the datastore:" + err.Error()) } @@ -62,7 +62,7 @@ func Set(gameID, url string) error { URL: url, } - f, err := os.OpenFile(filepath.Join(datastorepath, gameID, "remote.json"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0740) + f, err := os.OpenFile(filepath.Join(datastorepath, gameID, "remote.json"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go index 35d7147..ff0f899 100644 --- a/pkg/repository/repository.go +++ b/pkg/repository/repository.go @@ -117,7 +117,7 @@ func (bi BackupIdentifier) Key() string { func NewLazyRepository(dataRootPath string) (*LazyRepository, error) { if m, err := os.Stat(dataRootPath); err != nil { if errors.Is(err, os.ErrNotExist) { - if err := os.MkdirAll(dataRootPath, 0740); err != nil { + if err := os.MkdirAll(dataRootPath, 0600); err != nil { return nil, fmt.Errorf("failed to make the directory: %w", err) } } else { @@ -137,8 +137,8 @@ func NewLazyRepository(dataRootPath string) (*LazyRepository, error) { func (l *LazyRepository) Mkdir(id Identifier) error { path := l.DataPath(id) if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { - slog.Debug("making directory", "path", path, "id", id, "perm", "0740") - return os.MkdirAll(path, 0740) + slog.Debug("making directory", "path", path, "id", id, "perm", "0600") + return os.MkdirAll(path, 0600) } return nil } @@ -182,7 +182,7 @@ func (l *LazyRepository) WriteBlob(ID Identifier) (io.Writer, error) { path := l.DataPath(ID) slog.Debug("loading write buffer...", "id", ID) - dst, err := os.OpenFile(filepath.Join(path, "data.tar.gz"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0740) + dst, err := os.OpenFile(filepath.Join(path, "data.tar.gz"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { return nil, fmt.Errorf("failed to open destination file: %w", err) } @@ -195,7 +195,7 @@ func (l *LazyRepository) WriteMetadata(id GameIdentifier, m Metadata) error { path := l.DataPath(id) slog.Debug("writing metadata", "id", id, "metadata", m) - dst, err := os.OpenFile(filepath.Join(path, "metadata.json"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0740) + dst, err := os.OpenFile(filepath.Join(path, "metadata.json"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { return fmt.Errorf("failed to open destination file: %w", err) } @@ -292,7 +292,7 @@ func (l *LazyRepository) ResetLastScan(id GameIdentifier) error { path := l.DataPath(id) slog.Debug("resetting last scan datetime for", "id", id) - f, err := os.OpenFile(filepath.Join(path, ".last_run"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0740) + f, err := os.OpenFile(filepath.Join(path, ".last_run"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { return fmt.Errorf("failed to open file: %w", err) } @@ -325,7 +325,7 @@ func (l *LazyRepository) ReadBlob(id Identifier) (io.ReadSeekCloser, error) { func (l *LazyRepository) SetRemote(id GameIdentifier, url string) error { path := l.DataPath(id) - src, err := os.OpenFile(filepath.Join(path, "remote.json"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0740) + src, err := os.OpenFile(filepath.Join(path, "remote.json"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { return fmt.Errorf("failed to open remote description: %w", err) } diff --git a/pkg/tools/archive/archive.go b/pkg/tools/archive/archive.go index 0ebef41..fb4584a 100644 --- a/pkg/tools/archive/archive.go +++ b/pkg/tools/archive/archive.go @@ -5,10 +5,17 @@ import ( "compress/gzip" "fmt" "io" + "log/slog" "os" "path/filepath" ) +const ( + // Tune these to your app’s needs + maxCompressedUpload = 500 << 20 // 500 MiB compressed + maxUncompressedOutput = 1000 << 20 // 100 MiB after inflate +) + func Untar(file io.Reader, path string) error { gzr, err := gzip.NewReader(file) if err != nil { @@ -49,26 +56,35 @@ func Untar(file io.Reader, path string) error { // if its a dir and it doesn't exist create it case tar.TypeDir: if _, err := os.Stat(target); err != nil { - if err := os.MkdirAll(target, 0755); err != nil { + if err := os.MkdirAll(target, 0600); err != nil { return err } } // if it's a file create it case tar.TypeReg: - f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, header.FileInfo().Mode()) if err != nil { return err } + limited := &io.LimitedReader{R: gzr, N: maxUncompressedOutput} + // copy over contents - if _, err := io.Copy(f, tr); err != nil { + if _, err := io.Copy(f, limited); err != nil { return err } // manually close here after each file operation; defering would cause each file close // to wait until all operations have completed. - f.Close() + if err := f.Close(); err != nil { + slog.Error("failed to close file", "err", err) + } + + if limited.N == 0 { + // Limit exhausted → likely bomb + return fmt.Errorf("payload too large after decompression") + } } } } diff --git a/pkg/tools/hash/hash.go b/pkg/tools/hash/hash.go index a0a93e8..c4df4ee 100644 --- a/pkg/tools/hash/hash.go +++ b/pkg/tools/hash/hash.go @@ -14,6 +14,7 @@ func FileMD5(fp string) (string, error) { } defer f.Close() + hasher := md5.New() if _, err := io.Copy(hasher, f); err != nil { return "", err