@@ -58,7 +58,7 @@ func FetchSource(config *VendorConfig, sourceName string) (*FetchResult, error)
5858 }
5959
6060 // Create destination directory
61- if err := os .MkdirAll (destDir , 0755 ); err != nil {
61+ if err := os .MkdirAll (destDir , 0750 ); err != nil {
6262 return nil , fmt .Errorf ("failed to create destination: %w" , err )
6363 }
6464
@@ -111,7 +111,7 @@ func fetchFromGitHub(source SourceConfig, destDir, ref string) (string, int, err
111111 tarballURL := fmt .Sprintf ("https://codeload.github.com/%s/%s/tar.gz/%s" , owner , repo , ref )
112112
113113 // Download tarball
114- resp , err := http .Get (tarballURL )
114+ resp , err := http .Get (tarballURL ) // #nosec G107 -- URL constructed from validated GitHub owner/repo
115115 if err != nil {
116116 return "" , 0 , fmt .Errorf ("failed to download tarball: %w" , err )
117117 }
@@ -239,31 +239,31 @@ func extractTarGz(reader io.Reader, destDir, subPath string, include, exclude []
239239
240240 switch header .Typeflag {
241241 case tar .TypeDir :
242- if err := os .MkdirAll (destPath , 0755 ); err != nil {
242+ if err := os .MkdirAll (destPath , 0750 ); err != nil {
243243 return filesExtracted , fmt .Errorf ("failed to create directory %s: %w" , destPath , err )
244244 }
245245 case tar .TypeReg , tar .TypeRegA :
246246 // Ensure parent directory exists
247- if err := os .MkdirAll (filepath .Dir (destPath ), 0755 ); err != nil {
247+ if err := os .MkdirAll (filepath .Dir (destPath ), 0750 ); err != nil {
248248 return filesExtracted , fmt .Errorf ("failed to create parent directory: %w" , err )
249249 }
250250
251- // Create file
252- outFile , err := os .Create ( destPath )
251+ // Create file with restricted permissions
252+ outFile , err := os .OpenFile ( filepath . Clean ( destPath ), os . O_WRONLY | os . O_CREATE | os . O_TRUNC , 0600 )
253253 if err != nil {
254254 return filesExtracted , fmt .Errorf ("failed to create file %s: %w" , destPath , err )
255255 }
256256
257- if _ , err := io .Copy (outFile , tr ); err != nil {
258- outFile .Close ()
257+ // Limit copy size to prevent decompression bombs (256MB per file)
258+ const maxFileSize = 256 << 20
259+ if _ , err := io .Copy (outFile , io .LimitReader (tr , maxFileSize )); err != nil {
260+ _ = outFile .Close ()
259261 return filesExtracted , fmt .Errorf ("failed to write file %s: %w" , destPath , err )
260262 }
261- outFile .Close ()
263+ _ = outFile .Close ()
262264
263- // Set file permissions
264- if err := os .Chmod (destPath , os .FileMode (header .Mode )); err != nil {
265- // Non-fatal, just log
266- }
265+ // Set file permissions (mask to permission bits only)
266+ _ = os .Chmod (destPath , os .FileMode (header .Mode )& os .ModePerm ) // #nosec G115
267267
268268 filesExtracted ++
269269 }
@@ -385,7 +385,7 @@ it is a build artifact that should be regenerated from the config file.
385385 configName , info .FetchCmd , dirName )
386386
387387 readmePath := filepath .Join (vendorDir , "README.md" )
388- return os .WriteFile (readmePath , []byte (readme ), 0644 )
388+ return os .WriteFile (readmePath , []byte (readme ), 0600 )
389389}
390390
391391// FetchAllSources fetches all sources defined in the config
@@ -440,7 +440,7 @@ func WriteLockFileFor(path string, lock *VendorLock, info ToolInfo) error {
440440
441441 content := header + string (data )
442442
443- if err := os .WriteFile (path , []byte (content ), 0644 ); err != nil {
443+ if err := os .WriteFile (filepath . Clean ( path ) , []byte (content ), 0600 ); err != nil {
444444 return fmt .Errorf ("failed to write lock file: %w" , err )
445445 }
446446
@@ -449,7 +449,7 @@ func WriteLockFileFor(path string, lock *VendorLock, info ToolInfo) error {
449449
450450// LoadLockFile loads a VendorLock from the specified path
451451func LoadLockFile (path string ) (* VendorLock , error ) {
452- data , err := os .ReadFile (path )
452+ data , err := os .ReadFile (filepath . Clean ( path ) )
453453 if err != nil {
454454 return nil , fmt .Errorf ("failed to read lock file: %w" , err )
455455 }
0 commit comments