Skip to content

Commit 7a1de9b

Browse files
committed
fix(builder): add path traversal protection to archive extraction
- Add sanitizePath helper to validate archive entry paths - Apply protection to extractTar for tar.gz, tar.bz2, tar.xz archives - Apply protection to extractZip for zip archives - Reject absolute paths and paths escaping destination directory - Unify security patterns with lib/fetch.go sanitizeTarPath
1 parent 7450439 commit 7a1de9b

1 file changed

Lines changed: 39 additions & 2 deletions

File tree

internal/builder/download.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,11 @@ func extractTar(archivePath, destPath, archiveType string, logger io.Writer) err
257257
continue
258258
}
259259

260-
target := filepath.Join(destPath, name)
260+
// Security: Validate path to prevent path traversal attacks
261+
target, err := sanitizePath(destPath, name)
262+
if err != nil {
263+
return err
264+
}
261265

262266
switch header.Typeflag {
263267
case tar.TypeDir:
@@ -323,7 +327,11 @@ func extractZip(archivePath, destPath string, logger io.Writer) error {
323327
continue
324328
}
325329

326-
target := filepath.Join(destPath, name)
330+
// Security: Validate path to prevent path traversal attacks
331+
target, err := sanitizePath(destPath, name)
332+
if err != nil {
333+
return err
334+
}
327335

328336
if file.FileInfo().IsDir() {
329337
if err := os.MkdirAll(target, 0755); err != nil {
@@ -358,3 +366,32 @@ func extractZip(archivePath, destPath string, logger io.Writer) error {
358366

359367
return nil
360368
}
369+
370+
// sanitizePath validates that an archive entry path is safe to extract.
371+
// It prevents path traversal attacks by ensuring the resolved path
372+
// stays within the destination directory.
373+
func sanitizePath(destDir, entryName string) (string, error) {
374+
// Reject absolute paths
375+
if filepath.IsAbs(entryName) {
376+
return "", fmt.Errorf("path traversal detected: absolute path %q not allowed", entryName)
377+
}
378+
379+
// Clean the path to resolve . and .. components
380+
cleanName := filepath.Clean(entryName)
381+
382+
// Reject paths that start with .. after cleaning
383+
if strings.HasPrefix(cleanName, "..") {
384+
return "", fmt.Errorf("path traversal detected: %q escapes destination directory", entryName)
385+
}
386+
387+
// Construct the full target path
388+
target := filepath.Join(destDir, cleanName)
389+
390+
// Final check: ensure the resolved path is within destDir
391+
// This catches edge cases where filepath.Join might not prevent traversal
392+
if !strings.HasPrefix(target, destDir+string(filepath.Separator)) && target != destDir {
393+
return "", fmt.Errorf("path traversal detected: %q resolves outside destination directory", entryName)
394+
}
395+
396+
return target, nil
397+
}

0 commit comments

Comments
 (0)