Meshery is an open source, cloud native manager that enables the design and management of Kubernetes-based infrastructure and applications. A SQL injection vulnerability in Meshery prior to version 0.7.22 may lead to arbitrary file write by using a SQL injection stacked queries payload, and the ATTACH DATABASE command. Additionally, attackers may be able to access and modify any data stored in the database, like performance profiles (which may contain session cookies), Meshery application data, or any Kubernetes configuration added to the system. The Meshery project exposes the function `GetAllEvents` at the API URL `/api/v2/events`. The sort query parameter read in `events_streamer.go` is directly used to build a SQL query in `events_persister.go`. Version 0.7.22 fixes this issue by using the `SanitizeOrderInput` function.

Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.

func ClearLine() {
	clearCmd := exec.Command("clear") // for UNIX-like systems
	if runtime.GOOS == "windows" {
		clearCmd = exec.Command("cmd", "/c", "cls") // for Windows
	}
	clearCmd.Stdout = os.Stdout
	_ = clearCmd.Run()
}

func (h *Handler) GetMeshSyncResourcesKinds(rw http.ResponseWriter, r *http.Request, _ *models.Preference, _ *models.User, provider models.Provider) {
	rw.Header().Set("Content-Type", "application/json")
	enc := json.NewEncoder(rw)

	page, offset, limit,
		search, order, sort, _ := getPaginationParams(r)

	filter := struct {
		ClusterIds []string `json:"clusterIds"`
	}{}

	var kinds []string
	var totalCount int64

	clusterIds := r.URL.Query().Get("clusterIds")
	if clusterIds != "" {
		err := json.Unmarshal([]byte(clusterIds), &filter.ClusterIds)
		if err != nil {
			h.log.Error(ErrFetchMeshSyncResources(err))
			http.Error(rw, ErrFetchMeshSyncResources(err).Error(), http.StatusInternalServerError)
			return
		}
	} else {
		filter.ClusterIds = []string{}
	}

	result := provider.GetGenericPersister().Model(&model.KubernetesResource{}).Distinct("kind").
		Where("kubernetes_resources.cluster_id IN (?)", filter.ClusterIds)

	if search != "" {
		result = result.Where("kubernetes_resources.kind LIKE ?", "%"+search+"%")
	}

	if limit != 0 {
		result = result.Limit(limit)
	}

	if offset != 0 {
		result = result.Offset(offset)
	}

	if order != "" {
		if sort == "desc" {
			result = result.Order(clause.OrderByColumn{Column: clause.Column{Name: order}, Desc: true})
		} else {
			result = result.Order(order)
		}
	}

	err := result.Pluck("kinds", &kinds).Error
	if err != nil {
		h.log.Error(ErrFetchMeshSyncResources(err))
		http.Error(rw, ErrFetchMeshSyncResources(err).Error(), http.StatusInternalServerError)
		return
	}

	response := &models.MeshSyncResourcesKindsAPIResponse{
		Kinds:      kinds,
		TotalCount: totalCount,
		Page:       page,
		PageSize:   limit,
	}

	if err := enc.Encode(response); err != nil {
		h.log.Error(ErrFetchMeshSyncResources(err))
		http.Error(rw, ErrFetchMeshSyncResources(err).Error(), http.StatusInternalServerError)
	}
}
func (h *Handler) DeleteMeshSyncResource(rw http.ResponseWriter, r *http.Request, _ *models.Preference, _ *models.User, provider models.Provider) {
	resourceID := mux.Vars(r)["id"]
	db := provider.GetGenericPersister()
	err := db.Model(&model.KubernetesResource{}).Delete(&model.KubernetesResource{ID: resourceID}).Error
	if err != nil {
		h.log.Error(models.ErrDelete(err, "meshsync data", http.StatusInternalServerError))
	}
}

func (e *EventsPersister) GetAllEvents(eventsFilter *events.EventsFilter, userID uuid.UUID) (*EventsResponse, error) {
	eventsDB := []*events.Event{}
	finder := e.DB.Model(&events.Event{}).Where("user_id = ?", userID)

	if len(eventsFilter.Category) != 0 {
		finder = finder.Where("category IN ?", eventsFilter.Category)
	}

	if len(eventsFilter.Action) != 0 {
		finder = finder.Where("action IN ?", eventsFilter.Action)
	}

	if len(eventsFilter.Severity) != 0 {
		finder = finder.Where("severity IN ?", eventsFilter.Severity)
	}

	if eventsFilter.Search != "" {
		finder = finder.Where("description LIKE ?", "%"+eventsFilter.Search+"%")
	}

	if eventsFilter.Status != "" {
		finder = finder.Where("status = ?", eventsFilter.Status)
	}

	if eventsFilter.Order == "asc" {
		finder = finder.Order(eventsFilter.SortOn)
	} else {
		finder = finder.Order(clause.OrderByColumn{Column: clause.Column{Name: eventsFilter.SortOn}, Desc: true})
	}

	var count int64
	finder.Count(&count)

	if eventsFilter.Offset != 0 {
		finder = finder.Offset(eventsFilter.Offset)
	}

	if eventsFilter.Limit != 0 {
		finder = finder.Limit(eventsFilter.Limit)
	}

	err := finder.Scan(&eventsDB).Error
	if err != nil {
		return nil, err
	}

	countBySeverity, err := e.getCountBySeverity(userID, eventsFilter.Status)

	if err != nil {
		return nil, err
	}

	return &EventsResponse{
		Events:               eventsDB,
		PageSize:             eventsFilter.Limit,
		TotalCount:           count,
		CountBySeverityLevel: countBySeverity,
	}, nil
}

func ClearLine() {
	clearCmd := exec.Command("clear") // for UNIX-like systems
	if runtime.GOOS == "windows" {
		clearCmd = exec.Command("cmd", "/c", "cls") // for Windows
	}
	clearCmd.Stdout = os.Stdout
	err := clearCmd.Run()
	if err != nil {
		log.Fatal(err)
	}
}

func (h *Handler) GetMeshSyncResourcesKinds(rw http.ResponseWriter, r *http.Request, _ *models.Preference, _ *models.User, provider models.Provider) {
	rw.Header().Set("Content-Type", "application/json")
	enc := json.NewEncoder(rw)

	page, offset, limit,
		search, order, sort, _ := getPaginationParams(r)

	filter := struct {
		ClusterIds []string `json:"clusterIds"`
	}{}

	var kinds []string
	var totalCount int64

	clusterIds := r.URL.Query().Get("clusterIds")
	if clusterIds != "" {
		err := json.Unmarshal([]byte(clusterIds), &filter.ClusterIds)
		if err != nil {
			h.log.Error(ErrFetchMeshSyncResources(err))
			http.Error(rw, ErrFetchMeshSyncResources(err).Error(), http.StatusInternalServerError)
			return
		}
	} else {
		filter.ClusterIds = []string{}
	}

	result := provider.GetGenericPersister().Model(&model.KubernetesResource{}).Distinct("kind").
		Where("kubernetes_resources.cluster_id IN (?)", filter.ClusterIds)

	if search != "" {
		result = result.Where("kubernetes_resources.kind LIKE ?", "%"+search+"%")
	}

	if limit != 0 {
		result = result.Limit(limit)
	}

	if offset != 0 {
		result = result.Offset(offset)
	}

	order = models.SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	if order != "" {
		if sort == "desc" {
			result = result.Order(clause.OrderByColumn{Column: clause.Column{Name: order}, Desc: true})
		} else {
			result = result.Order(order)
		}
	}

	err := result.Pluck("kinds", &kinds).Error
	if err != nil {
		h.log.Error(ErrFetchMeshSyncResources(err))
		http.Error(rw, ErrFetchMeshSyncResources(err).Error(), http.StatusInternalServerError)
		return
	}

	response := &models.MeshSyncResourcesKindsAPIResponse{
		Kinds:      kinds,
		TotalCount: totalCount,
		Page:       page,
		PageSize:   limit,
	}

	if err := enc.Encode(response); err != nil {
		h.log.Error(ErrFetchMeshSyncResources(err))
		http.Error(rw, ErrFetchMeshSyncResources(err).Error(), http.StatusInternalServerError)
	}
}
func (h *Handler) DeleteMeshSyncResource(rw http.ResponseWriter, r *http.Request, _ *models.Preference, _ *models.User, provider models.Provider) {
	resourceID := mux.Vars(r)["id"]
	db := provider.GetGenericPersister()
	err := db.Model(&model.KubernetesResource{}).Delete(&model.KubernetesResource{ID: resourceID}).Error
	if err != nil {
		h.log.Error(models.ErrDelete(err, "meshsync data", http.StatusInternalServerError))
	}
}

func (e *EventsPersister) GetAllEvents(eventsFilter *events.EventsFilter, userID uuid.UUID) (*EventsResponse, error) {
	eventsDB := []*events.Event{}
	finder := e.DB.Model(&events.Event{}).Where("user_id = ?", userID)

	if len(eventsFilter.Category) != 0 {
		finder = finder.Where("category IN ?", eventsFilter.Category)
	}

	if len(eventsFilter.Action) != 0 {
		finder = finder.Where("action IN ?", eventsFilter.Action)
	}

	if len(eventsFilter.Severity) != 0 {
		finder = finder.Where("severity IN ?", eventsFilter.Severity)
	}

	if eventsFilter.Search != "" {
		finder = finder.Where("description LIKE ?", "%"+eventsFilter.Search+"%")
	}

	if eventsFilter.Status != "" {
		finder = finder.Where("status = ?", eventsFilter.Status)
	}

	sortOn := SanitizeOrderInput(eventsFilter.SortOn, []string{"created_at", "updated_at", "name"})
	if eventsFilter.Order == "asc" {
		finder = finder.Order(sortOn)
	} else {
		finder = finder.Order(clause.OrderByColumn{Column: clause.Column{Name: sortOn}, Desc: true})
	}

	var count int64
	finder.Count(&count)

	if eventsFilter.Offset != 0 {
		finder = finder.Offset(eventsFilter.Offset)
	}

	if eventsFilter.Limit != 0 {
		finder = finder.Limit(eventsFilter.Limit)
	}

	err := finder.Scan(&eventsDB).Error
	if err != nil {
		return nil, err
	}

	countBySeverity, err := e.getCountBySeverity(userID, eventsFilter.Status)

	if err != nil {
		return nil, err
	}

	return &EventsResponse{
		Events:               eventsDB,
		PageSize:             eventsFilter.Limit,
		TotalCount:           count,
		CountBySeverityLevel: countBySeverity,
	}, nil
}

--- a/mesheryctl/pkg/utils/helpers.go
+++ b/mesheryctl/pkg/utils/helpers.go
@@ -4,5 +4,8 @@
 		clearCmd = exec.Command("cmd", "/c", "cls") // for Windows
 	}
 	clearCmd.Stdout = os.Stdout
-	_ = clearCmd.Run()
+	err := clearCmd.Run()
+	if err != nil {
+		log.Fatal(err)
+	}
 }

--- a/server/handlers/meshsync_handler.go
+++ b/server/handlers/meshsync_handler.go
@@ -39,6 +39,7 @@
 		result = result.Offset(offset)
 	}

+	order = models.SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
 	if order != "" {
 		if sort == "desc" {
 			result = result.Order(clause.OrderByColumn{Column: clause.Column{Name: order}, Desc: true})

--- a/server/models/events_persister.go
+++ b/server/models/events_persister.go
@@ -22,10 +22,11 @@
 		finder = finder.Where("status = ?", eventsFilter.Status)
 	}

+	sortOn := SanitizeOrderInput(eventsFilter.SortOn, []string{"created_at", "updated_at", "name"})
 	if eventsFilter.Order == "asc" {
-		finder = finder.Order(eventsFilter.SortOn)
+		finder = finder.Order(sortOn)
 	} else {
-		finder = finder.Order(clause.OrderByColumn{Column: clause.Column{Name: eventsFilter.SortOn}, Desc: true})
+		finder = finder.Order(clause.OrderByColumn{Column: clause.Column{Name: sortOn}, Desc: true})
 	}

 	var count int64


Streamlit is a data oriented application development framework for python. Snowflake Streamlit open source addressed a security vulnerability via the static file sharing feature. Users of hosted Streamlit app(s) on Windows were vulnerable to a path traversal vulnerability when the static file sharing feature is enabled. An attacker could utilize the vulnerability to leak the password hash of the Windows user running Streamlit. The vulnerability was patched on Jul 25, 2024, as part of Streamlit open source version 1.37.0. The vulnerability only affects Windows.

External Control of File Name or Path

The product allows user input to control or influence paths or file names that are used in filesystem operations.

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

class AppStaticFileHandler(tornado.web.StaticFileHandler):
    def initialize(self, path: str, default_filename: str | None = None) -> None:
        super().initialize(path, default_filename)
        mimetypes.add_type("image/webp", ".webp")

    def validate_absolute_path(self, root: str, absolute_path: str) -> str | None:
        full_path = os.path.realpath(absolute_path)

        if os.path.isdir(full_path):
            # we don't want to serve directories, and serve only files
            raise tornado.web.HTTPError(404)

        if os.path.commonpath([full_path, root]) != root:
            # Don't allow misbehaving clients to break out of the static files directory
            _LOGGER.warning(
                "Serving files outside of the static directory is not supported"
            )
            raise tornado.web.HTTPError(404)

        if (
            os.path.exists(full_path)
            and os.path.getsize(full_path) > MAX_APP_STATIC_FILE_SIZE
        ):
            raise tornado.web.HTTPError(
                404,
                "File is too large, its size should not exceed "
                f"{MAX_APP_STATIC_FILE_SIZE} bytes",
                reason="File is too large",
            )

        return super().validate_absolute_path(root, absolute_path)

class AppStaticFileHandler(tornado.web.StaticFileHandler):
    def initialize(self, path: str, default_filename: str | None = None) -> None:
        super().initialize(path, default_filename)
        mimetypes.add_type("image/webp", ".webp")

    def validate_absolute_path(self, root: str, absolute_path: str) -> str | None:
        full_path = os.path.realpath(absolute_path)

        ret_val = super().validate_absolute_path(root, absolute_path)

        if os.path.isdir(full_path):
            # we don't want to serve directories, and serve only files
            raise tornado.web.HTTPError(404)

        if os.path.commonpath([full_path, root]) != root:
            # Don't allow misbehaving clients to break out of the static files directory
            _LOGGER.warning(
                "Serving files outside of the static directory is not supported"
            )
            raise tornado.web.HTTPError(404)

        if (
            os.path.exists(full_path)
            and os.path.getsize(full_path) > MAX_APP_STATIC_FILE_SIZE
        ):
            raise tornado.web.HTTPError(
                404,
                "File is too large, its size should not exceed "
                f"{MAX_APP_STATIC_FILE_SIZE} bytes",
                reason="File is too large",
            )

        return ret_val

--- a/lib/streamlit/web/server/app_static_file_handler.py
+++ b/lib/streamlit/web/server/app_static_file_handler.py
@@ -5,6 +5,8 @@

     def validate_absolute_path(self, root: str, absolute_path: str) -> str | None:
         full_path = os.path.realpath(absolute_path)
+
+        ret_val = super().validate_absolute_path(root, absolute_path)

         if os.path.isdir(full_path):
             # we don't want to serve directories, and serve only files
@@ -28,4 +30,4 @@
                 reason="File is too large",
             )

-        return super().validate_absolute_path(root, absolute_path)
+        return ret_val


Products.PluggableAuthService is a pluggable Zope authentication and authorization framework. In Products.PluggableAuthService before version 2.6.0 there is an open redirect vulnerability. A maliciously crafted link to the login form and login functionality could redirect the browser to a different website. The problem has been fixed in version 2.6.1. Depending on how you have installed Products.PluggableAuthService, you should change the buildout version pin to `2.6.1` and re-run the buildout, or if you used `pip` simply do `pip install "Products.PluggableAuthService>=2.6.1".

URL Redirection to Untrusted Site ('Open Redirect')

The web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a redirect.

    def unauthorized(self):
        req = self.REQUEST
        resp = req['RESPONSE']

        # If we set the auth cookie before, delete it now.
        if self.cookie_name in resp.cookies:
            del resp.cookies[self.cookie_name]

        # Redirect if desired.
        url = self.getLoginURL()
        if url is not None:
            came_from = req.get('came_from', None)

            if came_from is None:
                came_from = req.get('ACTUAL_URL', '')
                query = req.get('QUERY_STRING')
                if query:
                    if not query.startswith('?'):
                        query = '?' + query
                    came_from = came_from + query
            else:
                # If came_from contains a value it means the user
                # must be coming through here a second time
                # Reasons could be typos when providing credentials
                # or a redirect loop (see below)
                req_url = req.get('ACTUAL_URL', '')

                if req_url and req_url == url:
                    # Oops... The login_form cannot be reached by the user -
                    # it might be protected itself due to misconfiguration -
                    # the only sane thing to do is to give up because we are
                    # in an endless redirect loop.
                    return 0

            if '?' in url:
                sep = '&'
            else:
                sep = '?'
            url = '%s%scame_from=%s' % (url, sep, quote(came_from))
            resp.redirect(url, lock=1)
            resp.setHeader('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT')
            resp.setHeader('Cache-Control', 'no-cache')
            return 1

        # Could not challenge.
        return 0

    def unauthorized(self):
        req = self.REQUEST
        resp = req['RESPONSE']

        # If we set the auth cookie before, delete it now.
        if self.cookie_name in resp.cookies:
            del resp.cookies[self.cookie_name]

        # Redirect if desired.
        url = self.getLoginURL()
        if url is not None:
            came_from = req.get('came_from', None)

            if came_from is None:
                came_from = req.get('ACTUAL_URL', '')
                query = req.get('QUERY_STRING')
                if query:
                    if not query.startswith('?'):
                        query = '?' + query
                    came_from = came_from + query
            else:
                # If came_from contains a value it means the user
                # must be coming through here a second time
                # Reasons could be typos when providing credentials
                # or a redirect loop (see below)
                req_url = req.get('ACTUAL_URL', '')

                if req_url and req_url == url:
                    # Oops... The login_form cannot be reached by the user -
                    # it might be protected itself due to misconfiguration -
                    # the only sane thing to do is to give up because we are
                    # in an endless redirect loop.
                    return 0

            # Sanitize the return URL ``came_from`` and only allow local URLs
            # to prevent an open exploitable redirect issue
            if came_from:
                parsed = urlparse(came_from)
                came_from = urlunparse(('', '') + parsed[2:])

            if '?' in url:
                sep = '&'
            else:
                sep = '?'
            url = '%s%scame_from=%s' % (url, sep, quote(came_from))
            resp.redirect(url, lock=1)
            resp.setHeader('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT')
            resp.setHeader('Cache-Control', 'no-cache')
            return 1

        # Could not challenge.
        return 0

--- a/src/Products/PluggableAuthService/plugins/CookieAuthHelper.py
+++ b/src/Products/PluggableAuthService/plugins/CookieAuthHelper.py
@@ -32,6 +32,12 @@
                     # in an endless redirect loop.
                     return 0

+            # Sanitize the return URL ``came_from`` and only allow local URLs
+            # to prevent an open exploitable redirect issue
+            if came_from:
+                parsed = urlparse(came_from)
+                came_from = urlunparse(('', '') + parsed[2:])
+
             if '?' in url:
                 sep = '&'
             else:


In Helm greater than or equal to 3.0.0 and less than 3.2.4, a path traversal attack is possible when installing Helm plugins from a tar archive over HTTP. It is possible for a malicious plugin author to inject a relative path into a plugin archive, and copy a file outside of the intended directory. This has been fixed in 3.2.4.

func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error {
	uncompressedStream, err := gzip.NewReader(buffer)
	if err != nil {
		return err
	}

	if err := os.MkdirAll(targetDir, 0755); err != nil {
		return err
	}

	tarReader := tar.NewReader(uncompressedStream)
	for {
		header, err := tarReader.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}

		path := filepath.Join(targetDir, header.Name)

		switch header.Typeflag {
		case tar.TypeDir:
			if err := os.Mkdir(path, 0755); err != nil {
				return err
			}
		case tar.TypeReg:
			outFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
			if err != nil {
				return err
			}
			if _, err := io.Copy(outFile, tarReader); err != nil {
				outFile.Close()
				return err
			}
			outFile.Close()
		// We don't want to process these extension header files.
		case tar.TypeXGlobalHeader, tar.TypeXHeader:
			continue
		default:
			return errors.Errorf("unknown type: %b in %s", header.Typeflag, header.Name)
		}
	}
	return nil

func cleanJoin(root, dest string) (string, error) {

	// On Windows, this is a drive separator. On UNIX-like, this is the path list separator.
	// In neither case do we want to trust a TAR that contains these.
	if strings.Contains(dest, ":") {
		return "", errors.New("path contains ':', which is illegal")
	}

	// The Go tar library does not convert separators for us.
	// We assume here, as we do elsewhere, that `\\` means a Windows path.
	dest = strings.ReplaceAll(dest, "\\", "/")

	// We want to alert the user that something bad was attempted. Cleaning it
	// is not a good practice.
	for _, part := range strings.Split(dest, "/") {
		if part == ".." {
			return "", errors.New("path contains '..', which is illegal")
		}
	}

	// If a path is absolute, the creator of the TAR is doing something shady.
	if path.IsAbs(dest) {
		return "", errors.New("path is absolute, which is illegal")
	}

	// SecureJoin will do some cleaning, as well as some rudimentary checking of symlinks.
	newpath, err := securejoin.SecureJoin(root, dest)
	if err != nil {
		return "", err
	}

	return filepath.ToSlash(newpath), nil
}

// Extract extracts compressed archives
//
// Implements Extractor.
func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error {
	uncompressedStream, err := gzip.NewReader(buffer)
	if err != nil {
		return err
	}

	if err := os.MkdirAll(targetDir, 0755); err != nil {
		return err
	}

	tarReader := tar.NewReader(uncompressedStream)
	for {
		header, err := tarReader.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}

		path, err := cleanJoin(targetDir, header.Name)
		if err != nil {
			return err
		}

		switch header.Typeflag {
		case tar.TypeDir:
			if err := os.Mkdir(path, 0755); err != nil {
				return err
			}
		case tar.TypeReg:
			outFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
			if err != nil {
				return err
			}
			if _, err := io.Copy(outFile, tarReader); err != nil {
				outFile.Close()
				return err
			}
			outFile.Close()
		// We don't want to process these extension header files.
		case tar.TypeXGlobalHeader, tar.TypeXHeader:
			continue
		default:
			return errors.Errorf("unknown type: %b in %s", header.Typeflag, header.Name)
		}
	}
	return nil

--- a/pkg/plugin/installer/http_installer.go
+++ b/pkg/plugin/installer/http_installer.go
@@ -1,3 +1,40 @@
+func cleanJoin(root, dest string) (string, error) {
+
+	// On Windows, this is a drive separator. On UNIX-like, this is the path list separator.
+	// In neither case do we want to trust a TAR that contains these.
+	if strings.Contains(dest, ":") {
+		return "", errors.New("path contains ':', which is illegal")
+	}
+
+	// The Go tar library does not convert separators for us.
+	// We assume here, as we do elsewhere, that `\\` means a Windows path.
+	dest = strings.ReplaceAll(dest, "\\", "/")
+
+	// We want to alert the user that something bad was attempted. Cleaning it
+	// is not a good practice.
+	for _, part := range strings.Split(dest, "/") {
+		if part == ".." {
+			return "", errors.New("path contains '..', which is illegal")
+		}
+	}
+
+	// If a path is absolute, the creator of the TAR is doing something shady.
+	if path.IsAbs(dest) {
+		return "", errors.New("path is absolute, which is illegal")
+	}
+
+	// SecureJoin will do some cleaning, as well as some rudimentary checking of symlinks.
+	newpath, err := securejoin.SecureJoin(root, dest)
+	if err != nil {
+		return "", err
+	}
+
+	return filepath.ToSlash(newpath), nil
+}
+
+// Extract extracts compressed archives
+//
+// Implements Extractor.
 func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error {
 	uncompressedStream, err := gzip.NewReader(buffer)
 	if err != nil {
@@ -18,7 +55,10 @@
 			return err
 		}

-		path := filepath.Join(targetDir, header.Name)
+		path, err := cleanJoin(targetDir, header.Name)
+		if err != nil {
+			return err
+		}

 		switch header.Typeflag {
 		case tar.TypeDir:

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2020-4053:latest
# bash /workspace/fix-run.sh
set -e

cd /workspace/helm
rm -rf ./pkg/plugin/installer/http_installer_test.go
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -timeout 30s -run ^TestExtract$ helm.sh/helm/v3/pkg/plugin/installer

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2020-4053:latest
# bash /workspace/unit_test.sh
set -e

cd /workspace/helm
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(TestHTTPInstallerUpdate|TestHTTPInstaller|TestStripName|TestHTTPInstallerNonExistentVersion|TestExtract)$' helm.sh/helm/v3/pkg/plugin/installer


In jsonwebtoken node module before 4.2.2 it is possible for an attacker to bypass verification when a token digitally signed with an asymmetric key (RS/ES family) of algorithms but instead the attacker send a token digitally signed with a symmetric algorithm (HS* family).

Use of a Broken or Risky Cryptographic Algorithm

The product uses a broken or risky cryptographic algorithm or protocol.

module.exports.verify = function(jwtString, secretOrPublicKey, options, callback) {
  if ((typeof options === 'function') && !callback) {
    callback = options;
    options = {};
  }

  if (!options) options = {};

  var done;

  if (callback) {
    done = function() {
      var args = Array.prototype.slice.call(arguments, 0);
      return process.nextTick(function() {
        callback.apply(null, args);
      });
    };
  } else {
    done = function(err, data) {
      if (err) throw err;
      return data;
    };
  }

  if (!jwtString){
    return done(new JsonWebTokenError('jwt must be provided'));
  }

  var parts = jwtString.split('.');

  if (parts.length !== 3){
    return done(new JsonWebTokenError('jwt malformed'));
  }

  if (parts[2].trim() === '' && secretOrPublicKey){
    return done(new JsonWebTokenError('jwt signature is required'));
  }

  var valid;

  try {
    valid = jws.verify(jwtString, secretOrPublicKey);
  } catch (e) {
    return done(e);
  }

  if (!valid)
    return done(new JsonWebTokenError('invalid signature'));

  var payload;

  try {
    payload = this.decode(jwtString);
  } catch(err) {
    return done(err);
  }

  if (typeof payload.exp !== 'undefined' && !options.ignoreExpiration) {
    if (typeof payload.exp !== 'number') {
      return done(new JsonWebTokenError('invalid exp value'));
    }
    if (Math.floor(Date.now() / 1000) >= payload.exp)
      return done(new TokenExpiredError('jwt expired', new Date(payload.exp * 1000)));
  }

  if (options.audience) {
    var audiences = Array.isArray(options.audience)? options.audience : [options.audience];
    var target = Array.isArray(payload.aud) ? payload.aud : [payload.aud];

    var match = target.some(function(aud) { return audiences.indexOf(aud) != -1; });

    if (!match)
      return done(new JsonWebTokenError('jwt audience invalid. expected: ' + audiences.join(' or ')));
  }

  if (options.issuer) {
    if (payload.iss !== options.issuer)
      return done(new JsonWebTokenError('jwt issuer invalid. expected: ' + options.issuer));
  }

  return done(null, payload);

module.exports.verify = function(jwtString, secretOrPublicKey, options, callback) {
  if ((typeof options === 'function') && !callback) {
    callback = options;
    options = {};
  }

  if (!options) options = {};

  var done;

  if (callback) {
    done = function() {
      var args = Array.prototype.slice.call(arguments, 0);
      return process.nextTick(function() {
        callback.apply(null, args);
      });
    };
  } else {
    done = function(err, data) {
      if (err) throw err;
      return data;
    };
  }

  if (!jwtString){
    return done(new JsonWebTokenError('jwt must be provided'));
  }

  var parts = jwtString.split('.');

  if (parts.length !== 3){
    return done(new JsonWebTokenError('jwt malformed'));
  }

  if (parts[2].trim() === '' && secretOrPublicKey){
    return done(new JsonWebTokenError('jwt signature is required'));
  }

  if (!options.algorithms) {
    options.algorithms = ~secretOrPublicKey.toString().indexOf('BEGIN CERTIFICATE') ?
                        [ 'RS256','RS384','RS512','ES256','ES384','ES512' ] :
                        [ 'HS256','HS384','HS512' ];
  }

  var valid;

  try {
    valid = jws.verify(jwtString, secretOrPublicKey);
  } catch (e) {
    return done(e);
  }

  if (!valid)
    return done(new JsonWebTokenError('invalid signature'));

  var payload;

  try {
    payload = this.decode(jwtString);
  } catch(err) {
    return done(err);
  }

  var header = jws.decode(jwtString).header;
  if (!~options.algorithms.indexOf(header.alg)) {
    return done(new JsonWebTokenError('invalid signature'));
  }

  if (typeof payload.exp !== 'undefined' && !options.ignoreExpiration) {
    if (typeof payload.exp !== 'number') {
      return done(new JsonWebTokenError('invalid exp value'));
    }
    if (Math.floor(Date.now() / 1000) >= payload.exp)
      return done(new TokenExpiredError('jwt expired', new Date(payload.exp * 1000)));
  }

  if (options.audience) {
    var audiences = Array.isArray(options.audience)? options.audience : [options.audience];
    var target = Array.isArray(payload.aud) ? payload.aud : [payload.aud];

    var match = target.some(function(aud) { return audiences.indexOf(aud) != -1; });

    if (!match)
      return done(new JsonWebTokenError('jwt audience invalid. expected: ' + audiences.join(' or ')));
  }

  if (options.issuer) {
    if (payload.iss !== options.issuer)
      return done(new JsonWebTokenError('jwt issuer invalid. expected: ' + options.issuer));
  }

  return done(null, payload);

--- a/index.js
+++ b/index.js
@@ -36,6 +36,12 @@
     return done(new JsonWebTokenError('jwt signature is required'));
   }

+  if (!options.algorithms) {
+    options.algorithms = ~secretOrPublicKey.toString().indexOf('BEGIN CERTIFICATE') ?
+                        [ 'RS256','RS384','RS512','ES256','ES384','ES512' ] :
+                        [ 'HS256','HS384','HS512' ];
+  }
+
   var valid;

   try {
@@ -53,6 +59,11 @@
     payload = this.decode(jwtString);
   } catch(err) {
     return done(err);
+  }
+
+  var header = jws.decode(jwtString).header;
+  if (!~options.algorithms.indexOf(header.alg)) {
+    return done(new JsonWebTokenError('invalid signature'));
   }

   if (typeof payload.exp !== 'undefined' && !options.ignoreExpiration) {


A vulnerability was found in CRI-O, where it can be requested to take a checkpoint archive of a container and later be asked to restore it. When it does that restoration, it attempts to restore the mounts from the restore archive instead of the pod request. As a result, the validations run on the pod spec, verifying that the pod has access to the mounts it specifies are not applicable to a restored container. This flaw allows a malicious user to trick CRI-O into restoring a pod that doesn't have access to host mounts. The user needs access to the kubelet or cri-o socket to call the restore endpoint and trigger the restore.

Improper Authorization

The product does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action.

Execution with Unnecessary Privileges

The product performs an operation at a privilege level that is higher than the minimum level required, which creates new weaknesses or amplifies the consequences of other weaknesses.

Improper Privilege Management

The product does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor.

func (s *Server) CRImportCheckpoint(
	ctx context.Context,
	createConfig *types.ContainerConfig,
	sb *sandbox.Sandbox, sandboxUID string,
) (ctrID string, retErr error) {
	var mountPoint string

	// Ensure that the image to restore the checkpoint from has been provided.
	if createConfig.Image == nil || createConfig.Image.Image == "" {
		return "", errors.New(`attribute "image" missing from container definition`)
	}

	inputImage := createConfig.Image.Image
	createMounts := createConfig.Mounts
	createAnnotations := createConfig.Annotations
	createLabels := createConfig.Labels

	restoreStorageImageID, err := s.checkIfCheckpointOCIImage(ctx, inputImage)
	if err != nil {
		return "", err
	}

	var restoreArchivePath string
	if restoreStorageImageID != nil {
		systemCtx, err := s.contextForNamespace(sb.Metadata().Namespace)
		if err != nil {
			return "", fmt.Errorf("get context for namespace: %w", err)
		}
		// WARNING: This hard-codes an assumption that SignaturePolicyPath set specifically for the namespace is never less restrictive
		// than the default system-wide policy, i.e. that if an image is successfully pulled, it always conforms to the system-wide policy.
		if systemCtx.SignaturePolicyPath != "" {
			return "", fmt.Errorf("namespaced signature policy %s defined for pods in namespace %s; signature validation is not supported for container restore", systemCtx.SignaturePolicyPath, sb.Metadata().Namespace)
		}

		log.Debugf(ctx, "Restoring from oci image %s", inputImage)

		// This is not out-of-process, but it is at least out of the CRI-O codebase; containers/storage uses raw strings.
		mountPoint, err = s.ContainerServer.StorageImageServer().GetStore().MountImage(restoreStorageImageID.IDStringForOutOfProcessConsumptionOnly(), nil, "")
		if err != nil {
			return "", err
		}

		log.Debugf(ctx, "Checkpoint image %s mounted at %v\n", restoreStorageImageID, mountPoint)

		defer func() {
			// This is not out-of-process, but it is at least out of the CRI-O codebase; containers/storage uses raw strings.
			if _, err := s.ContainerServer.StorageImageServer().GetStore().UnmountImage(restoreStorageImageID.IDStringForOutOfProcessConsumptionOnly(), true); err != nil {
				log.Errorf(ctx, "Could not unmount checkpoint image %s: %q", restoreStorageImageID, err)
			}
		}()
	} else {
		// First get the container definition from the
		// tarball to a temporary directory
		archiveFile, err := os.Open(inputImage)
		if err != nil {
			return "", fmt.Errorf("failed to open checkpoint archive %s for import: %w", inputImage, err)
		}
		defer func(f *os.File) {
			if err := f.Close(); err != nil {
				log.Errorf(ctx, "Unable to close file %s: %q", f.Name(), err)
			}
		}(archiveFile)

		restoreArchivePath = inputImage
		options := &archive.TarOptions{
			// Here we only need the files config.dump and spec.dump
			ExcludePatterns: []string{
				"artifacts",
				"ctr.log",
				metadata.RootFsDiffTar,
				metadata.NetworkStatusFile,
				metadata.DeletedFilesFile,
				metadata.CheckpointDirectory,
			},
		}
		mountPoint, err = os.MkdirTemp("", "checkpoint")
		if err != nil {
			return "", err
		}
		defer func() {
			if err := os.RemoveAll(mountPoint); err != nil {
				log.Errorf(ctx, "Could not recursively remove %s: %q", mountPoint, err)
			}
		}()
		err = archive.Untar(archiveFile, mountPoint, options)
		if err != nil {
			return "", fmt.Errorf("unpacking of checkpoint archive %s failed: %w", mountPoint, err)
		}
		log.Debugf(ctx, "Unpacked checkpoint in %s", mountPoint)
	}

	// Load spec.dump from temporary directory
	dumpSpec := new(spec.Spec)
	if _, err := metadata.ReadJSONFile(dumpSpec, mountPoint, metadata.SpecDumpFile); err != nil {
		return "", fmt.Errorf("failed to read %q: %w", metadata.SpecDumpFile, err)
	}

	// Load config.dump from temporary directory
	config := new(metadata.ContainerConfig)
	if _, err := metadata.ReadJSONFile(config, mountPoint, metadata.ConfigDumpFile); err != nil {
		return "", fmt.Errorf("failed to read %q: %w", metadata.ConfigDumpFile, err)
	}

	ctrMetadata := types.ContainerMetadata{}
	originalAnnotations := make(map[string]string)
	originalLabels := make(map[string]string)

	if dumpSpec.Annotations[annotations.ContainerManager] == "libpod" {
		// This is an import from Podman
		ctrMetadata.Name = config.Name
		ctrMetadata.Attempt = 0
	} else {
		if err := json.Unmarshal([]byte(dumpSpec.Annotations[annotations.Metadata]), &ctrMetadata); err != nil {
			return "", fmt.Errorf("failed to read %q: %w", annotations.Metadata, err)
		}
		if createConfig.Metadata != nil && createConfig.Metadata.Name != "" {
			ctrMetadata.Name = createConfig.Metadata.Name
		}
		if err := json.Unmarshal([]byte(dumpSpec.Annotations[annotations.Annotations]), &originalAnnotations); err != nil {
			return "", fmt.Errorf("failed to read %q: %w", annotations.Annotations, err)
		}

		if err := json.Unmarshal([]byte(dumpSpec.Annotations[annotations.Labels]), &originalLabels); err != nil {
			return "", fmt.Errorf("failed to read %q: %w", annotations.Labels, err)
		}
		if sandboxUID != "" {
			if _, ok := originalLabels[kubetypes.KubernetesPodUIDLabel]; ok {
				originalLabels[kubetypes.KubernetesPodUIDLabel] = sandboxUID
			}
			if _, ok := originalAnnotations[kubetypes.KubernetesPodUIDLabel]; ok {
				originalAnnotations[kubetypes.KubernetesPodUIDLabel] = sandboxUID
			}
		}

		if createLabels != nil {
			fixupLabels := []string{
				// Update the container name. It has already been update in metadata.Name.
				// It also needs to be updated in the container labels.
				kubetypes.KubernetesContainerNameLabel,
				// Update pod name in the labels.
				kubetypes.KubernetesPodNameLabel,
				// Also update namespace.
				kubetypes.KubernetesPodNamespaceLabel,
			}

			for _, annotation := range fixupLabels {
				_, ok1 := createLabels[annotation]
				_, ok2 := originalLabels[annotation]

				// If the value is not set in the original container or
				// if it is not set in the new container, just skip
				// the step of updating metadata.
				if ok1 && ok2 {
					originalLabels[annotation] = createLabels[annotation]
				}
			}
		}

		if createAnnotations != nil {
			// The hash also needs to be update or Kubernetes thinks the container needs to be restarted
			_, ok1 := createAnnotations["io.kubernetes.container.hash"]
			_, ok2 := originalAnnotations["io.kubernetes.container.hash"]

			if ok1 && ok2 {
				originalAnnotations["io.kubernetes.container.hash"] = createAnnotations["io.kubernetes.container.hash"]
			}
		}
	}

	stopMutex := sb.StopMutex()
	stopMutex.RLock()
	defer stopMutex.RUnlock()
	if sb.Stopped() {
		return "", fmt.Errorf("CreateContainer failed as the sandbox was stopped: %s", sb.ID())
	}

	ctr, err := container.New()
	if err != nil {
		return "", fmt.Errorf("failed to create container: %w", err)
	}

	// Newer checkpoints archives have RootfsImageRef set
	// and using it for the restore is more correct.
	// For the Kubernetes use case the output of 'crictl ps'
	// contains for the original container under 'IMAGE' something
	// like 'registry/path/container@sha256:123444444...'.
	// The restored container was, however, only displaying something
	// like 'registry/path/container'.
	// This had two problems, first, the output from the restored
	// container was different, but the bigger problem was, that
	// CRI-O might pull the wrong image from the registry.
	// If the container in the registry was updated (new latest tag)
	// all of a sudden the wrong base image would be downloaded.
	rootFSImage := config.RootfsImageName
	if config.RootfsImageRef != "" {
		id, err := storage.ParseStorageImageIDFromOutOfProcessData(config.RootfsImageRef)
		if err != nil {
			return "", fmt.Errorf("invalid RootfsImageRef %q: %w", config.RootfsImageRef, err)
		}
		// This is not quite out-of-process consumption, but types.ContainerConfig is at least
		// a cross-process API, and this value is correct in that API.
		rootFSImage = id.IDStringForOutOfProcessConsumptionOnly()
	}
	containerConfig := &types.ContainerConfig{
		Metadata: &types.ContainerMetadata{
			Name:    ctrMetadata.Name,
			Attempt: ctrMetadata.Attempt,
		},
		Image: &types.ImageSpec{
			Image: rootFSImage,
		},
		Linux: &types.LinuxContainerConfig{
			Resources:       &types.LinuxContainerResources{},
			SecurityContext: &types.LinuxContainerSecurityContext{},
		},
		Annotations: originalAnnotations,
		Labels:      originalLabels,
	}

	if createConfig.Linux != nil {
		if createConfig.Linux.Resources != nil {
			containerConfig.Linux.Resources = createConfig.Linux.Resources
		}

		if createConfig.Linux.SecurityContext != nil {
			containerConfig.Linux.SecurityContext = createConfig.Linux.SecurityContext
		}
	}

	if dumpSpec.Linux != nil {
		if dumpSpec.Linux.MaskedPaths != nil {
			containerConfig.Linux.SecurityContext.MaskedPaths = dumpSpec.Linux.MaskedPaths
		}

		if dumpSpec.Linux.ReadonlyPaths != nil {
			containerConfig.Linux.SecurityContext.ReadonlyPaths = dumpSpec.Linux.ReadonlyPaths
		}
	}

	ignoreMounts := map[string]bool{
		"/proc":              true,
		"/dev":               true,
		"/dev/pts":           true,
		"/dev/mqueue":        true,
		"/sys":               true,
		"/sys/fs/cgroup":     true,
		"/dev/shm":           true,
		"/etc/resolv.conf":   true,
		"/etc/hostname":      true,
		"/run/secrets":       true,
		"/run/.containerenv": true,
	}

	for _, m := range dumpSpec.Mounts {
		// Following mounts are ignored as they might point to the
		// wrong location and if ignored the mounts will correctly
		// be setup to point to the new location.
		if ignoreMounts[m.Destination] {
			continue
		}
		mount := &types.Mount{
			ContainerPath: m.Destination,
			HostPath:      m.Source,
		}

		for _, createMount := range createMounts {
			if createMount.ContainerPath == m.Destination {
				mount.HostPath = createMount.HostPath
			}
		}

		for _, opt := range m.Options {
			switch opt {
			case "ro":
				mount.Readonly = true
			case "rro":
				mount.RecursiveReadOnly = true
			case "rprivate":
				mount.Propagation = types.MountPropagation_PROPAGATION_PRIVATE
			case "rshared":
				mount.Propagation = types.MountPropagation_PROPAGATION_BIDIRECTIONAL
			case "rslaved":
				mount.Propagation = types.MountPropagation_PROPAGATION_HOST_TO_CONTAINER
			}
		}

		// Recursive Read-only (RRO) support requires the mount to be
		// read-only and the mount propagation set to private.
		if mount.RecursiveReadOnly {
			mount.Readonly = true
			mount.Propagation = types.MountPropagation_PROPAGATION_PRIVATE
		}

		log.Debugf(ctx, "Adding mounts %#v", mount)
		containerConfig.Mounts = append(containerConfig.Mounts, mount)
	}
	sandboxConfig := &types.PodSandboxConfig{
		Metadata: &types.PodSandboxMetadata{
			Name:      sb.Metadata().Name,
			Uid:       sb.Metadata().Uid,
			Namespace: sb.Metadata().Namespace,
			Attempt:   sb.Metadata().Attempt,
		},
		Linux: &types.LinuxPodSandboxConfig{},
	}

	if err := ctr.SetConfig(containerConfig, sandboxConfig); err != nil {
		return "", fmt.Errorf("setting container config: %w", err)
	}

	if err := ctr.SetNameAndID(""); err != nil {
		return "", fmt.Errorf("setting container name and ID: %w", err)
	}

	if _, err = s.ReserveContainerName(ctr.ID(), ctr.Name()); err != nil {
		return "", fmt.Errorf("kubelet may be retrying requests that are timing out in CRI-O due to system load: %w", err)
	}

	defer func() {
		if retErr != nil {
			log.Infof(ctx, "RestoreCtr: releasing container name %s", ctr.Name())
			s.ReleaseContainerName(ctx, ctr.Name())
		}
	}()
	ctr.SetRestore(true)

	newContainer, err := s.createSandboxContainer(ctx, ctr, sb)
	if err != nil {
		return "", err
	}
	defer func() {
		if retErr != nil {
			log.Infof(ctx, "RestoreCtr: deleting container %s from storage", ctr.ID())
			err2 := s.StorageRuntimeServer().DeleteContainer(ctx, ctr.ID())
			if err2 != nil {
				log.Warnf(ctx, "Failed to cleanup container directory: %v", err2)
			}
		}
	}()

	s.addContainer(ctx, newContainer)

	defer func() {
		if retErr != nil {
			log.Infof(ctx, "RestoreCtr: removing container %s", newContainer.ID())
			s.removeContainer(ctx, newContainer)
		}
	}()

	if err := s.CtrIDIndex().Add(ctr.ID()); err != nil {
		return "", err
	}
	defer func() {
		if retErr != nil {
			log.Infof(ctx, "RestoreCtr: deleting container ID %s from idIndex", ctr.ID())
			if err := s.CtrIDIndex().Delete(ctr.ID()); err != nil {
				log.Warnf(ctx, "Couldn't delete ctr id %s from idIndex", ctr.ID())
			}
		}
	}()

	newContainer.SetCreated()
	newContainer.SetRestore(true)
	newContainer.SetRestoreArchivePath(restoreArchivePath)
	newContainer.SetRestoreStorageImageID(restoreStorageImageID)
	newContainer.SetCheckpointedAt(config.CheckpointedAt)

	if isContextError(ctx.Err()) {
		log.Infof(ctx, "RestoreCtr: context was either canceled or the deadline was exceeded: %v", ctx.Err())
		return "", ctx.Err()
	}
	return ctr.ID(), nil
}

func (s *Server) CRImportCheckpoint(
	ctx context.Context,
	createConfig *types.ContainerConfig,
	sb *sandbox.Sandbox, sandboxUID string,
) (ctrID string, retErr error) {
	var mountPoint string

	// Ensure that the image to restore the checkpoint from has been provided.
	if createConfig.Image == nil || createConfig.Image.Image == "" {
		return "", errors.New(`attribute "image" missing from container definition`)
	}

	if createConfig.Metadata == nil && createConfig.Metadata.Name == "" {
		return "", errors.New(`attribute "metadata" missing from container definition`)
	}

	inputImage := createConfig.Image.Image
	createMounts := createConfig.Mounts
	createAnnotations := createConfig.Annotations
	createLabels := createConfig.Labels

	restoreStorageImageID, err := s.checkIfCheckpointOCIImage(ctx, inputImage)
	if err != nil {
		return "", err
	}

	var restoreArchivePath string
	if restoreStorageImageID != nil {
		systemCtx, err := s.contextForNamespace(sb.Metadata().Namespace)
		if err != nil {
			return "", fmt.Errorf("get context for namespace: %w", err)
		}
		// WARNING: This hard-codes an assumption that SignaturePolicyPath set specifically for the namespace is never less restrictive
		// than the default system-wide policy, i.e. that if an image is successfully pulled, it always conforms to the system-wide policy.
		if systemCtx.SignaturePolicyPath != "" {
			return "", fmt.Errorf("namespaced signature policy %s defined for pods in namespace %s; signature validation is not supported for container restore", systemCtx.SignaturePolicyPath, sb.Metadata().Namespace)
		}

		log.Debugf(ctx, "Restoring from oci image %s", inputImage)

		// This is not out-of-process, but it is at least out of the CRI-O codebase; containers/storage uses raw strings.
		mountPoint, err = s.ContainerServer.StorageImageServer().GetStore().MountImage(restoreStorageImageID.IDStringForOutOfProcessConsumptionOnly(), nil, "")
		if err != nil {
			return "", err
		}

		log.Debugf(ctx, "Checkpoint image %s mounted at %v\n", restoreStorageImageID, mountPoint)

		defer func() {
			// This is not out-of-process, but it is at least out of the CRI-O codebase; containers/storage uses raw strings.
			if _, err := s.ContainerServer.StorageImageServer().GetStore().UnmountImage(restoreStorageImageID.IDStringForOutOfProcessConsumptionOnly(), true); err != nil {
				log.Errorf(ctx, "Could not unmount checkpoint image %s: %q", restoreStorageImageID, err)
			}
		}()
	} else {
		// First get the container definition from the
		// tarball to a temporary directory
		archiveFile, err := os.Open(inputImage)
		if err != nil {
			return "", fmt.Errorf("failed to open checkpoint archive %s for import: %w", inputImage, err)
		}
		defer func(f *os.File) {
			if err := f.Close(); err != nil {
				log.Errorf(ctx, "Unable to close file %s: %q", f.Name(), err)
			}
		}(archiveFile)

		restoreArchivePath = inputImage
		options := &archive.TarOptions{
			// Here we only need the files config.dump and spec.dump
			ExcludePatterns: []string{
				"artifacts",
				"ctr.log",
				metadata.RootFsDiffTar,
				metadata.NetworkStatusFile,
				metadata.DeletedFilesFile,
				metadata.CheckpointDirectory,
			},
		}
		mountPoint, err = os.MkdirTemp("", "checkpoint")
		if err != nil {
			return "", err
		}
		defer func() {
			if err := os.RemoveAll(mountPoint); err != nil {
				log.Errorf(ctx, "Could not recursively remove %s: %q", mountPoint, err)
			}
		}()
		err = archive.Untar(archiveFile, mountPoint, options)
		if err != nil {
			return "", fmt.Errorf("unpacking of checkpoint archive %s failed: %w", mountPoint, err)
		}
		log.Debugf(ctx, "Unpacked checkpoint in %s", mountPoint)
	}

	// Load spec.dump from temporary directory
	dumpSpec := new(spec.Spec)
	if _, err := metadata.ReadJSONFile(dumpSpec, mountPoint, metadata.SpecDumpFile); err != nil {
		return "", fmt.Errorf("failed to read %q: %w", metadata.SpecDumpFile, err)
	}

	// Load config.dump from temporary directory
	config := new(metadata.ContainerConfig)
	if _, err := metadata.ReadJSONFile(config, mountPoint, metadata.ConfigDumpFile); err != nil {
		return "", fmt.Errorf("failed to read %q: %w", metadata.ConfigDumpFile, err)
	}

	originalAnnotations := make(map[string]string)

	if err := json.Unmarshal([]byte(dumpSpec.Annotations[annotations.Annotations]), &originalAnnotations); err != nil {
		return "", fmt.Errorf("failed to read %q: %w", annotations.Annotations, err)
	}

	if sandboxUID != "" {
		if _, ok := originalAnnotations[kubetypes.KubernetesPodUIDLabel]; ok {
			originalAnnotations[kubetypes.KubernetesPodUIDLabel] = sandboxUID
		}
	}

	if createAnnotations != nil {
		// The hash also needs to be update or Kubernetes thinks the container needs to be restarted
		_, ok1 := createAnnotations["io.kubernetes.container.hash"]
		_, ok2 := originalAnnotations["io.kubernetes.container.hash"]

		if ok1 && ok2 {
			originalAnnotations["io.kubernetes.container.hash"] = createAnnotations["io.kubernetes.container.hash"]
		}
	}

	stopMutex := sb.StopMutex()
	stopMutex.RLock()
	defer stopMutex.RUnlock()
	if sb.Stopped() {
		return "", fmt.Errorf("CreateContainer failed as the sandbox was stopped: %s", sb.ID())
	}

	ctr, err := container.New()
	if err != nil {
		return "", fmt.Errorf("failed to create container: %w", err)
	}

	// Newer checkpoints archives have RootfsImageRef set
	// and using it for the restore is more correct.
	// For the Kubernetes use case the output of 'crictl ps'
	// contains for the original container under 'IMAGE' something
	// like 'registry/path/container@sha256:123444444...'.
	// The restored container was, however, only displaying something
	// like 'registry/path/container'.
	// This had two problems, first, the output from the restored
	// container was different, but the bigger problem was, that
	// CRI-O might pull the wrong image from the registry.
	// If the container in the registry was updated (new latest tag)
	// all of a sudden the wrong base image would be downloaded.
	rootFSImage := config.RootfsImageName
	if config.RootfsImageRef != "" {
		id, err := storage.ParseStorageImageIDFromOutOfProcessData(config.RootfsImageRef)
		if err != nil {
			return "", fmt.Errorf("invalid RootfsImageRef %q: %w", config.RootfsImageRef, err)
		}
		// This is not quite out-of-process consumption, but types.ContainerConfig is at least
		// a cross-process API, and this value is correct in that API.
		rootFSImage = id.IDStringForOutOfProcessConsumptionOnly()
	}
	containerConfig := &types.ContainerConfig{
		Metadata: &types.ContainerMetadata{
			Name:    createConfig.Metadata.Name,
			Attempt: createConfig.Metadata.Attempt,
		},
		Image: &types.ImageSpec{
			Image: rootFSImage,
		},
		Linux: &types.LinuxContainerConfig{
			Resources:       &types.LinuxContainerResources{},
			SecurityContext: &types.LinuxContainerSecurityContext{},
		},
		Annotations: originalAnnotations,
		// The labels are nod changed or adapted. They are just taken from the CRI
		// request without any modification (in contrast to the annotations).
		Labels: createLabels,
	}

	if createConfig.Linux != nil {
		if createConfig.Linux.Resources != nil {
			containerConfig.Linux.Resources = createConfig.Linux.Resources
		}

		if createConfig.Linux.SecurityContext != nil {
			containerConfig.Linux.SecurityContext = createConfig.Linux.SecurityContext
		}
	}

	if dumpSpec.Linux != nil {
		if dumpSpec.Linux.MaskedPaths != nil {
			containerConfig.Linux.SecurityContext.MaskedPaths = dumpSpec.Linux.MaskedPaths
		}

		if dumpSpec.Linux.ReadonlyPaths != nil {
			containerConfig.Linux.SecurityContext.ReadonlyPaths = dumpSpec.Linux.ReadonlyPaths
		}
	}

	ignoreMounts := map[string]bool{
		"/proc":              true,
		"/dev":               true,
		"/dev/pts":           true,
		"/dev/mqueue":        true,
		"/sys":               true,
		"/sys/fs/cgroup":     true,
		"/dev/shm":           true,
		"/etc/resolv.conf":   true,
		"/etc/hostname":      true,
		"/run/secrets":       true,
		"/run/.containerenv": true,
	}

	// It is necessary to ensure that all bind mounts in the checkpoint archive are defined
	// in the create container requested coming in via the CRI. If this check would not
	// be here it would be possible to create a checkpoint archive that mounts some random
	// file/directory on the host with the user knowing as it will happen without specifying
	// it in the container definition.
	missingMount := []string{}

	for _, m := range dumpSpec.Mounts {
		// Following mounts are ignored as they might point to the
		// wrong location and if ignored the mounts will correctly
		// be setup to point to the new location.
		if ignoreMounts[m.Destination] {
			continue
		}
		mount := &types.Mount{
			ContainerPath: m.Destination,
		}

		bindMountFound := false
		for _, createMount := range createMounts {
			if createMount.ContainerPath == m.Destination {
				mount.HostPath = createMount.HostPath
				mount.Readonly = createMount.Readonly
				mount.RecursiveReadOnly = createMount.RecursiveReadOnly
				mount.Propagation = createMount.Propagation
				mount.RecursiveReadOnly = createMount.RecursiveReadOnly
				bindMountFound = true
			}
		}
		if !bindMountFound {
			missingMount = append(missingMount, m.Destination)
			// If one mount is missing we can skip over any further code as we have
			// to abort the restore process anyway. Not using break to get all missing
			// mountpoints in one error message.
			continue
		}

		log.Debugf(ctx, "Adding mounts %#v", mount)
		containerConfig.Mounts = append(containerConfig.Mounts, mount)
	}
	if len(missingMount) > 0 {
		return "", fmt.Errorf(
			"restoring %q expects following bind mounts defined (%s)",
			inputImage,
			strings.Join(missingMount, ","),
		)
	}

	sandboxConfig := &types.PodSandboxConfig{
		Metadata: &types.PodSandboxMetadata{
			Name:      sb.Metadata().Name,
			Uid:       sb.Metadata().Uid,
			Namespace: sb.Metadata().Namespace,
			Attempt:   sb.Metadata().Attempt,
		},
		Linux: &types.LinuxPodSandboxConfig{},
	}

	if err := ctr.SetConfig(containerConfig, sandboxConfig); err != nil {
		return "", fmt.Errorf("setting container config: %w", err)
	}

	if err := ctr.SetNameAndID(""); err != nil {
		return "", fmt.Errorf("setting container name and ID: %w", err)
	}

	if _, err = s.ReserveContainerName(ctr.ID(), ctr.Name()); err != nil {
		return "", fmt.Errorf("kubelet may be retrying requests that are timing out in CRI-O due to system load: %w", err)
	}

	defer func() {
		if retErr != nil {
			log.Infof(ctx, "RestoreCtr: releasing container name %s", ctr.Name())
			s.ReleaseContainerName(ctx, ctr.Name())
		}
	}()
	ctr.SetRestore(true)

	newContainer, err := s.createSandboxContainer(ctx, ctr, sb)
	if err != nil {
		return "", err
	}
	defer func() {
		if retErr != nil {
			log.Infof(ctx, "RestoreCtr: deleting container %s from storage", ctr.ID())
			err2 := s.StorageRuntimeServer().DeleteContainer(ctx, ctr.ID())
			if err2 != nil {
				log.Warnf(ctx, "Failed to cleanup container directory: %v", err2)
			}
		}
	}()

	s.addContainer(ctx, newContainer)

	defer func() {
		if retErr != nil {
			log.Infof(ctx, "RestoreCtr: removing container %s", newContainer.ID())
			s.removeContainer(ctx, newContainer)
		}
	}()

	if err := s.CtrIDIndex().Add(ctr.ID()); err != nil {
		return "", err
	}
	defer func() {
		if retErr != nil {
			log.Infof(ctx, "RestoreCtr: deleting container ID %s from idIndex", ctr.ID())
			if err := s.CtrIDIndex().Delete(ctr.ID()); err != nil {
				log.Warnf(ctx, "Couldn't delete ctr id %s from idIndex", ctr.ID())
			}
		}
	}()

	newContainer.SetCreated()
	newContainer.SetRestore(true)
	newContainer.SetRestoreArchivePath(restoreArchivePath)
	newContainer.SetRestoreStorageImageID(restoreStorageImageID)
	newContainer.SetCheckpointedAt(config.CheckpointedAt)

	if isContextError(ctx.Err()) {
		log.Infof(ctx, "RestoreCtr: context was either canceled or the deadline was exceeded: %v", ctx.Err())
		return "", ctx.Err()
	}
	return ctr.ID(), nil
}

--- a/server/container_restore.go
+++ b/server/container_restore.go
@@ -8,6 +8,10 @@
 	// Ensure that the image to restore the checkpoint from has been provided.
 	if createConfig.Image == nil || createConfig.Image.Image == "" {
 		return "", errors.New(`attribute "image" missing from container definition`)
+	}
+
+	if createConfig.Metadata == nil && createConfig.Metadata.Name == "" {
+		return "", errors.New(`attribute "metadata" missing from container definition`)
 	}

 	inputImage := createConfig.Image.Image
@@ -101,69 +105,25 @@
 		return "", fmt.Errorf("failed to read %q: %w", metadata.ConfigDumpFile, err)
 	}

-	ctrMetadata := types.ContainerMetadata{}
 	originalAnnotations := make(map[string]string)
-	originalLabels := make(map[string]string)
-
-	if dumpSpec.Annotations[annotations.ContainerManager] == "libpod" {
-		// This is an import from Podman
-		ctrMetadata.Name = config.Name
-		ctrMetadata.Attempt = 0
-	} else {
-		if err := json.Unmarshal([]byte(dumpSpec.Annotations[annotations.Metadata]), &ctrMetadata); err != nil {
-			return "", fmt.Errorf("failed to read %q: %w", annotations.Metadata, err)
-		}
-		if createConfig.Metadata != nil && createConfig.Metadata.Name != "" {
-			ctrMetadata.Name = createConfig.Metadata.Name
-		}
-		if err := json.Unmarshal([]byte(dumpSpec.Annotations[annotations.Annotations]), &originalAnnotations); err != nil {
-			return "", fmt.Errorf("failed to read %q: %w", annotations.Annotations, err)
-		}
-
-		if err := json.Unmarshal([]byte(dumpSpec.Annotations[annotations.Labels]), &originalLabels); err != nil {
-			return "", fmt.Errorf("failed to read %q: %w", annotations.Labels, err)
-		}
-		if sandboxUID != "" {
-			if _, ok := originalLabels[kubetypes.KubernetesPodUIDLabel]; ok {
-				originalLabels[kubetypes.KubernetesPodUIDLabel] = sandboxUID
-			}
-			if _, ok := originalAnnotations[kubetypes.KubernetesPodUIDLabel]; ok {
-				originalAnnotations[kubetypes.KubernetesPodUIDLabel] = sandboxUID
-			}
-		}
-
-		if createLabels != nil {
-			fixupLabels := []string{
-				// Update the container name. It has already been update in metadata.Name.
-				// It also needs to be updated in the container labels.
-				kubetypes.KubernetesContainerNameLabel,
-				// Update pod name in the labels.
-				kubetypes.KubernetesPodNameLabel,
-				// Also update namespace.
-				kubetypes.KubernetesPodNamespaceLabel,
-			}
-
-			for _, annotation := range fixupLabels {
-				_, ok1 := createLabels[annotation]
-				_, ok2 := originalLabels[annotation]
-
-				// If the value is not set in the original container or
-				// if it is not set in the new container, just skip
-				// the step of updating metadata.
-				if ok1 && ok2 {
-					originalLabels[annotation] = createLabels[annotation]
-				}
-			}
-		}
-
-		if createAnnotations != nil {
-			// The hash also needs to be update or Kubernetes thinks the container needs to be restarted
-			_, ok1 := createAnnotations["io.kubernetes.container.hash"]
-			_, ok2 := originalAnnotations["io.kubernetes.container.hash"]
-
-			if ok1 && ok2 {
-				originalAnnotations["io.kubernetes.container.hash"] = createAnnotations["io.kubernetes.container.hash"]
-			}
+
+	if err := json.Unmarshal([]byte(dumpSpec.Annotations[annotations.Annotations]), &originalAnnotations); err != nil {
+		return "", fmt.Errorf("failed to read %q: %w", annotations.Annotations, err)
+	}
+
+	if sandboxUID != "" {
+		if _, ok := originalAnnotations[kubetypes.KubernetesPodUIDLabel]; ok {
+			originalAnnotations[kubetypes.KubernetesPodUIDLabel] = sandboxUID
+		}
+	}
+
+	if createAnnotations != nil {
+		// The hash also needs to be update or Kubernetes thinks the container needs to be restarted
+		_, ok1 := createAnnotations["io.kubernetes.container.hash"]
+		_, ok2 := originalAnnotations["io.kubernetes.container.hash"]
+
+		if ok1 && ok2 {
+			originalAnnotations["io.kubernetes.container.hash"] = createAnnotations["io.kubernetes.container.hash"]
 		}
 	}

@@ -203,8 +163,8 @@
 	}
 	containerConfig := &types.ContainerConfig{
 		Metadata: &types.ContainerMetadata{
-			Name:    ctrMetadata.Name,
-			Attempt: ctrMetadata.Attempt,
+			Name:    createConfig.Metadata.Name,
+			Attempt: createConfig.Metadata.Attempt,
 		},
 		Image: &types.ImageSpec{
 			Image: rootFSImage,
@@ -214,7 +174,9 @@
 			SecurityContext: &types.LinuxContainerSecurityContext{},
 		},
 		Annotations: originalAnnotations,
-		Labels:      originalLabels,
+		// The labels are nod changed or adapted. They are just taken from the CRI
+		// request without any modification (in contrast to the annotations).
+		Labels: createLabels,
 	}

 	if createConfig.Linux != nil {
@@ -251,6 +213,13 @@
 		"/run/.containerenv": true,
 	}

+	// It is necessary to ensure that all bind mounts in the checkpoint archive are defined
+	// in the create container requested coming in via the CRI. If this check would not
+	// be here it would be possible to create a checkpoint archive that mounts some random
+	// file/directory on the host with the user knowing as it will happen without specifying
+	// it in the container definition.
+	missingMount := []string{}
+
 	for _, m := range dumpSpec.Mounts {
 		// Following mounts are ignored as they might point to the
 		// wrong location and if ignored the mounts will correctly
@@ -260,40 +229,38 @@
 		}
 		mount := &types.Mount{
 			ContainerPath: m.Destination,
-			HostPath:      m.Source,
-		}
-
+		}
+
+		bindMountFound := false
 		for _, createMount := range createMounts {
 			if createMount.ContainerPath == m.Destination {
 				mount.HostPath = createMount.HostPath
-			}
-		}
-
-		for _, opt := range m.Options {
-			switch opt {
-			case "ro":
-				mount.Readonly = true
-			case "rro":
-				mount.RecursiveReadOnly = true
-			case "rprivate":
-				mount.Propagation = types.MountPropagation_PROPAGATION_PRIVATE
-			case "rshared":
-				mount.Propagation = types.MountPropagation_PROPAGATION_BIDIRECTIONAL
-			case "rslaved":
-				mount.Propagation = types.MountPropagation_PROPAGATION_HOST_TO_CONTAINER
-			}
-		}
-
-		// Recursive Read-only (RRO) support requires the mount to be
-		// read-only and the mount propagation set to private.
-		if mount.RecursiveReadOnly {
-			mount.Readonly = true
-			mount.Propagation = types.MountPropagation_PROPAGATION_PRIVATE
+				mount.Readonly = createMount.Readonly
+				mount.RecursiveReadOnly = createMount.RecursiveReadOnly
+				mount.Propagation = createMount.Propagation
+				mount.RecursiveReadOnly = createMount.RecursiveReadOnly
+				bindMountFound = true
+			}
+		}
+		if !bindMountFound {
+			missingMount = append(missingMount, m.Destination)
+			// If one mount is missing we can skip over any further code as we have
+			// to abort the restore process anyway. Not using break to get all missing
+			// mountpoints in one error message.
+			continue
 		}

 		log.Debugf(ctx, "Adding mounts %#v", mount)
 		containerConfig.Mounts = append(containerConfig.Mounts, mount)
 	}
+	if len(missingMount) > 0 {
+		return "", fmt.Errorf(
+			"restoring %q expects following bind mounts defined (%s)",
+			inputImage,
+			strings.Join(missingMount, ","),
+		)
+	}
+
 	sandboxConfig := &types.PodSandboxConfig{
 		Metadata: &types.PodSandboxMetadata{
 			Name:      sb.Metadata().Name,


Improper Privilege Management in GitHub repository polonel/trudesk prior to 1.2.2.

module.exports = function (middleware, router, controllers) {
  // Shorten Vars
  const apiv2Auth = middleware.apiv2
  const apiv2 = controllers.api.v2
  const isAdmin = middleware.isAdmin
  const isAgent = middleware.isAgent
  const isAgentOrAdmin = middleware.isAgentOrAdmin
  const canUser = middleware.canUser

  // Common
  router.post('/api/v2/login', controllers.api.v2.common.login)
  router.post('/api/v2/token', controllers.api.v2.common.token)

  // Accounts
  router.get('/api/v2/accounts', apiv2Auth, apiv2.accounts.get)
  router.post('/api/v2/accounts', apiv2Auth, apiv2.accounts.create)
  router.put('/api/v2/accounts/:username', apiv2Auth, apiv2.accounts.update)

  // Tickets
  router.get('/api/v2/tickets', apiv2Auth, apiv2.tickets.get)
  router.post('/api/v2/tickets', apiv2Auth, apiv2.tickets.create)
  router.post('/api/v2/tickets/transfer/:uid', apiv2Auth, isAdmin, apiv2.tickets.transferToThirdParty)
  router.get('/api/v2/tickets/:uid', apiv2Auth, apiv2.tickets.single)
  router.put('/api/v2/tickets/batch', apiv2Auth, apiv2.tickets.batchUpdate)
  router.put('/api/v2/tickets/:uid', apiv2Auth, apiv2.tickets.update)
  router.delete('/api/v2/tickets/:uid', apiv2Auth, apiv2.tickets.delete)
  router.delete('/api/v2/tickets/deleted/:id', apiv2Auth, isAdmin, apiv2.tickets.permDelete)

  // Groups
  router.get('/api/v2/groups', apiv2Auth, apiv2.groups.get)
  router.post('/api/v2/groups', apiv2Auth, apiv2.groups.create)
  router.put('/api/v2/groups/:id', apiv2Auth, apiv2.groups.update)
  router.delete('/api/v2/groups/:id', apiv2Auth, apiv2.groups.delete)

  // Teams
  router.get('/api/v2/teams', apiv2Auth, apiv2.teams.get)
  router.post('/api/v2/teams', apiv2Auth, apiv2.teams.create)
  router.put('/api/v2/teams/:id', apiv2Auth, apiv2.teams.update)
  router.delete('/api/v2/teams/:id', apiv2Auth, apiv2.teams.delete)

  // Departments
  router.get('/api/v2/departments', apiv2Auth, apiv2.departments.get)
  router.post('/api/v2/departments', apiv2Auth, apiv2.departments.create)
  router.put('/api/v2/departments/:id', apiv2Auth, apiv2.departments.update)
  router.delete('/api/v2/departments/:id', apiv2Auth, apiv2.departments.delete)

  // Notices
  router.get('/api/v2/notices', apiv2Auth, apiv2.notices.get)
  router.put('/api/v2/notices/:id', apiv2Auth, canUser('notices:update'), apiv2.notices.update)
  router.put('/api/v2/notices/:id/activate', apiv2Auth, canUser('notices:activate'), apiv2.notices.activate)
  router.get('/api/v2/notices/clear', apiv2Auth, canUser('notices:deactivate'), apiv2.notices.clear)
  router.delete('/api/v2/notices/:id', apiv2Auth, canUser('notices:delete'), apiv2.notices.delete)

  // ElasticSearch
  router.get('/api/v2/es/search', middleware.api, apiv2.elasticsearch.search)
  router.get('/api/v2/es/rebuild', apiv2Auth, isAdmin, apiv2.elasticsearch.rebuild)
  router.get('/api/v2/es/status', apiv2Auth, isAdmin, apiv2.elasticsearch.status)

  router.get('/api/v2/mailer/check', apiv2Auth, isAdmin, apiv2.mailer.check)
}

module.exports = function (middleware, router, controllers) {
  // Shorten Vars
  const apiv2Auth = middleware.apiv2
  const apiv2 = controllers.api.v2
  const isAdmin = middleware.isAdmin
  const isAgent = middleware.isAgent
  const isAgentOrAdmin = middleware.isAgentOrAdmin
  const canUser = middleware.canUser

  // Common
  router.post('/api/v2/login', controllers.api.v2.common.login)
  router.post('/api/v2/token', controllers.api.v2.common.token)

  // Accounts
  router.get('/api/v2/accounts', apiv2Auth, canUser('accounts:view'), apiv2.accounts.get)
  router.post('/api/v2/accounts', apiv2Auth, canUser('accounts:create'), apiv2.accounts.create)
  router.put('/api/v2/accounts/:username', canUser('accounts:update'), apiv2Auth, apiv2.accounts.update)

  // Tickets
  router.get('/api/v2/tickets', apiv2Auth, canUser('tickets:view'), apiv2.tickets.get)
  router.post('/api/v2/tickets', apiv2Auth, canUser('tickets:create'), apiv2.tickets.create)
  router.post('/api/v2/tickets/transfer/:uid', apiv2Auth, isAdmin, apiv2.tickets.transferToThirdParty)
  router.get('/api/v2/tickets/:uid', apiv2Auth, canUser('tickets:view'), apiv2.tickets.single)
  router.put('/api/v2/tickets/batch', apiv2Auth, canUser('tickets:update'), apiv2.tickets.batchUpdate)
  router.put('/api/v2/tickets/:uid', apiv2Auth, canUser('tickets:update'), apiv2.tickets.update)
  router.delete('/api/v2/tickets/:uid', apiv2Auth, canUser('tickets:delete'), apiv2.tickets.delete)
  router.delete('/api/v2/tickets/deleted/:id', apiv2Auth, isAdmin, apiv2.tickets.permDelete)

  // Groups
  router.get('/api/v2/groups', apiv2Auth, apiv2.groups.get)
  router.post('/api/v2/groups', apiv2Auth, canUser('groups:create'), apiv2.groups.create)
  router.put('/api/v2/groups/:id', apiv2Auth, canUser('groups:update'), apiv2.groups.update)
  router.delete('/api/v2/groups/:id', apiv2Auth, canUser('groups:delete'), apiv2.groups.delete)

  // Teams
  router.get('/api/v2/teams', apiv2Auth, canUser('teams:view'), apiv2.teams.get)
  router.post('/api/v2/teams', apiv2Auth, canUser('teams:create'), apiv2.teams.create)
  router.put('/api/v2/teams/:id', apiv2Auth, canUser('teams:update'), apiv2.teams.update)
  router.delete('/api/v2/teams/:id', apiv2Auth, canUser('teams:delete'), apiv2.teams.delete)

  // Departments
  router.get('/api/v2/departments', apiv2Auth, canUser('departments:view'), apiv2.departments.get)
  router.post('/api/v2/departments', apiv2Auth, canUser('departments:create'), apiv2.departments.create)
  router.put('/api/v2/departments/:id', apiv2Auth, canUser('departments:update'), apiv2.departments.update)
  router.delete('/api/v2/departments/:id', apiv2Auth, canUser('departments:delete'), apiv2.departments.delete)

  // Notices
  router.get('/api/v2/notices', apiv2Auth, apiv2.notices.get)
  router.put('/api/v2/notices/:id', apiv2Auth, canUser('notices:update'), apiv2.notices.update)
  router.put('/api/v2/notices/:id/activate', apiv2Auth, canUser('notices:activate'), apiv2.notices.activate)
  router.get('/api/v2/notices/clear', apiv2Auth, canUser('notices:deactivate'), apiv2.notices.clear)
  router.delete('/api/v2/notices/:id', apiv2Auth, canUser('notices:delete'), apiv2.notices.delete)

  // ElasticSearch
  router.get('/api/v2/es/search', middleware.api, apiv2.elasticsearch.search)
  router.get('/api/v2/es/rebuild', apiv2Auth, isAdmin, apiv2.elasticsearch.rebuild)
  router.get('/api/v2/es/status', apiv2Auth, isAdmin, apiv2.elasticsearch.status)

  router.get('/api/v2/mailer/check', apiv2Auth, isAdmin, apiv2.mailer.check)
}

--- a/src/controllers/api/v2/routes.js
+++ b/src/controllers/api/v2/routes.js
@@ -12,37 +12,37 @@
   router.post('/api/v2/token', controllers.api.v2.common.token)

   // Accounts
-  router.get('/api/v2/accounts', apiv2Auth, apiv2.accounts.get)
-  router.post('/api/v2/accounts', apiv2Auth, apiv2.accounts.create)
-  router.put('/api/v2/accounts/:username', apiv2Auth, apiv2.accounts.update)
+  router.get('/api/v2/accounts', apiv2Auth, canUser('accounts:view'), apiv2.accounts.get)
+  router.post('/api/v2/accounts', apiv2Auth, canUser('accounts:create'), apiv2.accounts.create)
+  router.put('/api/v2/accounts/:username', canUser('accounts:update'), apiv2Auth, apiv2.accounts.update)

   // Tickets
-  router.get('/api/v2/tickets', apiv2Auth, apiv2.tickets.get)
-  router.post('/api/v2/tickets', apiv2Auth, apiv2.tickets.create)
+  router.get('/api/v2/tickets', apiv2Auth, canUser('tickets:view'), apiv2.tickets.get)
+  router.post('/api/v2/tickets', apiv2Auth, canUser('tickets:create'), apiv2.tickets.create)
   router.post('/api/v2/tickets/transfer/:uid', apiv2Auth, isAdmin, apiv2.tickets.transferToThirdParty)
-  router.get('/api/v2/tickets/:uid', apiv2Auth, apiv2.tickets.single)
-  router.put('/api/v2/tickets/batch', apiv2Auth, apiv2.tickets.batchUpdate)
-  router.put('/api/v2/tickets/:uid', apiv2Auth, apiv2.tickets.update)
-  router.delete('/api/v2/tickets/:uid', apiv2Auth, apiv2.tickets.delete)
+  router.get('/api/v2/tickets/:uid', apiv2Auth, canUser('tickets:view'), apiv2.tickets.single)
+  router.put('/api/v2/tickets/batch', apiv2Auth, canUser('tickets:update'), apiv2.tickets.batchUpdate)
+  router.put('/api/v2/tickets/:uid', apiv2Auth, canUser('tickets:update'), apiv2.tickets.update)
+  router.delete('/api/v2/tickets/:uid', apiv2Auth, canUser('tickets:delete'), apiv2.tickets.delete)
   router.delete('/api/v2/tickets/deleted/:id', apiv2Auth, isAdmin, apiv2.tickets.permDelete)

   // Groups
   router.get('/api/v2/groups', apiv2Auth, apiv2.groups.get)
-  router.post('/api/v2/groups', apiv2Auth, apiv2.groups.create)
-  router.put('/api/v2/groups/:id', apiv2Auth, apiv2.groups.update)
-  router.delete('/api/v2/groups/:id', apiv2Auth, apiv2.groups.delete)
+  router.post('/api/v2/groups', apiv2Auth, canUser('groups:create'), apiv2.groups.create)
+  router.put('/api/v2/groups/:id', apiv2Auth, canUser('groups:update'), apiv2.groups.update)
+  router.delete('/api/v2/groups/:id', apiv2Auth, canUser('groups:delete'), apiv2.groups.delete)

   // Teams
-  router.get('/api/v2/teams', apiv2Auth, apiv2.teams.get)
-  router.post('/api/v2/teams', apiv2Auth, apiv2.teams.create)
-  router.put('/api/v2/teams/:id', apiv2Auth, apiv2.teams.update)
-  router.delete('/api/v2/teams/:id', apiv2Auth, apiv2.teams.delete)
+  router.get('/api/v2/teams', apiv2Auth, canUser('teams:view'), apiv2.teams.get)
+  router.post('/api/v2/teams', apiv2Auth, canUser('teams:create'), apiv2.teams.create)
+  router.put('/api/v2/teams/:id', apiv2Auth, canUser('teams:update'), apiv2.teams.update)
+  router.delete('/api/v2/teams/:id', apiv2Auth, canUser('teams:delete'), apiv2.teams.delete)

   // Departments
-  router.get('/api/v2/departments', apiv2Auth, apiv2.departments.get)
-  router.post('/api/v2/departments', apiv2Auth, apiv2.departments.create)
-  router.put('/api/v2/departments/:id', apiv2Auth, apiv2.departments.update)
-  router.delete('/api/v2/departments/:id', apiv2Auth, apiv2.departments.delete)
+  router.get('/api/v2/departments', apiv2Auth, canUser('departments:view'), apiv2.departments.get)
+  router.post('/api/v2/departments', apiv2Auth, canUser('departments:create'), apiv2.departments.create)
+  router.put('/api/v2/departments/:id', apiv2Auth, canUser('departments:update'), apiv2.departments.update)
+  router.delete('/api/v2/departments/:id', apiv2Auth, canUser('departments:delete'), apiv2.departments.delete)

   // Notices
   router.get('/api/v2/notices', apiv2Auth, apiv2.notices.get)


Ghost before 5.42.1 allows remote attackers to read arbitrary files within the active theme's folder via /assets/built%2F..%2F..%2F/ directory traversal. This occurs in frontend/web/middleware/static-theme.js.

function isAllowedFile(file) {
    const allowedFiles = ['manifest.json'];
    const allowedPath = '/assets/';
    const alwaysDeny = ['.hbs'];

    const ext = path.extname(file);
    const base = path.basename(file);

    return allowedFiles.includes(base) || (file.startsWith(allowedPath) && !alwaysDeny.includes(ext));
}

function isAllowedFile(file) {
    const decodedFilePath = decode(file);
    if (decodedFilePath === -1) {
        return false;
    }

    const normalizedFilePath = path.normalize(decodedFilePath);

    const allowedFiles = ['manifest.json'];
    const allowedPath = '/assets/';
    const alwaysDeny = ['.hbs'];

    const ext = path.extname(normalizedFilePath);
    const base = path.basename(normalizedFilePath);

    return allowedFiles.includes(base) || (normalizedFilePath.startsWith(allowedPath) && !alwaysDeny.includes(ext));
}

--- a/ghost/core/core/frontend/web/middleware/static-theme.js
+++ b/ghost/core/core/frontend/web/middleware/static-theme.js
@@ -1,10 +1,17 @@
 function isAllowedFile(file) {
+    const decodedFilePath = decode(file);
+    if (decodedFilePath === -1) {
+        return false;
+    }
+
+    const normalizedFilePath = path.normalize(decodedFilePath);
+
     const allowedFiles = ['manifest.json'];
     const allowedPath = '/assets/';
     const alwaysDeny = ['.hbs'];

-    const ext = path.extname(file);
-    const base = path.basename(file);
+    const ext = path.extname(normalizedFilePath);
+    const base = path.basename(normalizedFilePath);

-    return allowedFiles.includes(base) || (file.startsWith(allowedPath) && !alwaysDeny.includes(ext));
+    return allowedFiles.includes(base) || (normalizedFilePath.startsWith(allowedPath) && !alwaysDeny.includes(ext));
 }


Authentik is an open-source Identity Provider. There is a bug in our implementation of PKCE that allows an attacker to circumvent the protection that PKCE offers. PKCE adds the code_challenge parameter to the authorization request and adds the code_verifier parameter to the token request. Prior to 2023.8.7 and 2023.10.7, a downgrade scenario is possible: if the attacker removes the code_challenge parameter from the authorization request, authentik will not do the PKCE check. Because of this bug, an attacker can circumvent the protection PKCE offers, such as CSRF attacks and code injection attacks.  Versions 2023.8.7 and 2023.10.7 fix the issue.

Improper Authentication

When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.

    def __post_init_code(self, raw_code: str, request: HttpRequest):
        if not raw_code:
            LOGGER.warning("Missing authorization code")
            raise TokenError("invalid_grant")

        allowed_redirect_urls = self.provider.redirect_uris.split()
        # At this point, no provider should have a blank redirect_uri, in case they do
        # this will check an empty array and raise an error
        try:
            if not any(fullmatch(x, self.redirect_uri) for x in allowed_redirect_urls):
                LOGGER.warning(
                    "Invalid redirect uri (regex comparison)",
                    redirect_uri=self.redirect_uri,
                    expected=allowed_redirect_urls,
                )
                Event.new(
                    EventAction.CONFIGURATION_ERROR,
                    message="Invalid redirect URI used by provider",
                    provider=self.provider,
                    redirect_uri=self.redirect_uri,
                    expected=allowed_redirect_urls,
                ).from_http(request)
                raise TokenError("invalid_client")
        except RegexError as exc:
            LOGGER.info("Failed to parse regular expression, checking directly", exc=exc)
            if not any(x == self.redirect_uri for x in allowed_redirect_urls):
                LOGGER.warning(
                    "Invalid redirect uri (strict comparison)",
                    redirect_uri=self.redirect_uri,
                    expected=allowed_redirect_urls,
                )
                Event.new(
                    EventAction.CONFIGURATION_ERROR,
                    message="Invalid redirect_uri configured",
                    provider=self.provider,
                ).from_http(request)
                raise TokenError("invalid_client")

        # Check against forbidden schemes
        if urlparse(self.redirect_uri).scheme in FORBIDDEN_URI_SCHEMES:
            raise TokenError("invalid_request")

        self.authorization_code = AuthorizationCode.objects.filter(code=raw_code).first()
        if not self.authorization_code:
            LOGGER.warning("Code does not exist", code=raw_code)
            raise TokenError("invalid_grant")

        if self.authorization_code.is_expired:
            LOGGER.warning(
                "Code is expired",
                token=raw_code,
            )
            raise TokenError("invalid_grant")

        if self.authorization_code.provider != self.provider or self.authorization_code.is_expired:
            LOGGER.warning("Invalid code: invalid client or code has expired")
            raise TokenError("invalid_grant")

        # Validate PKCE parameters.
        if self.authorization_code.code_challenge:
            # Authorization code had PKCE but we didn't get one
            if not self.code_verifier:
                raise TokenError("invalid_request")
            if self.authorization_code.code_challenge_method == PKCE_METHOD_S256:
                new_code_challenge = (
                    urlsafe_b64encode(sha256(self.code_verifier.encode("ascii")).digest())
                    .decode("utf-8")
                    .replace("=", "")
                )
            else:
                new_code_challenge = self.code_verifier

            if new_code_challenge != self.authorization_code.code_challenge:
                LOGGER.warning("Code challenge not matching")
                raise TokenError("invalid_grant")

    def __post_init_code(self, raw_code: str, request: HttpRequest):
        if not raw_code:
            LOGGER.warning("Missing authorization code")
            raise TokenError("invalid_grant")

        allowed_redirect_urls = self.provider.redirect_uris.split()
        # At this point, no provider should have a blank redirect_uri, in case they do
        # this will check an empty array and raise an error
        try:
            if not any(fullmatch(x, self.redirect_uri) for x in allowed_redirect_urls):
                LOGGER.warning(
                    "Invalid redirect uri (regex comparison)",
                    redirect_uri=self.redirect_uri,
                    expected=allowed_redirect_urls,
                )
                Event.new(
                    EventAction.CONFIGURATION_ERROR,
                    message="Invalid redirect URI used by provider",
                    provider=self.provider,
                    redirect_uri=self.redirect_uri,
                    expected=allowed_redirect_urls,
                ).from_http(request)
                raise TokenError("invalid_client")
        except RegexError as exc:
            LOGGER.info("Failed to parse regular expression, checking directly", exc=exc)
            if not any(x == self.redirect_uri for x in allowed_redirect_urls):
                LOGGER.warning(
                    "Invalid redirect uri (strict comparison)",
                    redirect_uri=self.redirect_uri,
                    expected=allowed_redirect_urls,
                )
                Event.new(
                    EventAction.CONFIGURATION_ERROR,
                    message="Invalid redirect_uri configured",
                    provider=self.provider,
                ).from_http(request)
                raise TokenError("invalid_client")

        # Check against forbidden schemes
        if urlparse(self.redirect_uri).scheme in FORBIDDEN_URI_SCHEMES:
            raise TokenError("invalid_request")

        self.authorization_code = AuthorizationCode.objects.filter(code=raw_code).first()
        if not self.authorization_code:
            LOGGER.warning("Code does not exist", code=raw_code)
            raise TokenError("invalid_grant")

        if self.authorization_code.is_expired:
            LOGGER.warning(
                "Code is expired",
                token=raw_code,
            )
            raise TokenError("invalid_grant")

        if self.authorization_code.provider != self.provider or self.authorization_code.is_expired:
            LOGGER.warning("Invalid code: invalid client or code has expired")
            raise TokenError("invalid_grant")

        # Validate PKCE parameters.
        if self.authorization_code.code_challenge:
            # Authorization code had PKCE but we didn't get one
            if not self.code_verifier:
                raise TokenError("invalid_grant")
            if self.authorization_code.code_challenge_method == PKCE_METHOD_S256:
                new_code_challenge = (
                    urlsafe_b64encode(sha256(self.code_verifier.encode("ascii")).digest())
                    .decode("utf-8")
                    .replace("=", "")
                )
            else:
                new_code_challenge = self.code_verifier

            if new_code_challenge != self.authorization_code.code_challenge:
                LOGGER.warning("Code challenge not matching")
                raise TokenError("invalid_grant")
        # Token request had a code_verifier but code did not have a code challenge
        # Prevent downgrade
        if not self.authorization_code.code_challenge and self.code_verifier:
            raise TokenError("invalid_grant")

--- a/authentik/providers/oauth2/views/token.py
+++ b/authentik/providers/oauth2/views/token.py
@@ -60,7 +60,7 @@
         if self.authorization_code.code_challenge:
             # Authorization code had PKCE but we didn't get one
             if not self.code_verifier:
-                raise TokenError("invalid_request")
+                raise TokenError("invalid_grant")
             if self.authorization_code.code_challenge_method == PKCE_METHOD_S256:
                 new_code_challenge = (
                     urlsafe_b64encode(sha256(self.code_verifier.encode("ascii")).digest())
@@ -73,3 +73,7 @@
             if new_code_challenge != self.authorization_code.code_challenge:
                 LOGGER.warning("Code challenge not matching")
                 raise TokenError("invalid_grant")
+        # Token request had a code_verifier but code did not have a code challenge
+        # Prevent downgrade
+        if not self.authorization_code.code_challenge and self.code_verifier:
+            raise TokenError("invalid_grant")


A path traversal vulnerability has been discovered in podman before version 1.4.0 in the way it handles symlinks inside containers. An attacker who has compromised an existing container can cause arbitrary files on the host filesystem to be read/written when an administrator tries to copy a file from/to the container.

type CpValues struct {
	PodmanCommand
	Extract bool
}

func init() {
	cpCommand.Command = _cpCommand
	flags := cpCommand.Flags()
	flags.BoolVar(&cpCommand.Extract, "extract", false, "Extract the tar file into the destination directory.")
	cpCommand.SetHelpTemplate(HelpTemplate())
	cpCommand.SetUsageTemplate(UsageTemplate())
	rootCmd.AddCommand(cpCommand.Command)
}

func cpCmd(c *cliconfig.CpValues) error {
	args := c.InputArgs
	if len(args) != 2 {
		return errors.Errorf("you must provide a source path and a destination path")
	}

	runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand)
	if err != nil {
		return errors.Wrapf(err, "could not get runtime")
	}
	defer runtime.Shutdown(false)

	extract := c.Flag("extract").Changed
	return copyBetweenHostAndContainer(runtime, args[0], args[1], extract)
}

func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest string, extract bool) error {

	srcCtr, srcPath := parsePath(runtime, src)
	destCtr, destPath := parsePath(runtime, dest)

	if (srcCtr == nil && destCtr == nil) || (srcCtr != nil && destCtr != nil) {
		return errors.Errorf("invalid arguments %s, %s you must use just one container", src, dest)
	}

	if len(srcPath) == 0 || len(destPath) == 0 {
		return errors.Errorf("invalid arguments %s, %s you must specify paths", src, dest)
	}
	ctr := srcCtr
	isFromHostToCtr := (ctr == nil)
	if isFromHostToCtr {
		ctr = destCtr
	}

	mountPoint, err := ctr.Mount()
	if err != nil {
		return err
	}
	defer ctr.Unmount(false)
	user, err := getUser(mountPoint, ctr.User())
	if err != nil {
		return err
	}
	idMappingOpts, err := ctr.IDMappings()
	if err != nil {
		return errors.Wrapf(err, "error getting IDMappingOptions")
	}
	containerOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
	hostUID, hostGID, err := util.GetHostIDs(convertIDMap(idMappingOpts.UIDMap), convertIDMap(idMappingOpts.GIDMap), user.UID, user.GID)
	if err != nil {
		return err
	}

	hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}

	var glob []string
	if isFromHostToCtr {
		if filepath.IsAbs(destPath) {
			destPath = filepath.Join(mountPoint, destPath)

		} else {
			if err = idtools.MkdirAllAndChownNew(filepath.Join(mountPoint, ctr.WorkingDir()), 0755, hostOwner); err != nil {
				return errors.Wrapf(err, "error creating directory %q", destPath)
			}
			destPath = filepath.Join(mountPoint, ctr.WorkingDir(), destPath)
		}
	} else {
		if filepath.IsAbs(srcPath) {
			srcPath = filepath.Join(mountPoint, srcPath)
		} else {
			srcPath = filepath.Join(mountPoint, ctr.WorkingDir(), srcPath)
		}
	}
	glob, err = filepath.Glob(srcPath)
	if err != nil {
		return errors.Wrapf(err, "invalid glob %q", srcPath)
	}
	if len(glob) == 0 {
		glob = append(glob, srcPath)
	}
	if !filepath.IsAbs(destPath) {
		dir, err := os.Getwd()
		if err != nil {
			return errors.Wrapf(err, "err getting current working directory")
		}
		destPath = filepath.Join(dir, destPath)
	}

	var lastError error
	for _, src := range glob {
		if src == "-" {
			src = os.Stdin.Name()
			extract = true
		}
		err := copy(src, destPath, dest, idMappingOpts, &containerOwner, extract, isFromHostToCtr)
		if lastError != nil {
			logrus.Error(lastError)
		}
		lastError = err
	}
	return lastError
}

type CpValues struct {
	PodmanCommand
	Extract bool
	Pause   bool
}

func init() {
	cpCommand.Command = _cpCommand
	flags := cpCommand.Flags()
	flags.BoolVar(&cpCommand.Extract, "extract", false, "Extract the tar file into the destination directory.")
	flags.BoolVar(&cpCommand.Pause, "pause", true, "Pause the container while copying")
	cpCommand.SetHelpTemplate(HelpTemplate())
	cpCommand.SetUsageTemplate(UsageTemplate())
	rootCmd.AddCommand(cpCommand.Command)
}

func cpCmd(c *cliconfig.CpValues) error {
	args := c.InputArgs
	if len(args) != 2 {
		return errors.Errorf("you must provide a source path and a destination path")
	}

	runtime, err := libpodruntime.GetRuntime(getContext(), &c.PodmanCommand)
	if err != nil {
		return errors.Wrapf(err, "could not get runtime")
	}
	defer runtime.Shutdown(false)

	return copyBetweenHostAndContainer(runtime, args[0], args[1], c.Extract, c.Pause)
}

func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest string, extract bool, pause bool) error {

	srcCtr, srcPath := parsePath(runtime, src)
	destCtr, destPath := parsePath(runtime, dest)

	if (srcCtr == nil && destCtr == nil) || (srcCtr != nil && destCtr != nil) {
		return errors.Errorf("invalid arguments %s, %s you must use just one container", src, dest)
	}

	if len(srcPath) == 0 || len(destPath) == 0 {
		return errors.Errorf("invalid arguments %s, %s you must specify paths", src, dest)
	}
	ctr := srcCtr
	isFromHostToCtr := (ctr == nil)
	if isFromHostToCtr {
		ctr = destCtr
	}

	mountPoint, err := ctr.Mount()
	if err != nil {
		return err
	}
	defer ctr.Unmount(false)

	// We can't pause rootless containers.
	if pause && rootless.IsRootless() {
		state, err := ctr.State()
		if err != nil {
			return err
		}
		if state == libpod.ContainerStateRunning {
			return errors.Errorf("cannot copy into running rootless container with pause set - pass --pause=false to force copying")
		}
	}

	if pause && !rootless.IsRootless() {
		if err := ctr.Pause(); err != nil {
			// An invalid state error is fine.
			// The container isn't running or is already paused.
			// TODO: We can potentially start the container while
			// the copy is running, which still allows a race where
			// malicious code could mess with the symlink.
			if errors.Cause(err) != libpod.ErrCtrStateInvalid {
				return err
			}
		} else if err == nil {
			// Only add the defer if we actually paused
			defer func() {
				if err := ctr.Unpause(); err != nil {
					logrus.Errorf("Error unpausing container after copying: %v", err)
				}
			}()
		}
	}

	user, err := getUser(mountPoint, ctr.User())
	if err != nil {
		return err
	}
	idMappingOpts, err := ctr.IDMappings()
	if err != nil {
		return errors.Wrapf(err, "error getting IDMappingOptions")
	}
	containerOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
	hostUID, hostGID, err := util.GetHostIDs(convertIDMap(idMappingOpts.UIDMap), convertIDMap(idMappingOpts.GIDMap), user.UID, user.GID)
	if err != nil {
		return err
	}

	hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}

	var glob []string
	if isFromHostToCtr {
		if filepath.IsAbs(destPath) {
			cleanedPath, err := securejoin.SecureJoin(mountPoint, destPath)
			if err != nil {
				return err
			}
			destPath = cleanedPath
		} else {
			ctrWorkDir, err := securejoin.SecureJoin(mountPoint, ctr.WorkingDir())
			if err != nil {
				return err
			}
			if err = idtools.MkdirAllAndChownNew(ctrWorkDir, 0755, hostOwner); err != nil {
				return errors.Wrapf(err, "error creating directory %q", destPath)
			}
			cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), destPath))
			if err != nil {
				return err
			}
			destPath = cleanedPath
		}
	} else {
		if filepath.IsAbs(srcPath) {
			cleanedPath, err := securejoin.SecureJoin(mountPoint, srcPath)
			if err != nil {
				return err
			}
			srcPath = cleanedPath
		} else {
			cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), srcPath))
			if err != nil {
				return err
			}
			srcPath = cleanedPath
		}
	}
	glob, err = filepath.Glob(srcPath)
	if err != nil {
		return errors.Wrapf(err, "invalid glob %q", srcPath)
	}
	if len(glob) == 0 {
		glob = append(glob, srcPath)
	}
	if !filepath.IsAbs(destPath) {
		dir, err := os.Getwd()
		if err != nil {
			return errors.Wrapf(err, "err getting current working directory")
		}
		destPath = filepath.Join(dir, destPath)
	}

	var lastError error
	for _, src := range glob {
		if src == "-" {
			src = os.Stdin.Name()
			extract = true
		}
		err := copy(src, destPath, dest, idMappingOpts, &containerOwner, extract, isFromHostToCtr)
		if lastError != nil {
			logrus.Error(lastError)
		}
		lastError = err
	}
	return lastError
}

--- a/cmd/podman/cliconfig/create.go
+++ b/cmd/podman/cliconfig/create.go
@@ -1,4 +1,5 @@
 type CpValues struct {
 	PodmanCommand
 	Extract bool
+	Pause   bool
 }

--- a/cmd/podman/cp.go
+++ b/cmd/podman/cp.go
@@ -2,6 +2,7 @@
 	cpCommand.Command = _cpCommand
 	flags := cpCommand.Flags()
 	flags.BoolVar(&cpCommand.Extract, "extract", false, "Extract the tar file into the destination directory.")
+	flags.BoolVar(&cpCommand.Pause, "pause", true, "Pause the container while copying")
 	cpCommand.SetHelpTemplate(HelpTemplate())
 	cpCommand.SetUsageTemplate(UsageTemplate())
 	rootCmd.AddCommand(cpCommand.Command)

--- a/cmd/podman/cp.go
+++ b/cmd/podman/cp.go
@@ -10,6 +10,5 @@
 	}
 	defer runtime.Shutdown(false)

-	extract := c.Flag("extract").Changed
-	return copyBetweenHostAndContainer(runtime, args[0], args[1], extract)
+	return copyBetweenHostAndContainer(runtime, args[0], args[1], c.Extract, c.Pause)
 }

--- a/cmd/podman/cp.go
+++ b/cmd/podman/cp.go
@@ -1,4 +1,4 @@
-func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest string, extract bool) error {
+func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest string, extract bool, pause bool) error {

 	srcCtr, srcPath := parsePath(runtime, src)
 	destCtr, destPath := parsePath(runtime, dest)
@@ -21,6 +21,38 @@
 		return err
 	}
 	defer ctr.Unmount(false)
+
+	// We can't pause rootless containers.
+	if pause && rootless.IsRootless() {
+		state, err := ctr.State()
+		if err != nil {
+			return err
+		}
+		if state == libpod.ContainerStateRunning {
+			return errors.Errorf("cannot copy into running rootless container with pause set - pass --pause=false to force copying")
+		}
+	}
+
+	if pause && !rootless.IsRootless() {
+		if err := ctr.Pause(); err != nil {
+			// An invalid state error is fine.
+			// The container isn't running or is already paused.
+			// TODO: We can potentially start the container while
+			// the copy is running, which still allows a race where
+			// malicious code could mess with the symlink.
+			if errors.Cause(err) != libpod.ErrCtrStateInvalid {
+				return err
+			}
+		} else if err == nil {
+			// Only add the defer if we actually paused
+			defer func() {
+				if err := ctr.Unpause(); err != nil {
+					logrus.Errorf("Error unpausing container after copying: %v", err)
+				}
+			}()
+		}
+	}
+
 	user, err := getUser(mountPoint, ctr.User())
 	if err != nil {
 		return err
@@ -40,19 +72,38 @@
 	var glob []string
 	if isFromHostToCtr {
 		if filepath.IsAbs(destPath) {
-			destPath = filepath.Join(mountPoint, destPath)
-
+			cleanedPath, err := securejoin.SecureJoin(mountPoint, destPath)
+			if err != nil {
+				return err
+			}
+			destPath = cleanedPath
 		} else {
-			if err = idtools.MkdirAllAndChownNew(filepath.Join(mountPoint, ctr.WorkingDir()), 0755, hostOwner); err != nil {
+			ctrWorkDir, err := securejoin.SecureJoin(mountPoint, ctr.WorkingDir())
+			if err != nil {
+				return err
+			}
+			if err = idtools.MkdirAllAndChownNew(ctrWorkDir, 0755, hostOwner); err != nil {
 				return errors.Wrapf(err, "error creating directory %q", destPath)
 			}
-			destPath = filepath.Join(mountPoint, ctr.WorkingDir(), destPath)
+			cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), destPath))
+			if err != nil {
+				return err
+			}
+			destPath = cleanedPath
 		}
 	} else {
 		if filepath.IsAbs(srcPath) {
-			srcPath = filepath.Join(mountPoint, srcPath)
+			cleanedPath, err := securejoin.SecureJoin(mountPoint, srcPath)
+			if err != nil {
+				return err
+			}
+			srcPath = cleanedPath
 		} else {
-			srcPath = filepath.Join(mountPoint, ctr.WorkingDir(), srcPath)
+			cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), srcPath))
+			if err != nil {
+				return err
+			}
+			srcPath = cleanedPath
 		}
 	}
 	glob, err = filepath.Glob(srcPath)