From edeb2eca0c23d7445544f53f02ca5d454c90368b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:16:29 +0100 Subject: [PATCH] chore(deps): bump google.golang.org/grpc from 1.78.0 to 1.79.3 in the go_modules group across 1 directory (#36) * chore(deps): bump google.golang.org/grpc Bumps the go_modules group with 1 update in the / directory: [google.golang.org/grpc](https://github.com/grpc/grpc-go). Updates `google.golang.org/grpc` from 1.78.0 to 1.79.3 - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.78.0...v1.79.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.79.3 dependency-type: indirect dependency-group: go_modules ... Signed-off-by: dependabot[bot] * Resolve merge conflicts and harden storage paths Co-authored-by: lukaszraczylo <2182556+lukaszraczylo@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lukaszraczylo <2182556+lukaszraczylo@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 +- pkg/storage/filesystem/filesystem.go | 75 ++++++++++++++++++++-------- pkg/storage/nfs/nfs.go | 29 ++++++++++- 4 files changed, 85 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index aa5f00f..9405569 100644 --- a/go.mod +++ b/go.mod @@ -126,6 +126,6 @@ require ( go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect - google.golang.org/grpc v1.78.0 // indirect + google.golang.org/grpc v1.79.3 // indirect google.golang.org/protobuf v1.36.12 // indirect ) diff --git a/go.sum b/go.sum index e03a765..d5999e9 100644 --- a/go.sum +++ b/go.sum @@ -298,8 +298,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1: google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/storage/filesystem/filesystem.go b/pkg/storage/filesystem/filesystem.go index d82fad8..37bfa39 100644 --- a/pkg/storage/filesystem/filesystem.go +++ b/pkg/storage/filesystem/filesystem.go @@ -91,17 +91,30 @@ func (fs *FilesystemStorage) Put(ctx context.Context, key string, data io.Reader metrics.RecordStorageOperation("filesystem", "put", "error") return err } - dir := filepath.Dir(path) + relPath, err := fs.relativeToBase(path) + if err != nil { + metrics.RecordStorageOperation("filesystem", "put", "error") + return err + } + dir := filepath.Dir(relPath) + root, err := os.OpenRoot(fs.basePath) + if err != nil { + metrics.RecordStorageOperation("filesystem", "put", "error") + return errors.Wrap(err, errors.ErrCodeStorageFailure, "failed to open storage root") + } + defer root.Close() // #nosec G307 -- Best-effort cleanup of opened storage root // Create directory - if mkErr := os.MkdirAll(dir, 0750); mkErr != nil { - metrics.RecordStorageOperation("filesystem", "put", "error") - return errors.Wrap(mkErr, errors.ErrCodeStorageFailure, "failed to create directory") + if dir != "." { + if mkErr := root.MkdirAll(dir, 0750); mkErr != nil { + metrics.RecordStorageOperation("filesystem", "put", "error") + return errors.Wrap(mkErr, errors.ErrCodeStorageFailure, "failed to create directory") + } } // Create temp file for atomic write - tempPath := path + ".tmp" - tempFile, err := os.Create(tempPath) // #nosec G304 -- Temp path is constructed from sanitized storage key + tempPath := relPath + ".tmp" + tempFile, err := root.Create(tempPath) if err != nil { metrics.RecordStorageOperation("filesystem", "put", "error") return errors.Wrap(err, errors.ErrCodeStorageFailure, "failed to create temp file") @@ -115,14 +128,14 @@ func (fs *FilesystemStorage) Put(ctx context.Context, key string, data io.Reader written, err := io.Copy(multiWriter, data) if err != nil { - _ = tempFile.Close() // #nosec G104 -- Cleanup, error not critical - _ = os.Remove(tempPath) // #nosec G104 -- Cleanup, error not critical + _ = tempFile.Close() // #nosec G104 -- Cleanup, error not critical + _ = root.Remove(tempPath) metrics.RecordStorageOperation("filesystem", "put", "error") return errors.Wrap(err, errors.ErrCodeStorageFailure, "failed to write data") } if err := tempFile.Close(); err != nil { - _ = os.Remove(tempPath) // #nosec G104 -- Cleanup, error not critical + _ = root.Remove(tempPath) metrics.RecordStorageOperation("filesystem", "put", "error") return errors.Wrap(err, errors.ErrCodeStorageFailure, "failed to close temp file") } @@ -133,13 +146,13 @@ func (fs *FilesystemStorage) Put(ctx context.Context, key string, data io.Reader sha256Sum := hex.EncodeToString(sha256Hash.Sum(nil)) if opts.ChecksumMD5 != "" && opts.ChecksumMD5 != md5Sum { - _ = os.Remove(tempPath) // #nosec G104 -- Cleanup, error not critical + _ = root.Remove(tempPath) metrics.RecordStorageOperation("filesystem", "put", "checksum_error") return errors.New(errors.ErrCodeChecksumMismatch, "MD5 checksum mismatch") } if opts.ChecksumSHA256 != "" && opts.ChecksumSHA256 != sha256Sum { - _ = os.Remove(tempPath) // #nosec G104 -- Cleanup, error not critical + _ = root.Remove(tempPath) metrics.RecordStorageOperation("filesystem", "put", "checksum_error") return errors.New(errors.ErrCodeChecksumMismatch, "SHA256 checksum mismatch") } @@ -151,13 +164,13 @@ func (fs *FilesystemStorage) Put(ctx context.Context, key string, data io.Reader fs.mu.Lock() if fs.quota > 0 && fs.used+written > fs.quota { fs.mu.Unlock() - _ = os.Remove(tempPath) // #nosec G104 -- Cleanup, error not critical + _ = root.Remove(tempPath) metrics.RecordStorageOperation("filesystem", "put", "quota_exceeded") return errors.QuotaExceeded(fs.quota) } - if err := os.Rename(tempPath, path); err != nil { + if err := root.Rename(tempPath, relPath); err != nil { fs.mu.Unlock() - _ = os.Remove(tempPath) // #nosec G104 -- Cleanup, error not critical + _ = root.Remove(tempPath) metrics.RecordStorageOperation("filesystem", "put", "error") return errors.Wrap(err, errors.ErrCodeStorageFailure, "failed to rename temp file") } @@ -408,22 +421,42 @@ func (fs *FilesystemStorage) keyToPath(key string) (string, error) { } target := filepath.Join(fs.basePath, cleaned) - - // Defense-in-depth: verify the resolved absolute path is contained - // within the base directory. targetAbs, err := filepath.Abs(target) if err != nil { return "", errors.Wrap(err, errors.ErrCodeStorageFailure, "failed to resolve path") } + if err := fs.ensureWithinBase(targetAbs); err != nil { + return "", errors.New(errors.ErrCodeStorageFailure, fmt.Sprintf("path traversal rejected: %s", key)) + } + + return targetAbs, nil +} + +func (fs *FilesystemStorage) ensureWithinBase(path string) error { + rel, err := fs.relativeToBase(path) + if err != nil { + return err + } + if rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { + return errors.New(errors.ErrCodeStorageFailure, fmt.Sprintf("path traversal rejected: %s", path)) + } + return nil +} + +func (fs *FilesystemStorage) relativeToBase(path string) (string, error) { baseAbs, err := filepath.Abs(fs.basePath) if err != nil { return "", errors.Wrap(err, errors.ErrCodeStorageFailure, "failed to resolve base path") } - if targetAbs != baseAbs && !strings.HasPrefix(targetAbs, baseAbs+string(os.PathSeparator)) { - return "", errors.New(errors.ErrCodeStorageFailure, fmt.Sprintf("path traversal rejected: %s", key)) + pathAbs, err := filepath.Abs(path) + if err != nil { + return "", errors.Wrap(err, errors.ErrCodeStorageFailure, "failed to resolve path") } - - return target, nil + rel, err := filepath.Rel(baseAbs, pathAbs) + if err != nil { + return "", errors.Wrap(err, errors.ErrCodeStorageFailure, "failed to compare path") + } + return rel, nil } // calculateUsage calculates current storage usage diff --git a/pkg/storage/nfs/nfs.go b/pkg/storage/nfs/nfs.go index d7f7775..750834f 100644 --- a/pkg/storage/nfs/nfs.go +++ b/pkg/storage/nfs/nfs.go @@ -130,7 +130,18 @@ func (s *Storage) Put(ctx context.Context, key string, data io.Reader, opts *sto s.logger.Warn().Err(err).Str("key", key).Msg("nfs: post-put path lookup failed; skipping fsync") return nil } - f, err := os.OpenFile(path, os.O_RDWR, 0) // #nosec G304 -- path resolved by sanitizing backend + relPath, err := relativeToBase(s.path, path) + if err != nil { + s.logger.Warn().Err(err).Str("key", key).Msg("nfs: post-put path escaped configured base; skipping fsync") + return nil + } + root, err := os.OpenRoot(s.path) + if err != nil { + s.logger.Warn().Err(err).Str("path", s.path).Msg("nfs: failed to open configured base; skipping fsync") + return nil + } + defer root.Close() // #nosec G307 -- Best-effort cleanup of opened NFS root + f, err := root.OpenFile(relPath, os.O_RDWR, 0) if err != nil { s.logger.Warn().Err(err).Str("key", key).Msg("nfs: post-put open failed; skipping fsync") return nil @@ -226,6 +237,22 @@ func (s *Storage) GetLocalPath(ctx context.Context, key string) (string, error) return s.fs.GetLocalPath(ctx, key) } +func relativeToBase(basePath, path string) (string, error) { + baseAbs, err := filepath.Abs(basePath) + if err != nil { + return "", errors.Wrap(err, errors.ErrCodeStorageFailure, "nfs: failed to resolve base path") + } + pathAbs, err := filepath.Abs(path) + if err != nil { + return "", errors.Wrap(err, errors.ErrCodeStorageFailure, "nfs: failed to resolve path") + } + rel, err := filepath.Rel(baseAbs, pathAbs) + if err != nil { + return "", errors.Wrap(err, errors.ErrCodeStorageFailure, "nfs: failed to compare path") + } + return rel, nil +} + // detectMountType returns the filesystem type backing path. Linux-only: on // other platforms the second return value is false. Implementation walks // /proc/mounts and selects the longest matching mount point, which is the