@@ -60,7 +60,7 @@ func Test_UntarNested_RejectsAbsoluteSymlinkWriteThrough(t *testing.T) {
6060 {hdr : tar.Header {Name : "escape-link/escape.txt" , Typeflag : tar .TypeReg , Mode : 0644 }, body : []byte ("escaped\n " )},
6161 })
6262
63- if err := UntarNested (bytes .NewReader (data ), installDir , false , true ); err == nil {
63+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , true ); err == nil {
6464 t .Fatal ("want error, got nil" )
6565 }
6666
@@ -92,7 +92,7 @@ func Test_UntarNested_RejectsRelativeSymlinkWriteThrough(t *testing.T) {
9292 {hdr : tar.Header {Name : "escape-link/escape.txt" , Typeflag : tar .TypeReg , Mode : 0644 }, body : []byte ("escaped\n " )},
9393 })
9494
95- if err := UntarNested (bytes .NewReader (data ), installDir , false , true ); err == nil {
95+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , true ); err == nil {
9696 t .Fatal ("want error, got nil" )
9797 }
9898
@@ -121,7 +121,7 @@ func Test_UntarNested_RejectsChainedSymlinkEscape(t *testing.T) {
121121 {hdr : tar.Header {Name : "hop1/hop2/outside/escape.txt" , Typeflag : tar .TypeReg , Mode : 0644 }, body : []byte ("escaped\n " )},
122122 })
123123
124- if err := UntarNested (bytes .NewReader (data ), installDir , false , true ); err == nil {
124+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , true ); err == nil {
125125 t .Fatal ("want error, got nil" )
126126 }
127127
@@ -149,7 +149,7 @@ func Test_UntarNested_RejectsPlantedEscapingSymlink(t *testing.T) {
149149 {hdr : tar.Header {Name : "hop1/hop2" , Typeflag : tar .TypeSymlink , Linkname : ".." , Mode : 0777 }},
150150 })
151151
152- if err := UntarNested (bytes .NewReader (data ), installDir , false , true ); err == nil {
152+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , true ); err == nil {
153153 t .Fatal ("want error, got nil" )
154154 }
155155
@@ -188,7 +188,7 @@ func Test_UntarNested_RejectsSymlinkTargetViaPreExistingSymlink(t *testing.T) {
188188 {hdr : tar.Header {Name : "planted" , Typeflag : tar .TypeSymlink , Linkname : "safe/file" , Mode : 0777 }},
189189 })
190190
191- if err := UntarNested (bytes .NewReader (data ), installDir , false , true ); err == nil {
191+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , true ); err == nil {
192192 t .Fatalf ("expected extraction to be rejected, got nil error" )
193193 }
194194
@@ -213,7 +213,7 @@ func Test_UntarNested_AllowsValidNestedFiles(t *testing.T) {
213213 {hdr : tar.Header {Name : "README.md" , Typeflag : tar .TypeReg , Mode : 0644 }, body : []byte ("hello\n " )},
214214 })
215215
216- if err := UntarNested (bytes .NewReader (data ), installDir , false , true ); err != nil {
216+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , true ); err != nil {
217217 t .Fatalf ("expected clean extraction, got: %v" , err )
218218 }
219219 for _ , rel := range []string {"bin/tool" , "README.md" } {
@@ -238,7 +238,7 @@ func Test_UntarNested_AllowsWriteThroughInternalSymlink(t *testing.T) {
238238 {hdr : tar.Header {Name : "link/file.txt" , Typeflag : tar .TypeReg , Mode : 0644 }, body : []byte ("hello\n " )},
239239 })
240240
241- if err := UntarNested (bytes .NewReader (data ), installDir , false , true ); err != nil {
241+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , true ); err != nil {
242242 t .Fatalf ("expected clean extraction with write through internal symlink, got: %v" , err )
243243 }
244244 if _ , err := os .Stat (filepath .Join (installDir , "subdir" , "file.txt" )); err != nil {
@@ -260,7 +260,7 @@ func Test_UntarNested_AllowsWriteThroughInternalSymlinkDir(t *testing.T) {
260260 {hdr : tar.Header {Name : "link/newdir" , Typeflag : tar .TypeDir , Mode : 0755 }},
261261 })
262262
263- if err := UntarNested (bytes .NewReader (data ), installDir , false , true ); err != nil {
263+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , true ); err != nil {
264264 t .Fatalf ("expected clean extraction with dir write-through internal symlink, got: %v" , err )
265265 }
266266 if _ , err := os .Stat (filepath .Join (installDir , "subdir" , "newdir" )); err != nil {
@@ -296,7 +296,7 @@ func Test_UntarNested_PreExistingSymlinkDoesNotCreateDirOutsideRoot(t *testing.T
296296 {hdr : tar.Header {Name : "link/subdir/escape.txt" , Typeflag : tar .TypeReg , Mode : 0644 }, body : []byte ("escaped\n " )},
297297 })
298298
299- if err := UntarNested (bytes .NewReader (data ), installDir , false , true ); err == nil {
299+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , true ); err == nil {
300300 t .Fatal ("want error, got nil" )
301301 }
302302
@@ -332,7 +332,7 @@ func Test_UntarNested_RejectsLeafSymlinkWriteThrough(t *testing.T) {
332332 {hdr : tar.Header {Name : "evil" , Typeflag : tar .TypeReg , Mode : 0644 }, body : []byte ("HACKED" )},
333333 })
334334
335- if err := UntarNested (bytes .NewReader (data ), installDir , false , true ); err == nil {
335+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , true ); err == nil {
336336 t .Fatal ("want error, got nil" )
337337 }
338338
@@ -354,7 +354,7 @@ func Test_UntarNested_AllowsValidInternalSymlink(t *testing.T) {
354354 {hdr : tar.Header {Name : "tool" , Typeflag : tar .TypeSymlink , Linkname : "tool-v1" , Mode : 0777 }},
355355 })
356356
357- if err := UntarNested (bytes .NewReader (data ), installDir , false , true ); err != nil {
357+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , true ); err != nil {
358358 t .Fatalf ("expected clean extraction with internal symlink, got: %v" , err )
359359 }
360360 linkPath := filepath .Join (installDir , "tool" )
@@ -381,7 +381,7 @@ func Test_UntarNested_CreatesParentDirForSymlink(t *testing.T) {
381381 {hdr : tar.Header {Name : "nested/link" , Typeflag : tar .TypeSymlink , Linkname : "tool-v1" , Mode : 0777 }},
382382 })
383383
384- if err := UntarNested (bytes .NewReader (data ), installDir , false , true ); err != nil {
384+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , true ); err != nil {
385385 t .Fatalf ("expected clean extraction with on-demand parent dir for symlink, got: %v" , err )
386386 }
387387 linkPath := filepath .Join (installDir , "nested" , "link" )
@@ -393,3 +393,25 @@ func Test_UntarNested_CreatesParentDirForSymlink(t *testing.T) {
393393 t .Fatalf ("expected %q to be a symlink" , linkPath )
394394 }
395395}
396+
397+ // When allowSymlinks is false, any symlink entry in the tar must be rejected,
398+ // even when the link target stays safely within root.
399+ func Test_UntarNested_RejectsSymlinkWhenDisabled (t * testing.T ) {
400+ installDir , err := os .MkdirTemp ("" , "arkade-untar-*" )
401+ if err != nil {
402+ t .Fatal (err )
403+ }
404+ defer os .RemoveAll (installDir )
405+
406+ data := buildTar (t , []tarEntry {
407+ {hdr : tar.Header {Name : "tool-v1" , Typeflag : tar .TypeReg , Mode : 0755 }, body : []byte ("bin\n " )},
408+ {hdr : tar.Header {Name : "tool" , Typeflag : tar .TypeSymlink , Linkname : "tool-v1" , Mode : 0777 }},
409+ })
410+
411+ if err := UntarNested (bytes .NewReader (data ), installDir , false , true , false ); err == nil {
412+ t .Fatalf ("expected error when extracting symlink with symlinks disabled, got nil" )
413+ }
414+ if _ , err := os .Lstat (filepath .Join (installDir , "tool" )); err == nil {
415+ t .Fatalf ("expected symlink not to be created when symlinks disabled" )
416+ }
417+ }
0 commit comments