github.com/anthropics/anthropic-sdk-go@v1.70.1/tools/agenttoolset/skillarchive_test.go (about)

     1  package agenttoolset
     2  
     3  import (
     4  	"archive/tar"
     5  	"archive/zip"
     6  	"bytes"
     7  	"compress/gzip"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func zipBytes(t *testing.T, entries map[string]string) []byte {
    19  	t.Helper()
    20  	var buf bytes.Buffer
    21  	zw := zip.NewWriter(&buf)
    22  	for name, body := range entries {
    23  		w, err := zw.Create(name)
    24  		if err != nil {
    25  			t.Fatalf("zip create %q: %v", name, err)
    26  		}
    27  		if _, err := w.Write([]byte(body)); err != nil {
    28  			t.Fatalf("zip write %q: %v", name, err)
    29  		}
    30  	}
    31  	if err := zw.Close(); err != nil {
    32  		t.Fatalf("zip close: %v", err)
    33  	}
    34  	return buf.Bytes()
    35  }
    36  
    37  func tarGzBytes(t *testing.T, entries map[string]string) []byte {
    38  	t.Helper()
    39  	var buf bytes.Buffer
    40  	gw := gzip.NewWriter(&buf)
    41  	tw := tar.NewWriter(gw)
    42  	for name, body := range entries {
    43  		if err := tw.WriteHeader(&tar.Header{Name: name, Mode: 0o644, Size: int64(len(body)), Typeflag: tar.TypeReg}); err != nil {
    44  			t.Fatalf("tar header %q: %v", name, err)
    45  		}
    46  		if _, err := tw.Write([]byte(body)); err != nil {
    47  			t.Fatalf("tar write %q: %v", name, err)
    48  		}
    49  	}
    50  	if err := tw.Close(); err != nil {
    51  		t.Fatalf("tar close: %v", err)
    52  	}
    53  	if err := gw.Close(); err != nil {
    54  		t.Fatalf("gzip close: %v", err)
    55  	}
    56  	return buf.Bytes()
    57  }
    58  
    59  // extractArchiveBytes writes archive bytes to a temp file and extracts it,
    60  // exercising the on-disk extraction path the real skill download now uses.
    61  func extractArchiveBytes(t *testing.T, data []byte, dest string) error {
    62  	t.Helper()
    63  	path := filepath.Join(t.TempDir(), "archive")
    64  	if err := os.WriteFile(path, data, 0o644); err != nil {
    65  		t.Fatalf("write archive: %v", err)
    66  	}
    67  	return extractSkillArchive(path, dest)
    68  }
    69  
    70  func TestExtractSkillArchive_Zip(t *testing.T) {
    71  	dest := filepath.Join(t.TempDir(), "s")
    72  	if err := extractArchiveBytes(t, zipBytes(t, map[string]string{"SKILL.md": "hi", "d/x.txt": "x"}), dest); err != nil {
    73  		t.Fatalf("extract: %v", err)
    74  	}
    75  	if got, _ := os.ReadFile(filepath.Join(dest, "SKILL.md")); string(got) != "hi" {
    76  		t.Fatalf("SKILL.md = %q", got)
    77  	}
    78  	if got, _ := os.ReadFile(filepath.Join(dest, "d", "x.txt")); string(got) != "x" {
    79  		t.Fatalf("d/x.txt = %q", got)
    80  	}
    81  }
    82  
    83  func TestExtractSkillArchive_TarGz(t *testing.T) {
    84  	dest := filepath.Join(t.TempDir(), "s")
    85  	if err := extractArchiveBytes(t, tarGzBytes(t, map[string]string{"SKILL.md": "tarred", "sub/y": "Y"}), dest); err != nil {
    86  		t.Fatalf("extract: %v", err)
    87  	}
    88  	if got, _ := os.ReadFile(filepath.Join(dest, "SKILL.md")); string(got) != "tarred" {
    89  		t.Fatalf("SKILL.md = %q", got)
    90  	}
    91  	if got, _ := os.ReadFile(filepath.Join(dest, "sub", "y")); string(got) != "Y" {
    92  		t.Fatalf("sub/y = %q", got)
    93  	}
    94  }
    95  
    96  func TestArchiveTopDir(t *testing.T) {
    97  	cases := []struct {
    98  		desc  string
    99  		names []string
   100  		want  string
   101  	}{
   102  		{"single wrapped skill", []string{"pdf/SKILL.md", "pdf/scripts/x.py"}, "pdf"},
   103  		{"single file under wrapper", []string{"pdf/SKILL.md"}, "pdf"},
   104  		{"dot-prefixed (tar -C dir .)", []string{"./", "./pdf/", "./pdf/SKILL.md", "./pdf/scripts/x"}, "pdf"},
   105  		{"flat archive", []string{"SKILL.md", "scripts/x.py"}, ""},
   106  		{"multiple roots", []string{"a/x", "b/y"}, ""},
   107  		{"only bare top dir", []string{"pdf/"}, ""},
   108  		{"empty", nil, ""},
   109  	}
   110  	for _, c := range cases {
   111  		t.Run(c.desc, func(t *testing.T) {
   112  			if got := archiveTopDir(c.names); got != c.want {
   113  				t.Errorf("archiveTopDir(%v) = %q, want %q", c.names, got, c.want)
   114  			}
   115  		})
   116  	}
   117  }
   118  
   119  func TestStripTopDir(t *testing.T) {
   120  	cases := []struct{ name, top, want string }{
   121  		{"pdf/SKILL.md", "pdf", "SKILL.md"},
   122  		{"pdf/scripts/x.py", "pdf", "scripts/x.py"},
   123  		{"pdf", "pdf", ""},                    // bare top-dir entry
   124  		{"./pdf/SKILL.md", "pdf", "SKILL.md"}, // dot-prefixed (tar -C dir .)
   125  		{"./pdf/", "pdf", ""},
   126  		{"SKILL.md", "", "SKILL.md"}, // no wrapper -> unchanged
   127  		{"other/x", "pdf", "other/x"},
   128  	}
   129  	for _, c := range cases {
   130  		if got := stripTopDir(c.name, c.top); got != c.want {
   131  			t.Errorf("stripTopDir(%q,%q) = %q, want %q", c.name, c.top, got, c.want)
   132  		}
   133  	}
   134  }
   135  
   136  // Skill bundles are wrapped in a directory named after the skill; extraction
   137  // must strip it so files land at dest/SKILL.md, not the doubled
   138  // dest/<skill>/SKILL.md.
   139  func TestExtractSkillArchive_StripsWrapperDir(t *testing.T) {
   140  	for _, tc := range []struct {
   141  		kind string
   142  		make func(*testing.T, map[string]string) []byte
   143  	}{
   144  		{"zip", zipBytes},
   145  		{"targz", tarGzBytes},
   146  	} {
   147  		t.Run(tc.kind, func(t *testing.T) {
   148  			dest := filepath.Join(t.TempDir(), "skills", "pdf")
   149  			// Dot-prefixed names mirror a real `tar -C dir .` / zip of "."
   150  			// bundle; the wrapper must still be detected and stripped.
   151  			data := tc.make(t, map[string]string{
   152  				"./pdf/SKILL.md":       "# PDF",
   153  				"./pdf/scripts/run.py": "print(1)",
   154  			})
   155  			if err := extractArchiveBytes(t, data, dest); err != nil {
   156  				t.Fatalf("extract: %v", err)
   157  			}
   158  			if got, _ := os.ReadFile(filepath.Join(dest, "SKILL.md")); string(got) != "# PDF" {
   159  				t.Fatalf("SKILL.md = %q, want %q", got, "# PDF")
   160  			}
   161  			if got, _ := os.ReadFile(filepath.Join(dest, "scripts", "run.py")); string(got) != "print(1)" {
   162  				t.Fatalf("scripts/run.py = %q", got)
   163  			}
   164  			if _, err := os.Stat(filepath.Join(dest, "pdf")); err == nil {
   165  				t.Fatal("wrapper dir was not stripped (skills/pdf/pdf/ doubling)")
   166  			}
   167  		})
   168  	}
   169  }
   170  
   171  // setSkillArchiveLimits temporarily lowers the extraction bounds so tests can
   172  // exercise the limits without building 10K-member / 1 GiB archives. Restored
   173  // via t.Cleanup; tests that use this must not run in parallel.
   174  func setSkillArchiveLimits(t *testing.T, members int, byteLimit int64) {
   175  	t.Helper()
   176  	oldM, oldB := skillArchiveMaxMembers, skillArchiveMaxBytes
   177  	skillArchiveMaxMembers, skillArchiveMaxBytes = members, byteLimit
   178  	t.Cleanup(func() { skillArchiveMaxMembers, skillArchiveMaxBytes = oldM, oldB })
   179  }
   180  
   181  // requireDestAbsent asserts the extraction destination does not exist — a
   182  // failed extraction must remove the partially written directory rather than
   183  // leave a half-extracted skill on disk.
   184  func requireDestAbsent(t *testing.T, dest string) {
   185  	t.Helper()
   186  	if _, err := os.Stat(dest); !os.IsNotExist(err) {
   187  		t.Fatalf("dest %q should have been removed after extraction failure, stat err = %v", dest, err)
   188  	}
   189  }
   190  
   191  func TestExtractSkillArchive_FailureRemovesDest(t *testing.T) {
   192  	cases := []struct {
   193  		desc string
   194  		data func(t *testing.T) []byte
   195  	}{
   196  		{
   197  			// Member-count cap fires before any file is written (zip pass 1).
   198  			desc: "over member limit",
   199  			data: func(t *testing.T) []byte {
   200  				return zipBytes(t, map[string]string{"a": "1", "b": "2", "c": "3"})
   201  			},
   202  		},
   203  		{
   204  			// Byte budget fires mid-extraction, after some files have already
   205  			// been written into dest.
   206  			desc: "over byte budget mid-extraction",
   207  			data: func(t *testing.T) []byte {
   208  				return zipBytes(t, map[string]string{"a": "abcdef", "b": "ghijkl"})
   209  			},
   210  		},
   211  	}
   212  	for _, tc := range cases {
   213  		t.Run(tc.desc, func(t *testing.T) {
   214  			setSkillArchiveLimits(t, 2, 8)
   215  			dest := filepath.Join(t.TempDir(), "s")
   216  			if err := extractArchiveBytes(t, tc.data(t), dest); err == nil {
   217  				t.Fatal("expected extraction to fail, got nil")
   218  			}
   219  			requireDestAbsent(t, dest)
   220  		})
   221  	}
   222  }
   223  
   224  // TestExtractTar_Pass1MemberBound proves the member-count cap fires during
   225  // pass 1 (top-dir detection) rather than only in pass 2. The archive ends in a
   226  // garbage block instead of the usual end-of-archive trailer: a bounded pass 1
   227  // stops at member N+1 with the "exceeds N members" error, while an unbounded
   228  // pass 1 would read through to the garbage and surface a tar parse error.
   229  func TestExtractTar_Pass1MemberBound(t *testing.T) {
   230  	setSkillArchiveLimits(t, 3, skillArchiveMaxBytes)
   231  
   232  	var buf bytes.Buffer
   233  	tw := tar.NewWriter(&buf)
   234  	for i := 0; i <= skillArchiveMaxMembers; i++ {
   235  		hdr := &tar.Header{Name: fmt.Sprintf("f%d", i), Mode: 0o644, Size: 0, Typeflag: tar.TypeReg}
   236  		if err := tw.WriteHeader(hdr); err != nil {
   237  			t.Fatalf("tar header: %v", err)
   238  		}
   239  	}
   240  	if err := tw.Flush(); err != nil {
   241  		t.Fatalf("tar flush: %v", err)
   242  	}
   243  	buf.Write(bytes.Repeat([]byte{0xFF}, 512))
   244  
   245  	dest := filepath.Join(t.TempDir(), "s")
   246  	err := extractArchiveBytes(t, buf.Bytes(), dest)
   247  	if err == nil {
   248  		t.Fatal("expected member-count error, got nil")
   249  	}
   250  	if !strings.Contains(err.Error(), "members") {
   251  		t.Fatalf("expected pass-1 member-count error, got: %v", err)
   252  	}
   253  	requireDestAbsent(t, dest)
   254  }
   255  
   256  // TestExtractTar_Pass1ByteBound proves the declared-size byte budget fires
   257  // during pass 1. The budget is computed from hdr.Size alone, so the test only
   258  // needs entries whose declared sizes sum past the (lowered) limit. As above,
   259  // the trailing garbage block proves pass 1 stopped at the cap rather than
   260  // reading through the whole archive.
   261  func TestExtractTar_Pass1ByteBound(t *testing.T) {
   262  	setSkillArchiveLimits(t, skillArchiveMaxMembers, 8)
   263  
   264  	var buf bytes.Buffer
   265  	tw := tar.NewWriter(&buf)
   266  	body := []byte("xxxxx") // 5 bytes; 3 entries declare 15 > 8
   267  	for i := 0; i < 3; i++ {
   268  		hdr := &tar.Header{Name: fmt.Sprintf("f%d", i), Mode: 0o644, Size: int64(len(body)), Typeflag: tar.TypeReg}
   269  		if err := tw.WriteHeader(hdr); err != nil {
   270  			t.Fatalf("tar header: %v", err)
   271  		}
   272  		if _, err := tw.Write(body); err != nil {
   273  			t.Fatalf("tar write: %v", err)
   274  		}
   275  	}
   276  	if err := tw.Flush(); err != nil {
   277  		t.Fatalf("tar flush: %v", err)
   278  	}
   279  	buf.Write(bytes.Repeat([]byte{0xFF}, 512))
   280  
   281  	dest := filepath.Join(t.TempDir(), "s")
   282  	err := extractArchiveBytes(t, buf.Bytes(), dest)
   283  	if err == nil {
   284  		t.Fatal("expected byte-budget error, got nil")
   285  	}
   286  	if !strings.Contains(err.Error(), "bytes") {
   287  		t.Fatalf("expected pass-1 byte-budget error, got: %v", err)
   288  	}
   289  	requireDestAbsent(t, dest)
   290  }
   291  
   292  type tarMember struct {
   293  	hdr  tar.Header
   294  	body string
   295  }
   296  
   297  // rawTarBytes writes members in order as an uncompressed tar stream. A member
   298  // whose Typeflag is tar.TypeRegA is written as TypeReg and then patched back
   299  // to the legacy '\x00' flag on the wire, since tar.Writer refuses to emit it.
   300  func rawTarBytes(t *testing.T, members ...tarMember) []byte {
   301  	t.Helper()
   302  	var buf bytes.Buffer
   303  	tw := tar.NewWriter(&buf)
   304  	var legacyOffsets []int
   305  	for _, m := range members {
   306  		hdr := m.hdr
   307  		hdr.Size = int64(len(m.body))
   308  		if hdr.Typeflag == tar.TypeRegA {
   309  			hdr.Typeflag = tar.TypeReg
   310  			require.NoError(t, tw.Flush())
   311  			legacyOffsets = append(legacyOffsets, buf.Len())
   312  		}
   313  		require.NoError(t, tw.WriteHeader(&hdr), "tar header %q", hdr.Name)
   314  		if len(m.body) > 0 {
   315  			_, err := tw.Write([]byte(m.body))
   316  			require.NoError(t, err, "tar write %q", hdr.Name)
   317  		}
   318  	}
   319  	require.NoError(t, tw.Close())
   320  	out := buf.Bytes()
   321  	for _, off := range legacyOffsets {
   322  		block := out[off : off+512]
   323  		block[156] = tar.TypeRegA
   324  		copy(block[148:156], "        ")
   325  		var sum int
   326  		for _, b := range block {
   327  			sum += int(b)
   328  		}
   329  		copy(block[148:156], fmt.Sprintf("%06o\x00 ", sum))
   330  	}
   331  	return out
   332  }
   333  
   334  type zipMember struct {
   335  	hdr  zip.FileHeader
   336  	body string
   337  }
   338  
   339  // Unix st_mode type bits as they appear in a zip entry's external attributes.
   340  const (
   341  	zipUnixHost = 3 << 8
   342  	sIFDIR      = 0o040000
   343  	sIFIFO      = 0o010000
   344  	sIFLNK      = 0o120000
   345  )
   346  
   347  func rawZipBytes(t *testing.T, members ...zipMember) []byte {
   348  	t.Helper()
   349  	var buf bytes.Buffer
   350  	zw := zip.NewWriter(&buf)
   351  	for _, m := range members {
   352  		hdr := m.hdr
   353  		w, err := zw.CreateHeader(&hdr)
   354  		require.NoError(t, err, "zip header %q", hdr.Name)
   355  		_, err = w.Write([]byte(m.body))
   356  		require.NoError(t, err, "zip write %q", hdr.Name)
   357  	}
   358  	require.NoError(t, zw.Close())
   359  	return buf.Bytes()
   360  }
   361  
   362  // requireOnlyPlainEntries asserts that every entry under dir, checked with
   363  // lstat, is a regular file or a directory.
   364  func requireOnlyPlainEntries(t *testing.T, dir string) {
   365  	t.Helper()
   366  	require.NoError(t, filepath.WalkDir(dir, func(p string, d os.DirEntry, err error) error {
   367  		require.NoError(t, err)
   368  		require.True(t, d.Type().IsRegular() || d.IsDir(), "%s is neither a regular file nor a directory (%v)", p, d.Type())
   369  		return nil
   370  	}))
   371  }
   372  
   373  func TestExtractSkillArchive_SkipsSpecialMembers(t *testing.T) {
   374  	tests := []struct {
   375  		description string
   376  		data        func(t *testing.T) []byte
   377  		wantFiles   map[string]string
   378  		wantDirs    []string
   379  		wantModes   map[string]os.FileMode
   380  		wantAbsent  []string
   381  	}{
   382  		{
   383  			description: "tar symlink, hardlink, FIFO and device members are skipped and the rest extracted with the wrapper stripped",
   384  			data: func(t *testing.T) []byte {
   385  				return rawTarBytes(t,
   386  					tarMember{hdr: tar.Header{Name: "pdf/SKILL.md", Typeflag: tar.TypeReg}, body: "# PDF"},
   387  					tarMember{hdr: tar.Header{Name: "pdf/scripts/run.sh", Typeflag: tar.TypeReg, Mode: 0o755}, body: "#!/bin/sh"},
   388  					tarMember{hdr: tar.Header{Name: "pdf/old", Typeflag: tar.TypeRegA}, body: "legacy"},
   389  					tarMember{hdr: tar.Header{Name: "pdf/abs", Typeflag: tar.TypeSymlink, Linkname: "/etc/passwd"}},
   390  					tarMember{hdr: tar.Header{Name: "pdf/rel", Typeflag: tar.TypeSymlink, Linkname: "SKILL.md"}},
   391  					tarMember{hdr: tar.Header{Name: "pdf/hl", Typeflag: tar.TypeLink, Linkname: "pdf/SKILL.md"}},
   392  					tarMember{hdr: tar.Header{Name: "pdf/fifo", Typeflag: tar.TypeFifo}},
   393  					tarMember{hdr: tar.Header{Name: "pdf/dev", Typeflag: tar.TypeChar, Devmajor: 1, Devminor: 3}},
   394  				)
   395  			},
   396  			wantFiles:  map[string]string{"SKILL.md": "# PDF", "scripts/run.sh": "#!/bin/sh", "old": "legacy"},
   397  			wantModes:  map[string]os.FileMode{"scripts/run.sh": 0o755},
   398  			wantAbsent: []string{"abs", "rel", "hl", "fifo", "dev", "pdf"},
   399  		},
   400  		{
   401  			description: "a top-level tar symlink does not defeat wrapper stripping",
   402  			data: func(t *testing.T) []byte {
   403  				return rawTarBytes(t,
   404  					tarMember{hdr: tar.Header{Name: "link", Typeflag: tar.TypeSymlink, Linkname: "/tmp"}},
   405  					tarMember{hdr: tar.Header{Name: "pdf/SKILL.md", Typeflag: tar.TypeReg}, body: "# PDF"},
   406  				)
   407  			},
   408  			wantFiles:  map[string]string{"SKILL.md": "# PDF"},
   409  			wantAbsent: []string{"link", "pdf"},
   410  		},
   411  		{
   412  			description: "a git-archive style PAX global header is not written out as a file and does not defeat wrapper stripping",
   413  			data: func(t *testing.T) []byte {
   414  				return rawTarBytes(t,
   415  					tarMember{hdr: tar.Header{Name: "pax_global_header", Typeflag: tar.TypeXGlobalHeader, PAXRecords: map[string]string{"comment": "0123abcd"}, Format: tar.FormatPAX}},
   416  					tarMember{hdr: tar.Header{Name: "pdf/SKILL.md", Typeflag: tar.TypeReg}, body: "# PDF"},
   417  				)
   418  			},
   419  			wantFiles:  map[string]string{"SKILL.md": "# PDF"},
   420  			wantAbsent: []string{"pax_global_header", "pdf"},
   421  		},
   422  		{
   423  			description: "a tar symlink named ../x is skipped and nothing named x appears beside dest",
   424  			data: func(t *testing.T) []byte {
   425  				return rawTarBytes(t,
   426  					tarMember{hdr: tar.Header{Name: "../x", Typeflag: tar.TypeSymlink, Linkname: "/etc"}},
   427  					tarMember{hdr: tar.Header{Name: "pdf/SKILL.md", Typeflag: tar.TypeReg}, body: "# PDF"},
   428  				)
   429  			},
   430  			wantFiles:  map[string]string{"SKILL.md": "# PDF"},
   431  			wantAbsent: []string{"x", "../x", "pdf"},
   432  		},
   433  		{
   434  			description: "zip Unix-host symlink and FIFO entries are skipped, not written out as regular files",
   435  			data: func(t *testing.T) []byte {
   436  				return rawZipBytes(t,
   437  					zipMember{hdr: zip.FileHeader{Name: "pdf/SKILL.md"}, body: "# PDF"},
   438  					zipMember{hdr: zip.FileHeader{Name: "pdf/lnk", CreatorVersion: zipUnixHost, ExternalAttrs: (sIFLNK | 0o777) << 16}, body: "/etc/passwd"},
   439  					zipMember{hdr: zip.FileHeader{Name: "pdf/fifo", CreatorVersion: zipUnixHost, ExternalAttrs: (sIFIFO | 0o644) << 16}},
   440  				)
   441  			},
   442  			wantFiles:  map[string]string{"SKILL.md": "# PDF"},
   443  			wantAbsent: []string{"lnk", "fifo", "pdf"},
   444  		},
   445  		{
   446  			description: "zip symlink type bits on a non-Unix-host entry are not a mode, so the entry is a regular file",
   447  			data: func(t *testing.T) []byte {
   448  				return rawZipBytes(t,
   449  					zipMember{hdr: zip.FileHeader{Name: "pdf/SKILL.md"}, body: "# PDF"},
   450  					zipMember{hdr: zip.FileHeader{Name: "pdf/lnk", CreatorVersion: 0, ExternalAttrs: (sIFLNK | 0o777) << 16}, body: "/etc/passwd"},
   451  				)
   452  			},
   453  			wantFiles:  map[string]string{"SKILL.md": "# PDF", "lnk": "/etc/passwd"},
   454  			wantAbsent: []string{"pdf"},
   455  		},
   456  		{
   457  			description: "zip Unix-host entries with zero type bits are files and S_IFDIR without a trailing slash is a directory",
   458  			data: func(t *testing.T) []byte {
   459  				return rawZipBytes(t,
   460  					zipMember{hdr: zip.FileHeader{Name: "pdf/SKILL.md", CreatorVersion: zipUnixHost, ExternalAttrs: 0o600 << 16}, body: "# PDF"},
   461  					zipMember{hdr: zip.FileHeader{Name: "pdf/run.sh", CreatorVersion: zipUnixHost, ExternalAttrs: 0o755 << 16}, body: "#!/bin/sh"},
   462  					zipMember{hdr: zip.FileHeader{Name: "pdf/sub", CreatorVersion: zipUnixHost, ExternalAttrs: (sIFDIR | 0o755) << 16}},
   463  				)
   464  			},
   465  			wantFiles:  map[string]string{"SKILL.md": "# PDF", "run.sh": "#!/bin/sh"},
   466  			wantModes:  map[string]os.FileMode{"run.sh": 0o755},
   467  			wantDirs:   []string{"sub"},
   468  			wantAbsent: []string{"pdf"},
   469  		},
   470  	}
   471  
   472  	for _, tc := range tests {
   473  		t.Run(tc.description, func(t *testing.T) {
   474  			parent := t.TempDir()
   475  			dest := filepath.Join(parent, "s")
   476  			require.NoError(t, extractArchiveBytes(t, tc.data(t), dest))
   477  
   478  			for rel, want := range tc.wantFiles {
   479  				fi, err := os.Lstat(filepath.Join(dest, rel))
   480  				require.NoError(t, err)
   481  				require.True(t, fi.Mode().IsRegular(), "%s must be a regular file, got %v", rel, fi.Mode())
   482  				got, err := os.ReadFile(filepath.Join(dest, rel))
   483  				require.NoError(t, err)
   484  				require.Equal(t, want, string(got), rel)
   485  			}
   486  			if runtime.GOOS != "windows" {
   487  				for rel, want := range tc.wantModes {
   488  					fi, err := os.Lstat(filepath.Join(dest, rel))
   489  					require.NoError(t, err)
   490  					require.Equal(t, want, fi.Mode().Perm(), rel)
   491  				}
   492  			}
   493  			for _, rel := range tc.wantDirs {
   494  				fi, err := os.Lstat(filepath.Join(dest, rel))
   495  				require.NoError(t, err)
   496  				require.True(t, fi.IsDir(), "%s must be a directory, got %v", rel, fi.Mode())
   497  			}
   498  			for _, rel := range tc.wantAbsent {
   499  				_, err := os.Lstat(filepath.Join(dest, rel))
   500  				require.ErrorIs(t, err, os.ErrNotExist, "%s must not exist", rel)
   501  			}
   502  			requireOnlyPlainEntries(t, dest)
   503  			siblings, err := os.ReadDir(parent)
   504  			require.NoError(t, err)
   505  			require.Len(t, siblings, 1, "nothing may be created outside dest")
   506  		})
   507  	}
   508  }
   509  
   510  func TestExtractSkillArchive_ConfinesTraversal(t *testing.T) {
   511  	parent := t.TempDir()
   512  	dest := filepath.Join(parent, "nested", "s")
   513  	if err := extractArchiveBytes(t, zipBytes(t, map[string]string{"../evil.txt": "pwn", "ok.txt": "fine"}), dest); err != nil {
   514  		t.Fatalf("extract: %v", err)
   515  	}
   516  	if _, err := os.Stat(filepath.Join(parent, "nested", "evil.txt")); err == nil {
   517  		t.Fatal("zip-slip member escaped the destination directory")
   518  	}
   519  	if got, _ := os.ReadFile(filepath.Join(dest, "evil.txt")); string(got) != "pwn" {
   520  		t.Fatalf("traversal member not confined into dest: %q", got)
   521  	}
   522  	if got, _ := os.ReadFile(filepath.Join(dest, "ok.txt")); string(got) != "fine" {
   523  		t.Fatalf("ok.txt = %q", got)
   524  	}
   525  }