diff --git a/evil_generator/main.go b/evil_generator/main.go
new file mode 100644
index 0000000..4e93732
--- /dev/null
+++ b/evil_generator/main.go
@@ -0,0 +1,106 @@
+// This utility is used to generate the archives used as testdata for zipslip vulnerability
+package main
+
+//go:generate go run main.go ../testdata/zipslip
+
+import (
+	"archive/tar"
+	"archive/zip"
+	"bytes"
+	"log"
+	"os"
+
+	"github.com/arduino/go-paths-helper"
+)
+
+func main() {
+	if len(os.Args) != 2 {
+		log.Fatal("Missing output directory")
+	}
+	outputDir := paths.New(os.Args[1])
+	if outputDir.IsNotDir() {
+		log.Fatalf("Output path %s is not a directory", outputDir)
+	}
+
+	evilPathTraversalFiles := []string{
+		"..",
+		"../../../../../../../../../../../../../../../../../../../../tmp/evil.txt",
+		"some/path/../../../../../../../../../../../../../../../../../../../../tmp/evil.txt",
+		"/../../../../../../../../../../../../../../../../../../../../tmp/evil.txt",
+		"/some/path/../../../../../../../../../../../../../../../../../../../../tmp/evil.txt",
+	}
+	winSpecificPathTraversalFiles := []string{
+		"..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\tmp\\evil.txt",
+		"some\\path\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\tmp\\evil.txt",
+		"\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\tmp\\evil.txt",
+		"\\some\\path\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\tmp\\evil.txt",
+	}
+	winSpecificPathTraversalFiles = append(winSpecificPathTraversalFiles, evilPathTraversalFiles...)
+
+	// Generate evil zip
+	{
+		buf := new(bytes.Buffer)
+		w := zip.NewWriter(buf)
+		for _, file := range winSpecificPathTraversalFiles {
+			if f, err := w.Create(file); err != nil {
+				log.Fatal(err)
+			} else if _, err = f.Write([]byte("TEST")); err != nil {
+				log.Fatal(err)
+			}
+		}
+		if err := w.Close(); err != nil {
+			log.Fatal(err)
+		}
+		if err := outputDir.Join("evil.zip").WriteFile(buf.Bytes()); err != nil {
+			log.Fatal(err)
+		}
+	}
+
+	// Generate evil tar
+	{
+		buf := new(bytes.Buffer)
+		w := tar.NewWriter(buf)
+		for _, file := range evilPathTraversalFiles {
+			if err := w.WriteHeader(&tar.Header{
+				Name: file,
+				Size: 4,
+				Mode: 0666,
+			}); err != nil {
+				log.Fatal(err)
+			}
+			if _, err := w.Write([]byte("TEST")); err != nil {
+				log.Fatal(err)
+			}
+		}
+		if err := w.Close(); err != nil {
+			log.Fatal(err)
+		}
+		if err := outputDir.Join("evil.tar").WriteFile(buf.Bytes()); err != nil {
+			log.Fatal(err)
+		}
+	}
+
+	// Generate evil tar for windows
+	{
+		buf := new(bytes.Buffer)
+		w := tar.NewWriter(buf)
+		for _, file := range winSpecificPathTraversalFiles {
+			if err := w.WriteHeader(&tar.Header{
+				Name: file,
+				Size: 4,
+				Mode: 0666,
+			}); err != nil {
+				log.Fatal(err)
+			}
+			if _, err := w.Write([]byte("TEST")); err != nil {
+				log.Fatal(err)
+			}
+		}
+		if err := w.Close(); err != nil {
+			log.Fatal(err)
+		}
+		if err := outputDir.Join("evil-win.tar").WriteFile(buf.Bytes()); err != nil {
+			log.Fatal(err)
+		}
+	}
+}
diff --git a/extractor.go b/extractor.go
index 6bf9085..4fbfc29 100644
--- a/extractor.go
+++ b/extractor.go
@@ -132,7 +132,10 @@ func (e *Extractor) Tar(ctx context.Context, body io.Reader, location string, re
 			continue
 		}
 
-		path = filepath.Join(location, path)
+		if path, err = safeJoin(location, path); err != nil {
+			continue
+		}
+
 		info := header.FileInfo()
 
 		switch header.Typeflag {
@@ -232,7 +235,10 @@ func (e *Extractor) Zip(ctx context.Context, body io.Reader, location string, re
 			continue
 		}
 
-		path = filepath.Join(location, path)
+		if path, err = safeJoin(location, path); err != nil {
+			continue
+		}
+
 		info := header.FileInfo()
 
 		switch {
@@ -316,3 +322,16 @@ func match(r io.Reader) (io.Reader, types.Type, error) {
 
 	return r, typ, err
 }
+
+// safeJoin performs a filepath.Join of 'parent' and 'subdir' but returns an error
+// if the resulting path points outside of 'parent'.
+func safeJoin(parent, subdir string) (string, error) {
+	res := filepath.Join(parent, subdir)
+	if !strings.HasSuffix(parent, string(os.PathSeparator)) {
+		parent += string(os.PathSeparator)
+	}
+	if !strings.HasPrefix(res, parent) {
+		return res, errors.Errorf("unsafe path join: '%s' with '%s'", parent, subdir)
+	}
+	return res, nil
+}
diff --git a/extractor_test.go b/extractor_test.go
index 60ce252..a56a863 100644
--- a/extractor_test.go
+++ b/extractor_test.go
@@ -3,12 +3,15 @@ package extract_test
 import (
 	"bytes"
 	"context"
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"runtime"
 	"testing"
 
 	"github.com/codeclysm/extract/v3"
+	"github.com/stretchr/testify/require"
 )
 
 func TestExtractor_Tar(t *testing.T) {
@@ -77,6 +80,40 @@ func TestExtractor_Zip(t *testing.T) {
 	testWalk(t, tmp, files)
 }
 
+func TestZipSlipHardening(t *testing.T) {
+	{
+		logger := &LoggingFS{}
+		extractor := extract.Extractor{FS: logger}
+		data, err := os.Open("testdata/zipslip/evil.zip")
+		require.NoError(t, err)
+		require.NoError(t, extractor.Zip(context.Background(), data, "/tmp/test", nil))
+		require.NoError(t, data.Close())
+		fmt.Print(logger)
+		require.Empty(t, logger.Journal)
+	}
+	{
+		logger := &LoggingFS{}
+		extractor := extract.Extractor{FS: logger}
+		data, err := os.Open("testdata/zipslip/evil.tar")
+		require.NoError(t, err)
+		require.NoError(t, extractor.Tar(context.Background(), data, "/tmp/test", nil))
+		require.NoError(t, data.Close())
+		fmt.Print(logger)
+		require.Empty(t, logger.Journal)
+	}
+
+	if runtime.GOOS == "windows" {
+		logger := &LoggingFS{}
+		extractor := extract.Extractor{FS: logger}
+		data, err := os.Open("testdata/zipslip/evil-win.tar")
+		require.NoError(t, err)
+		require.NoError(t, extractor.Tar(context.Background(), data, "/tmp/test", nil))
+		require.NoError(t, data.Close())
+		fmt.Print(logger)
+		require.Empty(t, logger.Journal)
+	}
+}
+
 // MockDisk is a disk that chroots to a directory
 type MockDisk struct {
 	Base string
diff --git a/go.mod b/go.mod
index 6fa9fd1..f088227 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/codeclysm/extract/v3
 go 1.14
 
 require (
+	github.com/arduino/go-paths-helper v1.2.0
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/h2non/filetype v1.0.6
 	github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5
diff --git a/go.sum b/go.sum
index 599ffb6..6b33dcb 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/arduino/go-paths-helper v1.2.0 h1:qDW93PR5IZUN/jzO4rCtexiwF8P4OIcOmcSgAYLZfY4=
+github.com/arduino/go-paths-helper v1.2.0/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
diff --git a/loggingfs_test.go b/loggingfs_test.go
new file mode 100644
index 0000000..b3d20a9
--- /dev/null
+++ b/loggingfs_test.go
@@ -0,0 +1,80 @@
+package extract_test
+
+import (
+	"fmt"
+	"os"
+)
+
+// LoggingFS is a disk that logs every operation, useful for unit-testing.
+type LoggingFS struct {
+	Journal []*LoggedOp
+}
+
+// LoggedOp is an operation logged in a LoggingFS journal.
+type LoggedOp struct {
+	Op      string
+	Path    string
+	OldPath string
+	Mode    os.FileMode
+	Flags   int
+}
+
+func (op *LoggedOp) String() string {
+	switch op.Op {
+	case "link":
+		return fmt.Sprintf("link     %s -> %s", op.Path, op.OldPath)
+	case "symlink":
+		return fmt.Sprintf("symlink  %s -> %s", op.Path, op.OldPath)
+	case "mkdirall":
+		return fmt.Sprintf("mkdirall %v %s", op.Mode, op.Path)
+	case "open":
+		return fmt.Sprintf("open     %v %s (flags=%04x)", op.Mode, op.Path, op.Flags)
+	}
+	panic("unknown LoggedOP " + op.Op)
+}
+
+func (m *LoggingFS) Link(oldname, newname string) error {
+	m.Journal = append(m.Journal, &LoggedOp{
+		Op:      "link",
+		OldPath: oldname,
+		Path:    newname,
+	})
+	return nil
+}
+
+func (m *LoggingFS) MkdirAll(path string, perm os.FileMode) error {
+	m.Journal = append(m.Journal, &LoggedOp{
+		Op:   "mkdirall",
+		Path: path,
+		Mode: perm,
+	})
+	return nil
+}
+
+func (m *LoggingFS) Symlink(oldname, newname string) error {
+	m.Journal = append(m.Journal, &LoggedOp{
+		Op:      "symlink",
+		OldPath: oldname,
+		Path:    newname,
+	})
+	return nil
+}
+
+func (m *LoggingFS) OpenFile(name string, flags int, perm os.FileMode) (*os.File, error) {
+	m.Journal = append(m.Journal, &LoggedOp{
+		Op:    "open",
+		Path:  name,
+		Mode:  perm,
+		Flags: flags,
+	})
+	return os.OpenFile(os.DevNull, flags, perm)
+}
+
+func (m *LoggingFS) String() string {
+	res := ""
+	for _, op := range m.Journal {
+		res += op.String()
+		res += "\n"
+	}
+	return res
+}
diff --git a/safejoin_test.go b/safejoin_test.go
new file mode 100644
index 0000000..66c019b
--- /dev/null
+++ b/safejoin_test.go
@@ -0,0 +1,35 @@
+package extract
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestSafeJoin(t *testing.T) {
+	ok := func(parent, subdir string) {
+		_, err := safeJoin(parent, subdir)
+		require.NoError(t, err, "joining '%s' and '%s'", parent, subdir)
+	}
+	ko := func(parent, subdir string) {
+		_, err := safeJoin(parent, subdir)
+		require.Error(t, err, "joining '%s' and '%s'", parent, subdir)
+	}
+	ok("/", "more/path")
+	ok("/path", "more/path")
+	ok("/path/", "more/path")
+	ok("/path/subdir", "more/path")
+	ok("/path/subdir/", "more/path")
+
+	ok("/", "..") // ! since we are extracting to / is ok-ish to accept ".."?
+	ko("/path", "..")
+	ko("/path/", "..")
+	ko("/path/subdir", "..")
+	ko("/path/subdir/", "..")
+
+	ok("/", "../pathpath") // ! since we are extracting to / is ok-ish to accept "../pathpath"?
+	ko("/path", "../pathpath")
+	ko("/path/", "../pathpath")
+	ko("/path/subdir", "../pathpath")
+	ko("/path/subdir/", "../pathpath")
+}
diff --git a/testdata/zipslip/evil-win.tar b/testdata/zipslip/evil-win.tar
new file mode 100644
index 0000000..b07302f
Binary files /dev/null and b/testdata/zipslip/evil-win.tar differ
diff --git a/testdata/zipslip/evil.tar b/testdata/zipslip/evil.tar
new file mode 100644
index 0000000..e42cedf
Binary files /dev/null and b/testdata/zipslip/evil.tar differ
diff --git a/testdata/zipslip/evil.zip b/testdata/zipslip/evil.zip
new file mode 100644
index 0000000..8353c00
Binary files /dev/null and b/testdata/zipslip/evil.zip differ