@@ -451,6 +451,132 @@ func TestExtractZipFile_WithDirectory(t *testing.T) {
451451 assert .Equal (t , "nested content" , string (content ))
452452}
453453
454+ func TestExtractZipFile_RawTraversalName (t * testing.T ) {
455+ tempDir := t .TempDir ()
456+ zipPath := filepath .Join (tempDir , "evil.zip" )
457+ extractDir := filepath .Join (tempDir , "extract" )
458+ require .NoError (t , os .MkdirAll (extractDir , 0o755 ))
459+
460+ // Entry name contains ".." directly; the raw substring guard in extractZipFile
461+ // must reject it before sanitizeZipPath is even consulted.
462+ createTestZip (t , zipPath , map [string ][]byte {
463+ "../evil.txt" : []byte ("malicious" ),
464+ })
465+
466+ err := extractZipFile (zipPath , extractDir )
467+ require .Error (t , err )
468+ assert .ErrorIs (t , err , ErrPRArtifactExtractFailed )
469+ assert .Contains (t , err .Error (), "Zip Slip" )
470+ }
471+
472+ func TestExtractZipFile_ParentDirCreationFails (t * testing.T ) {
473+ tempDir := t .TempDir ()
474+ zipPath := filepath .Join (tempDir , "test.zip" )
475+ extractDir := filepath .Join (tempDir , "extract" )
476+ require .NoError (t , os .MkdirAll (extractDir , 0o755 ))
477+
478+ // Pre-create a regular file where the entry's parent directory needs to go,
479+ // forcing os.MkdirAll(parentDir, ...) to fail with "not a directory".
480+ blockedPath := filepath .Join (extractDir , "blocked" )
481+ require .NoError (t , os .WriteFile (blockedPath , []byte ("i am a file, not a dir" ), 0o644 ))
482+
483+ createTestZip (t , zipPath , map [string ][]byte {
484+ "blocked/file.txt" : []byte ("content" ),
485+ })
486+
487+ err := extractZipFile (zipPath , extractDir )
488+ require .Error (t , err )
489+ assert .ErrorIs (t , err , ErrPRArtifactExtractFailed )
490+ assert .Contains (t , err .Error (), "failed to create parent dir" )
491+ }
492+
493+ func TestExtractZipFile_EntryCreationFails (t * testing.T ) {
494+ tempDir := t .TempDir ()
495+ zipPath := filepath .Join (tempDir , "test.zip" )
496+ extractDir := filepath .Join (tempDir , "extract" )
497+ require .NoError (t , os .MkdirAll (extractDir , 0o755 ))
498+
499+ // Pre-create a directory at the exact destination path of the file entry,
500+ // forcing os.Create(destPath) inside extractZipEntry to fail.
501+ collidingPath := filepath .Join (extractDir , "file.txt" )
502+ require .NoError (t , os .MkdirAll (collidingPath , 0o755 ))
503+
504+ createTestZip (t , zipPath , map [string ][]byte {
505+ "file.txt" : []byte ("content" ),
506+ })
507+
508+ err := extractZipFile (zipPath , extractDir )
509+ require .Error (t , err )
510+ assert .ErrorIs (t , err , ErrPRArtifactExtractFailed )
511+ }
512+
513+ func TestVerifyWithinDestDir (t * testing.T ) {
514+ baseDir := t .TempDir ()
515+ cleanDestDir := filepath .Clean (baseDir ) + string (os .PathSeparator )
516+
517+ tests := []struct {
518+ name string
519+ path string
520+ wantErr bool
521+ }{
522+ {
523+ name : "path within dest dir" ,
524+ path : filepath .Join (baseDir , "sub" , "file.txt" ),
525+ wantErr : false ,
526+ },
527+ {
528+ name : "path exactly at dest dir root" ,
529+ path : filepath .Clean (baseDir ),
530+ wantErr : false ,
531+ },
532+ {
533+ name : `path is parent of dest dir (rel == "..")` ,
534+ path : filepath .Dir (filepath .Clean (baseDir )),
535+ wantErr : true ,
536+ },
537+ {
538+ name : `path is sibling of dest dir (rel starts with "../")` ,
539+ path : filepath .Join (filepath .Dir (filepath .Clean (baseDir )), "sibling" , "file.txt" ),
540+ wantErr : true ,
541+ },
542+ }
543+
544+ for _ , tt := range tests {
545+ t .Run (tt .name , func (t * testing.T ) {
546+ err := verifyWithinDestDir (tt .path , cleanDestDir , "entry.txt" )
547+
548+ if tt .wantErr {
549+ require .Error (t , err )
550+ assert .ErrorIs (t , err , ErrPRArtifactExtractFailed )
551+ assert .Contains (t , err .Error (), "escapes destination" )
552+ return
553+ }
554+ assert .NoError (t , err )
555+ })
556+ }
557+ }
558+
559+ func TestExtractZipEntry_PathEscape (t * testing.T ) {
560+ tempDir := t .TempDir ()
561+ zipPath := filepath .Join (tempDir , "test.zip" )
562+ createTestZip (t , zipPath , map [string ][]byte {"file.txt" : []byte ("content" )})
563+
564+ r , err := zip .OpenReader (zipPath )
565+ require .NoError (t , err )
566+ defer r .Close ()
567+ require .Len (t , r .File , 1 )
568+
569+ // destPath is outside cleanDestDir even though the entry name itself is benign,
570+ // forcing extractZipEntry's own adjacent-to-sink guard to reject it.
571+ cleanDestDir := filepath .Join (tempDir , "extract" ) + string (os .PathSeparator )
572+ outsidePath := filepath .Join (tempDir , "outside" , "file.txt" )
573+
574+ err = extractZipEntry (r .File [0 ], outsidePath , cleanDestDir )
575+ require .Error (t , err )
576+ assert .ErrorIs (t , err , ErrPRArtifactExtractFailed )
577+ assert .Contains (t , err .Error (), "escapes destination" )
578+ }
579+
454580func TestCopyFile_DestDirNotFound (t * testing.T ) {
455581 tempDir := t .TempDir ()
456582 srcPath := filepath .Join (tempDir , "source.txt" )
0 commit comments