From 6489f1afa4636140fbeea86a748e982168a8bf65 Mon Sep 17 00:00:00 2001 From: Sungkyu Yoo Date: Sun, 19 Apr 2026 22:32:59 +0900 Subject: [PATCH 1/3] Potential fix for code scanning alert no. 6: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- internal/services/lambda/store.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/internal/services/lambda/store.go b/internal/services/lambda/store.go index 1debbde..47c0625 100644 --- a/internal/services/lambda/store.go +++ b/internal/services/lambda/store.go @@ -300,9 +300,22 @@ func (s *LambdaStore) DeleteFunction(accountID, functionName string) error { return ErrFunctionNotFound } - // Remove the code directory (best-effort; ignore errors). - codeDir := filepath.Join(s.codeDir, accountID, functionName) - _ = os.RemoveAll(codeDir) + // Remove the code directory (best-effort; ignore errors), but only if the + // resolved path stays within the configured base code directory. + baseDirAbs, err := filepath.Abs(filepath.Clean(s.codeDir)) + if err != nil { + return err + } + codeDirAbs, err := filepath.Abs(filepath.Clean(filepath.Join(baseDirAbs, accountID, functionName))) + if err != nil { + return err + } + rel, err := filepath.Rel(baseDirAbs, codeDirAbs) + if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) || filepath.IsAbs(rel) { + return fmt.Errorf("invalid function path") + } + + _ = os.RemoveAll(codeDirAbs) return nil } From 400bf0a1862e7dc2a3778454e7a9996746c329d0 Mon Sep 17 00:00:00 2001 From: Sung-Kyu Yoo Date: Sun, 19 Apr 2026 23:33:54 +0900 Subject: [PATCH 2/3] fix: simplify DeleteFunction path check, skip deletion on validation failure - Use direct prefix check (baseDirAbs + separator) instead of filepath.Rel - Skip os.RemoveAll instead of returning error when path is outside base dir --- internal/services/lambda/store.go | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/internal/services/lambda/store.go b/internal/services/lambda/store.go index 47c0625..1bd3b59 100644 --- a/internal/services/lambda/store.go +++ b/internal/services/lambda/store.go @@ -302,21 +302,14 @@ func (s *LambdaStore) DeleteFunction(accountID, functionName string) error { // Remove the code directory (best-effort; ignore errors), but only if the // resolved path stays within the configured base code directory. - baseDirAbs, err := filepath.Abs(filepath.Clean(s.codeDir)) - if err != nil { - return err - } - codeDirAbs, err := filepath.Abs(filepath.Clean(filepath.Join(baseDirAbs, accountID, functionName))) - if err != nil { - return err - } - rel, err := filepath.Rel(baseDirAbs, codeDirAbs) - if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) || filepath.IsAbs(rel) { - return fmt.Errorf("invalid function path") + codeDirAbs := filepath.Join(s.codeDir, accountID, functionName) + cleaned := filepath.Clean(codeDirAbs) + baseDirAbs, _ := filepath.Abs(s.codeDir) + absCleaned, _ := filepath.Abs(cleaned) + if strings.HasPrefix(absCleaned, baseDirAbs+string(filepath.Separator)) { + _ = os.RemoveAll(absCleaned) } - _ = os.RemoveAll(codeDirAbs) - return nil } From 7b18ed273e010cf9b5591564c566f738987efebc Mon Sep 17 00:00:00 2001 From: Sung-Kyu Yoo Date: Mon, 20 Apr 2026 04:13:22 +0900 Subject: [PATCH 3/3] fix: harden path containment checks with filepath.Rel and error propagation Replace strings.HasPrefix-based containment check with filepath.Rel to correctly handle edge cases (exact base match, prefix collisions like /base vs /base2). Propagate filepath.Abs errors instead of silently ignoring them in codePath. Add slog.Debug logging in DeleteFunction when path validation rejects a deletion. --- internal/services/lambda/store.go | 33 ++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/internal/services/lambda/store.go b/internal/services/lambda/store.go index 1bd3b59..4b15981 100644 --- a/internal/services/lambda/store.go +++ b/internal/services/lambda/store.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "os" "path/filepath" "strconv" @@ -149,9 +150,19 @@ func (s *LambdaStore) Close() error { func (s *LambdaStore) codePath(accountID, functionName string) (string, error) { joined := filepath.Join(s.codeDir, accountID, functionName, "code.zip") cleaned := filepath.Clean(joined) - absBase, _ := filepath.Abs(s.codeDir) - absCleaned, _ := filepath.Abs(cleaned) - if !strings.HasPrefix(absCleaned, absBase+string(filepath.Separator)) { + absBase, err := filepath.Abs(s.codeDir) + if err != nil { + return "", fmt.Errorf("resolve base directory: %w", err) + } + absCleaned, err := filepath.Abs(cleaned) + if err != nil { + return "", fmt.Errorf("resolve code path: %w", err) + } + rel, err := filepath.Rel(absBase, absCleaned) + if err != nil { + return "", fmt.Errorf("compute relative path: %w", err) + } + if strings.HasPrefix(rel, "..") { return "", fmt.Errorf("path traversal detected: %s", cleaned) } return cleaned, nil @@ -304,10 +315,18 @@ func (s *LambdaStore) DeleteFunction(accountID, functionName string) error { // resolved path stays within the configured base code directory. codeDirAbs := filepath.Join(s.codeDir, accountID, functionName) cleaned := filepath.Clean(codeDirAbs) - baseDirAbs, _ := filepath.Abs(s.codeDir) - absCleaned, _ := filepath.Abs(cleaned) - if strings.HasPrefix(absCleaned, baseDirAbs+string(filepath.Separator)) { - _ = os.RemoveAll(absCleaned) + baseDirAbs, err := filepath.Abs(s.codeDir) + if err == nil { + absCleaned, err := filepath.Abs(cleaned) + if err == nil { + rel, err := filepath.Rel(baseDirAbs, absCleaned) + if err == nil && !strings.HasPrefix(rel, "..") { + _ = os.RemoveAll(absCleaned) + } else { + slog.Debug("skipped code directory removal: path outside base directory", + "path", absCleaned, "base", baseDirAbs) + } + } } return nil