Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions pkg/cli/logs_download.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,14 @@ func flattenArtifactTree(sourceDir, artifactDir, outputDir, label string, verbos
destPath := filepath.Join(outputDir, relPath)

if info.IsDir() {
// Create directory in destination with owner+group permissions only (0750)
if err := os.MkdirAll(destPath, 0750); err != nil {
// Create directory in destination with world-readable permissions (0755)
if err := os.MkdirAll(destPath, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %w", destPath, err)
}
logsDownloadLog.Printf("Created directory: %s", destPath)
} else {
// Ensure parent directory exists with owner+group permissions only (0750)
if err := os.MkdirAll(filepath.Dir(destPath), 0750); err != nil {
// Ensure parent directory exists with world-readable permissions (0755)
if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
return fmt.Errorf("failed to create parent directory for %s: %w", destPath, err)
}

Expand Down Expand Up @@ -386,7 +386,7 @@ func extractZipFile(f *zip.File, destDir string, verbose bool) (extractErr error

// Create directory if it's a directory entry
if f.FileInfo().IsDir() {
return os.MkdirAll(filePath, 0750)
return os.MkdirAll(filePath, 0755)
}

// Decompression bomb protection - limit individual file size to 1GB
Expand All @@ -397,7 +397,7 @@ func extractZipFile(f *zip.File, destDir string, verbose bool) (extractErr error
}

// Create parent directory if needed
if err := os.MkdirAll(filepath.Dir(filePath), 0750); err != nil {
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}

Expand Down
12 changes: 12 additions & 0 deletions pkg/cli/logs_download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,18 @@ func TestUnzipFile(t *testing.T) {
if string(content) != "Job 1 logs" {
t.Errorf("Extracted subdirectory content mismatch: got %q, want %q", string(content), "Job 1 logs")
}

// Verify that extracted subdirectories have world-readable permissions (0755, not 0750)
// so that non-root users (e.g., runner uid=1001) can browse the logs
subdirPath := filepath.Join(destDir, "logs")
info, err := os.Stat(subdirPath)
if err != nil {
t.Fatalf("Failed to stat extracted subdirectory: %v", err)
}
perm := info.Mode().Perm()
if perm&0o005 == 0 {
t.Errorf("Extracted subdirectory has restrictive permissions %04o (world-read/execute bits not set); expected 0755", perm)
Comment on lines +502 to +503

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The permission assertion is too weak for the stated requirement: perm&0o005 == 0 will pass if either world-read or world-execute is set, but “browse the logs” generally requires both (read to list, execute to traverse). Consider asserting perm&0o005 == 0o005 (or explicitly checking both bits) so the test fails for modes like 0751 / 0754 that still prevent directory listing or traversal.

Suggested change
if perm&0o005 == 0 {
t.Errorf("Extracted subdirectory has restrictive permissions %04o (world-read/execute bits not set); expected 0755", perm)
if perm&0o005 != 0o005 {
t.Errorf("Extracted subdirectory has restrictive permissions %04o (world-read and world-execute bits must both be set); expected 0755", perm)

Copilot uses AI. Check for mistakes.
}
}

func TestUnzipFileZipSlipPrevention(t *testing.T) {
Expand Down
Loading