The kubectl cp command allows copying files between containers and the user machine. To copy files from a container, Kubernetes runs tar inside the container to create a tar archive, copies it over the network, and kubectl unpacks it on the user’s machine. If the tar binary in the container is malicious, it could run any code and output unexpected, malicious results. An attacker could use this to write files to any path on the user’s machine when kubectl cp is called, limited only by the system permissions of the local user. Kubernetes affected versions include versions prior to 1.12.9, versions prior to 1.13.6, versions prior to 1.14.2, and versions 1.1, 1.2, 1.4, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10, 1.11.

func (o *CopyOptions) untarAll(reader io.Reader, destFile, prefix string) error {
	entrySeq := -1

	// TODO: use compression here?
	tarReader := tar.NewReader(reader)
	for {
		header, err := tarReader.Next()
		if err != nil {
			if err != io.EOF {
				return err
			}
			break
		}
		entrySeq++
		mode := header.FileInfo().Mode()
		// all the files will start with the prefix, which is the directory where
		// they were located on the pod, we need to strip down that prefix, but
		// if the prefix is missing it means the tar was tempered with
		if !strings.HasPrefix(header.Name, prefix) {
			return fmt.Errorf("tar contents corrupted")
		}
		outFileName := path.Join(destFile, clean(header.Name[len(prefix):]))
		baseName := path.Dir(outFileName)
		if err := os.MkdirAll(baseName, 0755); err != nil {
			return err
		}
		if header.FileInfo().IsDir() {
			if err := os.MkdirAll(outFileName, 0755); err != nil {
				return err
			}
			continue
		}

		// handle coping remote file into local directory
		if entrySeq == 0 && !header.FileInfo().IsDir() {
			exists, err := dirExists(outFileName)
			if err != nil {
				return err
			}
			if exists {
				outFileName = filepath.Join(outFileName, path.Base(clean(header.Name)))
			}
		}

		if mode&os.ModeSymlink != 0 {
			linkname := header.Linkname
			// error is returned if linkname can't be made relative to destFile,
			// but relative can end up being ../dir that's why we also need to
			// verify if relative path is the same after Clean-ing
			relative, err := filepath.Rel(destFile, linkname)
			if path.IsAbs(linkname) && (err != nil || relative != stripPathShortcuts(relative)) {
				fmt.Fprintf(o.IOStreams.ErrOut, "warning: link %q is pointing to %q which is outside target destination, skipping\n", outFileName, header.Linkname)
				continue
			}
			if err := os.Symlink(linkname, outFileName); err != nil {
				return err
			}
		} else {
			outFile, err := os.Create(outFileName)
			if err != nil {
				return err
			}
			defer outFile.Close()
			if _, err := io.Copy(outFile, tarReader); err != nil {
				return err
			}
			if err := outFile.Close(); err != nil {
				return err
			}
		}
	}

	if entrySeq == -1 {
		//if no file was copied
		errInfo := fmt.Sprintf("error: %s no such file or directory", prefix)
		return errors.New(errInfo)
	}
	return nil
}

func dirExists(path string) (bool, error) {
	fi, err := os.Stat(path)
	if err == nil && fi.IsDir() {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func (o *CopyOptions) untarAll(reader io.Reader, destDir, prefix string) error {
	// TODO: use compression here?
	tarReader := tar.NewReader(reader)
	for {
		header, err := tarReader.Next()
		if err != nil {
			if err != io.EOF {
				return err
			}
			break
		}

		// All the files will start with the prefix, which is the directory where
		// they were located on the pod, we need to strip down that prefix, but
		// if the prefix is missing it means the tar was tempered with.
		// For the case where prefix is empty we need to ensure that the path
		// is not absolute, which also indicates the tar file was tempered with.
		if !strings.HasPrefix(header.Name, prefix) {
			return fmt.Errorf("tar contents corrupted")
		}

		// basic file information
		mode := header.FileInfo().Mode()
		destFileName := path.Join(destDir, header.Name[len(prefix):])
		baseName := path.Dir(destFileName)

		if err := os.MkdirAll(baseName, 0755); err != nil {
			return err
		}
		if header.FileInfo().IsDir() {
			if err := os.MkdirAll(destFileName, 0755); err != nil {
				return err
			}
			continue
		}

		// We need to ensure that the destination file is always within boundries
		// of the destination directory. This prevents any kind of path traversal
		// from within tar archive.
		dir, file := filepath.Split(destFileName)
		evaledPath, err := filepath.EvalSymlinks(dir)
		if err != nil {
			return err
		}
		// For scrutiny we verify both the actual destination as well as we follow
		// all the links that might lead outside of the destination directory.
		if !isDestRelative(destDir, destFileName) || !isDestRelative(destDir, filepath.Join(evaledPath, file)) {
			fmt.Fprintf(o.IOStreams.ErrOut, "warning: link %q is pointing to %q which is outside target destination, skipping\n", destFileName, header.Linkname)
			continue
		}

		if mode&os.ModeSymlink != 0 {
			linkname := header.Linkname
			// We need to ensure that the link destination is always within boundries
			// of the destination directory. This prevents any kind of path traversal
			// from within tar archive.
			if !isDestRelative(destDir, linkJoin(destFileName, linkname)) {
				fmt.Fprintf(o.IOStreams.ErrOut, "warning: link %q is pointing to %q which is outside target destination, skipping\n", destFileName, header.Linkname)
				continue
			}
			if err := os.Symlink(linkname, destFileName); err != nil {
				return err
			}
		} else {
			outFile, err := os.Create(destFileName)
			if err != nil {
				return err
			}
			defer outFile.Close()
			if _, err := io.Copy(outFile, tarReader); err != nil {
				return err
			}
			if err := outFile.Close(); err != nil {
				return err
			}
		}
	}

	return nil
}

func linkJoin(base, link string) string {
	if filepath.IsAbs(link) {
		return link
	}
	return filepath.Join(base, link)
}

func isDestRelative(base, dest string) bool {
	fullPath := dest
	if !filepath.IsAbs(dest) {
		fullPath = filepath.Join(base, dest)
	}
	relative, err := filepath.Rel(base, fullPath)
	if err != nil {
		return false
	}
	return relative == "." || relative == stripPathShortcuts(relative)
}

--- a/pkg/kubectl/cmd/cp/cp.go
+++ b/pkg/kubectl/cmd/cp/cp.go
@@ -1,6 +1,4 @@
-func (o *CopyOptions) untarAll(reader io.Reader, destFile, prefix string) error {
-	entrySeq := -1
-
+func (o *CopyOptions) untarAll(reader io.Reader, destDir, prefix string) error {
 	// TODO: use compression here?
 	tarReader := tar.NewReader(reader)
 	for {
@@ -11,52 +9,60 @@
 			}
 			break
 		}
-		entrySeq++
-		mode := header.FileInfo().Mode()
-		// all the files will start with the prefix, which is the directory where
+
+		// All the files will start with the prefix, which is the directory where
 		// they were located on the pod, we need to strip down that prefix, but
-		// if the prefix is missing it means the tar was tempered with
+		// if the prefix is missing it means the tar was tempered with.
+		// For the case where prefix is empty we need to ensure that the path
+		// is not absolute, which also indicates the tar file was tempered with.
 		if !strings.HasPrefix(header.Name, prefix) {
 			return fmt.Errorf("tar contents corrupted")
 		}
-		outFileName := path.Join(destFile, clean(header.Name[len(prefix):]))
-		baseName := path.Dir(outFileName)
+
+		// basic file information
+		mode := header.FileInfo().Mode()
+		destFileName := path.Join(destDir, header.Name[len(prefix):])
+		baseName := path.Dir(destFileName)
+
 		if err := os.MkdirAll(baseName, 0755); err != nil {
 			return err
 		}
 		if header.FileInfo().IsDir() {
-			if err := os.MkdirAll(outFileName, 0755); err != nil {
+			if err := os.MkdirAll(destFileName, 0755); err != nil {
 				return err
 			}
 			continue
 		}

-		// handle coping remote file into local directory
-		if entrySeq == 0 && !header.FileInfo().IsDir() {
-			exists, err := dirExists(outFileName)
-			if err != nil {
-				return err
-			}
-			if exists {
-				outFileName = filepath.Join(outFileName, path.Base(clean(header.Name)))
-			}
+		// We need to ensure that the destination file is always within boundries
+		// of the destination directory. This prevents any kind of path traversal
+		// from within tar archive.
+		dir, file := filepath.Split(destFileName)
+		evaledPath, err := filepath.EvalSymlinks(dir)
+		if err != nil {
+			return err
+		}
+		// For scrutiny we verify both the actual destination as well as we follow
+		// all the links that might lead outside of the destination directory.
+		if !isDestRelative(destDir, destFileName) || !isDestRelative(destDir, filepath.Join(evaledPath, file)) {
+			fmt.Fprintf(o.IOStreams.ErrOut, "warning: link %q is pointing to %q which is outside target destination, skipping\n", destFileName, header.Linkname)
+			continue
 		}

 		if mode&os.ModeSymlink != 0 {
 			linkname := header.Linkname
-			// error is returned if linkname can't be made relative to destFile,
-			// but relative can end up being ../dir that's why we also need to
-			// verify if relative path is the same after Clean-ing
-			relative, err := filepath.Rel(destFile, linkname)
-			if path.IsAbs(linkname) && (err != nil || relative != stripPathShortcuts(relative)) {
-				fmt.Fprintf(o.IOStreams.ErrOut, "warning: link %q is pointing to %q which is outside target destination, skipping\n", outFileName, header.Linkname)
+			// We need to ensure that the link destination is always within boundries
+			// of the destination directory. This prevents any kind of path traversal
+			// from within tar archive.
+			if !isDestRelative(destDir, linkJoin(destFileName, linkname)) {
+				fmt.Fprintf(o.IOStreams.ErrOut, "warning: link %q is pointing to %q which is outside target destination, skipping\n", destFileName, header.Linkname)
 				continue
 			}
-			if err := os.Symlink(linkname, outFileName); err != nil {
+			if err := os.Symlink(linkname, destFileName); err != nil {
 				return err
 			}
 		} else {
-			outFile, err := os.Create(outFileName)
+			outFile, err := os.Create(destFileName)
 			if err != nil {
 				return err
 			}
@@ -70,10 +76,5 @@
 		}
 	}

-	if entrySeq == -1 {
-		//if no file was copied
-		errInfo := fmt.Sprintf("error: %s no such file or directory", prefix)
-		return errors.New(errInfo)
-	}
 	return nil
 }

--- a/pkg/kubectl/cmd/cp/cp.go
+++ b/pkg/kubectl/cmd/cp/cp.go
@@ -1,10 +1,6 @@
-func dirExists(path string) (bool, error) {
-	fi, err := os.Stat(path)
-	if err == nil && fi.IsDir() {
-		return true, nil
+func linkJoin(base, link string) string {
+	if filepath.IsAbs(link) {
+		return link
 	}
-	if os.IsNotExist(err) {
-		return false, nil
-	}
-	return false, err
+	return filepath.Join(base, link)
 }

--- /dev/null
+++ b/pkg/kubectl/cmd/cp/cp.go
@@ -0,0 +1,11 @@
+func isDestRelative(base, dest string) bool {
+	fullPath := dest
+	if !filepath.IsAbs(dest) {
+		fullPath = filepath.Join(base, dest)
+	}
+	relative, err := filepath.Rel(base, fullPath)
+	if err != nil {
+		return false
+	}
+	return relative == "." || relative == stripPathShortcuts(relative)
+}


The OpenTelemetry Collector module AWS firehose receiver is for ingesting AWS Kinesis Data Firehose delivery stream messages and parsing the records received based on the configured record type. `awsfirehosereceiver` allows unauthenticated remote requests, even when configured to require a key. OpenTelemetry Collector can be configured to receive CloudWatch metrics via an AWS Firehose Stream. Firehose sets the header `X-Amz-Firehose-Access-Key` with an arbitrary configured string. The OpenTelemetry Collector awsfirehosereceiver can optionally be configured to require this key on incoming requests. However, when this is configured it **still accepts incoming requests with no key**. Only OpenTelemetry Collector users configured with the “alpha” `awsfirehosereceiver` module are affected. This module was added in version v0.49.0 of the “Contrib” distribution (or may be included in custom builds). There is a risk of unauthorized users writing metrics. Carefully crafted metrics could hide other malicious activity. There is no risk of exfiltrating data. It’s likely these endpoints will be exposed to the public internet, as Firehose does not support private HTTP endpoints. A fix was introduced in PR #34847 and released with v0.108.0. All users are advised to upgrade. There are no known workarounds for this vulnerability.

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 (fmr *firehoseReceiver) validate(r *http.Request) (int, error) {
	if accessKey := r.Header.Get(headerFirehoseAccessKey); accessKey != "" && accessKey != string(fmr.config.AccessKey) {
		return http.StatusUnauthorized, errInvalidAccessKey
	}
	return http.StatusAccepted, nil
}

func (fmr *firehoseReceiver) validate(r *http.Request) (int, error) {
	if string(fmr.config.AccessKey) == "" {
		// No access key is configured - accept all requests.
		return http.StatusAccepted, nil
	}
	if accessKey := r.Header.Get(headerFirehoseAccessKey); accessKey == string(fmr.config.AccessKey) {
		return http.StatusAccepted, nil
	}
	return http.StatusUnauthorized, errInvalidAccessKey
}

--- a/receiver/awsfirehosereceiver/receiver.go
+++ b/receiver/awsfirehosereceiver/receiver.go
@@ -1,6 +1,10 @@
 func (fmr *firehoseReceiver) validate(r *http.Request) (int, error) {
-	if accessKey := r.Header.Get(headerFirehoseAccessKey); accessKey != "" && accessKey != string(fmr.config.AccessKey) {
-		return http.StatusUnauthorized, errInvalidAccessKey
+	if string(fmr.config.AccessKey) == "" {
+		// No access key is configured - accept all requests.
+		return http.StatusAccepted, nil
 	}
-	return http.StatusAccepted, nil
+	if accessKey := r.Header.Get(headerFirehoseAccessKey); accessKey == string(fmr.config.AccessKey) {
+		return http.StatusAccepted, nil
+	}
+	return http.StatusUnauthorized, errInvalidAccessKey
 }

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

cd /workspace/opentelemetry-collector-contrib
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
cd ./receiver/awsfirehosereceiver && go test  -timeout 30s -run ^TestFirehoseRequest$ github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsfirehosereceiver

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

cd /workspace/opentelemetry-collector-contrib
git apply --whitespace=nowarn /workspace/fix.patch
cd ./receiver/awsfirehosereceiver && go test -timeout 30s -run '^(TestStart|TestFirehoseRequest)$' github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsfirehosereceiver


An SQL injection vulnerability in Traffic Ops in Apache Traffic Control <= 8.0.1, >= 8.0.0 allows a privileged user with role "admin", "federation", "operations", "portal", or "steering" to execute arbitrary SQL against the database by sending a specially-crafted PUT request.

Users are recommended to upgrade to version Apache Traffic Control 8.0.2 if you run an affected version of Traffic Ops.

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 Validate(dsrc tc.DeliveryServiceRequestCommentV5) error {
	errs := validation.Errors{
		"deliveryServiceRequestId": validation.Validate(dsrc.DeliveryServiceRequestID, validation.NotNil),
		"value":                    validation.Validate(dsrc.Value, validation.NotNil),
	}
	return util.JoinErrs(tovalidate.ToErrors(errs))
}

func Update(w http.ResponseWriter, r *http.Request) {
	var deliveryServiceRequestComment tc.DeliveryServiceRequestCommentV5

	inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, nil)
	if userErr != nil || sysErr != nil {
		api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
		return
	}
	defer inf.Close()

	tx := inf.Tx.Tx
	if err := json.NewDecoder(r.Body).Decode(&deliveryServiceRequestComment); err != nil {
		api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
		return
	}

	if err := Validate(deliveryServiceRequestComment); err != nil {
		api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
		return
	}

	var current tc.DeliveryServiceRequestCommentV5
	err := inf.Tx.QueryRowx(selectQuery() + `WHERE dsrc.id=` + inf.Params["id"]).StructScan(&current)
	if err != nil {
		api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("scanning deliveryservice_request_comment: "+err.Error()))
		return
	}

	userID := tc.IDNoMod(inf.User.ID)
	if current.AuthorID != userID {
		api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("comments can only be updated by the author"), nil)
		return
	}
	deliveryServiceRequestComment.AuthorID = current.AuthorID
	idParam := inf.Params["id"]
	id, parseErr := strconv.Atoi(idParam)
	if parseErr != nil {
		api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("id must be an integer"), nil)
		return
	}
	deliveryServiceRequestComment.ID = id
	userErr, sysErr, sc := api.CheckIfUnModified(r.Header, inf.Tx, id, "deliveryservice_request_comment")
	if userErr != nil || sysErr != nil {
		api.HandleErr(w, r, tx, sc, userErr, sysErr)
		return
	}

	rows, err := inf.Tx.NamedQuery(updateQuery(), deliveryServiceRequestComment)
	if err != nil {
		userErr, sysErr, sc := api.ParseDBError(err)
		api.HandleErr(w, r, tx, sc, userErr, sysErr)
		return
	}
	defer rows.Close()

	if !rows.Next() {
		api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no deliveryservice_request_comment found with this id"), nil)
		return
	}
	lastUpdated := time.Time{}
	if err := rows.Scan(&lastUpdated); err != nil {
		api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("scanning lastUpdated from deliveryservice_request_comment insert: "+err.Error()))
		return
	}
	deliveryServiceRequestComment.LastUpdated = time.Now()

	if rows.Next() {
		api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("deliveryservice_request_comment update affected too many rows: >1"))
		return
	}

	alerts := tc.CreateAlerts(tc.SuccessLevel, "deliveryservice_request_comment was updated.")
	api.WriteAlertsObj(w, r, http.StatusOK, alerts, deliveryServiceRequestComment)
	changeLogMsg := fmt.Sprintf("DELIVERYSERVICE_REQUEST_COMMENT: %d, ID: %d, ACTION: Updated deliveryservice_request_comment", deliveryServiceRequestComment.ID, deliveryServiceRequestComment.ID)
	api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
}

func Validate(dsrc tc.DeliveryServiceRequestCommentV5) error {
	errs := validation.Errors{
		"id":                       validation.Validate(dsrc.ID, validation.NotNil),
		"deliveryServiceRequestId": validation.Validate(dsrc.DeliveryServiceRequestID, validation.NotNil),
		"value":                    validation.Validate(dsrc.Value, validation.NotNil),
	}
	return util.JoinErrs(tovalidate.ToErrors(errs))
}

func Update(w http.ResponseWriter, r *http.Request) {
	var deliveryServiceRequestComment tc.DeliveryServiceRequestCommentV5

	inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, nil)
	if userErr != nil || sysErr != nil {
		api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
		return
	}
	defer inf.Close()

	tx := inf.Tx.Tx
	if err := json.NewDecoder(r.Body).Decode(&deliveryServiceRequestComment); err != nil {
		api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
		return
	}
	idParam := inf.Params["id"]
	id, parseErr := strconv.Atoi(idParam)
	if parseErr != nil {
		api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("id must be an integer"), nil)
		return
	}
	deliveryServiceRequestComment.ID = id

	if err := Validate(deliveryServiceRequestComment); err != nil {
		api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
		return
	}

	var current tc.DeliveryServiceRequestCommentV5
	err := inf.Tx.QueryRowx(selectQuery() + `WHERE dsrc.id=` + strconv.Itoa(deliveryServiceRequestComment.ID)).StructScan(&current)
	if err != nil {
		api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("scanning deliveryservice_request_comment: "+err.Error()))
		return
	}

	userID := tc.IDNoMod(inf.User.ID)
	if current.AuthorID != userID {
		api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("comments can only be updated by the author"), nil)
		return
	}
	deliveryServiceRequestComment.AuthorID = current.AuthorID
	userErr, sysErr, sc := api.CheckIfUnModified(r.Header, inf.Tx, id, "deliveryservice_request_comment")
	if userErr != nil || sysErr != nil {
		api.HandleErr(w, r, tx, sc, userErr, sysErr)
		return
	}

	rows, err := inf.Tx.NamedQuery(updateQuery(), deliveryServiceRequestComment)
	if err != nil {
		userErr, sysErr, sc := api.ParseDBError(err)
		api.HandleErr(w, r, tx, sc, userErr, sysErr)
		return
	}
	defer rows.Close()

	if !rows.Next() {
		api.HandleErr(w, r, tx, http.StatusNotFound, errors.New("no deliveryservice_request_comment found with this id"), nil)
		return
	}
	lastUpdated := time.Time{}
	if err := rows.Scan(&lastUpdated); err != nil {
		api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("scanning lastUpdated from deliveryservice_request_comment insert: "+err.Error()))
		return
	}
	deliveryServiceRequestComment.LastUpdated = time.Now()

	if rows.Next() {
		api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("deliveryservice_request_comment update affected too many rows: >1"))
		return
	}

	alerts := tc.CreateAlerts(tc.SuccessLevel, "deliveryservice_request_comment was updated.")
	api.WriteAlertsObj(w, r, http.StatusOK, alerts, deliveryServiceRequestComment)
	changeLogMsg := fmt.Sprintf("DELIVERYSERVICE_REQUEST_COMMENT: %d, ID: %d, ACTION: Updated deliveryservice_request_comment", deliveryServiceRequestComment.ID, deliveryServiceRequestComment.ID)
	api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
}

--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments.go
@@ -1,5 +1,6 @@
 func Validate(dsrc tc.DeliveryServiceRequestCommentV5) error {
 	errs := validation.Errors{
+		"id":                       validation.Validate(dsrc.ID, validation.NotNil),
 		"deliveryServiceRequestId": validation.Validate(dsrc.DeliveryServiceRequestID, validation.NotNil),
 		"value":                    validation.Validate(dsrc.Value, validation.NotNil),
 	}

--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments.go
@@ -13,6 +13,13 @@
 		api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
 		return
 	}
+	idParam := inf.Params["id"]
+	id, parseErr := strconv.Atoi(idParam)
+	if parseErr != nil {
+		api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("id must be an integer"), nil)
+		return
+	}
+	deliveryServiceRequestComment.ID = id

 	if err := Validate(deliveryServiceRequestComment); err != nil {
 		api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
@@ -20,7 +27,7 @@
 	}

 	var current tc.DeliveryServiceRequestCommentV5
-	err := inf.Tx.QueryRowx(selectQuery() + `WHERE dsrc.id=` + inf.Params["id"]).StructScan(&current)
+	err := inf.Tx.QueryRowx(selectQuery() + `WHERE dsrc.id=` + strconv.Itoa(deliveryServiceRequestComment.ID)).StructScan(&current)
 	if err != nil {
 		api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, errors.New("scanning deliveryservice_request_comment: "+err.Error()))
 		return
@@ -32,13 +39,6 @@
 		return
 	}
 	deliveryServiceRequestComment.AuthorID = current.AuthorID
-	idParam := inf.Params["id"]
-	id, parseErr := strconv.Atoi(idParam)
-	if parseErr != nil {
-		api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("id must be an integer"), nil)
-		return
-	}
-	deliveryServiceRequestComment.ID = id
 	userErr, sysErr, sc := api.CheckIfUnModified(r.Header, inf.Tx, id, "deliveryservice_request_comment")
 	if userErr != nil || sysErr != nil {
 		api.HandleErr(w, r, tx, sc, userErr, sysErr)


Nodebb is an open source Node.js based forum software. Prior to v1.18.5, a path traversal vulnerability was present that allowed users to access JSON files outside of the expected `languages/` directory. The vulnerability has been patched as of v1.18.5. Users are advised to upgrade as soon as possible.

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.

Languages.get = async function (language, namespace) {
	const data = await fs.promises.readFile(path.join(languagesPath, language, `${namespace}.json`), 'utf8');
	const parsed = JSON.parse(data) || {};
	const result = await plugins.hooks.fire('filter:languages.get', {
		language,
		namespace,
		data: parsed,
	});
	return result.data;
};

Languages.get = async function (language, namespace) {
	const pathToLanguageFile = path.join(languagesPath, language, `${namespace}.json`);
	if (!pathToLanguageFile.startsWith(languagesPath)) {
		throw new Error('[[error:invalid-path]]');
	}
	const data = await fs.promises.readFile(pathToLanguageFile, 'utf8');
	const parsed = JSON.parse(data) || {};
	const result = await plugins.hooks.fire('filter:languages.get', {
		language,
		namespace,
		data: parsed,
	});
	return result.data;
};

--- a/src/languages.js
+++ b/src/languages.js
@@ -1,5 +1,9 @@
 Languages.get = async function (language, namespace) {
-	const data = await fs.promises.readFile(path.join(languagesPath, language, `${namespace}.json`), 'utf8');
+	const pathToLanguageFile = path.join(languagesPath, language, `${namespace}.json`);
+	if (!pathToLanguageFile.startsWith(languagesPath)) {
+		throw new Error('[[error:invalid-path]]');
+	}
+	const data = await fs.promises.readFile(pathToLanguageFile, 'utf8');
 	const parsed = JSON.parse(data) || {};
 	const result = await plugins.hooks.fire('filter:languages.get', {
 		language,


fstream before 1.0.12 is vulnerable to Arbitrary File Overwrite. Extracting tarballs containing a hardlink to a file that already exists in the system, and a file that matches the hardlink, will overwrite the system's file with the contents of the extracted file. The fstream.DirWriter() function is vulnerable.

Improper Link Resolution Before File Access ('Link Following')

The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.

  function statCb (er, current) {
    if (self.filter && !self.filter.call(who, who, current)) {
      self._aborted = true
      self.emit('end')
      self.emit('close')
      return
    }

    // if it's not there, great.  We'll just create it.
    // if it is there, then we'll need to change whatever differs
    if (er || !current) {
      return create(self)
    }

    self._old = current
    var currentType = getType(current)

    // if it's a type change, then we need to clobber or error.
    // if it's not a type change, then let the impl take care of it.
    if (currentType !== self.type) {
      return rimraf(self._path, function (er) {
        if (er) return self.error(er)
        self._old = null
        create(self)
      })
    }

    // otherwise, just handle in the app-specific way
    // this creates a fs.WriteStream, or mkdir's, or whatever
    create(self)
  }
}

  function statCb (er, current) {
    if (self.filter && !self.filter.call(who, who, current)) {
      self._aborted = true
      self.emit('end')
      self.emit('close')
      return
    }

    // if it's not there, great.  We'll just create it.
    // if it is there, then we'll need to change whatever differs
    if (er || !current) {
      return create(self)
    }

    self._old = current
    var currentType = getType(current)

    // if it's a type change, then we need to clobber or error.
    // if it's not a type change, then let the impl take care of it.
    if (currentType !== self.type || self.type === 'File' && current.nlink > 1) {
      return rimraf(self._path, function (er) {
        if (er) return self.error(er)
        self._old = null
        create(self)
      })
    }

    // otherwise, just handle in the app-specific way
    // this creates a fs.WriteStream, or mkdir's, or whatever
    create(self)
  }
}

--- a/lib/writer.js
+++ b/lib/writer.js
@@ -17,7 +17,7 @@

     // if it's a type change, then we need to clobber or error.
     // if it's not a type change, then let the impl take care of it.
-    if (currentType !== self.type) {
+    if (currentType !== self.type || self.type === 'File' && current.nlink > 1) {
       return rimraf(self._path, function (er) {
         if (er) return self.error(er)
         self._old = null


The pullit package before 1.4.0 for Node.js allows OS Command Injection because eval is used on an attacker-supplied Git branch name.

Improper Control of Generation of Code ('Code Injection')

The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.

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

The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.

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

The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.

const {
  execSync
} = require('child_process');

  fetch(id) {
    return this.github.pullRequests
      .get({
        owner: this.owner,
        repo: this.repo,
        number: id
      })
      .then(res => {
        const branch = res.data.head.ref;
        execSync(
          `git fetch origin pull/${id}/head:${branch} && git checkout ${branch}`
        );
      })
      .catch(err => {
        console.log('Error: Could not find the specified pull request.');
      });
  }

  fetch(id) {
    return this.github.pullRequests
      .get({
        owner: this.owner,
        repo: this.repo,
        number: id
      })
      .then(res => {
        const branch = res.data.head.ref;
        execFileSync("git", ["fetch", "origin", `pull/${id}/head:${branch}`]);
        execFileSync("git", ["checkout", branch]);
      })
      .catch(err => {
        console.log("Error: Could not find the specified pull request.");
      });
  }

--- a/src/index.js
+++ b/src/index.js
@@ -1,3 +1 @@
-const {
-  execSync
-} = require('child_process');
+const { execFileSync } = require("child_process");

--- a/src/index.js
+++ b/src/index.js
@@ -7,11 +7,10 @@
       })
       .then(res => {
         const branch = res.data.head.ref;
-        execSync(
-          `git fetch origin pull/${id}/head:${branch} && git checkout ${branch}`
-        );
+        execFileSync("git", ["fetch", "origin", `pull/${id}/head:${branch}`]);
+        execFileSync("git", ["checkout", branch]);
       })
       .catch(err => {
-        console.log('Error: Could not find the specified pull request.');
+        console.log("Error: Could not find the specified pull request.");
       });
   }


The package joblib from 0 and before 1.2.0 are vulnerable to Arbitrary Code Execution via the pre_dispatch flag in Parallel() class due to the eval() statement.

    def __call__(self, iterable):
        if self._jobs:
            raise ValueError('This Parallel instance is already running')
        # A flag used to abort the dispatching of jobs in case an
        # exception is found
        self._aborting = False

        if not self._managed_backend:
            n_jobs = self._initialize_backend()
        else:
            n_jobs = self._effective_n_jobs()

        if isinstance(self._backend, LokyBackend):
            # For the loky backend, we add a callback executed when reducing
            # BatchCalls, that makes the loky executor use a temporary folder
            # specific to this Parallel object when pickling temporary memmaps.
            # This callback is necessary to ensure that several Parallel
            # objects using the same resuable executor don't use the same
            # temporary resources.

            def _batched_calls_reducer_callback():
                # Relevant implementation detail: the following lines, called
                # when reducing BatchedCalls, are called in a thread-safe
                # situation, meaning that the context of the temporary folder
                # manager will not be changed in between the callback execution
                # and the end of the BatchedCalls pickling. The reason is that
                # pickling (the only place where set_current_context is used)
                # is done from a single thread (the queue_feeder_thread).
                self._backend._workers._temp_folder_manager.set_current_context(  # noqa
                    self._id
                )
            self._reducer_callback = _batched_calls_reducer_callback

        # self._effective_n_jobs should be called in the Parallel.__call__
        # thread only -- store its value in an attribute for further queries.
        self._cached_effective_n_jobs = n_jobs

        backend_name = self._backend.__class__.__name__
        if n_jobs == 0:
            raise RuntimeError("%s has no active worker." % backend_name)

        self._print("Using backend %s with %d concurrent workers.",
                    (backend_name, n_jobs))
        if hasattr(self._backend, 'start_call'):
            self._backend.start_call()
        iterator = iter(iterable)
        pre_dispatch = self.pre_dispatch

        if pre_dispatch == 'all' or n_jobs == 1:
            # prevent further dispatch via multiprocessing callback thread
            self._original_iterator = None
            self._pre_dispatch_amount = 0
        else:
            self._original_iterator = iterator
            if hasattr(pre_dispatch, 'endswith'):
                pre_dispatch = eval(
                    pre_dispatch,
                    {"n_jobs": n_jobs, "__builtins__": {}},  # globals
                    {}  # locals
                )
            self._pre_dispatch_amount = pre_dispatch = int(pre_dispatch)

            # The main thread will consume the first pre_dispatch items and
            # the remaining items will later be lazily dispatched by async
            # callbacks upon task completions.

            # TODO: this iterator should be batch_size * n_jobs
            iterator = itertools.islice(iterator, self._pre_dispatch_amount)

        self._start_time = time.time()
        self.n_dispatched_batches = 0
        self.n_dispatched_tasks = 0
        self.n_completed_tasks = 0
        # Use a caching dict for callables that are pickled with cloudpickle to
        # improve performances. This cache is used only in the case of
        # functions that are defined in the __main__ module, functions that are
        # defined locally (inside another function) and lambda expressions.
        self._pickle_cache = dict()
        try:
            # Only set self._iterating to True if at least a batch
            # was dispatched. In particular this covers the edge
            # case of Parallel used with an exhausted iterator. If
            # self._original_iterator is None, then this means either
            # that pre_dispatch == "all", n_jobs == 1 or that the first batch
            # was very quick and its callback already dispatched all the
            # remaining jobs.
            self._iterating = False
            if self.dispatch_one_batch(iterator):
                self._iterating = self._original_iterator is not None

            while self.dispatch_one_batch(iterator):
                pass

            if pre_dispatch == "all" or n_jobs == 1:
                # The iterable was consumed all at once by the above for loop.
                # No need to wait for async callbacks to trigger to
                # consumption.
                self._iterating = False

            with self._backend.retrieval_context():
                self.retrieve()
            # Make sure that we get a last message telling us we are done
            elapsed_time = time.time() - self._start_time
            self._print('Done %3i out of %3i | elapsed: %s finished',
                        (len(self._output), len(self._output),
                         short_format_time(elapsed_time)))
        finally:
            if hasattr(self._backend, 'stop_call'):
                self._backend.stop_call()
            if not self._managed_backend:
                self._terminate_backend()
            self._jobs = list()
            self._pickle_cache = None
        output = self._output
        self._output = None
        return output

    def __call__(self, iterable):
        if self._jobs:
            raise ValueError('This Parallel instance is already running')
        # A flag used to abort the dispatching of jobs in case an
        # exception is found
        self._aborting = False

        if not self._managed_backend:
            n_jobs = self._initialize_backend()
        else:
            n_jobs = self._effective_n_jobs()

        if isinstance(self._backend, LokyBackend):
            # For the loky backend, we add a callback executed when reducing
            # BatchCalls, that makes the loky executor use a temporary folder
            # specific to this Parallel object when pickling temporary memmaps.
            # This callback is necessary to ensure that several Parallel
            # objects using the same resuable executor don't use the same
            # temporary resources.

            def _batched_calls_reducer_callback():
                # Relevant implementation detail: the following lines, called
                # when reducing BatchedCalls, are called in a thread-safe
                # situation, meaning that the context of the temporary folder
                # manager will not be changed in between the callback execution
                # and the end of the BatchedCalls pickling. The reason is that
                # pickling (the only place where set_current_context is used)
                # is done from a single thread (the queue_feeder_thread).
                self._backend._workers._temp_folder_manager.set_current_context(  # noqa
                    self._id
                )
            self._reducer_callback = _batched_calls_reducer_callback

        # self._effective_n_jobs should be called in the Parallel.__call__
        # thread only -- store its value in an attribute for further queries.
        self._cached_effective_n_jobs = n_jobs

        backend_name = self._backend.__class__.__name__
        if n_jobs == 0:
            raise RuntimeError("%s has no active worker." % backend_name)

        self._print("Using backend %s with %d concurrent workers.",
                    (backend_name, n_jobs))
        if hasattr(self._backend, 'start_call'):
            self._backend.start_call()
        iterator = iter(iterable)
        pre_dispatch = self.pre_dispatch

        if pre_dispatch == 'all' or n_jobs == 1:
            # prevent further dispatch via multiprocessing callback thread
            self._original_iterator = None
            self._pre_dispatch_amount = 0
        else:
            self._original_iterator = iterator
            if hasattr(pre_dispatch, 'endswith'):
                pre_dispatch = eval_expr(
                    pre_dispatch.replace("n_jobs", str(n_jobs))
                )
            self._pre_dispatch_amount = pre_dispatch = int(pre_dispatch)

            # The main thread will consume the first pre_dispatch items and
            # the remaining items will later be lazily dispatched by async
            # callbacks upon task completions.

            # TODO: this iterator should be batch_size * n_jobs
            iterator = itertools.islice(iterator, self._pre_dispatch_amount)

        self._start_time = time.time()
        self.n_dispatched_batches = 0
        self.n_dispatched_tasks = 0
        self.n_completed_tasks = 0
        # Use a caching dict for callables that are pickled with cloudpickle to
        # improve performances. This cache is used only in the case of
        # functions that are defined in the __main__ module, functions that are
        # defined locally (inside another function) and lambda expressions.
        self._pickle_cache = dict()
        try:
            # Only set self._iterating to True if at least a batch
            # was dispatched. In particular this covers the edge
            # case of Parallel used with an exhausted iterator. If
            # self._original_iterator is None, then this means either
            # that pre_dispatch == "all", n_jobs == 1 or that the first batch
            # was very quick and its callback already dispatched all the
            # remaining jobs.
            self._iterating = False
            if self.dispatch_one_batch(iterator):
                self._iterating = self._original_iterator is not None

            while self.dispatch_one_batch(iterator):
                pass

            if pre_dispatch == "all" or n_jobs == 1:
                # The iterable was consumed all at once by the above for loop.
                # No need to wait for async callbacks to trigger to
                # consumption.
                self._iterating = False

            with self._backend.retrieval_context():
                self.retrieve()
            # Make sure that we get a last message telling us we are done
            elapsed_time = time.time() - self._start_time
            self._print('Done %3i out of %3i | elapsed: %s finished',
                        (len(self._output), len(self._output),
                         short_format_time(elapsed_time)))
        finally:
            if hasattr(self._backend, 'stop_call'):
                self._backend.stop_call()
            if not self._managed_backend:
                self._terminate_backend()
            self._jobs = list()
            self._pickle_cache = None
        output = self._output
        self._output = None
        return output

--- a/joblib/parallel.py
+++ b/joblib/parallel.py
@@ -53,10 +53,8 @@
         else:
             self._original_iterator = iterator
             if hasattr(pre_dispatch, 'endswith'):
-                pre_dispatch = eval(
-                    pre_dispatch,
-                    {"n_jobs": n_jobs, "__builtins__": {}},  # globals
-                    {}  # locals
+                pre_dispatch = eval_expr(
+                    pre_dispatch.replace("n_jobs", str(n_jobs))
                 )
             self._pre_dispatch_amount = pre_dispatch = int(pre_dispatch)


Missing Authorization in GitHub repository go-gitea/gitea prior to 1.16.4.

Missing Authorization

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

Authorization Bypass Through User-Controlled Key

The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data.

func Auth(serviceName, userName, passwd string) (string, error) {
	t, err := pam.StartFunc(serviceName, userName, func(s pam.Style, msg string) (string, error) {
		switch s {
		case pam.PromptEchoOff:
			return passwd, nil
		case pam.PromptEchoOn, pam.ErrorMsg, pam.TextInfo:
			return "", nil
		}
		return "", errors.New("Unrecognized PAM message style")
	})

	if err != nil {
		return "", err
	}

	if err = t.Authenticate(0); err != nil {
		return "", err
	}

	if err = t.AcctMgmt(0); err != nil {
	  return "", err
  }

--- a/modules/auth/pam/pam.go
+++ b/modules/auth/pam/pam.go
@@ -16,3 +16,7 @@
 	if err = t.Authenticate(0); err != nil {
 		return "", err
 	}
+
+	if err = t.AcctMgmt(0); err != nil {
+	  return "", err
+  }


A maliciously crafted URL to a Django (1.10 before 1.10.7, 1.9 before 1.9.13, and 1.8 before 1.8.18) site using the ``django.views.static.serve()`` view could redirect to any other domain, aka an open redirect vulnerability.

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 serve(request, path, document_root=None, show_indexes=False):
    """
    Serve static files below a given point in the directory structure.

    To use, put a URL pattern such as::

        from django.views.static import serve

        url(r'^(?P<path>.*)$', serve, {'document_root': '/path/to/my/files/'})

    in your URLconf. You must provide the ``document_root`` param. You may
    also set ``show_indexes`` to ``True`` if you'd like to serve a basic index
    of the directory.  This index view will use the template hardcoded below,
    but if you'd like to override it, you can create a template called
    ``static/directory_index.html``.
    """
    path = posixpath.normpath(unquote(path))
    path = path.lstrip('/')
    newpath = ''
    for part in path.split('/'):
        if not part:
            # Strip empty path components.
            continue
        drive, part = os.path.splitdrive(part)
        head, part = os.path.split(part)
        if part in (os.curdir, os.pardir):
            # Strip '.' and '..' in path.
            continue
        newpath = os.path.join(newpath, part).replace('\\', '/')
    if newpath and path != newpath:
        return HttpResponseRedirect(newpath)
    fullpath = os.path.join(document_root, newpath)
    if os.path.isdir(fullpath):
        if show_indexes:
            return directory_index(newpath, fullpath)
        raise Http404(_("Directory indexes are not allowed here."))
    if not os.path.exists(fullpath):
        raise Http404(_('"%(path)s" does not exist') % {'path': fullpath})
    # Respect the If-Modified-Since header.
    statobj = os.stat(fullpath)
    if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
                              statobj.st_mtime, statobj.st_size):
        return HttpResponseNotModified()
    content_type, encoding = mimetypes.guess_type(fullpath)
    content_type = content_type or 'application/octet-stream'
    response = FileResponse(open(fullpath, 'rb'), content_type=content_type)
    response["Last-Modified"] = http_date(statobj.st_mtime)
    if stat.S_ISREG(statobj.st_mode):
        response["Content-Length"] = statobj.st_size
    if encoding:
        response["Content-Encoding"] = encoding
    return response

def serve(request, path, document_root=None, show_indexes=False):
    """
    Serve static files below a given point in the directory structure.

    To use, put a URL pattern such as::

        from django.views.static import serve

        url(r'^(?P<path>.*)$', serve, {'document_root': '/path/to/my/files/'})

    in your URLconf. You must provide the ``document_root`` param. You may
    also set ``show_indexes`` to ``True`` if you'd like to serve a basic index
    of the directory.  This index view will use the template hardcoded below,
    but if you'd like to override it, you can create a template called
    ``static/directory_index.html``.
    """
    path = posixpath.normpath(unquote(path)).lstrip('/')
    fullpath = safe_join(document_root, path)
    if os.path.isdir(fullpath):
        if show_indexes:
            return directory_index(path, fullpath)
        raise Http404(_("Directory indexes are not allowed here."))
    if not os.path.exists(fullpath):
        raise Http404(_('"%(path)s" does not exist') % {'path': fullpath})
    # Respect the If-Modified-Since header.
    statobj = os.stat(fullpath)
    if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
                              statobj.st_mtime, statobj.st_size):
        return HttpResponseNotModified()
    content_type, encoding = mimetypes.guess_type(fullpath)
    content_type = content_type or 'application/octet-stream'
    response = FileResponse(open(fullpath, 'rb'), content_type=content_type)
    response["Last-Modified"] = http_date(statobj.st_mtime)
    if stat.S_ISREG(statobj.st_mode):
        response["Content-Length"] = statobj.st_size
    if encoding:
        response["Content-Encoding"] = encoding
    return response

--- a/django/views/static.py
+++ b/django/views/static.py
@@ -14,25 +14,11 @@
     but if you'd like to override it, you can create a template called
     ``static/directory_index.html``.
     """
-    path = posixpath.normpath(unquote(path))
-    path = path.lstrip('/')
-    newpath = ''
-    for part in path.split('/'):
-        if not part:
-            # Strip empty path components.
-            continue
-        drive, part = os.path.splitdrive(part)
-        head, part = os.path.split(part)
-        if part in (os.curdir, os.pardir):
-            # Strip '.' and '..' in path.
-            continue
-        newpath = os.path.join(newpath, part).replace('\\', '/')
-    if newpath and path != newpath:
-        return HttpResponseRedirect(newpath)
-    fullpath = os.path.join(document_root, newpath)
+    path = posixpath.normpath(unquote(path)).lstrip('/')
+    fullpath = safe_join(document_root, path)
     if os.path.isdir(fullpath):
         if show_indexes:
-            return directory_index(newpath, fullpath)
+            return directory_index(path, fullpath)
         raise Http404(_("Directory indexes are not allowed here."))
     if not os.path.exists(fullpath):
         raise Http404(_('"%(path)s" does not exist') % {'path': fullpath})


regclient is a Docker and OCI Registry Client in Go. A malicious registry could return a different digest for a pinned manifest without detection. This vulnerability is fixed in 0.7.1.

Insufficient Verification of Data Authenticity

The product does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data.

func New(opts ...Opts) (Manifest, error) {
	mc := manifestConfig{}
	for _, opt := range opts {
		opt(&mc)
	}
	c := common{
		r:         mc.r,
		desc:      mc.desc,
		rawBody:   mc.raw,
		rawHeader: mc.header,
	}
	// extract fields from header where available
	if mc.header != nil {
		if c.desc.MediaType == "" {
			c.desc.MediaType = mediatype.Base(mc.header.Get("Content-Type"))
		}
		if c.desc.Size == 0 {
			cl, _ := strconv.Atoi(mc.header.Get("Content-Length"))
			c.desc.Size = int64(cl)
		}
		if c.desc.Digest == "" {
			c.desc.Digest, _ = digest.Parse(mc.header.Get("Docker-Content-Digest"))
		}
		c.setRateLimit(mc.header)
	}
	if mc.orig != nil {
		return fromOrig(c, mc.orig)
	}
	return fromCommon(c)
}

func fromOrig(c common, orig interface{}) (Manifest, error) {
	var mt string
	var m Manifest
	origDigest := c.desc.Digest

	mj, err := json.Marshal(orig)
	if err != nil {
		return nil, err
	}
	c.manifSet = true
	if len(c.rawBody) == 0 {
		c.rawBody = mj
	}
	if _, ok := orig.(schema1.SignedManifest); !ok {
		c.desc.Digest = c.desc.DigestAlgo().FromBytes(mj)
	}
	if c.desc.Size == 0 {
		c.desc.Size = int64(len(mj))
	}
	// create manifest based on type
	switch mOrig := orig.(type) {
	case schema1.Manifest:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.Docker1Manifest
		m = &docker1Manifest{
			common:   c,
			Manifest: mOrig,
		}
	case schema1.SignedManifest:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.Docker1ManifestSigned
		// recompute digest on the canonical data
		c.desc.Digest = c.desc.DigestAlgo().FromBytes(mOrig.Canonical)
		m = &docker1SignedManifest{
			common:         c,
			SignedManifest: mOrig,
		}
	case schema2.Manifest:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.Docker2Manifest
		m = &docker2Manifest{
			common:   c,
			Manifest: mOrig,
		}
	case schema2.ManifestList:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.Docker2ManifestList
		m = &docker2ManifestList{
			common:       c,
			ManifestList: mOrig,
		}
	case v1.Manifest:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.OCI1Manifest
		m = &oci1Manifest{
			common:   c,
			Manifest: mOrig,
		}
	case v1.Index:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.OCI1ManifestList
		m = &oci1Index{
			common: c,
			Index:  orig.(v1.Index),
		}
	case v1.ArtifactManifest:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.OCI1Artifact
		m = &oci1Artifact{
			common:           c,
			ArtifactManifest: mOrig,
		}
	default:
		return nil, fmt.Errorf("unsupported type to convert to a manifest: %T", orig)
	}
	// verify media type
	err = verifyMT(c.desc.MediaType, mt)
	if err != nil {
		return nil, err
	}
	// verify digest didn't change
	if origDigest != "" && origDigest != c.desc.Digest {
		return nil, fmt.Errorf("manifest digest mismatch, expected %s, computed %s", origDigest, c.desc.Digest)
	}
	return m, nil
}

func fromCommon(c common) (Manifest, error) {
	var err error
	var m Manifest
	var mt string
	origDigest := c.desc.Digest
	// extract common data from from rawBody
	if len(c.rawBody) > 0 {
		c.manifSet = true
		// extract media type from body, either explicitly or with duck typing
		if c.desc.MediaType == "" {
			mt := struct {
				MediaType     string                  `json:"mediaType,omitempty"`
				SchemaVersion int                     `json:"schemaVersion,omitempty"`
				Signatures    []interface{}           `json:"signatures,omitempty"`
				Manifests     []descriptor.Descriptor `json:"manifests,omitempty"`
				Layers        []descriptor.Descriptor `json:"layers,omitempty"`
			}{}
			err = json.Unmarshal(c.rawBody, &mt)
			if mt.MediaType != "" {
				c.desc.MediaType = mt.MediaType
			} else if mt.SchemaVersion == 1 && len(mt.Signatures) > 0 {
				c.desc.MediaType = mediatype.Docker1ManifestSigned
			} else if mt.SchemaVersion == 1 {
				c.desc.MediaType = mediatype.Docker1Manifest
			} else if len(mt.Manifests) > 0 {
				if strings.HasPrefix(mt.Manifests[0].MediaType, "application/vnd.docker.") {
					c.desc.MediaType = mediatype.Docker2ManifestList
				} else {
					c.desc.MediaType = mediatype.OCI1ManifestList
				}
			} else if len(mt.Layers) > 0 {
				if strings.HasPrefix(mt.Layers[0].MediaType, "application/vnd.docker.") {
					c.desc.MediaType = mediatype.Docker2Manifest
				} else {
					c.desc.MediaType = mediatype.OCI1Manifest
				}
			}
		}
		// compute digest
		if c.desc.MediaType != mediatype.Docker1ManifestSigned {
			d := c.desc.DigestAlgo().FromBytes(c.rawBody)
			c.desc.Digest = d
			c.desc.Size = int64(len(c.rawBody))
		}
	}
	switch c.desc.MediaType {
	case mediatype.Docker1Manifest:
		var mOrig schema1.Manifest
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
		}
		m = &docker1Manifest{common: c, Manifest: mOrig}
	case mediatype.Docker1ManifestSigned:
		var mOrig schema1.SignedManifest
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
			d := c.desc.DigestAlgo().FromBytes(mOrig.Canonical)
			c.desc.Digest = d
			c.desc.Size = int64(len(mOrig.Canonical))
		}
		m = &docker1SignedManifest{common: c, SignedManifest: mOrig}
	case mediatype.Docker2Manifest:
		var mOrig schema2.Manifest
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
		}
		m = &docker2Manifest{common: c, Manifest: mOrig}
	case mediatype.Docker2ManifestList:
		var mOrig schema2.ManifestList
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
		}
		m = &docker2ManifestList{common: c, ManifestList: mOrig}
	case mediatype.OCI1Manifest:
		var mOrig v1.Manifest
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
		}
		m = &oci1Manifest{common: c, Manifest: mOrig}
	case mediatype.OCI1ManifestList:
		var mOrig v1.Index
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
		}
		m = &oci1Index{common: c, Index: mOrig}
	case mediatype.OCI1Artifact:
		var mOrig v1.ArtifactManifest
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
		}
		m = &oci1Artifact{common: c, ArtifactManifest: mOrig}
	default:
		return nil, fmt.Errorf("%w: \"%s\"", errs.ErrUnsupportedMediaType, c.desc.MediaType)
	}
	if err != nil {
		return nil, fmt.Errorf("error unmarshaling manifest for %s: %w", c.r.CommonName(), err)
	}
	// verify media type
	err = verifyMT(c.desc.MediaType, mt)
	if err != nil {
		return nil, err
	}
	// verify digest didn't change
	if origDigest != "" && origDigest != c.desc.Digest {
		return nil, fmt.Errorf("manifest digest mismatch, expected %s, computed %s", origDigest, c.desc.Digest)
	}
	return m, nil
}

func New(opts ...Opts) (Manifest, error) {
	mc := manifestConfig{}
	for _, opt := range opts {
		opt(&mc)
	}
	c := common{
		r:         mc.r,
		desc:      mc.desc,
		rawBody:   mc.raw,
		rawHeader: mc.header,
	}
	if c.r.Digest != "" && c.desc.Digest == "" {
		dig, err := digest.Parse(c.r.Digest)
		if err != nil {
			return nil, fmt.Errorf("failed to parse digest from ref: %w", err)
		}
		c.desc.Digest = dig
	}
	// extract fields from header where available
	if mc.header != nil {
		if c.desc.MediaType == "" {
			c.desc.MediaType = mediatype.Base(mc.header.Get("Content-Type"))
		}
		if c.desc.Size == 0 {
			cl, _ := strconv.Atoi(mc.header.Get("Content-Length"))
			c.desc.Size = int64(cl)
		}
		if c.desc.Digest == "" {
			c.desc.Digest, _ = digest.Parse(mc.header.Get("Docker-Content-Digest"))
		}
		c.setRateLimit(mc.header)
	}
	if mc.orig != nil {
		return fromOrig(c, mc.orig)
	}
	return fromCommon(c)
}

func fromOrig(c common, orig interface{}) (Manifest, error) {
	var mt string
	var m Manifest
	origDigest := c.desc.Digest

	mj, err := json.Marshal(orig)
	if err != nil {
		return nil, err
	}
	c.manifSet = true
	if len(c.rawBody) == 0 {
		c.rawBody = mj
	}
	if _, ok := orig.(schema1.SignedManifest); !ok {
		c.desc.Digest = c.desc.DigestAlgo().FromBytes(mj)
	}
	if c.desc.Size == 0 {
		c.desc.Size = int64(len(mj))
	}
	// create manifest based on type
	switch mOrig := orig.(type) {
	case schema1.Manifest:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.Docker1Manifest
		m = &docker1Manifest{
			common:   c,
			Manifest: mOrig,
		}
	case schema1.SignedManifest:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.Docker1ManifestSigned
		// recompute digest on the canonical data
		c.desc.Digest = c.desc.DigestAlgo().FromBytes(mOrig.Canonical)
		m = &docker1SignedManifest{
			common:         c,
			SignedManifest: mOrig,
		}
	case schema2.Manifest:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.Docker2Manifest
		m = &docker2Manifest{
			common:   c,
			Manifest: mOrig,
		}
	case schema2.ManifestList:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.Docker2ManifestList
		m = &docker2ManifestList{
			common:       c,
			ManifestList: mOrig,
		}
	case v1.Manifest:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.OCI1Manifest
		m = &oci1Manifest{
			common:   c,
			Manifest: mOrig,
		}
	case v1.Index:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.OCI1ManifestList
		m = &oci1Index{
			common: c,
			Index:  orig.(v1.Index),
		}
	case v1.ArtifactManifest:
		mt = mOrig.MediaType
		c.desc.MediaType = mediatype.OCI1Artifact
		m = &oci1Artifact{
			common:           c,
			ArtifactManifest: mOrig,
		}
	default:
		return nil, fmt.Errorf("unsupported type to convert to a manifest: %T", orig)
	}
	// verify media type
	err = verifyMT(c.desc.MediaType, mt)
	if err != nil {
		return nil, err
	}
	// verify digest didn't change
	if origDigest != "" && origDigest != c.desc.Digest {
		return nil, fmt.Errorf("manifest digest mismatch, expected %s, computed %s%.0w", origDigest, c.desc.Digest, errs.ErrDigestMismatch)
	}
	return m, nil
}

func fromCommon(c common) (Manifest, error) {
	var err error
	var m Manifest
	var mt string
	origDigest := c.desc.Digest
	// extract common data from from rawBody
	if len(c.rawBody) > 0 {
		c.manifSet = true
		// extract media type from body, either explicitly or with duck typing
		if c.desc.MediaType == "" {
			mt := struct {
				MediaType     string                  `json:"mediaType,omitempty"`
				SchemaVersion int                     `json:"schemaVersion,omitempty"`
				Signatures    []interface{}           `json:"signatures,omitempty"`
				Manifests     []descriptor.Descriptor `json:"manifests,omitempty"`
				Layers        []descriptor.Descriptor `json:"layers,omitempty"`
			}{}
			err = json.Unmarshal(c.rawBody, &mt)
			if mt.MediaType != "" {
				c.desc.MediaType = mt.MediaType
			} else if mt.SchemaVersion == 1 && len(mt.Signatures) > 0 {
				c.desc.MediaType = mediatype.Docker1ManifestSigned
			} else if mt.SchemaVersion == 1 {
				c.desc.MediaType = mediatype.Docker1Manifest
			} else if len(mt.Manifests) > 0 {
				if strings.HasPrefix(mt.Manifests[0].MediaType, "application/vnd.docker.") {
					c.desc.MediaType = mediatype.Docker2ManifestList
				} else {
					c.desc.MediaType = mediatype.OCI1ManifestList
				}
			} else if len(mt.Layers) > 0 {
				if strings.HasPrefix(mt.Layers[0].MediaType, "application/vnd.docker.") {
					c.desc.MediaType = mediatype.Docker2Manifest
				} else {
					c.desc.MediaType = mediatype.OCI1Manifest
				}
			}
		}
		// compute digest
		if c.desc.MediaType != mediatype.Docker1ManifestSigned {
			d := c.desc.DigestAlgo().FromBytes(c.rawBody)
			c.desc.Digest = d
			c.desc.Size = int64(len(c.rawBody))
		}
	}
	switch c.desc.MediaType {
	case mediatype.Docker1Manifest:
		var mOrig schema1.Manifest
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
		}
		m = &docker1Manifest{common: c, Manifest: mOrig}
	case mediatype.Docker1ManifestSigned:
		var mOrig schema1.SignedManifest
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
			d := c.desc.DigestAlgo().FromBytes(mOrig.Canonical)
			c.desc.Digest = d
			c.desc.Size = int64(len(mOrig.Canonical))
		}
		m = &docker1SignedManifest{common: c, SignedManifest: mOrig}
	case mediatype.Docker2Manifest:
		var mOrig schema2.Manifest
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
		}
		m = &docker2Manifest{common: c, Manifest: mOrig}
	case mediatype.Docker2ManifestList:
		var mOrig schema2.ManifestList
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
		}
		m = &docker2ManifestList{common: c, ManifestList: mOrig}
	case mediatype.OCI1Manifest:
		var mOrig v1.Manifest
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
		}
		m = &oci1Manifest{common: c, Manifest: mOrig}
	case mediatype.OCI1ManifestList:
		var mOrig v1.Index
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
		}
		m = &oci1Index{common: c, Index: mOrig}
	case mediatype.OCI1Artifact:
		var mOrig v1.ArtifactManifest
		if len(c.rawBody) > 0 {
			err = json.Unmarshal(c.rawBody, &mOrig)
			mt = mOrig.MediaType
		}
		m = &oci1Artifact{common: c, ArtifactManifest: mOrig}
	default:
		return nil, fmt.Errorf("%w: \"%s\"", errs.ErrUnsupportedMediaType, c.desc.MediaType)
	}
	if err != nil {
		return nil, fmt.Errorf("error unmarshaling manifest for %s: %w", c.r.CommonName(), err)
	}
	// verify media type
	err = verifyMT(c.desc.MediaType, mt)
	if err != nil {
		return nil, err
	}
	// verify digest didn't change
	if origDigest != "" && origDigest != c.desc.Digest {
		return nil, fmt.Errorf("manifest digest mismatch, expected %s, computed %s%.0w", origDigest, c.desc.Digest, errs.ErrDigestMismatch)
	}
	return m, nil
}

--- a/types/manifest/manifest.go
+++ b/types/manifest/manifest.go
@@ -8,6 +8,13 @@
 		desc:      mc.desc,
 		rawBody:   mc.raw,
 		rawHeader: mc.header,
+	}
+	if c.r.Digest != "" && c.desc.Digest == "" {
+		dig, err := digest.Parse(c.r.Digest)
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse digest from ref: %w", err)
+		}
+		c.desc.Digest = dig
 	}
 	// extract fields from header where available
 	if mc.header != nil {

--- a/types/manifest/manifest.go
+++ b/types/manifest/manifest.go
@@ -80,7 +80,7 @@
 	}
 	// verify digest didn't change
 	if origDigest != "" && origDigest != c.desc.Digest {
-		return nil, fmt.Errorf("manifest digest mismatch, expected %s, computed %s", origDigest, c.desc.Digest)
+		return nil, fmt.Errorf("manifest digest mismatch, expected %s, computed %s%.0w", origDigest, c.desc.Digest, errs.ErrDigestMismatch)
 	}
 	return m, nil
 }

--- a/types/manifest/manifest.go
+++ b/types/manifest/manifest.go
@@ -109,7 +109,7 @@
 	}
 	// verify digest didn't change
 	if origDigest != "" && origDigest != c.desc.Digest {
-		return nil, fmt.Errorf("manifest digest mismatch, expected %s, computed %s", origDigest, c.desc.Digest)
+		return nil, fmt.Errorf("manifest digest mismatch, expected %s, computed %s%.0w", origDigest, c.desc.Digest, errs.ErrDigestMismatch)
 	}
 	return m, nil
 }

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

cd /workspace/regclient
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test -timeout 30s -run ^TestNew$ github.com/regclient/regclient/types/manifest

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

cd /workspace/regclient
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(TestModify|TestSet)$'


sshproxy is used on a gateway to transparently proxy a user SSH connection on the gateway to an internal host via SSH. Prior to version 1.6.3, any user authorized to connect to a ssh server using `sshproxy` can inject options to the `ssh` command executed by `sshproxy`. All versions of `sshproxy` are impacted. The problem is patched starting in version 1.6.3. The only workaround is to use the `force_command` option in `sshproxy.yaml`, but it's rarely relevant.

func mainExitCode() int {
	defer func() {
		// log error in case of panic()
		if err := recover(); err != nil {
			log.Errorf("program panicked: %s", err)
			log.Errorf("Stack: %s", debug.Stack())
		}
	}()

	var err error
	start := time.Now()

	versionFlag := flag.Bool("version", false, "show version number and exit")
	flag.Usage = usage
	flag.Parse()

	if *versionFlag {
		fmt.Fprintf(os.Stderr, "sshproxy version %s\n", SshproxyVersion)
		return 0
	}

	configFile := defaultConfig
	if flag.NArg() != 0 {
		configFile = flag.Arg(0)
	}

	currentUser, err := user.Current()
	if err != nil {
		log.Fatalf("Cannot find current user: %s", err)
	}
	username := currentUser.Username

	sshConnection := os.Getenv("SSH_CONNECTION")
	if sshConnection == "" {
		log.Fatal("No SSH_CONNECTION environment variable")
	}

	sshInfos, err := NewSSHInfo(sshConnection)
	if err != nil {
		log.Fatalf("parsing SSH_CONNECTION '%s': %s", sshConnection, err)
	}

	conninfo := &ConnInfo{
		Start: start,
		User:  username,
		SSH:   sshInfos,
	}

	sid := utils.CalcSessionID(conninfo.User, conninfo.Start, conninfo.SSH.Src())

	groups, err := utils.GetGroups()
	if err != nil {
		log.Fatalf("Cannot find current user groups: %s", err)
	}

	config, err := utils.LoadConfig(configFile, username, sid, start, groups)
	if err != nil {
		log.Fatalf("Reading configuration '%s': %s", configFile, err)
	}

	logformat := fmt.Sprintf("%%{time:2006-01-02 15:04:05} %%{level} %s: %%{message}", sid)
	syslogformat := fmt.Sprintf("%%{level} %s: %%{message}", sid)
	utils.MustSetupLogging("sshproxy", config.Log, logformat, syslogformat, config.Debug)

	for _, configLine := range utils.PrintConfig(config, groups) {
		log.Debug(configLine)
	}

	log.Infof("%s connected from %s to sshd listening on %s", username, sshInfos.Src(), sshInfos.Dst())
	defer log.Info("disconnected")

	cli, err := utils.NewEtcdClient(config, log)
	if err != nil {
		log.Errorf("Cannot contact etcd cluster to update state: %v", err)
	}

	if cli != nil && cli.IsAlive() {
		if config.MaxConnectionsPerUser > 0 {
			userConnectionsCount, err := cli.GetUserConnectionsCount(username)
			if err != nil {
				log.Fatalf("Getting user connections count: %s", err)
			}
			log.Debugf("Number of connections of %s: %d", username, userConnectionsCount)
			if userConnectionsCount >= config.MaxConnectionsPerUser {
				fmt.Fprintln(os.Stderr, "Too many simultaneous connections")
				log.Fatalf("Max connections per user reached for %s", username)
			}
		}
	} else {
		if config.Etcd.Mandatory {
			log.Fatal("Etcd is mandatory but unavailable")
		}
	}

	service, hostport, forceCommand, commandMustMatch, etcdKeyTTL, environment, err := findDestination(cli, username, config.Routes, sshInfos.Dst(), config.CheckInterval)
	switch {
	case err != nil:
		log.Fatalf("Finding destination: %s", err)
	case hostport == "":
		errorBanner := ""
		if cli != nil && cli.IsAlive() {
			errorBanner, _, _ = cli.GetErrorBanner()
		}
		if errorBanner == "" {
			errorBanner = config.ErrorBanner
		}
		if errorBanner != "" {
			fmt.Println(errorBanner)
		}
		log.Fatal("Cannot find a valid destination")
	}
	host, port, err := utils.SplitHostPort(hostport)
	if err != nil {
		log.Fatalf("Invalid destination '%s': %s", hostport, err)
	}

	log.Debugf("service = %s", service)

	// merge service environment with global environment
	for k, v := range environment {
		config.Environment[k] = v
	}
	setEnvironment(config.Environment)

	// waitgroup and channel to stop our background command when exiting.
	var wg sync.WaitGroup
	ctx, cancel := context.WithCancel(context.Background())
	defer func() {
		cancel()
		wg.Wait()
	}()

	sigChannel := make(chan os.Signal, 1)
	signal.Notify(sigChannel, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM)
	go func() {
		s := <-sigChannel
		log.Infof("Got signal %s, exiting", s)
		cancel()
	}()

	var etcdPath string
	var tmpKeepAliveChan <-chan *clientv3.LeaseKeepAliveResponse
	// Register destination in etcd and keep it alive while running.
	if cli != nil && cli.IsAlive() {
		key := fmt.Sprintf("%s@%s", username, service)
		keepAliveChan, eP, err := cli.SetDestination(ctx, key, sshInfos.Dst(), hostport, etcdKeyTTL)
		etcdPath = eP
		if err != nil {
			log.Warningf("setting destination in etcd: %v", err)
		}
		wg.Add(1)
		go func() {
			defer wg.Done()
			for {
				select {
				case isChanAlive := <-keepAliveChan:
					if isChanAlive == nil {
						cli.Disable()
						tmpKeepAliveChan, err = cli.NewLease(ctx)
						if err != nil {
							log.Warningf("getting a new lease in etcd: %v", err)
						} else {
							keepAliveChan = tmpKeepAliveChan
							cli.Enable()
						}
					}
				case <-ctx.Done():
					return
				}
			}
		}()
	}

	// launch background command
	if config.BgCommand != "" {
		wg.Add(1)
		go func() {
			defer wg.Done()
			cmd := prepareBackgroundCommand(ctx, config.BgCommand, config.Debug)
			if _, err := runCommand(cmd, false); err != nil {
				select {
				case <-ctx.Done():
					// stay silent as the session is now finished
				default:
					log.Errorf("error running background command: %s", err)
				}
			}
		}()
	}

	// Launch goroutine which exits sshproxy if it's attached to PID 1
	// (which means its ssh parent connection is dead).
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			select {
			case <-time.After(1 * time.Second):
				if os.Getppid() == 1 {
					log.Warning("SSH parent connection is dead")
					cancel()
					return
				}
			case <-ctx.Done():
				return
			}
		}
	}()

	originalCmd := os.Getenv("SSH_ORIGINAL_COMMAND")
	log.Debugf("original command = %s", originalCmd)

	interactiveCommand := term.IsTerminal(os.Stdout.Fd())
	log.Debugf("interactiveCommand = %v", interactiveCommand)

	sshArgs := config.SSH.Args
	envSshproxyArgs := strings.Fields(os.Getenv("SSHPROXY_ARGS"))
	if len(envSshproxyArgs) != 0 {
		sshArgs = append(sshArgs, envSshproxyArgs...)
	}
	if port != utils.DefaultSSHPort {
		sshArgs = append(sshArgs, "-p", port)
	}
	doCmd := ""
	if forceCommand != "" {
		log.Debugf("forceCommand = %s", forceCommand)
		doCmd = forceCommand
	} else if originalCmd != "" {
		doCmd = originalCmd
	}
	commandTranslated := false
	if doCmd != "" {
		if commandMustMatch && originalCmd != doCmd {
			log.Errorf("error executing proxied ssh command: originalCmd \"%s\" does not match forceCommand \"%s\"", originalCmd, forceCommand)
			return 1
		}
		for fromCmd, translateCmdConf := range config.TranslateCommands {
			if doCmd == fromCmd {
				log.Debugf("translateCmdConf = %+v", translateCmdConf)
				sshArgs = append(sshArgs, translateCmdConf.SSHArgs...)
				sshArgs = append(sshArgs, "--", host, translateCmdConf.Command)
				if config.Dump != "" && translateCmdConf.DisableDump {
					config.Dump = "etcd"
				}
				commandTranslated = true
				break
			}
		}
		if !commandTranslated {
			if interactiveCommand {
				// Force TTY allocation because the user probably asked for it.
				sshArgs = append(sshArgs, "-t")
			}
			sshArgs = append(sshArgs, host, doCmd)
		}
	} else {
		sshArgs = append(sshArgs, host)
	}
	cmd := exec.CommandContext(ctx, config.SSH.Exe, sshArgs...)
	log.Debugf("command = %s %q", cmd.Path, cmd.Args)

	var recorder *Recorder
	if config.Dump != "" {
		recorder = NewRecorder(conninfo, config.Dump, doCmd, config.EtcdStatsInterval.Duration(), config.LogStatsInterval.Duration(), config.DumpLimitSize, config.DumpLimitWindow.Duration())

		wg.Add(1)
		go func() {
			defer wg.Done()
			recorder.Run(ctx, cli, etcdPath)
		}()
	}

	log.Infof("proxied to %s (service: %s)", hostport, service)

	var rc int
	if interactiveCommand {
		rc, err = runTtyCommand(cmd, recorder)
	} else {
		rc, err = runStdCommand(cmd, recorder)
	}
	if err != nil {
		log.Errorf("error executing proxied ssh command: %s", err)
	}

	// return command exit code
	return rc
}

func mainExitCode() int {
	defer func() {
		// log error in case of panic()
		if err := recover(); err != nil {
			log.Errorf("program panicked: %s", err)
			log.Errorf("Stack: %s", debug.Stack())
		}
	}()

	var err error
	start := time.Now()

	versionFlag := flag.Bool("version", false, "show version number and exit")
	flag.Usage = usage
	flag.Parse()

	if *versionFlag {
		fmt.Fprintf(os.Stderr, "sshproxy version %s\n", SshproxyVersion)
		return 0
	}

	configFile := defaultConfig
	if flag.NArg() != 0 {
		configFile = flag.Arg(0)
	}

	currentUser, err := user.Current()
	if err != nil {
		log.Fatalf("Cannot find current user: %s", err)
	}
	username := currentUser.Username

	sshConnection := os.Getenv("SSH_CONNECTION")
	if sshConnection == "" {
		log.Fatal("No SSH_CONNECTION environment variable")
	}

	sshInfos, err := NewSSHInfo(sshConnection)
	if err != nil {
		log.Fatalf("parsing SSH_CONNECTION '%s': %s", sshConnection, err)
	}

	conninfo := &ConnInfo{
		Start: start,
		User:  username,
		SSH:   sshInfos,
	}

	sid := utils.CalcSessionID(conninfo.User, conninfo.Start, conninfo.SSH.Src())

	groups, err := utils.GetGroups()
	if err != nil {
		log.Fatalf("Cannot find current user groups: %s", err)
	}

	config, err := utils.LoadConfig(configFile, username, sid, start, groups)
	if err != nil {
		log.Fatalf("Reading configuration '%s': %s", configFile, err)
	}

	logformat := fmt.Sprintf("%%{time:2006-01-02 15:04:05} %%{level} %s: %%{message}", sid)
	syslogformat := fmt.Sprintf("%%{level} %s: %%{message}", sid)
	utils.MustSetupLogging("sshproxy", config.Log, logformat, syslogformat, config.Debug)

	for _, configLine := range utils.PrintConfig(config, groups) {
		log.Debug(configLine)
	}

	log.Infof("%s connected from %s to sshd listening on %s", username, sshInfos.Src(), sshInfos.Dst())
	defer log.Info("disconnected")

	cli, err := utils.NewEtcdClient(config, log)
	if err != nil {
		log.Errorf("Cannot contact etcd cluster to update state: %v", err)
	}

	if cli != nil && cli.IsAlive() {
		if config.MaxConnectionsPerUser > 0 {
			userConnectionsCount, err := cli.GetUserConnectionsCount(username)
			if err != nil {
				log.Fatalf("Getting user connections count: %s", err)
			}
			log.Debugf("Number of connections of %s: %d", username, userConnectionsCount)
			if userConnectionsCount >= config.MaxConnectionsPerUser {
				fmt.Fprintln(os.Stderr, "Too many simultaneous connections")
				log.Fatalf("Max connections per user reached for %s", username)
			}
		}
	} else {
		if config.Etcd.Mandatory {
			log.Fatal("Etcd is mandatory but unavailable")
		}
	}

	service, hostport, forceCommand, commandMustMatch, etcdKeyTTL, environment, err := findDestination(cli, username, config.Routes, sshInfos.Dst(), config.CheckInterval)
	switch {
	case err != nil:
		log.Fatalf("Finding destination: %s", err)
	case hostport == "":
		errorBanner := ""
		if cli != nil && cli.IsAlive() {
			errorBanner, _, _ = cli.GetErrorBanner()
		}
		if errorBanner == "" {
			errorBanner = config.ErrorBanner
		}
		if errorBanner != "" {
			fmt.Println(errorBanner)
		}
		log.Fatal("Cannot find a valid destination")
	}
	host, port, err := utils.SplitHostPort(hostport)
	if err != nil {
		log.Fatalf("Invalid destination '%s': %s", hostport, err)
	}

	log.Debugf("service = %s", service)

	// merge service environment with global environment
	for k, v := range environment {
		config.Environment[k] = v
	}
	setEnvironment(config.Environment)

	// waitgroup and channel to stop our background command when exiting.
	var wg sync.WaitGroup
	ctx, cancel := context.WithCancel(context.Background())
	defer func() {
		cancel()
		wg.Wait()
	}()

	sigChannel := make(chan os.Signal, 1)
	signal.Notify(sigChannel, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM)
	go func() {
		s := <-sigChannel
		log.Infof("Got signal %s, exiting", s)
		cancel()
	}()

	var etcdPath string
	var tmpKeepAliveChan <-chan *clientv3.LeaseKeepAliveResponse
	// Register destination in etcd and keep it alive while running.
	if cli != nil && cli.IsAlive() {
		key := fmt.Sprintf("%s@%s", username, service)
		keepAliveChan, eP, err := cli.SetDestination(ctx, key, sshInfos.Dst(), hostport, etcdKeyTTL)
		etcdPath = eP
		if err != nil {
			log.Warningf("setting destination in etcd: %v", err)
		}
		wg.Add(1)
		go func() {
			defer wg.Done()
			for {
				select {
				case isChanAlive := <-keepAliveChan:
					if isChanAlive == nil {
						cli.Disable()
						tmpKeepAliveChan, err = cli.NewLease(ctx)
						if err != nil {
							log.Warningf("getting a new lease in etcd: %v", err)
						} else {
							keepAliveChan = tmpKeepAliveChan
							cli.Enable()
						}
					}
				case <-ctx.Done():
					return
				}
			}
		}()
	}

	// launch background command
	if config.BgCommand != "" {
		wg.Add(1)
		go func() {
			defer wg.Done()
			cmd := prepareBackgroundCommand(ctx, config.BgCommand, config.Debug)
			if _, err := runCommand(cmd, false); err != nil {
				select {
				case <-ctx.Done():
					// stay silent as the session is now finished
				default:
					log.Errorf("error running background command: %s", err)
				}
			}
		}()
	}

	// Launch goroutine which exits sshproxy if it's attached to PID 1
	// (which means its ssh parent connection is dead).
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			select {
			case <-time.After(1 * time.Second):
				if os.Getppid() == 1 {
					log.Warning("SSH parent connection is dead")
					cancel()
					return
				}
			case <-ctx.Done():
				return
			}
		}
	}()

	originalCmd := os.Getenv("SSH_ORIGINAL_COMMAND")
	log.Debugf("original command = %s", originalCmd)

	interactiveCommand := term.IsTerminal(os.Stdout.Fd())
	log.Debugf("interactiveCommand = %v", interactiveCommand)

	sshArgs := config.SSH.Args
	envSshproxyArgs := strings.Fields(os.Getenv("SSHPROXY_ARGS"))
	if len(envSshproxyArgs) != 0 {
		sshArgs = append(sshArgs, envSshproxyArgs...)
	}
	if port != utils.DefaultSSHPort {
		sshArgs = append(sshArgs, "-p", port)
	}
	doCmd := ""
	if forceCommand != "" {
		log.Debugf("forceCommand = %s", forceCommand)
		doCmd = forceCommand
	} else if originalCmd != "" {
		doCmd = originalCmd
	}
	commandTranslated := false
	if doCmd != "" {
		if commandMustMatch && originalCmd != doCmd {
			log.Errorf("error executing proxied ssh command: originalCmd \"%s\" does not match forceCommand \"%s\"", originalCmd, forceCommand)
			return 1
		}
		for fromCmd, translateCmdConf := range config.TranslateCommands {
			if doCmd == fromCmd {
				log.Debugf("translateCmdConf = %+v", translateCmdConf)
				sshArgs = append(sshArgs, translateCmdConf.SSHArgs...)
				sshArgs = append(sshArgs, host, "--", translateCmdConf.Command)
				if config.Dump != "" && translateCmdConf.DisableDump {
					config.Dump = "etcd"
				}
				commandTranslated = true
				break
			}
		}
		if !commandTranslated {
			if interactiveCommand {
				// Force TTY allocation because the user probably asked for it.
				sshArgs = append(sshArgs, "-t")
			}
			sshArgs = append(sshArgs, host, "--", doCmd)
		}
	} else {
		sshArgs = append(sshArgs, host)
	}
	cmd := exec.CommandContext(ctx, config.SSH.Exe, sshArgs...)
	log.Debugf("command = %s %q", cmd.Path, cmd.Args)

	var recorder *Recorder
	if config.Dump != "" {
		recorder = NewRecorder(conninfo, config.Dump, doCmd, config.EtcdStatsInterval.Duration(), config.LogStatsInterval.Duration(), config.DumpLimitSize, config.DumpLimitWindow.Duration())

		wg.Add(1)
		go func() {
			defer wg.Done()
			recorder.Run(ctx, cli, etcdPath)
		}()
	}

	log.Infof("proxied to %s (service: %s)", hostport, service)

	var rc int
	if interactiveCommand {
		rc, err = runTtyCommand(cmd, recorder)
	} else {
		rc, err = runStdCommand(cmd, recorder)
	}
	if err != nil {
		log.Errorf("error executing proxied ssh command: %s", err)
	}

	// return command exit code
	return rc
}

--- a/cmd/sshproxy/sshproxy.go
+++ b/cmd/sshproxy/sshproxy.go
@@ -238,7 +238,7 @@
 			if doCmd == fromCmd {
 				log.Debugf("translateCmdConf = %+v", translateCmdConf)
 				sshArgs = append(sshArgs, translateCmdConf.SSHArgs...)
-				sshArgs = append(sshArgs, "--", host, translateCmdConf.Command)
+				sshArgs = append(sshArgs, host, "--", translateCmdConf.Command)
 				if config.Dump != "" && translateCmdConf.DisableDump {
 					config.Dump = "etcd"
 				}
@@ -251,7 +251,7 @@
 				// Force TTY allocation because the user probably asked for it.
 				sshArgs = append(sshArgs, "-t")
 			}
-			sshArgs = append(sshArgs, host, doCmd)
+			sshArgs = append(sshArgs, host, "--", doCmd)
 		}
 	} else {
 		sshArgs = append(sshArgs, host)


FastAPI is a web framework for building APIs with Python 3.6+ based on standard Python type hints. FastAPI versions lower than 0.65.2 that used cookies for authentication in path operations that received JSON payloads sent by browsers were vulnerable to a Cross-Site Request Forgery (CSRF) attack. In versions lower than 0.65.2, FastAPI would try to read the request payload as JSON even if the content-type header sent was not set to application/json or a compatible JSON media type (e.g. application/geo+json). A request with a content type of text/plain containing JSON data would be accepted and the JSON data would be extracted. Requests with content type text/plain are exempt from CORS preflights, for being considered Simple requests. The browser will execute them right away including cookies, and the text content could be a JSON string that would be parsed and accepted by the FastAPI application. This is fixed in FastAPI 0.65.2. The request data is now parsed as JSON only if the content-type header is application/json or another JSON compatible media type like application/geo+json. It's best to upgrade to the latest FastAPI, but if updating is not possible then a middleware or a dependency that checks the content-type header and aborts the request if it is not application/json or another JSON compatible content type can act as a mitigating workaround.

Cross-Site Request Forgery (CSRF)

The web application does not, or cannot, sufficiently verify whether a request was intentionally provided by the user who sent the request, which could have originated from an unauthorized actor.

    async def app(request: Request) -> Response:
        try:
            body = None
            if body_field:
                if is_body_form:
                    body = await request.form()
                else:
                    body_bytes = await request.body()
                    if body_bytes:
                        body = await request.json()
        except json.JSONDecodeError as e:
            raise RequestValidationError([ErrorWrapper(e, ("body", e.pos))], body=e.doc)
        except Exception as e:
            raise HTTPException(
                status_code=400, detail="There was an error parsing the body"
            ) from e
        solved_result = await solve_dependencies(
            request=request,
            dependant=dependant,
            body=body,
            dependency_overrides_provider=dependency_overrides_provider,
        )
        values, errors, background_tasks, sub_response, _ = solved_result
        if errors:
            raise RequestValidationError(errors, body=body)
        else:
            raw_response = await run_endpoint_function(
                dependant=dependant, values=values, is_coroutine=is_coroutine
            )

            if isinstance(raw_response, Response):
                if raw_response.background is None:
                    raw_response.background = background_tasks
                return raw_response
            response_data = await serialize_response(
                field=response_field,
                response_content=raw_response,
                include=response_model_include,
                exclude=response_model_exclude,
                by_alias=response_model_by_alias,
                exclude_unset=response_model_exclude_unset,
                exclude_defaults=response_model_exclude_defaults,
                exclude_none=response_model_exclude_none,
                is_coroutine=is_coroutine,
            )
            response = actual_response_class(
                content=response_data,
                status_code=status_code,
                background=background_tasks,  # type: ignore # in Starlette
            )
            response.headers.raw.extend(sub_response.headers.raw)
            if sub_response.status_code:
                response.status_code = sub_response.status_code
            return response

    async def app(request: Request) -> Response:
        try:
            body: Any = None
            if body_field:
                if is_body_form:
                    body = await request.form()
                else:
                    body_bytes = await request.body()
                    if body_bytes:
                        json_body: Any = Undefined
                        content_type_value = request.headers.get("content-type")
                        if content_type_value:
                            message = email.message.Message()
                            message["content-type"] = content_type_value
                            if message.get_content_maintype() == "application":
                                subtype = message.get_content_subtype()
                                if subtype == "json" or subtype.endswith("+json"):
                                    json_body = await request.json()
                        if json_body != Undefined:
                            body = json_body
                        else:
                            body = body_bytes
        except json.JSONDecodeError as e:
            raise RequestValidationError([ErrorWrapper(e, ("body", e.pos))], body=e.doc)
        except Exception as e:
            raise HTTPException(
                status_code=400, detail="There was an error parsing the body"
            ) from e
        solved_result = await solve_dependencies(
            request=request,
            dependant=dependant,
            body=body,
            dependency_overrides_provider=dependency_overrides_provider,
        )
        values, errors, background_tasks, sub_response, _ = solved_result
        if errors:
            raise RequestValidationError(errors, body=body)
        else:
            raw_response = await run_endpoint_function(
                dependant=dependant, values=values, is_coroutine=is_coroutine
            )

            if isinstance(raw_response, Response):
                if raw_response.background is None:
                    raw_response.background = background_tasks
                return raw_response
            response_data = await serialize_response(
                field=response_field,
                response_content=raw_response,
                include=response_model_include,
                exclude=response_model_exclude,
                by_alias=response_model_by_alias,
                exclude_unset=response_model_exclude_unset,
                exclude_defaults=response_model_exclude_defaults,
                exclude_none=response_model_exclude_none,
                is_coroutine=is_coroutine,
            )
            response = actual_response_class(
                content=response_data,
                status_code=status_code,
                background=background_tasks,  # type: ignore # in Starlette
            )
            response.headers.raw.extend(sub_response.headers.raw)
            if sub_response.status_code:
                response.status_code = sub_response.status_code
            return response

--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -1,13 +1,25 @@
     async def app(request: Request) -> Response:
         try:
-            body = None
+            body: Any = None
             if body_field:
                 if is_body_form:
                     body = await request.form()
                 else:
                     body_bytes = await request.body()
                     if body_bytes:
-                        body = await request.json()
+                        json_body: Any = Undefined
+                        content_type_value = request.headers.get("content-type")
+                        if content_type_value:
+                            message = email.message.Message()
+                            message["content-type"] = content_type_value
+                            if message.get_content_maintype() == "application":
+                                subtype = message.get_content_subtype()
+                                if subtype == "json" or subtype.endswith("+json"):
+                                    json_body = await request.json()
+                        if json_body != Undefined:
+                            body = json_body
+                        else:
+                            body = body_bytes
         except json.JSONDecodeError as e:
             raise RequestValidationError([ErrorWrapper(e, ("body", e.pos))], body=e.doc)
         except Exception as e:


A vulnerability was found in viakondratiuk cash-machine. It has been declared as critical. This vulnerability affects the function is_card_pin_at_session/update_failed_attempts of the file machine.py. The manipulation leads to sql injection. The name of the patch is 62a6e24efdfa195b70d7df140d8287fdc38eb66d. It is recommended to apply a patch to fix this issue. The identifier of this vulnerability is VDB-218896.

def get_card(request, cc_number):
    q = "select * from cards where cc_number = '%s'" % cc_number.replace('-', '')
    row = request.db.execute(q).fetchone()
    if row is not None:
        return dict(
            id = row[0],
            cc_number = row[1],
            pin = row[2],
            failed_attempts = row[3],
            status = row[4],
            balance = row[5],
            cc_dashed = cc_number,
            valid_pin = False
        )

def block_card(request):
    card = request.session['card']
    request.db.execute("update cards set status = 'blocked' where id = %s" % card['id'])
    request.db.commit()

def get_card(request, cc_number):
    row = request.db.execute("select * from cards where cc_number = ?", (cc_number.replace('-', ''),)).fetchone()
    if row is not None:
        return dict(
            id = row[0],
            cc_number = row[1],
            pin = row[2],
            failed_attempts = row[3],
            status = row[4],
            balance = row[5],
            cc_dashed = cc_number,
            valid_pin = False
        )

def block_card(request):
    card = request.session['card']
    request.db.execute("update cards set status = 'blocked' where id = ?", (card['id'],))
    request.db.commit()

--- a/machine.py
+++ b/machine.py
@@ -1,6 +1,5 @@
 def get_card(request, cc_number):
-    q = "select * from cards where cc_number = '%s'" % cc_number.replace('-', '')
-    row = request.db.execute(q).fetchone()
+    row = request.db.execute("select * from cards where cc_number = ?", (cc_number.replace('-', ''),)).fetchone()
     if row is not None:
         return dict(
             id = row[0],

--- a/machine.py
+++ b/machine.py
@@ -1,4 +1,4 @@
 def block_card(request):
     card = request.session['card']
-    request.db.execute("update cards set status = 'blocked' where id = %s" % card['id'])
+    request.db.execute("update cards set status = 'blocked' where id = ?", (card['id'],))
     request.db.commit()