@@ -153,6 +153,23 @@ func isSafePathComponent(v string) bool {
153153 ! strings .ContainsAny (v , "/\\ " )
154154}
155155
156+ // isWithinDir returns true if child resolves to a path within parent.
157+ func isWithinDir (child , parent string ) bool {
158+ absChild , err := filepath .Abs (filepath .Clean (child ))
159+ if err != nil {
160+ return false
161+ }
162+ absParent , err := filepath .Abs (filepath .Clean (parent ))
163+ if err != nil {
164+ return false
165+ }
166+ rel , err := filepath .Rel (absParent , absChild )
167+ if err != nil {
168+ return false
169+ }
170+ return rel != ".." && ! strings .HasPrefix (rel , ".." + string (filepath .Separator )) && ! filepath .IsAbs (rel )
171+ }
172+
156173// codePath returns the filesystem path for a function's code zip.
157174// It validates that accountID and functionName are single path segments and that
158175// the resolved path stays within s.codeDir to prevent path traversal.
@@ -196,7 +213,7 @@ func (s *LambdaStore) CreateFunction(info *FunctionInfo, codeZip []byte) (*Funct
196213 return nil , err
197214 }
198215 dir := filepath .Dir (path )
199- if ! strings . HasPrefix ( filepath . Clean ( dir ), filepath . Clean ( s .codeDir ) + string ( filepath . Separator ) ) {
216+ if ! isWithinDir ( dir , s .codeDir ) {
200217 return nil , fmt .Errorf ("path traversal detected: %s" , dir )
201218 }
202219 if err := os .MkdirAll (dir , 0o755 ); err != nil {
@@ -414,7 +431,7 @@ func (s *LambdaStore) UpdateFunctionCode(accountID, functionName string, codeZip
414431 return nil , err
415432 }
416433 dir := filepath .Dir (path )
417- if ! strings . HasPrefix ( filepath . Clean ( dir ), filepath . Clean ( s .codeDir ) + string ( filepath . Separator ) ) {
434+ if ! isWithinDir ( dir , s .codeDir ) {
418435 return nil , fmt .Errorf ("path traversal detected: %s" , dir )
419436 }
420437 if err := os .MkdirAll (dir , 0o755 ); err != nil {
0 commit comments